From d6ae8dc112deb479f345c94970dde442332c8c27 Mon Sep 17 00:00:00 2001 From: Oriol Brufau Date: Wed, 16 Aug 2023 08:24:42 +0200 Subject: [PATCH] Revert "Backport several style changes from Gecko (5) (#30099)" (#30104) This reverts commit 8e15389caedd9b8e1b87cc9e4bfe8350a581546d. --- Cargo.lock | 17 +- components/atoms/static_atoms.txt | 6 - components/derive_common/cg.rs | 5 - components/layout/Cargo.toml | 4 +- components/layout/display_list/builder.rs | 21 +- components/layout/display_list/gradient.rs | 4 +- components/layout/fragment.rs | 7 - components/layout/lib.rs | 2 - components/layout/query.rs | 10 +- components/layout/table.rs | 8 +- components/layout/table_cell.rs | 10 +- components/layout/table_row.rs | 28 +- components/layout/tests/size_of.rs | 21 + .../layout_2020/display_list/gradient.rs | 4 +- components/layout_2020/display_list/mod.rs | 19 +- .../display_list/stacking_context.rs | 2 +- components/layout_2020/query.rs | 8 +- components/layout_2020/style_ext.rs | 2 +- components/layout_thread/lib.rs | 3 +- components/msg/Cargo.toml | 4 +- components/msg/constellation_msg.rs | 9 - components/msg/lib.rs | 2 - components/msg/tests/size_of.rs | 31 + components/script/dom/cssrule.rs | 5 +- components/script/dom/element.rs | 18 +- components/script/dom/node.rs | 14 +- components/script/layout_dom/element.rs | 36 +- components/selectors/Cargo.toml | 1 - components/selectors/context.rs | 19 - components/selectors/lib.rs | 3 - components/selectors/matching.rs | 108 +- components/selectors/parser.rs | 4 - components/selectors/rustfmt.toml | 1 - components/selectors/tree.rs | 31 +- components/size_of_test/Cargo.toml | 3 - components/size_of_test/lib.rs | 32 +- components/style/Cargo.toml | 3 +- components/style/animation.rs | 32 +- components/style/applicable_declarations.rs | 11 +- components/style/build_gecko.rs | 6 +- components/style/context.rs | 77 +- components/style/custom_properties.rs | 29 +- components/style/data.rs | 6 - components/style/dom.rs | 18 +- components/style/dom_apis.rs | 13 +- components/style/element_state.rs | 8 +- components/style/gecko/arc_types.rs | 7 +- components/style/gecko/data.rs | 14 +- components/style/gecko/media_features.rs | 502 +++++--- components/style/gecko/media_queries.rs | 60 +- .../style/gecko/non_ts_pseudo_class_list.rs | 4 +- components/style/gecko/selector_parser.rs | 31 +- components/style/gecko/snapshot_helpers.rs | 4 +- components/style/gecko/wrapper.rs | 83 +- components/style/global_style_data.rs | 62 +- .../invalidation/element/document_state.rs | 3 +- .../invalidation/element/element_wrapper.rs | 16 +- .../invalidation/element/invalidation_map.rs | 2 - .../element/state_and_attributes.rs | 12 +- components/style/invalidation/stylesheets.rs | 3 +- components/style/lib.rs | 9 +- components/style/logical_geometry.rs | 63 +- components/style/macros.rs | 8 - components/style/matching.rs | 69 +- .../media_condition.rs} | 117 +- .../media_feature.rs} | 118 +- .../media_queries/media_feature_expression.rs | 535 ++++++++ components/style/media_queries/media_list.rs | 12 +- components/style/media_queries/media_query.rs | 8 +- components/style/media_queries/mod.rs | 6 + components/style/piecewise_linear.rs | 223 ---- components/style/properties/cascade.rs | 220 ++-- .../properties/counted_unknown_properties.py | 1 + components/style/properties/data.py | 2 - components/style/properties/gecko.mako.rs | 488 +++---- .../helpers/animated_properties.mako.rs | 4 +- .../style/properties/longhands/box.mako.rs | 220 +++- .../style/properties/longhands/margin.mako.rs | 10 - .../style/properties/longhands/svg.mako.rs | 6 +- .../style/properties/longhands/ui.mako.rs | 204 +-- .../style/properties/properties.mako.rs | 252 ++-- .../style/properties/shorthands/box.mako.rs | 351 ++++- .../style/properties/shorthands/ui.mako.rs | 317 ----- .../style/queries/feature_expression.rs | 711 ---------- components/style/queries/mod.rs | 19 - components/style/queries/values.rs | 36 - components/style/rule_collector.rs | 14 +- components/style/rule_tree/core.rs | 8 +- components/style/rule_tree/mod.rs | 2 +- components/style/selector_map.rs | 65 +- components/style/servo/media_queries.rs | 33 +- components/style/sharing/checks.rs | 5 +- components/style/sharing/mod.rs | 36 +- components/style/style_resolver.rs | 42 +- .../style/stylesheets/container_rule.rs | 281 ---- components/style/stylesheets/layer_rule.rs | 13 +- components/style/stylesheets/media_rule.rs | 4 +- components/style/stylesheets/mod.rs | 26 +- components/style/stylesheets/rule_list.rs | 16 +- components/style/stylesheets/rule_parser.rs | 89 +- .../style/stylesheets/rules_iterator.rs | 4 - .../style/stylesheets/scroll_timeline_rule.rs | 1 + components/style/stylesheets/stylesheet.rs | 1 - components/style/stylesheets/viewport_rule.rs | 3 +- components/style/stylist.rs | 268 ++-- components/style/traversal.rs | 36 +- components/style/values/animated/color.rs | 1146 +++++++++-------- components/style/values/computed/box.rs | 8 +- components/style/values/computed/color.rs | 90 +- components/style/values/computed/image.rs | 5 +- components/style/values/computed/length.rs | 2 +- components/style/values/computed/mod.rs | 57 +- components/style/values/computed/page.rs | 8 +- .../style/values/computed/percentage.rs | 7 - components/style/values/computed/ratio.rs | 10 - components/style/values/computed/svg.rs | 4 +- components/style/values/generics/calc.rs | 20 - components/style/values/generics/color.rs | 299 +---- components/style/values/generics/image.rs | 17 +- components/style/values/generics/mod.rs | 71 - components/style/values/generics/page.rs | 10 +- components/style/values/mod.rs | 188 +-- components/style/values/specified/box.rs | 458 ++++--- components/style/values/specified/calc.rs | 20 - components/style/values/specified/color.rs | 236 ++-- components/style/values/specified/image.rs | 50 +- components/style/values/specified/length.rs | 407 +----- components/style/values/specified/mod.rs | 9 +- components/style/values/specified/page.rs | 8 +- .../style/values/specified/percentage.rs | 33 +- components/style/values/specified/position.rs | 1 - .../values/specified/source_size_list.rs | 18 +- components/style/values/specified/text.rs | 111 +- components/style_derive/parse.rs | 79 +- components/style_derive/rustfmt.toml | 1 - .../style_derive/specified_value_info.rs | 9 +- components/style_derive/to_css.rs | 120 +- components/style_traits/Cargo.toml | 1 - components/style_traits/lib.rs | 7 - components/style_traits/values.rs | 40 +- servo-tidy.toml | 29 +- tests/unit/style/Cargo.toml | 1 + tests/unit/style/lib.rs | 5 + tests/unit/style/size_of.rs | 51 + tests/unit/style/specified_values.rs | 50 + tests/unit/style/stylist.rs | 4 +- .../viewport-units-compute.html.ini | 90 ++ .../viewport-units-keyframes.html.ini | 60 + .../viewport-units-media-queries.html.ini | 30 + .../viewport-units-parsing.html.ini | 60 + .../viewport-units-media-queries.html.ini | 30 + .../viewport-units-parsing.html.ini | 60 + 152 files changed, 4622 insertions(+), 5862 deletions(-) create mode 100644 components/layout/tests/size_of.rs create mode 100644 components/msg/tests/size_of.rs delete mode 100644 components/selectors/rustfmt.toml rename components/style/{queries/condition.rs => media_queries/media_condition.rs} (57%) rename components/style/{queries/feature.rs => media_queries/media_feature.rs} (57%) create mode 100644 components/style/media_queries/media_feature_expression.rs delete mode 100644 components/style/piecewise_linear.rs delete mode 100644 components/style/properties/shorthands/ui.mako.rs delete mode 100644 components/style/queries/feature_expression.rs delete mode 100644 components/style/queries/mod.rs delete mode 100644 components/style/queries/values.rs delete mode 100644 components/style/stylesheets/container_rule.rs delete mode 100644 components/style_derive/rustfmt.toml create mode 100644 tests/unit/style/size_of.rs create mode 100644 tests/unit/style/specified_values.rs create mode 100644 tests/wpt/meta-legacy-layout/css/css-values/viewport-units-compute.html.ini create mode 100644 tests/wpt/meta-legacy-layout/css/css-values/viewport-units-keyframes.html.ini create mode 100644 tests/wpt/meta-legacy-layout/css/css-values/viewport-units-parsing.html.ini create mode 100644 tests/wpt/meta/css/css-values/viewport-units-parsing.html.ini diff --git a/Cargo.lock b/Cargo.lock index 060e603caf2..910e8e4c197 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5372,7 +5372,6 @@ dependencies = [ "phf_codegen", "precomputed-hash", "servo_arc", - "size_of_test", "smallvec", "to_shmem", "to_shmem_derive", @@ -5916,9 +5915,6 @@ checksum = "fa8f3741c7372e75519bd9346068370c9cdaabcc1f9599cbcf2a2719352286b7" [[package]] name = "size_of_test" version = "0.0.1" -dependencies = [ - "static_assertions", -] [[package]] name = "slab" @@ -6046,12 +6042,6 @@ 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" @@ -6155,7 +6145,6 @@ dependencies = [ "servo_url", "smallbitvec", "smallvec", - "static_assertions", "string_cache", "style_derive", "style_traits", @@ -6197,6 +6186,7 @@ dependencies = [ "servo_atoms", "servo_config", "servo_url", + "size_of_test", "std_test_override", "style", "style_traits", @@ -6218,7 +6208,6 @@ dependencies = [ "servo_arc", "servo_atoms", "servo_url", - "size_of_test", "to_shmem", "to_shmem_derive", "webrender_api", @@ -6702,9 +6691,9 @@ checksum = "9e79c4d996edb816c91e4308506774452e55e95c3c9de07b6729e17e15a5ef81" [[package]] name = "uluru" -version = "3.0.0" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "794a32261a1f5eb6a4462c81b59cec87b5c27d5deea7dd1ac8fc781c41d226db" +checksum = "308dcc9d947b227796f851adb99936fb060681a89c002c9c1928404a3b6c2d72" dependencies = [ "arrayvec 0.7.1", ] diff --git a/components/atoms/static_atoms.txt b/components/atoms/static_atoms.txt index 9ef4e7c5f94..7e4f385169e 100644 --- a/components/atoms/static_atoms.txt +++ b/components/atoms/static_atoms.txt @@ -1,4 +1,3 @@ --moz-content-preferred-color-scheme -moz-gtk-csd-close-button-position -moz-gtk-csd-maximize-button-position -moz-gtk-csd-menu-radius @@ -13,9 +12,7 @@ animationcancel animationend animationiteration animationstart -aspect-ratio beforeunload -block-size button canplay canplaythrough @@ -53,13 +50,11 @@ fullscreenchange fullscreenerror gattserverdisconnected hashchange -height hidden icecandidate iceconnectionstatechange icegatheringstatechange image -inline-size input inputsourceschange invalid @@ -87,7 +82,6 @@ none number onchange open -orientation pagehide pageshow password diff --git a/components/derive_common/cg.rs b/components/derive_common/cg.rs index 022a589eb58..93b624483c7 100644 --- a/components/derive_common/cg.rs +++ b/components/derive_common/cg.rs @@ -373,11 +373,6 @@ 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 fe501517c6e..d11f508c502 100644 --- a/components/layout/Cargo.toml +++ b/components/layout/Cargo.toml @@ -44,7 +44,6 @@ 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" } @@ -53,3 +52,6 @@ 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 93da81c3532..e6b16710d22 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.clone()); + let background_color = style.resolve_color(background.background_color); // XXXManishearth the below method should ideally use an iterator over // backgrounds self.build_display_list_for_background_if_applicable_with_background( @@ -1037,9 +1037,7 @@ 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.clone()) - .to_layout(), + color: style.resolve_color(box_shadow.base.color).to_layout(), offset: LayoutVector2D::new( box_shadow.base.horizontal.px(), box_shadow.base.vertical.px(), @@ -1085,10 +1083,10 @@ impl Fragment { let border_style_struct = style.get_border(); let mut colors = SideOffsets2D::new( - 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(), + border_style_struct.border_top_color, + border_style_struct.border_right_color, + border_style_struct.border_bottom_color, + border_style_struct.border_left_color, ); let mut border_style = SideOffsets2D::new( border_style_struct.border_top_style, @@ -1318,7 +1316,7 @@ impl Fragment { // Append the outline to the display list. let color = style - .resolve_color(style.get_outline().outline_color.clone()) + .resolve_color(style.get_outline().outline_color) .to_layout(); let base = state.create_base_display_item( clip, @@ -1453,8 +1451,7 @@ 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.clone()); + let background_color = style.resolve_color(style.get_background().background_color); let base = state.create_base_display_item( stacking_relative_border_box, self.node, @@ -2058,7 +2055,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.clone()).to_layout(), + color: self.style.resolve_color(shadow.color).to_layout(), blur_radius: shadow.blur.px(), }, }, diff --git a/components/layout/display_list/gradient.rs b/components/layout/display_list/gradient.rs index 4ec54479972..0f5394b69c9 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.clone()).to_layout(), + color: style.resolve_color(stop.color).to_layout(), }) } stops diff --git a/components/layout/fragment.rs b/components/layout/fragment.rs index 7f36e110499..5cf56b6cbdf 100644 --- a/components/layout/fragment.rs +++ b/components/layout/fragment.rs @@ -158,11 +158,6 @@ 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)?; @@ -217,8 +212,6 @@ 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 aab98048a54..3f41157c1cd 100644 --- a/components/layout/lib.rs +++ b/components/layout/lib.rs @@ -16,8 +16,6 @@ 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 d54003971f5..175ddb2679f 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, None); + let styles = resolve_style(&mut context, element, RuleInclusion::All, None); styles.primary().clone() } } else { @@ -916,13 +916,7 @@ pub fn process_resolved_style_request<'dom>( thread_local: &mut tlc, }; - let styles = resolve_style( - &mut context, - element, - RuleInclusion::All, - pseudo.as_ref(), - None, - ); + let styles = resolve_style(&mut context, element, RuleInclusion::All, pseudo.as_ref()); 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 36c78d9bb81..ca4097635f9 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.clone()); + .push_or_set(i, *this_inline_border); 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(ref table_border) => { + PreviousBlockCollapsedBorders::FromTable(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.clone()); + let next_block = next_block.push_or_set(i, *this_block_border); 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.clone()); + let background_color = sty.resolve_color(background.background_color); 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 6408da05dee..3a70158be26 100644 --- a/components/layout/table_cell.rs +++ b/components/layout/table_cell.rs @@ -375,7 +375,7 @@ impl fmt::Debug for TableCellFlow { } } -#[derive(Clone, Debug, Serialize)] +#[derive(Clone, Copy, 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.clone(), - self.inline_end_border.color.clone(), - self.block_end_border.color.clone(), - self.inline_start_border.color.clone(), + self.block_start_border.color, + self.inline_end_border.color, + self.block_end_border.color, + self.inline_start_border.color, ); *border_colors = logical_border_colors.to_physical(writing_mode); diff --git a/components/layout/table_row.rs b/components/layout/table_row.rs index b9090c39c94..0d959b7456c 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, Debug)] +#[derive(Clone, Copy, 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.clone(), + color: css_style.get_border().border_top_color, 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.clone(), + color: css_style.get_border().border_right_color, 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.clone(), + color: css_style.get_border().border_bottom_color, 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.clone(), + color: css_style.get_border().border_left_color, provenance: provenance, } } @@ -883,18 +883,18 @@ impl CollapsedBorder { match (self.style, other.style) { // Step 1. (BorderStyle::Hidden, _) => {}, - (_, BorderStyle::Hidden) => *self = other.clone(), + (_, BorderStyle::Hidden) => *self = *other, // Step 2. - (BorderStyle::None, _) => *self = other.clone(), + (BorderStyle::None, _) => *self = *other, (_, BorderStyle::None) => {}, // Step 3. _ if self.width > other.width => {}, - _ if self.width < other.width => *self = other.clone(), + _ if self.width < other.width => *self = *other, (this_style, other_style) if this_style > other_style => {}, - (this_style, other_style) if this_style < other_style => *self = other.clone(), + (this_style, other_style) if this_style < other_style => *self = *other, // Step 4. _ if (self.provenance as i8) >= other.provenance as i8 => {}, - _ => *self = other.clone(), + _ => *self = *other, } } } @@ -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.clone()), + .map_or(CollapsedBorder::new(), |x| *x), inline_end_border: border_collapse_info .collapsed_borders_for_row .inline .get(child_index + 1) - .map_or(CollapsedBorder::new(), |x| x.clone()), + .map_or(CollapsedBorder::new(), |x| *x), block_start_border: border_collapse_info .collapsed_borders_for_row .block_start .get(child_index) - .map_or(CollapsedBorder::new(), |x| x.clone()), + .map_or(CollapsedBorder::new(), |x| *x), block_end_border: border_collapse_info .collapsed_borders_for_row .block_end .get(child_index) - .map_or(CollapsedBorder::new(), |x| x.clone()), + .map_or(CollapsedBorder::new(), |x| *x), 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 new file mode 100644 index 00000000000..dec10937177 --- /dev/null +++ b/components/layout/tests/size_of.rs @@ -0,0 +1,21 @@ +/* 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 da8ef61eb86..c9738da49be 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.clone())), + color: super::rgba(style.resolve_color(*color)), position: None, }), GradientItem::ComplexColorStop { color, position } => stops.push(ColorStop { - color: super::rgba(style.resolve_color(color.clone())), + color: super::rgba(style.resolve_color(*color)), 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 c2c7713df13..80670108991 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() - .into_rgba(color); + .to_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.clone()); + let background_color = style.resolve_color(b.background_color); if background_color.alpha > 0 { // https://drafts.csswg.org/css-backgrounds/#background-color // “The background color is clipped according to the background-clip @@ -685,15 +685,10 @@ 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.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()), + 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), radius: self.border_radius, do_aa: true, }); @@ -723,7 +718,7 @@ impl<'a> BuilderForBoxFragment<'a> { OutlineStyle::Auto => BorderStyle::Solid, OutlineStyle::BorderStyle(s) => s, }; - let side = self.build_border_side(style, outline.outline_color.clone()); + let side = self.build_border_side(style, outline.outline_color); 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 77dc310b471..666e3225bed 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.clone()); + let background_color = style.resolve_color(style.get_background().background_color); 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 e409f5bdd59..a2524bb6bcd 100644 --- a/components/layout_2020/query.rs +++ b/components/layout_2020/query.rs @@ -361,13 +361,7 @@ 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(), - None, - ); + let styles = resolve_style(&mut context, element, RuleInclusion::All, pseudo.as_ref()); 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 8d8740bb961..274f581eb87 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.clone()); + let color = self.resolve_color(background.background_color); color.alpha == 0 && background .background_image diff --git a/components/layout_thread/lib.rs b/components/layout_thread/lib.rs index 2202bb123a1..3d0bc1a1cc7 100644 --- a/components/layout_thread/lib.rs +++ b/components/layout_thread/lib.rs @@ -1796,8 +1796,7 @@ fn get_root_flow_background_color(flow: &mut dyn Flow) -> ColorF { .fragment .style .get_background() - .background_color - .clone(), + .background_color, ); ColorF::new( color.red_f32(), diff --git a/components/msg/Cargo.toml b/components/msg/Cargo.toml index 49dfb7232bd..8bcf5680a2d 100644 --- a/components/msg/Cargo.toml +++ b/components/msg/Cargo.toml @@ -19,5 +19,7 @@ malloc_size_of = { path = "../malloc_size_of" } malloc_size_of_derive = { workspace = true } parking_lot = { workspace = true } serde = { workspace = true } -size_of_test = { path = "../size_of_test" } webrender_api = { workspace = true } + +[dev-dependencies] +size_of_test = { path = "../size_of_test" } diff --git a/components/msg/constellation_msg.rs b/components/msg/constellation_msg.rs index 3dcff217485..72223cdb387 100644 --- a/components/msg/constellation_msg.rs +++ b/components/msg/constellation_msg.rs @@ -198,9 +198,6 @@ 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| { @@ -243,9 +240,6 @@ 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| { @@ -275,9 +269,6 @@ 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 03aa40eb8f1..4fe53230c46 100644 --- a/components/msg/lib.rs +++ b/components/msg/lib.rs @@ -12,7 +12,5 @@ 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 new file mode 100644 index 00000000000..4fc1861ea31 --- /dev/null +++ b/components/msg/tests/size_of.rs @@ -0,0 +1,31 @@ +/* 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 b9cc5c19dbd..560124ef030 100644 --- a/components/script/dom/cssrule.rs +++ b/components/script/dom/cssrule.rs @@ -98,9 +98,8 @@ impl CSSRule { DomRoot::upcast(CSSSupportsRule::new(window, parent_stylesheet, s)) }, StyleCssRule::Page(_) => unreachable!(), - StyleCssRule::Container(_) => unimplemented!(), // TODO - StyleCssRule::Document(_) => unimplemented!(), // TODO - StyleCssRule::Viewport(_) => 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 ed04d2e562c..3304e23cb64 100644 --- a/components/script/dom/element.rs +++ b/components/script/dom/element.rs @@ -111,7 +111,6 @@ 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; @@ -3219,11 +3218,15 @@ 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, - ) -> bool { + _: &mut F, + ) -> bool + where + F: FnMut(&Self, ElementSelectorFlags), + { match *pseudo_class { // https://github.com/servo/servo/issues/8718 NonTSPseudoClass::Link | NonTSPseudoClass::AnyLink => self.is_link(), @@ -3304,15 +3307,6 @@ 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 7e3367b9b2a..a4913faa683 100644 --- a/components/script/dom/node.rs +++ b/components/script/dom/node.rs @@ -79,9 +79,7 @@ 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, NeedsSelectorFlags, -}; +use selectors::matching::{matches_selector_list, MatchingContext, MatchingMode}; use selectors::parser::SelectorList; use servo_arc::Arc; use servo_url::ServoUrl; @@ -475,7 +473,6 @@ 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) { @@ -959,13 +956,8 @@ 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(), - NeedsSelectorFlags::No, - ); + let mut ctx = + MatchingContext::new(MatchingMode::Normal, None, None, doc.quirks_mode()); 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 91be4baa6ed..b85e72443c3 100644 --- a/components/script/layout_dom/element.rs +++ b/components/script/layout_dom/element.rs @@ -358,6 +358,14 @@ 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) || @@ -452,10 +460,6 @@ 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 @@ -569,11 +573,15 @@ 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, - ) -> bool { + _: &mut F, + ) -> bool + where + F: FnMut(&Self, ElementSelectorFlags), + { match *pseudo_class { // https://github.com/servo/servo/issues/8718 NonTSPseudoClass::Link | NonTSPseudoClass::AnyLink => self.is_link(), @@ -662,10 +670,6 @@ 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 @@ -854,11 +858,15 @@ impl<'dom, LayoutDataType: LayoutDataTrait> ::selectors::Element } } - fn match_non_ts_pseudo_class( + fn match_non_ts_pseudo_class( &self, _: &NonTSPseudoClass, _: &mut MatchingContext, - ) -> bool { + _: &mut F, + ) -> bool + where + F: FnMut(&Self, ElementSelectorFlags), + { // NB: This could maybe be implemented warn!("ServoThreadSafeLayoutElement::match_non_ts_pseudo_class called"); false @@ -899,10 +907,6 @@ 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 880c4db0c61..2a91f50c636 100644 --- a/components/selectors/Cargo.toml +++ b/components/selectors/Cargo.toml @@ -28,7 +28,6 @@ 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 f595389d2f2..9146b133e38 100644 --- a/components/selectors/context.rs +++ b/components/selectors/context.rs @@ -68,14 +68,6 @@ 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/ @@ -148,7 +140,6 @@ where pub extra_data: Impl::ExtraMatchingData, quirks_mode: QuirksMode, - needs_selector_flags: NeedsSelectorFlags, classes_and_ids_case_sensitivity: CaseSensitivity, _impl: ::std::marker::PhantomData, } @@ -163,7 +154,6 @@ 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, @@ -171,7 +161,6 @@ where nth_index_cache, VisitedHandlingMode::AllLinksUnvisited, quirks_mode, - needs_selector_flags, ) } @@ -182,7 +171,6 @@ where nth_index_cache: Option<&'a mut NthIndexCache>, visited_handling: VisitedHandlingMode, quirks_mode: QuirksMode, - needs_selector_flags: NeedsSelectorFlags, ) -> Self { Self { matching_mode, @@ -191,7 +179,6 @@ 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, @@ -226,12 +213,6 @@ 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 2b785a8cd65..17f81c535b6 100644 --- a/components/selectors/lib.rs +++ b/components/selectors/lib.rs @@ -5,9 +5,6 @@ // 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 3ded93e44b9..92c13958985 100644 --- a/components/selectors/matching.rs +++ b/components/selectors/matching.rs @@ -78,7 +78,8 @@ 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); + let matches = matches_selector(selector, 0, None, element, context, &mut |_, _| {}); + if matches { return true; } @@ -183,15 +184,17 @@ 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 { @@ -202,7 +205,7 @@ where } } - matches_complex_selector(selector.iter_from(offset), element, context) + matches_complex_selector(selector.iter_from(offset), element, context, flags_setter) } /// Whether a compound selector matched, and whether it was the rightmost @@ -274,7 +277,7 @@ where ); for component in iter { - if !matches_simple_selector(component, element, &mut local_context) { + if !matches_simple_selector(component, element, &mut local_context, &mut |_, _| {}) { return CompoundSelectorMatchingResult::NotMatched; } } @@ -290,13 +293,15 @@ 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. @@ -329,7 +334,7 @@ where } let result = - matches_complex_selector_internal(iter, element, context, Rightmost::Yes); + matches_complex_selector_internal(iter, element, context, flags_setter, Rightmost::Yes); matches!(result, SelectorMatchingResult::Matched) } @@ -453,14 +458,16 @@ 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 {:?}", @@ -471,16 +478,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()) { - if context.needs_selector_flags() { - element.apply_selector_flags( - ElementSelectorFlags::HAS_SLOW_SELECTOR_LATER_SIBLINGS - ); - } + flags_setter( + element, + ElementSelectorFlags::HAS_SLOW_SELECTOR_LATER_SIBLINGS, + ); } if !matches_compound_selector { @@ -525,6 +532,7 @@ where selector_iter.clone(), &element, context, + flags_setter, Rightmost::No, ) }); @@ -587,14 +595,16 @@ 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); @@ -633,17 +643,19 @@ where }; iter::once(selector) .chain(selector_iter) - .all(|simple| matches_simple_selector(simple, element, &mut local_context)) + .all(|simple| matches_simple_selector(simple, element, &mut local_context, flags_setter)) } /// 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()); @@ -691,7 +703,7 @@ where // are never flattened tree slottables. !element.is_html_slot_element() && context.shared.nest(|context| { - matches_complex_selector(selector.iter(), element, context) + matches_complex_selector(selector.iter(), element, context, flags_setter) }) }, Component::PseudoElement(ref pseudo) => { @@ -783,18 +795,16 @@ where return false; } - element.match_non_ts_pseudo_class(pc, &mut context.shared) + element.match_non_ts_pseudo_class(pc, &mut context.shared, flags_setter) }, - Component::FirstChild => matches_first_child(element, context.shared), - Component::LastChild => matches_last_child(element, context.shared), + Component::FirstChild => matches_first_child(element, flags_setter), + Component::LastChild => matches_last_child(element, flags_setter), Component::OnlyChild => { - matches_first_child(element, context.shared) && matches_last_child(element, context.shared) + matches_first_child(element, flags_setter) && matches_last_child(element, flags_setter) }, Component::Root => element.is_root(), Component::Empty => { - if context.shared.needs_selector_flags() { - element.apply_selector_flags(ElementSelectorFlags::HAS_EMPTY_SELECTOR); - } + flags_setter(element, ElementSelectorFlags::HAS_EMPTY_SELECTOR); element.is_empty() }, Component::Host(ref selector) => { @@ -804,7 +814,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) + matches_complex_selector(selector.iter(), element, context, flags_setter) }) }) }, @@ -813,30 +823,30 @@ where None => element.is_root(), }, Component::NthChild(a, b) => { - matches_generic_nth_child(element, context.shared, a, b, false, false) + matches_generic_nth_child(element, context, a, b, false, false, flags_setter) }, Component::NthLastChild(a, b) => { - matches_generic_nth_child(element, context.shared, a, b, false, true) + matches_generic_nth_child(element, context, a, b, false, true, flags_setter) }, Component::NthOfType(a, b) => { - matches_generic_nth_child(element, context.shared, a, b, true, false) + matches_generic_nth_child(element, context, a, b, true, false, flags_setter) }, Component::NthLastOfType(a, b) => { - matches_generic_nth_child(element, context.shared, a, b, true, true) + matches_generic_nth_child(element, context, a, b, true, true, flags_setter) }, Component::FirstOfType => { - matches_generic_nth_child(element, context.shared, 0, 1, true, false) + matches_generic_nth_child(element, context, 0, 1, true, false, flags_setter) }, Component::LastOfType => { - matches_generic_nth_child(element, context.shared, 0, 1, true, true) + matches_generic_nth_child(element, context, 0, 1, true, true, flags_setter) }, Component::OnlyOfType => { - matches_generic_nth_child(element, context.shared, 0, 1, true, false) && - matches_generic_nth_child(element, context.shared, 0, 1, true, true) + matches_generic_nth_child(element, context, 0, 1, true, false, flags_setter) && + matches_generic_nth_child(element, context, 0, 1, true, true, flags_setter) }, Component::Is(ref list) | Component::Where(ref list) => context.shared.nest(|context| { for selector in &**list { - if matches_complex_selector(selector.iter(), element, context) { + if matches_complex_selector(selector.iter(), element, context, flags_setter) { return true; } } @@ -844,7 +854,7 @@ where }), Component::Negation(ref list) => context.shared.nest_for_negation(|context| { for selector in &**list { - if matches_complex_selector(selector.iter(), element, context) { + if matches_complex_selector(selector.iter(), element, context, flags_setter) { return false; } } @@ -863,31 +873,35 @@ 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 MatchingContext, + context: &mut LocalMatchingContext, 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; } - if context.needs_selector_flags() { - element.apply_selector_flags(if is_from_end { + flags_setter( + element, + 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)); @@ -978,23 +992,21 @@ where } #[inline] -fn matches_first_child(element: &E, context: &MatchingContext) -> bool +fn matches_first_child(element: &E, flags_setter: &mut F) -> bool where E: Element, + F: FnMut(&E, ElementSelectorFlags), { - if context.needs_selector_flags() { - element.apply_selector_flags(ElementSelectorFlags::HAS_EDGE_CHILD_SELECTOR); - } + flags_setter(element, ElementSelectorFlags::HAS_EDGE_CHILD_SELECTOR); element.prev_sibling_element().is_none() } #[inline] -fn matches_last_child(element: &E, context: &MatchingContext) -> bool +fn matches_last_child(element: &E, flags_setter: &mut F) -> bool where E: Element, + F: FnMut(&E, ElementSelectorFlags), { - if context.needs_selector_flags() { - element.apply_selector_flags(ElementSelectorFlags::HAS_EDGE_CHILD_SELECTOR); - } + flags_setter(element, ElementSelectorFlags::HAS_EDGE_CHILD_SELECTOR); element.next_sibling_element().is_none() } diff --git a/components/selectors/parser.rs b/components/selectors/parser.rs index cf342fc8ea8..c6650102616 100644 --- a/components/selectors/parser.rs +++ b/components/selectors/parser.rs @@ -160,8 +160,6 @@ 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>), @@ -186,8 +184,6 @@ 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 deleted file mode 100644 index c7ad93bafe3..00000000000 --- a/components/selectors/rustfmt.toml +++ /dev/null @@ -1 +0,0 @@ -disable_all_formatting = true diff --git a/components/selectors/tree.rs b/components/selectors/tree.rs index c1f0f9341c7..47cea73a208 100644 --- a/components/selectors/tree.rs +++ b/components/selectors/tree.rs @@ -77,11 +77,14 @@ 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, - ) -> bool; + flags_setter: &mut F, + ) -> bool + where + F: FnMut(&Self, ElementSelectorFlags); fn match_pseudo_element( &self, @@ -89,30 +92,6 @@ 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 faea55c5c1c..4776581cc52 100644 --- a/components/size_of_test/Cargo.toml +++ b/components/size_of_test/Cargo.toml @@ -8,6 +8,3 @@ 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 18e45175e8c..4e2452232f3 100644 --- a/components/size_of_test/lib.rs +++ b/components/size_of_test/lib.rs @@ -2,13 +2,33 @@ * 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 { - ($t: ty, $expected_size: expr) => { - #[cfg(target_pointer_width = "64")] - $crate::const_assert_eq!(std::mem::size_of::<$t>(), $expected_size); + ($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!() + ) + } + } }; } diff --git a/components/style/Cargo.toml b/components/style/Cargo.toml index 44ac2998e83..6c5196d93f8 100644 --- a/components/style/Cargo.toml +++ b/components/style/Cargo.toml @@ -62,14 +62,13 @@ servo_config = { path = "../config", optional = true } servo_url = { path = "../url", optional = true } smallbitvec = "2.3.0" smallvec = "1.0" -static_assertions = "1.1" string_cache = { version = "0.8", optional = true } style_derive = { path = "../style_derive" } style_traits = { path = "../style_traits" } time = "0.1" to_shmem = { path = "../to_shmem" } to_shmem_derive = { path = "../to_shmem_derive" } -uluru = "3.0" +uluru = "2" unicode-bidi = "0.3" unicode-segmentation = "1.0" void = "1.0.2" diff --git a/components/style/animation.rs b/components/style/animation.rs index 1df3dc08cca..7f0c7e6a588 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 new_ui = new_style.get_ui(); - let index = new_ui + let index = new_style + .get_box() .animation_name_iter() .position(|animation_name| Some(&self.name) == animation_name.as_atom()); let index = match index { @@ -464,7 +464,7 @@ impl Animation { None => return true, }; - new_ui.animation_duration_mod(index).seconds() == 0. + new_style.get_box().animation_duration_mod(index).seconds() == 0. } /// Given the current time, advances this animation to the next iteration, @@ -1073,10 +1073,10 @@ impl ElementAnimationSet { old_style: &ComputedValues, new_style: &Arc, ) { - let style = new_style.get_ui(); - let timing_function = style.transition_timing_function_mod(index); - let duration = style.transition_duration_mod(index); - let delay = style.transition_delay_mod(index).seconds() as f64; + let box_style = new_style.get_box(); + let timing_function = box_style.transition_timing_function_mod(index); + let duration = box_style.transition_duration_mod(index); + let delay = box_style.transition_delay_mod(index).seconds() as f64; let now = context.current_time_for_animations; // Only start a new transition if the style actually changes between @@ -1344,15 +1344,15 @@ pub fn maybe_start_animations( ) where E: TElement, { - let style = new_style.get_ui(); - for (i, name) in style.animation_name_iter().enumerate() { + let box_style = new_style.get_box(); + for (i, name) in box_style.animation_name_iter().enumerate() { let name = match name.as_atom() { Some(atom) => atom, None => continue, }; debug!("maybe_start_animations: name={}", name); - let duration = style.animation_duration_mod(i).seconds() as f64; + let duration = box_style.animation_duration_mod(i).seconds() as f64; if duration == 0. { continue; } @@ -1375,14 +1375,14 @@ pub fn maybe_start_animations( // NB: This delay may be negative, meaning that the animation may be created // in a state where we have advanced one or more iterations or even that the // animation begins in a finished state. - let delay = style.animation_delay_mod(i).seconds(); + let delay = box_style.animation_delay_mod(i).seconds(); - let iteration_state = match style.animation_iteration_count_mod(i) { + let iteration_state = match box_style.animation_iteration_count_mod(i) { AnimationIterationCount::Infinite => KeyframesIterationState::Infinite(0.0), AnimationIterationCount::Number(n) => KeyframesIterationState::Finite(0.0, n.into()), }; - let animation_direction = style.animation_direction_mod(i); + let animation_direction = box_style.animation_direction_mod(i); let initial_direction = match animation_direction { AnimationDirection::Normal | AnimationDirection::Alternate => { @@ -1396,7 +1396,7 @@ pub fn maybe_start_animations( let now = context.current_time_for_animations; let started_at = now + delay as f64; let mut starting_progress = (now - started_at) / duration; - let state = match style.animation_play_state_mod(i) { + let state = match box_style.animation_play_state_mod(i) { AnimationPlayState::Paused => AnimationState::Paused(starting_progress), AnimationPlayState::Running => AnimationState::Pending, }; @@ -1406,7 +1406,7 @@ pub fn maybe_start_animations( &keyframe_animation, context, new_style, - style.animation_timing_function_mod(i), + new_style.get_box().animation_timing_function_mod(i), resolver, ); @@ -1416,7 +1416,7 @@ pub fn maybe_start_animations( computed_steps, started_at, duration, - fill_mode: style.animation_fill_mode_mod(i), + fill_mode: box_style.animation_fill_mode_mod(i), delay: delay as f64, iteration_state, state, diff --git a/components/style/applicable_declarations.rs b/components/style/applicable_declarations.rs index 387dcdab07d..5849e7c2250 100644 --- a/components/style/applicable_declarations.rs +++ b/components/style/applicable_declarations.rs @@ -42,7 +42,13 @@ pub struct CascadePriority { layer_order: LayerOrder, } -const_assert_eq!(std::mem::size_of::(), std::mem::size_of::()); +#[allow(dead_code)] +fn size_assert() { + #[allow(unsafe_code)] + unsafe { + std::mem::transmute::(0u32) + }; +} impl PartialOrd for CascadePriority { #[inline] @@ -202,6 +208,3 @@ impl ApplicableDeclarationBlock { (self.source, self.cascade_priority) } } - -// Size of this struct determines sorting and selector-matching performance. -size_of_test!(ApplicableDeclarationBlock, 24); diff --git a/components/style/build_gecko.rs b/components/style/build_gecko.rs index 538b641d706..9c411b3ddb2 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("allowlist-functions", |b, item| b.allowlist_function(item)) + .handle_str_items("whitelist-functions", |b, item| b.allowlist_function(item)) .handle_str_items("bitfield-enums", |b, item| b.bitfield_enum(item)) .handle_str_items("rusty-enums", |b, item| b.rustified_enum(item)) - .handle_str_items("allowlist-vars", |b, item| b.allowlist_var(item)) - .handle_str_items("allowlist-types", |b, item| b.allowlist_type(item)) + .handle_str_items("whitelist-vars", |b, item| b.allowlist_var(item)) + .handle_str_items("whitelist-types", |b, item| b.allowlist_type(item)) .handle_str_items("opaque-types", |b, item| b.opaque_type(item)) .handle_table_items("cbindgen-types", |b, item| { let gecko = item["gecko"].as_str().unwrap(); diff --git a/components/style/context.rs b/components/style/context.rs index 633479c5a7a..a7d5aa4a67b 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,6 +41,7 @@ use style_traits::DevicePixel; #[cfg(feature = "servo")] use style_traits::SpeculativePainter; use time; +use uluru::LRUCache; pub use selectors::matching::QuirksMode; @@ -516,6 +517,65 @@ impl 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 @@ -637,6 +697,11 @@ 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 @@ -654,6 +719,7 @@ 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, @@ -663,6 +729,15 @@ 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 054e5a17c74..7be700ec350 100644 --- a/components/style/custom_properties.rs +++ b/components/style/custom_properties.rs @@ -64,27 +64,6 @@ fn get_safearea_inset_right(device: &Device) -> VariableValue { VariableValue::pixels(device.safe_area_insets().right) } -#[cfg(feature = "gecko")] -fn get_content_preferred_color_scheme(device: &Device) -> VariableValue { - use crate::gecko::media_features::PrefersColorScheme; - let prefers_color_scheme = unsafe { - crate::gecko_bindings::bindings::Gecko_MediaFeatures_PrefersColorScheme( - device.document(), - /* use_content = */ true, - ) - }; - VariableValue::ident(match prefers_color_scheme { - PrefersColorScheme::Light => "light", - PrefersColorScheme::Dark => "dark", - }) -} - -#[cfg(feature = "servo")] -fn get_content_preferred_color_scheme(_device: &Device) -> VariableValue { - // TODO: implement this. - VariableValue::ident("light") -} - static ENVIRONMENT_VARIABLES: [EnvironmentVariable; 4] = [ make_variable!(atom!("safe-area-inset-top"), get_safearea_inset_top), make_variable!(atom!("safe-area-inset-bottom"), get_safearea_inset_bottom), @@ -120,7 +99,7 @@ macro_rules! lnf_int_variable { }}; } -static CHROME_ENVIRONMENT_VARIABLES: [EnvironmentVariable; 6] = [ +static CHROME_ENVIRONMENT_VARIABLES: [EnvironmentVariable; 5] = [ lnf_int_variable!( atom!("-moz-gtk-csd-titlebar-radius"), TitlebarRadius, @@ -142,7 +121,6 @@ static CHROME_ENVIRONMENT_VARIABLES: [EnvironmentVariable; 6] = [ GTKCSDMaximizeButtonPosition, integer ), - make_variable!(atom!("-moz-content-preferred-color-scheme"), get_content_preferred_color_scheme), ]; impl CssEnvironment { @@ -350,11 +328,6 @@ impl VariableValue { }) } - /// Create VariableValue from an int. - fn ident(ident: &'static str) -> Self { - Self::from_token(Token::Ident(ident.into())) - } - /// Create VariableValue from a float amount of CSS pixels. fn pixels(number: f32) -> Self { // FIXME (https://github.com/servo/rust-cssparser/issues/266): diff --git a/components/style/data.rs b/components/style/data.rs index 2fa0b39c24d..758adf57a68 100644 --- a/components/style/data.rs +++ b/components/style/data.rs @@ -159,9 +159,6 @@ pub struct ElementStyles { pub pseudos: EagerPseudoStyles, } -// There's one of these per rendered elements so it better be small. -size_of_test!(ElementStyles, 16); - impl ElementStyles { /// Returns the primary style. pub fn get_primary(&self) -> Option<&Arc> { @@ -252,9 +249,6 @@ pub struct ElementData { pub flags: ElementDataFlags, } -// There's one of these per rendered elements so it better be small. -size_of_test!(ElementData, 24); - /// The kind of restyle that a single element should do. #[derive(Debug)] pub enum RestyleKind { diff --git a/components/style/dom.rs b/components/style/dom.rs index 83466eb21e6..308006efd11 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::{QuirksMode, VisitedHandlingMode}; +use selectors::matching::{ElementSelectorFlags, QuirksMode, VisitedHandlingMode}; use selectors::sink::Push; use selectors::Element as SelectorsElement; use servo_arc::{Arc, ArcBorrow}; @@ -734,6 +734,19 @@ pub trait TElement: /// native anonymous content can opt out of this style fixup.) fn skip_item_display_fixup(&self) -> bool; + /// Sets selector flags, which indicate what kinds of selectors may have + /// matched on this element and therefore what kind of work may need to + /// be performed when DOM state changes. + /// + /// This is unsafe, like all the flag-setting methods, because it's only safe + /// to call with exclusive access to the element. When setting flags on the + /// parent during parallel traversal, we use SequentialTask to queue up the + /// set to run after the threads join. + unsafe fn set_selector_flags(&self, flags: ElementSelectorFlags); + + /// Returns true if the element has all the specified selector flags. + fn has_selector_flags(&self, flags: ElementSelectorFlags) -> bool; + /// In Gecko, element has a flag that represents the element may have /// any type of animations or not to bail out animation stuff early. /// Whereas Servo doesn't have such flag. @@ -928,9 +941,6 @@ pub trait TElement: /// Returns element's namespace. fn namespace(&self) -> &::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 e6f1cbf565c..6d19801cfc5 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, NeedsSelectorFlags}; +use selectors::matching::{self, MatchingContext, MatchingMode}; use selectors::parser::{Combinator, Component, LocalName, SelectorImpl}; use selectors::{Element, NthIndexCache, SelectorList}; use smallvec::SmallVec; @@ -26,13 +26,7 @@ pub fn element_matches( where E: Element, { - let mut context = MatchingContext::new( - MatchingMode::Normal, - None, - None, - quirks_mode, - NeedsSelectorFlags::No, - ); + let mut context = MatchingContext::new(MatchingMode::Normal, None, None, quirks_mode); context.scope_element = Some(element.opaque()); context.current_host = element.containing_shadow_host().map(|e| e.opaque()); matching::matches_selector_list(selector_list, element, &mut context) @@ -54,7 +48,6 @@ where None, Some(&mut nth_index_cache), quirks_mode, - NeedsSelectorFlags::No, ); context.scope_element = Some(element.opaque()); context.current_host = element.containing_shadow_host().map(|e| e.opaque()); @@ -625,8 +618,8 @@ pub fn query_selector( 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 f8f4629ef9b..30c65dc9feb 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 modal element in top layer - const IN_TOPMOST_MODAL_TOP_LAYER_STATE = 1 << 44; + /// State for the topmost dialog element in top layer + const IN_TOPMOST_MODAL_DIALOG_STATE = 1 << 44; /// Initially used for the devtools highlighter, but now somehow only /// used for the devtools accessibility inspector. const IN_DEVTOOLS_HIGHLIGHTED_STATE = 1 << 45; @@ -148,5 +148,9 @@ bitflags! { const LTR_LOCALE = 1 << 2; /// LWTheme status const LWTHEME = 1 << 3; + /// LWTheme status + const LWTHEME_BRIGHTTEXT = 1 << 4; + /// LWTheme status + const LWTHEME_DARKTEXT = 1 << 5; } } diff --git a/components/style/gecko/arc_types.rs b/components/style/gecko/arc_types.rs index 893812ea710..4fb08c3a309 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, RawServoContainerRule, ServoCssRules, + RawServoSupportsRule, ServoCssRules, }; use crate::gecko_bindings::sugar::ownership::{HasArcFFI, HasFFI, Strong}; use crate::media_queries::MediaList; @@ -26,7 +26,7 @@ use crate::stylesheets::keyframes_rule::Keyframe; use crate::stylesheets::{ CounterStyleRule, CssRules, DocumentRule, FontFaceRule, FontFeatureValuesRule, ImportRule, KeyframesRule, LayerBlockRule, LayerStatementRule, MediaRule, NamespaceRule, PageRule, - ScrollTimelineRule, StyleRule, StylesheetContents, SupportsRule, ContainerRule, + ScrollTimelineRule, StyleRule, StylesheetContents, SupportsRule, }; use servo_arc::{Arc, ArcBorrow}; use std::{mem, ptr}; @@ -98,9 +98,6 @@ impl_arc_ffi!(Locked => 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 cabae61d563..39f7d7cc8d8 100644 --- a/components/style/gecko/data.rs +++ b/components/style/gecko/data.rs @@ -129,12 +129,6 @@ impl StylesheetInDocument for GeckoStyleSheet { pub struct PerDocumentStyleDataImpl { /// Rule processor. pub stylist: Stylist, - - /// A cache from element to resolved style. - pub undisplayed_style_cache: crate::traversal::UndisplayedStyleCache, - - /// The generation for which our cache is valid. - pub undisplayed_style_cache_generation: u64, } /// The data itself is an `AtomicRefCell`, which guarantees the proper semantics @@ -149,8 +143,6 @@ impl PerDocumentStyleData { PerDocumentStyleData(AtomicRefCell::new(PerDocumentStyleDataImpl { stylist: Stylist::new(device, quirks_mode.into()), - undisplayed_style_cache: Default::default(), - undisplayed_style_cache_generation: 0, })) } @@ -185,6 +177,12 @@ impl PerDocumentStyleDataImpl { self.stylist.device().default_computed_values_arc() } + /// Returns whether visited styles are enabled. + #[inline] + pub fn visited_styles_enabled(&self) -> bool { + unsafe { bindings::Gecko_VisitedStylesEnabled(self.stylist.device().document()) } + } + /// Measure heap usage. pub fn add_size_of(&self, ops: &mut MallocSizeOfOps, sizes: &mut ServoStyleSetSizes) { self.stylist.add_size_of(ops, sizes); diff --git a/components/style/gecko/media_features.rs b/components/style/gecko/media_features.rs index 8c93dfa24ce..af57be431f9 100644 --- a/components/style/gecko/media_features.rs +++ b/components/style/gecko/media_features.rs @@ -6,10 +6,13 @@ use crate::gecko_bindings::bindings; use crate::gecko_bindings::structs; -use crate::queries::feature::{AllowsRanges, Evaluator, FeatureFlags, QueryFeatureDescription}; -use crate::queries::values::Orientation; +use crate::media_queries::media_feature::{AllowsRanges, ParsingRequirements}; +use crate::media_queries::media_feature::{Evaluator, MediaFeatureDescription}; +use crate::media_queries::media_feature_expression::RangeOrOperator; use crate::media_queries::{Device, MediaType}; -use crate::values::computed::{Context, CSSPixelLength, Ratio, Resolution}; +use crate::values::computed::CSSPixelLength; +use crate::values::computed::Ratio; +use crate::values::computed::Resolution; use app_units::Au; use euclid::default::Size2D; @@ -23,56 +26,150 @@ fn device_size(device: &Device) -> Size2D { } /// https://drafts.csswg.org/mediaqueries-4/#width -fn eval_width(context: &Context) -> CSSPixelLength { - CSSPixelLength::new(context.device().au_viewport_size().width.to_f32_px()) +fn eval_width( + device: &Device, + value: Option, + range_or_operator: Option, +) -> bool { + RangeOrOperator::evaluate( + range_or_operator, + value.map(Au::from), + device.au_viewport_size().width, + ) } /// https://drafts.csswg.org/mediaqueries-4/#device-width -fn eval_device_width(context: &Context) -> CSSPixelLength { - CSSPixelLength::new(device_size(context.device()).width.to_f32_px()) +fn eval_device_width( + device: &Device, + value: Option, + range_or_operator: Option, +) -> bool { + RangeOrOperator::evaluate( + range_or_operator, + value.map(Au::from), + device_size(device).width, + ) } /// https://drafts.csswg.org/mediaqueries-4/#height -fn eval_height(context: &Context) -> CSSPixelLength { - CSSPixelLength::new(context.device().au_viewport_size().height.to_f32_px()) +fn eval_height( + device: &Device, + value: Option, + range_or_operator: Option, +) -> bool { + RangeOrOperator::evaluate( + range_or_operator, + value.map(Au::from), + device.au_viewport_size().height, + ) } /// https://drafts.csswg.org/mediaqueries-4/#device-height -fn eval_device_height(context: &Context) -> CSSPixelLength { - CSSPixelLength::new(device_size(context.device()).height.to_f32_px()) +fn eval_device_height( + device: &Device, + value: Option, + range_or_operator: Option, +) -> bool { + RangeOrOperator::evaluate( + range_or_operator, + value.map(Au::from), + device_size(device).height, + ) } -fn eval_aspect_ratio_for(context: &Context, get_size: F) -> Ratio +fn eval_aspect_ratio_for( + device: &Device, + query_value: Option, + range_or_operator: Option, + get_size: F, +) -> bool where F: FnOnce(&Device) -> Size2D, { - let size = get_size(context.device()); - Ratio::new(size.width.0 as f32, size.height.0 as f32) + // A ratio of 0/0 behaves as the ratio 1/0, so we need to call used_value() + // to convert it if necessary. + // FIXME: we may need to update here once + // https://github.com/w3c/csswg-drafts/issues/4954 got resolved. + let query_value = match query_value { + Some(v) => v.used_value(), + None => return true, + }; + + let size = get_size(device); + let value = Ratio::new(size.width.0 as f32, size.height.0 as f32); + RangeOrOperator::evaluate_with_query_value(range_or_operator, query_value, value) } /// https://drafts.csswg.org/mediaqueries-4/#aspect-ratio -fn eval_aspect_ratio(context: &Context) -> Ratio { - eval_aspect_ratio_for(context, Device::au_viewport_size) +fn eval_aspect_ratio( + device: &Device, + query_value: Option, + range_or_operator: Option, +) -> bool { + eval_aspect_ratio_for( + device, + query_value, + range_or_operator, + Device::au_viewport_size, + ) } /// https://drafts.csswg.org/mediaqueries-4/#device-aspect-ratio -fn eval_device_aspect_ratio(context: &Context) -> Ratio { - eval_aspect_ratio_for(context, device_size) +fn eval_device_aspect_ratio( + device: &Device, + query_value: Option, + range_or_operator: Option, +) -> bool { + eval_aspect_ratio_for(device, query_value, range_or_operator, device_size) } /// https://compat.spec.whatwg.org/#css-media-queries-webkit-device-pixel-ratio -fn eval_device_pixel_ratio(context: &Context) -> f32 { - eval_resolution(context).dppx() +fn eval_device_pixel_ratio( + device: &Device, + query_value: Option, + 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, + } } /// https://drafts.csswg.org/mediaqueries-4/#orientation -fn eval_orientation(context: &Context, value: Option) -> bool { - Orientation::eval(context.device().au_viewport_size(), value) +fn eval_orientation(device: &Device, value: Option) -> bool { + eval_orientation_for(device, value, Device::au_viewport_size) } /// FIXME: There's no spec for `-moz-device-orientation`. -fn eval_device_orientation(context: &Context, value: Option) -> bool { - Orientation::eval(device_size(context.device()), value) +fn eval_device_orientation(device: &Device, value: Option) -> bool { + eval_orientation_for(device, value, device_size) } /// Values for the display-mode media feature. @@ -87,23 +184,25 @@ pub enum DisplayMode { } /// https://w3c.github.io/manifest/#the-display-mode-media-feature -fn eval_display_mode(context: &Context, query_value: Option) -> bool { +fn eval_display_mode(device: &Device, query_value: Option) -> bool { match query_value { - Some(v) => v == unsafe { bindings::Gecko_MediaFeatures_GetDisplayMode(context.device().document()) }, + Some(v) => v == unsafe { bindings::Gecko_MediaFeatures_GetDisplayMode(device.document()) }, None => true, } } /// https://drafts.csswg.org/mediaqueries-4/#grid -fn eval_grid(_: &Context) -> bool { +fn eval_grid(_: &Device, query_value: Option, _: Option) -> bool { // Gecko doesn't support grid devices (e.g., ttys), so the 'grid' feature // is always 0. - false + let supports_grid = false; + query_value.map_or(supports_grid, |v| v == supports_grid) } /// https://compat.spec.whatwg.org/#css-media-queries-webkit-transform-3d -fn eval_transform_3d(_: &Context) -> bool { - true +fn eval_transform_3d(_: &Device, query_value: Option, _: Option) -> bool { + let supports_transforms = true; + query_value.map_or(supports_transforms, |v| v == supports_transforms) } #[derive(Clone, Copy, Debug, FromPrimitive, Parse, ToCss)] @@ -114,33 +213,58 @@ enum Scan { } /// https://drafts.csswg.org/mediaqueries-4/#scan -fn eval_scan(_: &Context, _: Option) -> bool { +fn eval_scan(_: &Device, _: 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(context: &Context) -> u32 { - unsafe { bindings::Gecko_MediaFeatures_GetColorDepth(context.device().document()) } +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) } /// https://drafts.csswg.org/mediaqueries-4/#color-index -fn eval_color_index(_: &Context) -> u32 { +fn eval_color_index( + _: &Device, + query_value: Option, + range_or_operator: Option, +) -> bool { // We should return zero if the device does not use a color lookup table. - 0 + let index = 0; + RangeOrOperator::evaluate(range_or_operator, query_value, index) } /// https://drafts.csswg.org/mediaqueries-4/#monochrome -fn eval_monochrome(context: &Context) -> u32 { +fn eval_monochrome( + device: &Device, + query_value: Option, + range_or_operator: Option, +) -> bool { // For color devices we should return 0. - unsafe { bindings::Gecko_MediaFeatures_GetMonochromeBitsPerPixel(context.device().document()) } + let depth = + unsafe { bindings::Gecko_MediaFeatures_GetMonochromeBitsPerPixel(device.document()) }; + RangeOrOperator::evaluate(range_or_operator, query_value, depth) } /// https://drafts.csswg.org/mediaqueries-4/#resolution -fn eval_resolution(context: &Context) -> Resolution { - let resolution_dppx = unsafe { bindings::Gecko_MediaFeatures_GetResolution(context.device().document()) }; - Resolution::from_dppx(resolution_dppx) +fn eval_resolution( + device: &Device, + query_value: Option, + 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, + ) } #[derive(Clone, Copy, Debug, FromPrimitive, Parse, ToCss)] @@ -159,22 +283,10 @@ pub enum PrefersColorScheme { Dark, } -/// Values for the dynamic-range and video-dynamic-range media features. -/// https://drafts.csswg.org/mediaqueries-5/#dynamic-range -/// This implements PartialOrd so that lower values will correctly match -/// higher capabilities. -#[derive(Clone, Copy, Debug, FromPrimitive, Parse, PartialEq, PartialOrd, ToCss)] -#[repr(u8)] -#[allow(missing_docs)] -pub enum DynamicRange { - Standard, - High, -} - /// https://drafts.csswg.org/mediaqueries-5/#prefers-reduced-motion -fn eval_prefers_reduced_motion(context: &Context, query_value: Option) -> bool { +fn eval_prefers_reduced_motion(device: &Device, query_value: Option) -> bool { let prefers_reduced = - unsafe { bindings::Gecko_MediaFeatures_PrefersReducedMotion(context.device().document()) }; + unsafe { bindings::Gecko_MediaFeatures_PrefersReducedMotion(device.document()) }; let query_value = match query_value { Some(v) => v, None => return prefers_reduced, @@ -191,20 +303,19 @@ fn eval_prefers_reduced_motion(context: &Context, query_value: Option) -> bool { +fn eval_prefers_contrast(device: &Device, query_value: Option) -> bool { let prefers_contrast = - unsafe { bindings::Gecko_MediaFeatures_PrefersContrast(context.device().document()) }; + unsafe { bindings::Gecko_MediaFeatures_PrefersContrast(device.document()) }; match query_value { Some(v) => v == prefers_contrast, None => prefers_contrast != PrefersContrast::NoPreference, @@ -223,8 +334,8 @@ pub enum ForcedColors { } /// https://drafts.csswg.org/mediaqueries-5/#forced-colors -fn eval_forced_colors(context: &Context, query_value: Option) -> bool { - let forced = !context.device().use_document_colors(); +fn eval_forced_colors(device: &Device, query_value: Option) -> bool { + let forced = !device.use_document_colors(); match query_value { Some(query_value) => forced == (query_value == ForcedColors::Active), None => forced, @@ -241,7 +352,7 @@ enum OverflowBlock { } /// https://drafts.csswg.org/mediaqueries-4/#mf-overflow-block -fn eval_overflow_block(context: &Context, query_value: Option) -> bool { +fn eval_overflow_block(device: &Device, 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 @@ -249,7 +360,7 @@ fn eval_overflow_block(context: &Context, query_value: Option) -> // billboard mode that doesn't support overflow at all). // // If this ever changes, don't forget to change eval_overflow_inline too. - let scrolling = context.device().media_type() != MediaType::print(); + let scrolling = device.media_type() != MediaType::print(); let query_value = match query_value { Some(v) => v, None => return true, @@ -270,9 +381,9 @@ enum OverflowInline { } /// https://drafts.csswg.org/mediaqueries-4/#mf-overflow-inline -fn eval_overflow_inline(context: &Context, query_value: Option) -> bool { +fn eval_overflow_inline(device: &Device, query_value: Option) -> bool { // See the note in eval_overflow_block. - let scrolling = context.device().media_type() != MediaType::print(); + let scrolling = device.media_type() != MediaType::print(); let query_value = match query_value { Some(v) => v, None => return scrolling, @@ -284,41 +395,13 @@ fn eval_overflow_inline(context: &Context, query_value: Option) } } -#[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( - context: &Context, + device: &Device, use_content: bool, query_value: Option, ) -> bool { let prefers_color_scheme = - unsafe { bindings::Gecko_MediaFeatures_PrefersColorScheme(context.device().document(), use_content) }; + unsafe { bindings::Gecko_MediaFeatures_PrefersColorScheme(device.document(), use_content) }; match query_value { Some(v) => prefers_color_scheme == v, None => true, @@ -326,34 +409,15 @@ fn do_eval_prefers_color_scheme( } /// https://drafts.csswg.org/mediaqueries-5/#prefers-color-scheme -fn eval_prefers_color_scheme(context: &Context, query_value: Option) -> bool { - do_eval_prefers_color_scheme(context, /* use_content = */ false, query_value) +fn eval_prefers_color_scheme(device: &Device, query_value: Option) -> bool { + do_eval_prefers_color_scheme(device, /* use_content = */ false, query_value) } fn eval_content_prefers_color_scheme( - context: &Context, + device: &Device, query_value: Option, ) -> bool { - do_eval_prefers_color_scheme(context, /* use_content = */ true, query_value) -} - -/// https://drafts.csswg.org/mediaqueries-5/#dynamic-range -fn eval_dynamic_range(context: &Context, query_value: Option) -> 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, - } + do_eval_prefers_color_scheme(device, /* use_content = */ true, query_value) } bitflags! { @@ -365,15 +429,15 @@ bitflags! { } } -fn primary_pointer_capabilities(context: &Context) -> PointerCapabilities { +fn primary_pointer_capabilities(device: &Device) -> PointerCapabilities { PointerCapabilities::from_bits_truncate(unsafe { - bindings::Gecko_MediaFeatures_PrimaryPointerCapabilities(context.device().document()) + bindings::Gecko_MediaFeatures_PrimaryPointerCapabilities(device.document()) }) } -fn all_pointer_capabilities(context: &Context) -> PointerCapabilities { +fn all_pointer_capabilities(device: &Device) -> PointerCapabilities { PointerCapabilities::from_bits_truncate(unsafe { - bindings::Gecko_MediaFeatures_AllPointerCapabilities(context.device().document()) + bindings::Gecko_MediaFeatures_AllPointerCapabilities(device.document()) }) } @@ -402,13 +466,13 @@ fn eval_pointer_capabilities( } /// https://drafts.csswg.org/mediaqueries-4/#pointer -fn eval_pointer(context: &Context, query_value: Option) -> bool { - eval_pointer_capabilities(query_value, primary_pointer_capabilities(context)) +fn eval_pointer(device: &Device, query_value: Option) -> bool { + eval_pointer_capabilities(query_value, primary_pointer_capabilities(device)) } /// https://drafts.csswg.org/mediaqueries-4/#descdef-media-any-pointer -fn eval_any_pointer(context: &Context, query_value: Option) -> bool { - eval_pointer_capabilities(query_value, all_pointer_capabilities(context)) +fn eval_any_pointer(device: &Device, query_value: Option) -> bool { + eval_pointer_capabilities(query_value, all_pointer_capabilities(device)) } #[derive(Clone, Copy, Debug, FromPrimitive, Parse, ToCss)] @@ -435,33 +499,54 @@ fn eval_hover_capabilities( } /// https://drafts.csswg.org/mediaqueries-4/#hover -fn eval_hover(context: &Context, query_value: Option) -> bool { - eval_hover_capabilities(query_value, primary_pointer_capabilities(context)) +fn eval_hover(device: &Device, query_value: Option) -> bool { + eval_hover_capabilities(query_value, primary_pointer_capabilities(device)) } /// https://drafts.csswg.org/mediaqueries-4/#descdef-media-any-hover -fn eval_any_hover(context: &Context, query_value: Option) -> bool { - eval_hover_capabilities(query_value, all_pointer_capabilities(context)) +fn eval_any_hover(device: &Device, query_value: Option) -> bool { + eval_hover_capabilities(query_value, all_pointer_capabilities(device)) } -fn eval_moz_is_glyph(context: &Context) -> bool { - context.device().document().mIsSVGGlyphsDocument() +fn eval_moz_is_glyph( + device: &Device, + query_value: Option, + _: Option, +) -> bool { + let is_glyph = device.document().mIsSVGGlyphsDocument(); + query_value.map_or(is_glyph, |v| v == is_glyph) } -fn eval_moz_print_preview(context: &Context) -> bool { - let is_print_preview = context.device().is_print_preview(); +fn eval_moz_print_preview( + device: &Device, + query_value: Option, + _: Option, +) -> bool { + let is_print_preview = device.is_print_preview(); if is_print_preview { - debug_assert_eq!(context.device().media_type(), MediaType::print()); + debug_assert_eq!(device.media_type(), MediaType::print()); } - is_print_preview + query_value.map_or(is_print_preview, |v| v == is_print_preview) } -fn eval_moz_non_native_content_theme(context: &Context) -> bool { - unsafe { bindings::Gecko_MediaFeatures_ShouldAvoidNativeTheme(context.device().document()) } +fn eval_moz_non_native_content_theme( + device: &Device, + query_value: Option, + _: 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_is_resource_document(context: &Context) -> bool { - unsafe { bindings::Gecko_MediaFeatures_IsResourceDocument(context.device().document()) } +fn eval_moz_is_resource_document( + device: &Device, + query_value: Option, + _: 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) } /// Allows front-end CSS to discern platform via media queries. @@ -487,7 +572,7 @@ pub enum Platform { WindowsWin10, } -fn eval_moz_platform(_: &Context, query_value: Option) -> bool { +fn eval_moz_platform(_: &Device, query_value: Option) -> bool { let query_value = match query_value { Some(v) => v, None => return false, @@ -496,22 +581,32 @@ fn eval_moz_platform(_: &Context, query_value: Option) -> bool { unsafe { bindings::Gecko_MediaFeatures_MatchesPlatform(query_value) } } -fn eval_moz_windows_non_native_menus(context: &Context) -> bool { +fn eval_moz_windows_non_native_menus( + device: &Device, + query_value: Option, + _: Option, +) -> bool { let use_non_native_menus = match static_prefs::pref!("browser.display.windows.non_native_menus") { 0 => false, 1 => true, _ => { - eval_moz_platform(context, Some(Platform::WindowsWin10)) && + eval_moz_platform(device, Some(Platform::WindowsWin10)) && get_lnf_int_as_bool(bindings::LookAndFeel_IntID::WindowsDefaultTheme as i32) }, }; - use_non_native_menus + query_value.map_or(use_non_native_menus, |v| v == use_non_native_menus) } -fn eval_moz_overlay_scrollbars(context: &Context) -> bool { - unsafe { bindings::Gecko_MediaFeatures_UseOverlayScrollbars(context.device().document()) } +fn eval_moz_overlay_scrollbars( + device: &Device, + query_value: Option, + _: Option, +) -> bool { + let use_overlay = + unsafe { bindings::Gecko_MediaFeatures_UseOverlayScrollbars(device.document()) }; + query_value.map_or(use_overlay, |v| v == use_overlay) } fn get_lnf_int(int_id: i32) -> i32 { @@ -540,15 +635,16 @@ fn get_scrollbar_end_forward(int_id: i32) -> bool { macro_rules! lnf_int_feature { ($feature_name:expr, $int_id:ident, $get_value:ident) => {{ - fn __eval(_: &Context) -> bool { - $get_value(bindings::LookAndFeel_IntID::$int_id as i32) + fn __eval(_: &Device, query_value: Option, _: Option) -> bool { + let value = $get_value(bindings::LookAndFeel_IntID::$int_id as i32); + query_value.map_or(value, |v| v == value) } feature!( $feature_name, AllowsRanges::No, Evaluator::BoolInteger(__eval), - FeatureFlags::CHROME_AND_UA_ONLY, + ParsingRequirements::CHROME_AND_UA_ONLY, ) }}; ($feature_name:expr, $int_id:ident) => {{ @@ -565,18 +661,18 @@ macro_rules! lnf_int_feature { /// pref, with `rust: true`. The feature name needs to be defined in /// `StaticAtoms.py` just like the others. In order to support dynamic changes, /// you also need to add them to kMediaQueryPrefs in nsXPLookAndFeel.cpp -#[allow(unused)] macro_rules! bool_pref_feature { ($feature_name:expr, $pref:tt) => {{ - fn __eval(_: &Context) -> bool { - static_prefs::pref!($pref) + fn __eval(_: &Device, query_value: Option, _: Option) -> bool { + let value = static_prefs::pref!($pref); + query_value.map_or(value, |v| v == value) } feature!( $feature_name, AllowsRanges::No, Evaluator::BoolInteger(__eval), - FeatureFlags::CHROME_AND_UA_ONLY, + ParsingRequirements::CHROME_AND_UA_ONLY, ) }}; } @@ -586,54 +682,54 @@ macro_rules! bool_pref_feature { /// to support new types in these entries and (2) ensuring that either /// nsPresContext::MediaFeatureValuesChanged is called when the value that /// would be returned by the evaluator function could change. -pub static MEDIA_FEATURES: [QueryFeatureDescription; 59] = [ +pub static MEDIA_FEATURES: [MediaFeatureDescription; 58] = [ feature!( atom!("width"), AllowsRanges::Yes, Evaluator::Length(eval_width), - FeatureFlags::empty(), + ParsingRequirements::empty(), ), feature!( atom!("height"), AllowsRanges::Yes, Evaluator::Length(eval_height), - FeatureFlags::empty(), + ParsingRequirements::empty(), ), feature!( atom!("aspect-ratio"), AllowsRanges::Yes, Evaluator::NumberRatio(eval_aspect_ratio), - FeatureFlags::empty(), + ParsingRequirements::empty(), ), feature!( atom!("orientation"), AllowsRanges::No, keyword_evaluator!(eval_orientation, Orientation), - FeatureFlags::empty(), + ParsingRequirements::empty(), ), feature!( atom!("device-width"), AllowsRanges::Yes, Evaluator::Length(eval_device_width), - FeatureFlags::empty(), + ParsingRequirements::empty(), ), feature!( atom!("device-height"), AllowsRanges::Yes, Evaluator::Length(eval_device_height), - FeatureFlags::empty(), + ParsingRequirements::empty(), ), feature!( atom!("device-aspect-ratio"), AllowsRanges::Yes, Evaluator::NumberRatio(eval_device_aspect_ratio), - FeatureFlags::empty(), + ParsingRequirements::empty(), ), feature!( atom!("-moz-device-orientation"), AllowsRanges::No, keyword_evaluator!(eval_device_orientation, Orientation), - FeatureFlags::empty(), + ParsingRequirements::empty(), ), // Webkit extensions that we support for de-facto web compatibility. // -webkit-{min|max}-device-pixel-ratio (controlled with its own pref): @@ -641,68 +737,68 @@ pub static MEDIA_FEATURES: [QueryFeatureDescription; 59] = [ atom!("device-pixel-ratio"), AllowsRanges::Yes, Evaluator::Float(eval_device_pixel_ratio), - FeatureFlags::WEBKIT_PREFIX, + ParsingRequirements::WEBKIT_PREFIX, ), // -webkit-transform-3d. feature!( atom!("transform-3d"), AllowsRanges::No, Evaluator::BoolInteger(eval_transform_3d), - FeatureFlags::WEBKIT_PREFIX, + ParsingRequirements::WEBKIT_PREFIX, ), feature!( atom!("-moz-device-pixel-ratio"), AllowsRanges::Yes, Evaluator::Float(eval_device_pixel_ratio), - FeatureFlags::empty(), + ParsingRequirements::empty(), ), feature!( atom!("resolution"), AllowsRanges::Yes, Evaluator::Resolution(eval_resolution), - FeatureFlags::empty(), + ParsingRequirements::empty(), ), feature!( atom!("display-mode"), AllowsRanges::No, keyword_evaluator!(eval_display_mode, DisplayMode), - FeatureFlags::empty(), + ParsingRequirements::empty(), ), feature!( atom!("grid"), AllowsRanges::No, Evaluator::BoolInteger(eval_grid), - FeatureFlags::empty(), + ParsingRequirements::empty(), ), feature!( atom!("scan"), AllowsRanges::No, keyword_evaluator!(eval_scan, Scan), - FeatureFlags::empty(), + ParsingRequirements::empty(), ), feature!( atom!("color"), AllowsRanges::Yes, Evaluator::Integer(eval_color), - FeatureFlags::empty(), + ParsingRequirements::empty(), ), feature!( atom!("color-index"), AllowsRanges::Yes, Evaluator::Integer(eval_color_index), - FeatureFlags::empty(), + ParsingRequirements::empty(), ), feature!( atom!("monochrome"), AllowsRanges::Yes, Evaluator::Integer(eval_monochrome), - FeatureFlags::empty(), + ParsingRequirements::empty(), ), feature!( atom!("prefers-reduced-motion"), AllowsRanges::No, keyword_evaluator!(eval_prefers_reduced_motion, PrefersReducedMotion), - FeatureFlags::empty(), + ParsingRequirements::empty(), ), feature!( atom!("prefers-contrast"), @@ -713,49 +809,31 @@ pub static MEDIA_FEATURES: [QueryFeatureDescription; 59] = [ // layout.css.prefers-contrast.enabled preference. See // disabed_by_pref in media_feature_expression.rs for how that // is done. - FeatureFlags::empty(), + ParsingRequirements::empty(), ), feature!( atom!("forced-colors"), AllowsRanges::No, keyword_evaluator!(eval_forced_colors, ForcedColors), - FeatureFlags::empty(), + ParsingRequirements::empty(), ), feature!( atom!("overflow-block"), AllowsRanges::No, keyword_evaluator!(eval_overflow_block, OverflowBlock), - FeatureFlags::empty(), + ParsingRequirements::empty(), ), feature!( atom!("overflow-inline"), AllowsRanges::No, keyword_evaluator!(eval_overflow_inline, OverflowInline), - FeatureFlags::empty(), - ), - feature!( - atom!("update"), - AllowsRanges::No, - keyword_evaluator!(eval_update, Update), - FeatureFlags::empty(), + ParsingRequirements::empty(), ), feature!( atom!("prefers-color-scheme"), AllowsRanges::No, keyword_evaluator!(eval_prefers_color_scheme, PrefersColorScheme), - FeatureFlags::empty(), - ), - feature!( - atom!("dynamic-range"), - AllowsRanges::No, - keyword_evaluator!(eval_dynamic_range, DynamicRange), - FeatureFlags::empty(), - ), - feature!( - atom!("video-dynamic-range"), - AllowsRanges::No, - keyword_evaluator!(eval_video_dynamic_range, DynamicRange), - FeatureFlags::empty(), + ParsingRequirements::empty(), ), // Evaluates to the preferred color scheme for content. Only useful in // chrome context, where the chrome color-scheme and the content @@ -764,31 +842,31 @@ pub static MEDIA_FEATURES: [QueryFeatureDescription; 59] = [ atom!("-moz-content-prefers-color-scheme"), AllowsRanges::No, keyword_evaluator!(eval_content_prefers_color_scheme, PrefersColorScheme), - FeatureFlags::CHROME_AND_UA_ONLY, + ParsingRequirements::CHROME_AND_UA_ONLY, ), feature!( atom!("pointer"), AllowsRanges::No, keyword_evaluator!(eval_pointer, Pointer), - FeatureFlags::empty(), + ParsingRequirements::empty(), ), feature!( atom!("any-pointer"), AllowsRanges::No, keyword_evaluator!(eval_any_pointer, Pointer), - FeatureFlags::empty(), + ParsingRequirements::empty(), ), feature!( atom!("hover"), AllowsRanges::No, keyword_evaluator!(eval_hover, Hover), - FeatureFlags::empty(), + ParsingRequirements::empty(), ), feature!( atom!("any-hover"), AllowsRanges::No, keyword_evaluator!(eval_any_hover, Hover), - FeatureFlags::empty(), + ParsingRequirements::empty(), ), // Internal -moz-is-glyph media feature: applies only inside SVG glyphs. // Internal because it is really only useful in the user agent anyway @@ -797,43 +875,43 @@ pub static MEDIA_FEATURES: [QueryFeatureDescription; 59] = [ atom!("-moz-is-glyph"), AllowsRanges::No, Evaluator::BoolInteger(eval_moz_is_glyph), - FeatureFlags::CHROME_AND_UA_ONLY, + ParsingRequirements::CHROME_AND_UA_ONLY, ), feature!( atom!("-moz-is-resource-document"), AllowsRanges::No, Evaluator::BoolInteger(eval_moz_is_resource_document), - FeatureFlags::CHROME_AND_UA_ONLY, + ParsingRequirements::CHROME_AND_UA_ONLY, ), feature!( atom!("-moz-platform"), AllowsRanges::No, keyword_evaluator!(eval_moz_platform, Platform), - FeatureFlags::CHROME_AND_UA_ONLY, + ParsingRequirements::CHROME_AND_UA_ONLY, ), feature!( atom!("-moz-print-preview"), AllowsRanges::No, Evaluator::BoolInteger(eval_moz_print_preview), - FeatureFlags::CHROME_AND_UA_ONLY, + ParsingRequirements::CHROME_AND_UA_ONLY, ), feature!( atom!("-moz-non-native-content-theme"), AllowsRanges::No, Evaluator::BoolInteger(eval_moz_non_native_content_theme), - FeatureFlags::CHROME_AND_UA_ONLY, + ParsingRequirements::CHROME_AND_UA_ONLY, ), feature!( atom!("-moz-windows-non-native-menus"), AllowsRanges::No, Evaluator::BoolInteger(eval_moz_windows_non_native_menus), - FeatureFlags::CHROME_AND_UA_ONLY, + ParsingRequirements::CHROME_AND_UA_ONLY, ), feature!( atom!("-moz-overlay-scrollbars"), AllowsRanges::No, Evaluator::BoolInteger(eval_moz_overlay_scrollbars), - FeatureFlags::CHROME_AND_UA_ONLY, + ParsingRequirements::CHROME_AND_UA_ONLY, ), lnf_int_feature!( atom!("-moz-scrollbar-start-backward"), @@ -855,6 +933,10 @@ pub static MEDIA_FEATURES: [QueryFeatureDescription; 59] = [ ScrollArrowStyle, get_scrollbar_end_forward ), + lnf_int_feature!( + atom!("-moz-scrollbar-thumb-proportional"), + ScrollSliderStyle + ), lnf_int_feature!(atom!("-moz-menubar-drag"), MenuBarDrag), lnf_int_feature!(atom!("-moz-windows-default-theme"), WindowsDefaultTheme), lnf_int_feature!(atom!("-moz-mac-graphite-theme"), MacGraphiteTheme), @@ -877,4 +959,8 @@ pub static MEDIA_FEATURES: [QueryFeatureDescription; 59] = [ GTKCSDReversedPlacement ), lnf_int_feature!(atom!("-moz-system-dark-theme"), SystemUsesDarkTheme), + bool_pref_feature!( + atom!("-moz-proton-places-tooltip"), + "browser.proton.places-tooltip.enabled" + ), ]; diff --git a/components/style/gecko/media_queries.rs b/components/style/gecko/media_queries.rs index 837cc61fffe..e4634d7eab9 100644 --- a/components/style/gecko/media_queries.rs +++ b/components/style/gecko/media_queries.rs @@ -17,7 +17,6 @@ use crate::values::computed::font::GenericFontFamily; use crate::values::computed::{ColorScheme, Length}; use crate::values::specified::color::SystemColor; use crate::values::specified::font::FONT_MEDIUM_PX; -use crate::values::specified::ViewportVariant; use crate::values::{CustomIdent, KeyframesName}; use app_units::{Au, AU_PER_PX}; use cssparser::RGBA; @@ -59,9 +58,6 @@ pub struct Device { /// Whether any styles computed in the document relied on the viewport size /// by using vw/vh/vmin/vmax units. used_viewport_size: AtomicBool, - /// Whether any styles computed in the document relied on the viewport size - /// by using dvw/dvh/dvmin/dvmax units. - used_dynamic_viewport_size: AtomicBool, /// The CssEnvironment object responsible of getting CSS environment /// variables. environment: CssEnvironment, @@ -104,7 +100,6 @@ impl Device { used_root_font_size: AtomicBool::new(false), used_font_metrics: AtomicBool::new(false), used_viewport_size: AtomicBool::new(false), - used_dynamic_viewport_size: AtomicBool::new(false), environment: CssEnvironment, } } @@ -272,8 +267,6 @@ impl Device { self.used_root_font_size.store(false, Ordering::Relaxed); self.used_font_metrics.store(false, Ordering::Relaxed); self.used_viewport_size.store(false, Ordering::Relaxed); - self.used_dynamic_viewport_size - .store(false, Ordering::Relaxed); } /// Returns whether we ever looked up the root font size of the Device. @@ -344,10 +337,7 @@ impl Device { /// Returns the current viewport size in app units, recording that it's been /// used for viewport unit resolution. - pub fn au_viewport_size_for_viewport_unit_resolution( - &self, - variant: ViewportVariant, - ) -> Size2D { + pub fn au_viewport_size_for_viewport_unit_resolution(&self) -> Size2D { self.used_viewport_size.store(true, Ordering::Relaxed); let pc = match self.pres_context() { Some(pc) => pc, @@ -358,42 +348,8 @@ impl Device { return self.page_size_minus_default_margin(pc); } - match variant { - ViewportVariant::UADefault => { - let size = &pc.mSizeForViewportUnits; - Size2D::new(Au(size.width), Au(size.height)) - }, - ViewportVariant::Small => { - let size = &pc.mVisibleArea; - Size2D::new(Au(size.width), Au(size.height)) - }, - ViewportVariant::Large => { - let size = &pc.mVisibleArea; - // Looks like IntCoordTyped is treated as if it's u32 in Rust. - debug_assert!( - /* pc.mDynamicToolbarMaxHeight >=0 && */ - pc.mDynamicToolbarMaxHeight < i32::MAX as u32 - ); - Size2D::new( - Au(size.width), - Au(size.height - + pc.mDynamicToolbarMaxHeight as i32 * pc.mCurAppUnitsPerDevPixel), - ) - }, - ViewportVariant::Dynamic => { - self.used_dynamic_viewport_size.store(true, Ordering::Relaxed); - let size = &pc.mVisibleArea; - // Looks like IntCoordTyped is treated as if it's u32 in Rust. - debug_assert!( - /* pc.mDynamicToolbarHeight >=0 && */ - pc.mDynamicToolbarHeight < i32::MAX as u32 - ); - Size2D::new( - Au(size.width), - Au(size.height + pc.mDynamicToolbarHeight as i32 * pc.mCurAppUnitsPerDevPixel), - ) - }, - } + let size = &pc.mSizeForViewportUnits; + Size2D::new(Au(size.width), Au(size.height)) } /// Returns whether we ever looked up the viewport size of the Device. @@ -401,21 +357,11 @@ impl Device { self.used_viewport_size.load(Ordering::Relaxed) } - /// Returns whether we ever looked up the dynamic viewport size of the Device. - pub fn used_dynamic_viewport_size(&self) -> bool { - self.used_dynamic_viewport_size.load(Ordering::Relaxed) - } - /// Returns whether font metrics have been queried. pub fn used_font_metrics(&self) -> bool { self.used_font_metrics.load(Ordering::Relaxed) } - /// Returns whether visited styles are enabled. - pub fn visited_styles_enabled(&self) -> bool { - unsafe { bindings::Gecko_VisitedStylesEnabled(self.document()) } - } - /// Returns the device pixel ratio. pub fn device_pixel_ratio(&self) -> Scale { 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 672e8104fe7..feb28feb0df 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", MozTopmostModal, IN_TOPMOST_MODAL_TOP_LAYER_STATE, PSEUDO_CLASS_ENABLED_IN_UA_SHEETS), + ("-moz-topmost-modal-dialog", MozTopmostModalDialog, IN_TOPMOST_MODAL_DIALOG_STATE, PSEUDO_CLASS_ENABLED_IN_UA_SHEETS), ("-moz-broken", MozBroken, IN_BROKEN_STATE, _), ("-moz-loading", MozLoading, IN_LOADING_STATE, _), ("-moz-has-dir-attr", MozHasDirAttr, IN_HAS_DIR_ATTR_STATE, PSEUDO_CLASS_ENABLED_IN_UA_SHEETS), @@ -92,6 +92,8 @@ macro_rules! apply_non_ts_list { ("-moz-is-html", MozIsHTML, _, _), ("-moz-placeholder", MozPlaceholder, _, _), ("-moz-lwtheme", MozLWTheme, _, PSEUDO_CLASS_ENABLED_IN_UA_SHEETS_AND_CHROME), + ("-moz-lwtheme-brighttext", MozLWThemeBrightText, _, PSEUDO_CLASS_ENABLED_IN_UA_SHEETS_AND_CHROME), + ("-moz-lwtheme-darktext", MozLWThemeDarkText, _, PSEUDO_CLASS_ENABLED_IN_UA_SHEETS_AND_CHROME), ("-moz-window-inactive", MozWindowInactive, _, _), ] } diff --git a/components/style/gecko/selector_parser.rs b/components/style/gecko/selector_parser.rs index 7cfb01e2794..8fc0900aece 100644 --- a/components/style/gecko/selector_parser.rs +++ b/components/style/gecko/selector_parser.rs @@ -139,6 +139,15 @@ impl NonTSPseudoClass { /// Returns whether the pseudo-class is enabled in content sheets. #[inline] fn is_enabled_in_content(&self) -> bool { + if matches!( + *self, + Self::MozLWTheme | Self::MozLWThemeBrightText | Self::MozLWThemeDarkText + ) { + return static_prefs::pref!("layout.css.moz-lwtheme.content.enabled"); + } + if let NonTSPseudoClass::MozLocaleDir(..) = *self { + return static_prefs::pref!("layout.css.moz-locale-dir.content.enabled"); + } !self.has_any_flag(NonTSPseudoClassFlag::PSEUDO_CLASS_ENABLED_IN_UA_SHEETS_AND_CHROME) } @@ -175,6 +184,8 @@ impl NonTSPseudoClass { }, NonTSPseudoClass::MozWindowInactive => DocumentState::WINDOW_INACTIVE, NonTSPseudoClass::MozLWTheme => DocumentState::LWTHEME, + NonTSPseudoClass::MozLWThemeBrightText => DocumentState::LWTHEME_BRIGHTTEXT, + NonTSPseudoClass::MozLWThemeDarkText => DocumentState::LWTHEME_DARKTEXT, _ => DocumentState::empty(), } } @@ -197,13 +208,15 @@ impl NonTSPseudoClass { NonTSPseudoClass::MozNativeAnonymous | // :-moz-placeholder is parsed but never matches. NonTSPseudoClass::MozPlaceholder | - // :-moz-lwtheme, :-moz-locale-dir and - // :-moz-window-inactive depend only on the state of the - // document, which is invariant across all the elements - // involved in a given style cache. - NonTSPseudoClass::MozLWTheme | + // :-moz-locale-dir and :-moz-window-inactive depend only on + // the state of the document, which is invariant across all + // the elements involved in a given style cache. NonTSPseudoClass::MozLocaleDir(_) | - NonTSPseudoClass::MozWindowInactive + NonTSPseudoClass::MozWindowInactive | + // Similar for the document themes. + NonTSPseudoClass::MozLWTheme | + NonTSPseudoClass::MozLWThemeBrightText | + NonTSPseudoClass::MozLWThemeDarkText ) } } @@ -445,9 +458,3 @@ unsafe impl HasFFI for SelectorList { } 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 d75cd275310..3cb325bb6b1 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 attr_array: *mut _ = *(*container) + let array: *mut u8 = *(*container) .__bindgen_anon_1 .mValue .as_ref() .__bindgen_anon_1 .mAtomArray .as_ref(); - let array = (*attr_array).mArray.as_ptr() as *const structs::nsTArray>; + let array = array 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 9c4c3c43118..ca2c3816ec9 100644 --- a/components/style/gecko/wrapper.rs +++ b/components/style/gecko/wrapper.rs @@ -66,8 +66,6 @@ use crate::stylist::CascadeData; use crate::values::{AtomIdent, AtomString}; use crate::CaseSensitivityExt; use crate::LocalName; -use app_units::Au; -use euclid::default::Size2D; use atomic_refcell::{AtomicRef, AtomicRefCell, AtomicRefMut}; use fxhash::FxHashMap; use selectors::attr::{AttrSelectorOperation, AttrSelectorOperator}; @@ -323,11 +321,6 @@ impl<'ln> GeckoNode<'ln> { self.flags() & (NODE_IS_IN_SHADOW_TREE as u32) != 0 } - #[inline] - fn is_connected(&self) -> bool { - self.get_bool_flag(nsINode_BooleanFlag::IsConnected) - } - /// WARNING: This logic is duplicated in Gecko's FlattenedTreeParentIsParent. /// Make sure to mirror any modifications in both places. #[inline] @@ -1037,21 +1030,6 @@ impl<'le> TElement for GeckoElement<'le> { } } - #[inline] - fn primary_box_size(&self) -> Size2D { - if !self.as_node().is_connected() { - return Size2D::zero(); - } - - unsafe { - let frame = self.0._base._base._base.__bindgen_anon_1.mPrimaryFrame.as_ref(); - if frame.is_null() { - return Size2D::zero(); - } - Size2D::new(Au((**frame).mRect.width), Au((**frame).mRect.height)) - } - } - /// Return the list of slotted nodes of this node. #[inline] fn slotted_nodes(&self) -> &[Self::ConcreteNode] { @@ -1415,6 +1393,16 @@ impl<'le> TElement for GeckoElement<'le> { self.is_root_of_native_anonymous_subtree() } + unsafe fn set_selector_flags(&self, flags: ElementSelectorFlags) { + debug_assert!(!flags.is_empty()); + self.set_flags(selector_flags_to_node_flags(flags)); + } + + fn has_selector_flags(&self, flags: ElementSelectorFlags) -> bool { + let node_flags = selector_flags_to_node_flags(flags); + (self.flags() & node_flags) == node_flags + } + #[inline] fn may_have_animations(&self) -> bool { if let Some(pseudo) = self.implemented_pseudo_element() { @@ -1513,7 +1501,7 @@ impl<'le> TElement for GeckoElement<'le> { ) -> bool { use crate::properties::LonghandIdSet; - let after_change_ui_style = after_change_style.get_ui(); + let after_change_box_style = after_change_style.get_box(); let existing_transitions = self.css_transitions_info(); let mut transitions_to_keep = LonghandIdSet::new(); for transition_property in after_change_style.transition_properties() { @@ -1523,7 +1511,7 @@ impl<'le> TElement for GeckoElement<'le> { transitions_to_keep.insert(physical_longhand); if self.needs_transitions_update_per_property( physical_longhand, - after_change_ui_style.transition_combined_duration_at(transition_property.index), + after_change_box_style.transition_combined_duration_at(transition_property.index), before_change_style, after_change_style, &existing_transitions, @@ -1605,9 +1593,23 @@ impl<'le> TElement for GeckoElement<'le> { use crate::properties::longhands::_x_lang::SpecifiedValue as SpecifiedLang; use crate::properties::longhands::_x_text_zoom::SpecifiedValue as SpecifiedZoom; use crate::properties::longhands::color::SpecifiedValue as SpecifiedColor; + use crate::properties::longhands::text_align::SpecifiedValue as SpecifiedTextAlign; use crate::stylesheets::layer_rule::LayerOrder; use crate::values::specified::color::Color; lazy_static! { + static ref TH_RULE: ApplicableDeclarationBlock = { + let global_style_data = &*GLOBAL_STYLE_DATA; + let pdb = PropertyDeclarationBlock::with_one( + PropertyDeclaration::TextAlign(SpecifiedTextAlign::MozCenterOrInherit), + Importance::Normal, + ); + let arc = Arc::new_leaked(global_style_data.shared_lock.wrap(pdb)); + ApplicableDeclarationBlock::from_declarations( + arc, + ServoCascadeLevel::PresHints, + LayerOrder::root(), + ) + }; static ref TABLE_COLOR_RULE: ApplicableDeclarationBlock = { let global_style_data = &*GLOBAL_STYLE_DATA; let pdb = PropertyDeclarationBlock::with_one( @@ -1652,7 +1654,9 @@ impl<'le> TElement for GeckoElement<'le> { let ns = self.namespace_id(); // elements get a default MozCenterOrInherit which may get overridden if ns == structs::kNameSpaceID_XHTML as i32 { - if self.local_name().as_ptr() == atom!("table").as_ptr() && + if self.local_name().as_ptr() == atom!("th").as_ptr() { + hints.push(TH_RULE.clone()); + } else if self.local_name().as_ptr() == atom!("table").as_ptr() && self.as_node().owner_doc().quirks_mode() == QuirksMode::Quirks { hints.push(TABLE_COLOR_RULE.clone()); @@ -1844,11 +1848,6 @@ impl<'le> ::selectors::Element for GeckoElement<'le> { None } - fn set_selector_flags(&self, flags: ElementSelectorFlags) { - debug_assert!(!flags.is_empty()); - self.set_flags(selector_flags_to_node_flags(flags)); - } - fn attr_matches( &self, ns: &NamespaceConstraint<&Namespace>, @@ -1961,11 +1960,15 @@ impl<'le> ::selectors::Element for GeckoElement<'le> { self.local_name() == other.local_name() && self.namespace() == other.namespace() } - fn match_non_ts_pseudo_class( + fn match_non_ts_pseudo_class( &self, pseudo_class: &NonTSPseudoClass, context: &mut MatchingContext, - ) -> bool { + flags_setter: &mut F, + ) -> bool + where + F: FnMut(&Self, ElementSelectorFlags), + { use selectors::matching::*; match *pseudo_class { NonTSPseudoClass::Autofill | @@ -2006,7 +2009,7 @@ impl<'le> ::selectors::Element for GeckoElement<'le> { NonTSPseudoClass::MozDirAttrRTL | NonTSPseudoClass::MozDirAttrLikeAuto | NonTSPseudoClass::MozModalDialog | - NonTSPseudoClass::MozTopmostModal | + NonTSPseudoClass::MozTopmostModalDialog | NonTSPseudoClass::Active | NonTSPseudoClass::Hover | NonTSPseudoClass::MozAutofillPreview | @@ -2021,9 +2024,7 @@ impl<'le> ::selectors::Element for GeckoElement<'le> { self.is_link() && context.visited_handling().matches_visited() }, NonTSPseudoClass::MozFirstNode => { - if context.needs_selector_flags() { - self.apply_selector_flags(ElementSelectorFlags::HAS_EDGE_CHILD_SELECTOR); - } + flags_setter(self, ElementSelectorFlags::HAS_EDGE_CHILD_SELECTOR); let mut elem = self.as_node(); while let Some(prev) = elem.prev_sibling() { if prev.contains_non_whitespace_content() { @@ -2034,9 +2035,7 @@ impl<'le> ::selectors::Element for GeckoElement<'le> { true }, NonTSPseudoClass::MozLastNode => { - if context.needs_selector_flags() { - self.apply_selector_flags(ElementSelectorFlags::HAS_EDGE_CHILD_SELECTOR); - } + flags_setter(self, ElementSelectorFlags::HAS_EDGE_CHILD_SELECTOR); let mut elem = self.as_node(); while let Some(next) = elem.next_sibling() { if next.contains_non_whitespace_content() { @@ -2047,9 +2046,7 @@ impl<'le> ::selectors::Element for GeckoElement<'le> { true }, NonTSPseudoClass::MozOnlyWhitespace => { - if context.needs_selector_flags() { - self.apply_selector_flags(ElementSelectorFlags::HAS_EMPTY_SELECTOR); - } + flags_setter(self, ElementSelectorFlags::HAS_EMPTY_SELECTOR); if self .as_node() .dom_children() @@ -2071,6 +2068,8 @@ impl<'le> ::selectors::Element for GeckoElement<'le> { NonTSPseudoClass::MozIsHTML => self.is_html_element_in_html_document(), NonTSPseudoClass::MozLWTheme | + NonTSPseudoClass::MozLWThemeBrightText | + NonTSPseudoClass::MozLWThemeDarkText | NonTSPseudoClass::MozLocaleDir(..) | NonTSPseudoClass::MozWindowInactive => { let state_bit = pseudo_class.document_state_flag(); diff --git a/components/style/global_style_data.rs b/components/style/global_style_data.rs index 576ac425b5a..80626267cdd 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::{Mutex, RwLock, RwLockReadGuard}; +use parking_lot::{RwLock, RwLockReadGuard}; use rayon; use std::env; -use std::io; -use std::thread; +use std::sync::atomic::{AtomicUsize, Ordering}; +use std::sync::Mutex; /// Global style data pub struct GlobalStyleData { @@ -43,32 +43,12 @@ fn thread_name(index: usize) -> String { format!("Style#{}", index) } -lazy_static! { - /// JoinHandles for spawned style threads. These will be joined during - /// StyleThreadPool::shutdown() after exiting the thread pool. - /// - /// This would be quite inefficient if rayon destroyed and re-created - /// threads regularly during threadpool operation in response to demand, - /// however rayon actually never destroys its threads until the entire - /// thread pool is shut-down, so the size of this list is bounded. - static ref STYLE_THREAD_JOIN_HANDLES: Mutex>> = - Mutex::new(Vec::new()); -} - -fn thread_spawn(options: rayon::ThreadBuilder) -> io::Result<()> { - let mut b = thread::Builder::new(); - if let Some(name) = options.name() { - b = b.name(name.to_owned()); - } - if let Some(stack_size) = options.stack_size() { - b = b.stack_size(stack_size); - } - let join_handle = b.spawn(|| options.run())?; - STYLE_THREAD_JOIN_HANDLES.lock().push(join_handle); - Ok(()) -} +// A counter so that we can wait for shutdown of all threads. See +// StyleThreadPool::shutdown. +static ALIVE_WORKER_THREADS: AtomicUsize = AtomicUsize::new(0); fn thread_startup(_index: usize) { + ALIVE_WORKER_THREADS.fetch_add(1, Ordering::Relaxed); thread_state::initialize_layout_worker_thread(); #[cfg(feature = "gecko")] unsafe { @@ -84,24 +64,33 @@ fn thread_shutdown(_: usize) { gecko_profiler::unregister_thread(); bindings::Gecko_SetJemallocThreadLocalArena(false); } + ALIVE_WORKER_THREADS.fetch_sub(1, Ordering::Relaxed); } impl StyleThreadPool { /// Shuts down the thread pool, waiting for all work to complete. pub fn shutdown() { - if STYLE_THREAD_JOIN_HANDLES.lock().is_empty() { + if ALIVE_WORKER_THREADS.load(Ordering::Relaxed) == 0 { return; } { // Drop the pool. let _ = STYLE_THREAD_POOL.lock().unwrap().style_thread_pool.write().take(); } - - // Join spawned threads until all of the threads have been joined. This - // will usually be pretty fast, as on shutdown there should be basically - // no threads left running. - while let Some(join_handle) = STYLE_THREAD_JOIN_HANDLES.lock().pop() { - let _ = join_handle.join(); + // Spin until all our threads are done. This will usually be pretty + // fast, as on shutdown there should be basically no threads left + // running. + // + // This still _technically_ doesn't give us the guarantee of TLS + // destructors running on the worker threads. For that we'd need help + // from rayon to properly join the threads. + // + // See https://github.com/rayon-rs/rayon/issues/688 + // + // So we instead intentionally leak TLS stuff (see BLOOM_KEY and co) for + // now until that's fixed. + while ALIVE_WORKER_THREADS.load(Ordering::Relaxed) != 0 { + std::thread::yield_now(); } } @@ -116,7 +105,7 @@ impl StyleThreadPool { lazy_static! { /// Global thread pool - pub static ref STYLE_THREAD_POOL: std::sync::Mutex = { + pub static ref STYLE_THREAD_POOL: Mutex = { let stylo_threads = env::var("STYLO_THREADS") .map(|s| s.parse::().expect("invalid STYLO_THREADS value")); let mut num_threads = match stylo_threads { @@ -160,7 +149,6 @@ lazy_static! { None } else { let workers = rayon::ThreadPoolBuilder::new() - .spawn_handler(thread_spawn) .num_threads(num_threads) .thread_name(thread_name) .start_handler(thread_startup) @@ -170,7 +158,7 @@ lazy_static! { workers.ok() }; - std::sync::Mutex::new(StyleThreadPool { + Mutex::new(StyleThreadPool { num_threads: if num_threads > 0 { Some(num_threads) } else { diff --git a/components/style/invalidation/element/document_state.rs b/components/style/invalidation/element/document_state.rs index 358257feac0..9ee97344a4c 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, NeedsSelectorFlags}; +use selectors::matching::{MatchingContext, MatchingMode, QuirksMode, VisitedHandlingMode}; /// A struct holding the members necessary to invalidate document state /// selectors. @@ -47,7 +47,6 @@ impl<'a, E: TElement, I> DocumentStateInvalidationProcessor<'a, E, I> { None, VisitedHandlingMode::AllLinksVisitedAndUnvisited, quirks_mode, - NeedsSelectorFlags::No, ); matching_context.extra_data = InvalidationMatchingData { diff --git a/components/style/invalidation/element/element_wrapper.rs b/components/style/invalidation/element/element_wrapper.rs index 84d0e5c351a..2aa5749fdee 100644 --- a/components/style/invalidation/element/element_wrapper.rs +++ b/components/style/invalidation/element/element_wrapper.rs @@ -166,11 +166,15 @@ where { type Impl = SelectorImpl; - fn match_non_ts_pseudo_class( + fn match_non_ts_pseudo_class( &self, pseudo_class: &NonTSPseudoClass, context: &mut MatchingContext, - ) -> bool { + _setter: &mut F, + ) -> bool + where + F: FnMut(&Self, ElementSelectorFlags), + { // Some pseudo-classes need special handling to evaluate them against // the snapshot. match *pseudo_class { @@ -228,20 +232,16 @@ where if flag.is_empty() { return self .element - .match_non_ts_pseudo_class(pseudo_class, context); + .match_non_ts_pseudo_class(pseudo_class, context, &mut |_, _| {}); } match self.snapshot().and_then(|s| s.state()) { Some(snapshot_state) => snapshot_state.intersects(flag), None => self .element - .match_non_ts_pseudo_class(pseudo_class, context), + .match_non_ts_pseudo_class(pseudo_class, context, &mut |_, _| {}), } } - fn set_selector_flags(&self, _flags: ElementSelectorFlags) { - debug_assert!(false, "Shouldn't need selector flags for invalidation"); - } - fn match_pseudo_element( &self, pseudo_element: &PseudoElement, diff --git a/components/style/invalidation/element/invalidation_map.rs b/components/style/invalidation/element/invalidation_map.rs index f083225b4b7..0bba423a17f 100644 --- a/components/style/invalidation/element/invalidation_map.rs +++ b/components/style/invalidation/element/invalidation_map.rs @@ -67,8 +67,6 @@ 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 1d02d52947b..bbb1fb46a80 100644 --- a/components/style/invalidation/element/state_and_attributes.rs +++ b/components/style/invalidation/element/state_and_attributes.rs @@ -19,7 +19,8 @@ use crate::selector_parser::Snapshot; use crate::stylesheets::origin::OriginSet; use crate::{Atom, WeakAtom}; use selectors::attr::CaseSensitivity; -use selectors::matching::{matches_selector, MatchingContext, MatchingMode, VisitedHandlingMode, NeedsSelectorFlags}; +use selectors::matching::matches_selector; +use selectors::matching::{MatchingContext, MatchingMode, VisitedHandlingMode}; use selectors::NthIndexCache; use smallvec::SmallVec; @@ -66,7 +67,6 @@ impl<'a, 'b: 'a, E: TElement + 'b> StateAndAttrInvalidationProcessor<'a, 'b, E> Some(nth_index_cache), VisitedHandlingMode::AllLinksVisitedAndUnvisited, shared_context.quirks_mode(), - NeedsSelectorFlags::No, ); Self { @@ -84,7 +84,7 @@ pub fn check_dependency( dependency: &Dependency, element: &E, wrapper: &W, - context: &mut MatchingContext<'_, E::Impl>, + mut context: &mut MatchingContext<'_, E::Impl>, ) -> bool where E: TElement, @@ -95,7 +95,8 @@ where dependency.selector_offset, None, element, - context, + &mut context, + &mut |_, _| {}, ); let matched_then = matches_selector( @@ -103,7 +104,8 @@ where dependency.selector_offset, None, wrapper, - context, + &mut context, + &mut |_, _| {}, ); matched_then != matches_now diff --git a/components/style/invalidation/stylesheets.rs b/components/style/invalidation/stylesheets.rs index 3c4ee845055..53130de7a2e 100644 --- a/components/style/invalidation/stylesheets.rs +++ b/components/style/invalidation/stylesheets.rs @@ -556,7 +556,6 @@ impl StylesheetInvalidationSet { FontFace(..) | Keyframes(..) | ScrollTimeline(..) | - Container(..) | Style(..) => { if is_generic_change { // TODO(emilio): We need to do this for selector / keyframe @@ -611,7 +610,7 @@ impl StylesheetInvalidationSet { } }, Document(..) | Namespace(..) | Import(..) | Media(..) | Supports(..) | - Container(..) | LayerStatement(..) | LayerBlock(..) => { + LayerStatement(..) | LayerBlock(..) => { // Do nothing, relevant nested rules are visited as part of the // iteration. }, diff --git a/components/style/lib.rs b/components/style/lib.rs index b1cec716fcf..2967fbcf7ce 100644 --- a/components/style/lib.rs +++ b/components/style/lib.rs @@ -63,8 +63,6 @@ pub use servo_arc; #[macro_use] extern crate servo_atoms; #[macro_use] -extern crate static_assertions; -#[macro_use] extern crate style_derive; #[macro_use] extern crate to_shmem_derive; @@ -103,12 +101,10 @@ pub mod invalidation; #[allow(missing_docs)] // TODO. pub mod logical_geometry; pub mod matching; +#[macro_use] pub mod media_queries; pub mod parallel; pub mod parser; -pub mod piecewise_linear; -#[macro_use] -pub mod queries; pub mod rule_cache; pub mod rule_collector; pub mod rule_tree; @@ -188,7 +184,7 @@ pub mod gecko_properties { } macro_rules! reexport_computed_values { - ( $( { $name: ident } )+ ) => { + ( $( { $name: ident, $boxed: expr } )+ ) => { /// Types for [computed values][computed]. /// /// [computed]: https://drafts.csswg.org/css-cascade/#computed @@ -202,6 +198,7 @@ macro_rules! reexport_computed_values { } } longhand_properties_idents!(reexport_computed_values); + #[cfg(feature = "gecko")] use crate::gecko_string_cache::WeakAtom; #[cfg(feature = "servo")] diff --git a/components/style/logical_geometry.rs b/components/style/logical_geometry.rs index c848462deff..d4d058adc61 100644 --- a/components/style/logical_geometry.rs +++ b/components/style/logical_geometry.rs @@ -168,11 +168,6 @@ impl WritingMode { flags } - /// Returns the `horizontal-tb` value. - pub fn horizontal_tb() -> Self { - Self::from_bits_truncate(0) - } - #[inline] pub fn is_vertical(&self) -> bool { self.intersects(WritingMode::VERTICAL) @@ -877,10 +872,10 @@ impl LogicalMargin { inline_start: T, ) -> LogicalMargin { LogicalMargin { - block_start, - inline_end, - block_end, - inline_start, + block_start: block_start, + inline_end: inline_end, + block_end: block_end, + inline_start: inline_start, debug_writing_mode: DebugWritingMode::new(mode), } } @@ -1055,18 +1050,6 @@ impl 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); @@ -1076,32 +1059,42 @@ impl LogicalMargin { let left; if mode.is_vertical() { if mode.is_vertical_lr() { - left = self.block_start.clone(); - right = self.block_end.clone(); + left = self.block_start; + right = self.block_end; } else { - right = self.block_start.clone(); - left = self.block_end.clone(); + right = self.block_start; + left = self.block_end; } if mode.is_inline_tb() { - top = self.inline_start.clone(); - bottom = self.inline_end.clone(); + top = self.inline_start; + bottom = self.inline_end; } else { - bottom = self.inline_start.clone(); - top = self.inline_end.clone(); + bottom = self.inline_start; + top = self.inline_end; } } else { - top = self.block_start.clone(); - bottom = self.block_end.clone(); + top = self.block_start; + bottom = self.block_end; if mode.is_bidi_ltr() { - left = self.inline_start.clone(); - right = self.inline_end.clone(); + left = self.inline_start; + right = self.inline_end; } else { - right = self.inline_start.clone(); - left = self.inline_end.clone(); + right = self.inline_start; + left = self.inline_end; } } SideOffsets2D::new(top, right, bottom, left) } + + #[inline] + pub fn convert(&self, mode_from: WritingMode, mode_to: WritingMode) -> LogicalMargin { + 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 3084d33dd3d..e27a554acf4 100644 --- a/components/style/macros.rs +++ b/components/style/macros.rs @@ -128,11 +128,3 @@ macro_rules! local_name { $crate::values::AtomIdent(atom!($s)) }; } - -/// Asserts the size of a type at compile time. -macro_rules! size_of_test { - ($t: ty, $expected_size: expr) => { - #[cfg(target_pointer_width = "64")] - const_assert_eq!(std::mem::size_of::<$t>(), $expected_size); - }; -} diff --git a/components/style/matching.rs b/components/style/matching.rs index 2e18a8de962..f2d300df4b2 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}; +use crate::context::{CascadeInputs, ElementCascadeInputs, QuirksMode, SelectorFlagsMap}; use crate::context::{SharedStyleContext, StyleContext}; use crate::data::{ElementData, ElementStyles}; use crate::dom::TElement; @@ -26,6 +26,7 @@ use crate::style_resolver::{PseudoElementResolution, StyleResolverForElement}; use crate::stylesheets::layer_rule::LayerOrder; use crate::stylist::RuleInclusion; use crate::traversal_flags::TraversalFlags; +use selectors::matching::ElementSelectorFlags; use servo_arc::{Arc, ArcBorrow}; /// Represents the result of comparing an element's old and new style. @@ -254,8 +255,8 @@ trait PrivateMatchMethods: TElement { new_style: &ComputedValues, pseudo_element: Option, ) -> bool { - let new_ui_style = new_style.get_ui(); - let new_style_specifies_animations = new_ui_style.specifies_animations(); + let new_box_style = new_style.get_box(); + let new_style_specifies_animations = new_box_style.specifies_animations(); let has_animations = self.has_css_animations(&context.shared, pseudo_element); if !new_style_specifies_animations && !has_animations { @@ -282,7 +283,7 @@ trait PrivateMatchMethods: TElement { }, }; - let old_ui_style = old_style.get_ui(); + let old_box_style = old_style.get_box(); let keyframes_or_timeline_could_have_changed = context .shared @@ -301,12 +302,12 @@ trait PrivateMatchMethods: TElement { } // If the animations changed, well... - if !old_ui_style.animations_equals(new_ui_style) { + if !old_box_style.animations_equals(new_box_style) { return true; } - let old_display = old_style.clone_display(); - let new_display = new_style.clone_display(); + let old_display = old_box_style.clone_display(); + let new_display = new_box_style.clone_display(); // If we were display: none, we may need to trigger animations. if old_display == Display::None && new_display != Display::None { @@ -341,13 +342,14 @@ trait PrivateMatchMethods: TElement { None => return false, }; + let new_box_style = new_style.get_box(); if !self.has_css_transitions(context.shared, pseudo_element) && - !new_style.get_ui().specifies_transitions() + !new_box_style.specifies_transitions() { return false; } - if new_style.clone_display().is_none() || old_style.clone_display().is_none() { + if new_box_style.clone_display().is_none() || old_style.clone_display().is_none() { return false; } @@ -764,8 +766,8 @@ trait PrivateMatchMethods: TElement { }, } - let old_display = old_values.clone_display(); - let new_display = new_values.clone_display(); + let old_display = old_values.get_box().clone_display(); + let new_display = new_values.get_box().clone_display(); if old_display != new_display { // If we used to be a display: none element, and no longer are, our @@ -1005,6 +1007,51 @@ pub trait MatchMethods: TElement { cascade_requirement } + /// Applies selector flags to an element, deferring mutations of the parent + /// until after the traversal. + /// + /// TODO(emilio): This is somewhat inefficient, because it doesn't take + /// advantage of us knowing that the traversal is sequential. + fn apply_selector_flags( + &self, + map: &mut SelectorFlagsMap, + 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/queries/condition.rs b/components/style/media_queries/media_condition.rs similarity index 57% rename from components/style/queries/condition.rs rename to components/style/media_queries/media_condition.rs index da8ad02d595..f735704556e 100644 --- a/components/style/queries/condition.rs +++ b/components/style/media_queries/media_condition.rs @@ -2,14 +2,13 @@ * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ -//! A query condition: +//! A media query condition: //! //! https://drafts.csswg.org/mediaqueries-4/#typedef-media-condition -//! https://drafts.csswg.org/css-contain-3/#typedef-container-condition -use super::{QueryFeatureExpression, FeatureType, FeatureFlags}; +use super::{Device, MediaFeatureExpression}; +use crate::context::QuirksMode; use crate::parser::ParserContext; -use crate::values::computed; use cssparser::{Parser, Token}; use std::fmt::{self, Write}; use style_traits::{CssWriter, ParseError, StyleParseErrorKind, ToCss}; @@ -29,38 +28,38 @@ enum AllowOr { No, } -/// Represents a condition. +/// Represents a media condition. #[derive(Clone, Debug, MallocSizeOf, PartialEq, ToShmem)] -pub enum QueryCondition { - /// A simple feature expression, implicitly parenthesized. - Feature(QueryFeatureExpression), +pub enum MediaCondition { + /// A simple media feature expression, implicitly parenthesized. + Feature(MediaFeatureExpression), /// A negation of a condition. - Not(Box), + Not(Box), /// A set of joint operations. - Operation(Box<[QueryCondition]>, Operator), + Operation(Box<[MediaCondition]>, Operator), /// A condition wrapped in parenthesis. - InParens(Box), + InParens(Box), } -impl ToCss for QueryCondition { +impl ToCss for MediaCondition { fn to_css(&self, dest: &mut CssWriter) -> fmt::Result where W: fmt::Write, { match *self { - // NOTE(emilio): QueryFeatureExpression already includes the + // NOTE(emilio): MediaFeatureExpression already includes the // parenthesis. - QueryCondition::Feature(ref f) => f.to_css(dest), - QueryCondition::Not(ref c) => { + MediaCondition::Feature(ref f) => f.to_css(dest), + MediaCondition::Not(ref c) => { dest.write_str("not ")?; c.to_css(dest) }, - QueryCondition::InParens(ref c) => { + MediaCondition::InParens(ref c) => { dest.write_char('(')?; c.to_css(dest)?; dest.write_char(')') }, - QueryCondition::Operation(ref list, op) => { + MediaCondition::Operation(ref list, op) => { let mut iter = list.iter(); iter.next().unwrap().to_css(dest)?; for item in iter { @@ -75,60 +74,28 @@ impl ToCss for QueryCondition { } } -impl QueryCondition { - /// Parse a single condition. +impl MediaCondition { + /// Parse a single media condition. pub fn parse<'i, 't>( context: &ParserContext, input: &mut Parser<'i, 't>, - feature_type: FeatureType, ) -> Result> { - Self::parse_internal(context, input, feature_type, AllowOr::Yes) + Self::parse_internal(context, input, AllowOr::Yes) } - 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. + /// Parse a single media condition, disallowing `or` expressions. /// - /// To be used from the legacy query syntax. + /// To be used from the legacy media query syntax. pub fn parse_disallow_or<'i, 't>( context: &ParserContext, input: &mut Parser<'i, 't>, - feature_type: FeatureType, ) -> Result> { - Self::parse_internal(context, input, feature_type, AllowOr::No) + Self::parse_internal(context, input, AllowOr::No) } fn parse_internal<'i, 't>( context: &ParserContext, input: &mut Parser<'i, 't>, - feature_type: FeatureType, allow_or: AllowOr, ) -> Result> { let location = input.current_source_location(); @@ -141,12 +108,12 @@ impl QueryCondition { }; if is_negation { - let inner_condition = Self::parse_in_parens(context, input, feature_type)?; - return Ok(QueryCondition::Not(Box::new(inner_condition))); + let inner_condition = Self::parse_in_parens(context, input)?; + return Ok(MediaCondition::Not(Box::new(inner_condition))); } // ParenthesisBlock. - let first_condition = Self::parse_paren_block(context, input, feature_type)?; + let first_condition = Self::parse_paren_block(context, input)?; let operator = match input.try_parse(Operator::parse) { Ok(op) => op, Err(..) => return Ok(first_condition), @@ -158,7 +125,7 @@ impl QueryCondition { let mut conditions = vec![]; conditions.push(first_condition); - conditions.push(Self::parse_in_parens(context, input, feature_type)?); + conditions.push(Self::parse_in_parens(context, input)?); let delim = match operator { Operator::And => "and", @@ -167,52 +134,50 @@ impl QueryCondition { loop { if input.try_parse(|i| i.expect_ident_matching(delim)).is_err() { - return Ok(QueryCondition::Operation( + return Ok(MediaCondition::Operation( conditions.into_boxed_slice(), operator, )); } - conditions.push(Self::parse_in_parens(context, input, feature_type)?); + conditions.push(Self::parse_in_parens(context, input)?); } } - /// Parse a condition in parentheses. + /// Parse a media condition in parentheses. pub fn parse_in_parens<'i, 't>( context: &ParserContext, input: &mut Parser<'i, 't>, - feature_type: FeatureType, ) -> Result> { input.expect_parenthesis_block()?; - Self::parse_paren_block(context, input, feature_type) + Self::parse_paren_block(context, input) } fn parse_paren_block<'i, 't>( context: &ParserContext, input: &mut Parser<'i, 't>, - feature_type: FeatureType, ) -> Result> { input.parse_nested_block(|input| { // Base case. - if let Ok(inner) = input.try_parse(|i| Self::parse(context, i, feature_type)) { - return Ok(QueryCondition::InParens(Box::new(inner))); + if let Ok(inner) = input.try_parse(|i| Self::parse(context, i)) { + return Ok(MediaCondition::InParens(Box::new(inner))); } - let expr = QueryFeatureExpression::parse_in_parenthesis_block(context, input, feature_type)?; - Ok(QueryCondition::Feature(expr)) + let expr = MediaFeatureExpression::parse_in_parenthesis_block(context, input)?; + Ok(MediaCondition::Feature(expr)) }) } /// Whether this condition matches the device and quirks mode. - pub fn matches(&self, context: &computed::Context) -> bool { + pub fn matches(&self, device: &Device, quirks_mode: QuirksMode) -> bool { match *self { - QueryCondition::Feature(ref f) => f.matches(context), - QueryCondition::InParens(ref c) => c.matches(context), - QueryCondition::Not(ref c) => !c.matches(context), - QueryCondition::Operation(ref conditions, op) => { + MediaCondition::Feature(ref f) => f.matches(device, quirks_mode), + MediaCondition::InParens(ref c) => c.matches(device, quirks_mode), + MediaCondition::Not(ref c) => !c.matches(device, quirks_mode), + MediaCondition::Operation(ref conditions, op) => { let mut iter = conditions.iter(); match op { - Operator::And => iter.all(|c| c.matches(context)), - Operator::Or => iter.any(|c| c.matches(context)), + Operator::And => iter.all(|c| c.matches(device, quirks_mode)), + Operator::Or => iter.any(|c| c.matches(device, quirks_mode)), } }, } diff --git a/components/style/queries/feature.rs b/components/style/media_queries/media_feature.rs similarity index 57% rename from components/style/queries/feature.rs rename to components/style/media_queries/media_feature.rs index 9efd64f8c23..e6edff3f68a 100644 --- a/components/style/queries/feature.rs +++ b/components/style/media_queries/media_feature.rs @@ -2,10 +2,13 @@ * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ -//! Query features. +//! Media features. +use super::media_feature_expression::RangeOrOperator; +use super::Device; use crate::parser::ParserContext; -use crate::values::computed::{self, CSSPixelLength, Resolution, Ratio}; +use crate::values::computed::Ratio; +use crate::values::computed::{CSSPixelLength, Resolution}; use crate::Atom; use cssparser::Parser; use std::fmt; @@ -14,7 +17,12 @@ use style_traits::ParseError; /// A generic discriminant for an enum value. pub type KeywordDiscriminant = u8; -type QueryFeatureGetter = fn(device: &computed::Context) -> T; +type MediaFeatureEvaluator = fn( + device: &Device, + // null == no value was given in the query. + value: Option, + range_or_operator: Option, +) -> bool; /// Serializes a given discriminant. /// @@ -28,19 +36,19 @@ pub type KeywordParser = for<'a, 'i, 't> fn( input: &'a mut Parser<'i, 't>, ) -> Result>; -/// An evaluator for a given feature. +/// An evaluator for a given media feature. /// /// This determines the kind of values that get parsed, too. #[allow(missing_docs)] pub enum Evaluator { - Length(QueryFeatureGetter), - Integer(QueryFeatureGetter), - Float(QueryFeatureGetter), - BoolInteger(QueryFeatureGetter), + Length(MediaFeatureEvaluator), + Integer(MediaFeatureEvaluator), + Float(MediaFeatureEvaluator), + BoolInteger(MediaFeatureEvaluator), /// A non-negative number ratio, such as the one from device-pixel-ratio. - NumberRatio(QueryFeatureGetter), + NumberRatio(MediaFeatureEvaluator), /// A resolution. - Resolution(QueryFeatureGetter), + Resolution(MediaFeatureEvaluator), /// A keyword value. Enumerated { /// The parser to get a discriminant given a string. @@ -52,8 +60,9 @@ pub enum Evaluator { serializer: KeywordSerializer, /// The evaluator itself. This is guaranteed to be called with a /// keyword that `parser` has produced. - evaluator: fn(&computed::Context, Option) -> bool, + evaluator: MediaFeatureEvaluator, }, + Ident(MediaFeatureEvaluator), } /// A simple helper macro to create a keyword evaluator. @@ -67,14 +76,14 @@ macro_rules! keyword_evaluator { context: &$crate::parser::ParserContext, input: &mut $crate::cssparser::Parser<'i, 't>, ) -> Result< - $crate::queries::feature::KeywordDiscriminant, + $crate::media_queries::media_feature::KeywordDiscriminant, ::style_traits::ParseError<'i>, > { let kw = <$keyword_type as $crate::parser::Parse>::parse(context, input)?; - Ok(kw as $crate::queries::feature::KeywordDiscriminant) + Ok(kw as $crate::media_queries::media_feature::KeywordDiscriminant) } - fn __serialize(kw: $crate::queries::feature::KeywordDiscriminant) -> String { + fn __serialize(kw: $crate::media_queries::media_feature::KeywordDiscriminant) -> String { // This unwrap is ok because the only discriminants that get // back to us is the ones that `parse` produces. let value: $keyword_type = ::num_traits::cast::FromPrimitive::from_u8(kw).unwrap(); @@ -82,17 +91,24 @@ macro_rules! keyword_evaluator { } fn __evaluate( - context: &$crate::values::computed::Context, - value: Option<$crate::queries::feature::KeywordDiscriminant>, + device: &$crate::media_queries::Device, + value: Option<$crate::media_queries::media_feature::KeywordDiscriminant>, + range_or_operator: Option< + $crate::media_queries::media_feature_expression::RangeOrOperator, + >, ) -> bool { + debug_assert!( + range_or_operator.is_none(), + "Since when do keywords accept ranges?" + ); // This unwrap is ok because the only discriminants that get // back to us is the ones that `parse` produces. let value: Option<$keyword_type> = value.map(|kw| ::num_traits::cast::FromPrimitive::from_u8(kw).unwrap()); - $actual_evaluator(context, value) + $actual_evaluator(device, value) } - $crate::queries::feature::Evaluator::Enumerated { + $crate::media_queries::media_feature::Evaluator::Enumerated { parser: __parse, serializer: __serialize, evaluator: __evaluate, @@ -101,46 +117,17 @@ macro_rules! keyword_evaluator { } bitflags! { - /// Different flags or toggles that change how a expression is parsed or - /// evaluated. - #[derive(ToShmem)] - pub struct FeatureFlags : u8 { + /// Different requirements or toggles that change how a expression is + /// parsed. + pub struct ParsingRequirements: u8 { /// The feature should only be parsed in chrome and ua sheets. const CHROME_AND_UA_ONLY = 1 << 0; /// The feature requires a -webkit- prefix. const WEBKIT_PREFIX = 1 << 1; - /// The feature requires the inline-axis containment. - const CONTAINER_REQUIRES_INLINE_AXIS = 1 << 2; - /// The feature requires the block-axis containment. - const CONTAINER_REQUIRES_BLOCK_AXIS = 1 << 3; - /// The feature requires containment in the physical width axis. - const CONTAINER_REQUIRES_WIDTH_AXIS = 1 << 4; - /// The feature requires containment in the physical height axis. - const CONTAINER_REQUIRES_HEIGHT_AXIS = 1 << 5; } } -impl FeatureFlags { - /// Returns parsing requirement flags. - pub fn parsing_requirements(self) -> Self { - self.intersection(Self::CHROME_AND_UA_ONLY | Self::WEBKIT_PREFIX) - } - - /// Returns all the container axis flags. - pub fn all_container_axes() -> Self { - Self::CONTAINER_REQUIRES_INLINE_AXIS | - Self::CONTAINER_REQUIRES_BLOCK_AXIS | - Self::CONTAINER_REQUIRES_WIDTH_AXIS | - Self::CONTAINER_REQUIRES_HEIGHT_AXIS - } - - /// Returns our subset of container axis flags. - pub fn container_axes(self) -> Self { - self.intersection(Self::all_container_axes()) - } -} - -/// Whether a feature allows ranges or not. +/// Whether a media feature allows ranges or not. #[derive(Clone, Copy, Debug, Eq, PartialEq)] #[allow(missing_docs)] pub enum AllowsRanges { @@ -148,45 +135,46 @@ pub enum AllowsRanges { No, } -/// A description of a feature. -pub struct QueryFeatureDescription { - /// The feature name, in ascii lowercase. +/// A description of a media feature. +pub struct MediaFeatureDescription { + /// The media feature name, in ascii lowercase. pub name: Atom, /// Whether min- / max- prefixes are allowed or not. pub allows_ranges: AllowsRanges, /// The evaluator, which we also use to determine which kind of value to /// parse. pub evaluator: Evaluator, - /// Different feature-specific flags. - pub flags: FeatureFlags, + /// Different requirements that need to hold for the feature to be + /// successfully parsed. + pub requirements: ParsingRequirements, } -impl QueryFeatureDescription { - /// Whether this feature allows ranges. +impl MediaFeatureDescription { + /// Whether this media feature allows ranges. #[inline] pub fn allows_ranges(&self) -> bool { self.allows_ranges == AllowsRanges::Yes } } -/// A simple helper to construct a `QueryFeatureDescription`. +/// A simple helper to construct a `MediaFeatureDescription`. macro_rules! feature { - ($name:expr, $allows_ranges:expr, $evaluator:expr, $flags:expr,) => { - $crate::queries::feature::QueryFeatureDescription { + ($name:expr, $allows_ranges:expr, $evaluator:expr, $reqs:expr,) => { + $crate::media_queries::media_feature::MediaFeatureDescription { name: $name, allows_ranges: $allows_ranges, evaluator: $evaluator, - flags: $flags, + requirements: $reqs, } }; } -impl fmt::Debug for QueryFeatureDescription { +impl fmt::Debug for MediaFeatureDescription { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - f.debug_struct("QueryFeatureDescription") + f.debug_struct("MediaFeatureExpression") .field("name", &self.name) .field("allows_ranges", &self.allows_ranges) - .field("flags", &self.flags) + .field("requirements", &self.requirements) .finish() } } diff --git a/components/style/media_queries/media_feature_expression.rs b/components/style/media_queries/media_feature_expression.rs new file mode 100644 index 00000000000..80827af401c --- /dev/null +++ b/components/style/media_queries/media_feature_expression.rs @@ -0,0 +1,535 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ + +//! Parsing for media feature expressions, like `(foo: bar)` or +//! `(width >= 400px)`. + +use super::media_feature::{Evaluator, MediaFeatureDescription}; +use super::media_feature::{KeywordDiscriminant, ParsingRequirements}; +use super::Device; +use crate::context::QuirksMode; +#[cfg(feature = "gecko")] +use crate::gecko::media_features::MEDIA_FEATURES; +use crate::parser::{Parse, ParserContext}; +#[cfg(feature = "servo")] +use crate::servo::media_queries::MEDIA_FEATURES; +use crate::str::{starts_with_ignore_ascii_case, string_as_ascii_lowercase}; +use crate::values::computed::{self, Ratio, ToComputedValue}; +use crate::values::specified::{Integer, Length, Number, Resolution}; +use crate::values::{serialize_atom_identifier, CSSFloat}; +use crate::{Atom, Zero}; +use cssparser::{Parser, Token}; +use std::cmp::{Ordering, PartialOrd}; +use std::fmt::{self, Write}; +use style_traits::{CssWriter, ParseError, StyleParseErrorKind, ToCss}; + +/// The kind of matching that should be performed on a media feature value. +#[derive(Clone, Copy, Debug, Eq, MallocSizeOf, PartialEq, ToShmem)] +pub enum Range { + /// At least the specified value. + Min, + /// At most the specified value. + Max, +} + +/// The operator that was specified in this media feature. +#[derive(Clone, Copy, Debug, Eq, MallocSizeOf, PartialEq, ToShmem)] +pub enum Operator { + /// = + Equal, + /// > + GreaterThan, + /// >= + GreaterThanEqual, + /// < + LessThan, + /// <= + LessThanEqual, +} + +impl ToCss for Operator { + fn to_css(&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 8ce317db534..abc2ae6dbc8 100644 --- a/components/style/media_queries/media_list.rs +++ b/components/style/media_queries/media_list.rs @@ -10,7 +10,6 @@ use super::{Device, MediaQuery, Qualifier}; use crate::context::QuirksMode; use crate::error_reporting::ContextualParseError; use crate::parser::ParserContext; -use crate::values::computed; use cssparser::{Delimiter, Parser}; use cssparser::{ParserInput, Token}; @@ -75,17 +74,15 @@ impl MediaList { pub fn evaluate(&self, device: &Device, quirks_mode: QuirksMode) -> bool { // Check if it is an empty media query list or any queries match. // https://drafts.csswg.org/mediaqueries-4/#mq-list - if self.media_queries.is_empty() { - return true; - } - - computed::Context::for_media_query_evaluation(device, quirks_mode, |context| { + self.media_queries.is_empty() || self.media_queries.iter().any(|mq| { let media_match = mq.media_type.matches(device.media_type()); // Check if the media condition match. let query_match = media_match && - mq.condition.as_ref().map_or(true, |c| c.matches(context)); + mq.condition + .as_ref() + .map_or(true, |c| c.matches(device, quirks_mode)); // Apply the logical NOT qualifier to the result match mq.qualifier { @@ -93,7 +90,6 @@ impl MediaList { _ => query_match, } }) - }) } /// Whether this `MediaList` contains no media queries. diff --git a/components/style/media_queries/media_query.rs b/components/style/media_queries/media_query.rs index 1e737fa695f..83e34c5037f 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 crate::queries::{QueryCondition, FeatureType}; +use super::media_condition::MediaCondition; use crate::parser::ParserContext; use crate::str::string_as_ascii_lowercase; use crate::values::CustomIdent; @@ -66,7 +66,7 @@ pub struct MediaQuery { pub media_type: MediaQueryType, /// The condition that this media query contains. This cannot have `or` /// in the first level. - pub condition: Option, + 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(QueryCondition::parse(context, input, FeatureType::Media)?) + Some(MediaCondition::parse(context, input)?) } else if input.try_parse(|i| i.expect_ident_matching("and")).is_ok() { - Some(QueryCondition::parse_disallow_or(context, input, FeatureType::Media)?) + Some(MediaCondition::parse_disallow_or(context, input)?) } else { None }; diff --git a/components/style/media_queries/mod.rs b/components/style/media_queries/mod.rs index 833f6f53cb9..ca385857481 100644 --- a/components/style/media_queries/mod.rs +++ b/components/style/media_queries/mod.rs @@ -6,9 +6,15 @@ //! //! [mq]: https://drafts.csswg.org/mediaqueries/ +mod media_condition; mod media_list; mod media_query; +#[macro_use] +pub mod media_feature; +pub mod media_feature_expression; +pub use self::media_condition::MediaCondition; +pub use self::media_feature_expression::MediaFeatureExpression; pub use self::media_list::MediaList; pub use self::media_query::{MediaQuery, MediaQueryType, MediaType, Qualifier}; diff --git a/components/style/piecewise_linear.rs b/components/style/piecewise_linear.rs deleted file mode 100644 index 0ac0220e44a..00000000000 --- a/components/style/piecewise_linear.rs +++ /dev/null @@ -1,223 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ - -//! A piecewise linear function, following CSS linear easing -/// draft as in https://github.com/w3c/csswg-drafts/pull/6533. -use euclid::approxeq::ApproxEq; -use itertools::Itertools; - -use crate::values::CSSFloat; - -type ValueType = CSSFloat; -/// a single entry in a piecewise linear function. -#[derive(Clone, Copy)] -#[repr(C)] -struct Entry { - x: ValueType, - y: ValueType, -} - -/// Representation of a piecewise linear function, a series of linear functions. -#[derive(Default)] -#[repr(C)] -pub struct PiecewiseLinearFunction { - entries: crate::OwnedSlice, -} - -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 3d3c6b833b8..37d47e1ce61 100644 --- a/components/style/properties/cascade.rs +++ b/components/style/properties/cascade.rs @@ -28,8 +28,34 @@ use smallvec::SmallVec; use std::borrow::Cow; use std::cell::RefCell; +/// We split the cascade in two phases: 'early' properties, and 'late' +/// properties. +/// +/// Early properties are the ones that don't have dependencies _and_ other +/// properties depend on, for example, writing-mode related properties, color +/// (for currentColor), or font-size (for em, etc). +/// +/// Late properties are all the others. +trait CascadePhase { + fn is_early() -> bool; +} + +struct EarlyProperties; +impl CascadePhase for EarlyProperties { + fn is_early() -> bool { + true + } +} + +struct LateProperties; +impl CascadePhase for LateProperties { + fn is_early() -> bool { + false + } +} + #[derive(Clone, Copy, Debug, Eq, PartialEq)] -enum CanHaveLogicalProperties { +enum ApplyResetProperties { No, Yes, } @@ -257,7 +283,6 @@ where let inherited_style = parent_style.unwrap_or(device.default_computed_values()); let mut declarations = SmallVec::<[(&_, CascadePriority); 32]>::new(); - let mut referenced_properties = LonghandIdSet::default(); let custom_properties = { let mut builder = CustomPropertiesBuilder::new(inherited_style.custom_properties(), device); @@ -265,8 +290,6 @@ where declarations.push((declaration, priority)); if let PropertyDeclaration::Custom(ref declaration) = *declaration { builder.cascade(declaration, priority); - } else { - referenced_properties.insert(declaration.id().as_longhand().unwrap()); } } @@ -292,74 +315,46 @@ where in_media_query: false, for_smil_animation: false, for_non_inherited_property: None, - container_info: None, quirks_mode, rule_cache_conditions: RefCell::new(rule_cache_conditions), }; - let using_cached_reset_properties; - let mut cascade = Cascade::new(&mut context, cascade_mode, &referenced_properties); - let mut shorthand_cache = ShorthandsWithPropertyReferencesCache::default(); + let using_cached_reset_properties = { + let mut cascade = Cascade::new(&mut context, cascade_mode); + let mut shorthand_cache = ShorthandsWithPropertyReferencesCache::default(); - let properties_to_apply = match cascade.cascade_mode { - CascadeMode::Visited { writing_mode } => { - cascade.context.builder.writing_mode = writing_mode; - // We never insert visited styles into the cache so we don't need to - // try looking it up. It also wouldn't be super-profitable, only a - // handful reset properties are non-inherited. - using_cached_reset_properties = false; - LonghandIdSet::visited_dependent() - }, - CascadeMode::Unvisited { visited_rules } => { - if cascade.apply_properties( - CanHaveLogicalProperties::No, - LonghandIdSet::writing_mode_group(), - declarations.iter().cloned(), - &mut shorthand_cache, - ) { - cascade.compute_writing_mode(); - } + cascade.apply_properties::( + ApplyResetProperties::Yes, + declarations.iter().cloned(), + &mut shorthand_cache, + ); - if cascade.apply_properties( - CanHaveLogicalProperties::No, - LonghandIdSet::fonts_and_color_group(), - declarations.iter().cloned(), - &mut shorthand_cache, - ) { - cascade.fixup_font_stuff(); - } + cascade.compute_visited_style_if_needed( + element, + parent_style, + parent_style_ignoring_first_line, + layout_parent_style, + guards, + ); - if let Some(visited_rules) = visited_rules { - cascade.compute_visited_style_if_needed( - element, - parent_style, - parent_style_ignoring_first_line, - layout_parent_style, - visited_rules, - guards, - ); - } + let using_cached_reset_properties = + cascade.try_to_use_cached_reset_properties(rule_cache, guards); - using_cached_reset_properties = - cascade.try_to_use_cached_reset_properties(rule_cache, guards); + let apply_reset = if using_cached_reset_properties { + ApplyResetProperties::No + } else { + ApplyResetProperties::Yes + }; - if using_cached_reset_properties { - LonghandIdSet::late_group_only_inherited() - } else { - LonghandIdSet::late_group() - } - } + cascade.apply_properties::( + apply_reset, + declarations.iter().cloned(), + &mut shorthand_cache, + ); + + using_cached_reset_properties }; - cascade.apply_properties( - CanHaveLogicalProperties::Yes, - properties_to_apply, - declarations.iter().cloned(), - &mut shorthand_cache, - ); - - cascade.finished_applying_properties(); - context.builder.clear_modified_reset(); if matches!(cascade_mode, CascadeMode::Unvisited { .. }) { @@ -418,7 +413,7 @@ fn tweak_when_ignoring_colors( fn alpha_channel(color: &Color, context: &computed::Context) -> u8 { // We assume here currentColor is opaque. - let color = color.to_computed_value(context).into_rgba(RGBA::new(0, 0, 0, 255)); + let color = color.to_computed_value(context).to_rgba(RGBA::new(0, 0, 0, 255)); color.alpha } @@ -433,17 +428,14 @@ fn tweak_when_ignoring_colors( // otherwise, this is needed to preserve semi-transparent // backgrounds. // - // NOTE(emilio): We honor transparent unconditionally, like we do - // for color, even though it causes issues like bug 1625036. The - // reasoning is that the conditions that trigger that (having - // mismatched widget and default backgrounds) are both uncommon, and - // broken in other applications as well, and not honoring - // transparent makes stuff uglier or break unconditionally + // NOTE(emilio): We revert even for alpha == 0. Not doing so would + // be a bit special casey, even though it causes issues like + // bug 1625036. The reasoning is that the conditions that trigger + // that (having mismatched widget and default backgrounds) are both + // uncommon, and broken in other applications as well, and not + // honoring transparent makes stuff uglier or break unconditionally // (bug 1666059, bug 1755713). let alpha = alpha_channel(color, context); - if alpha == 0 { - return; - } let mut color = context.builder.device.default_background_color(); color.alpha = alpha; declarations_to_apply_unless_overriden @@ -505,8 +497,6 @@ fn tweak_when_ignoring_colors( struct Cascade<'a, 'b: 'a> { context: &'a mut computed::Context<'b>, cascade_mode: CascadeMode<'a>, - /// All the properties that have a declaration in the cascade. - referenced: &'a LonghandIdSet, seen: LonghandIdSet, author_specified: LonghandIdSet, reverted_set: LonghandIdSet, @@ -514,15 +504,10 @@ struct Cascade<'a, 'b: 'a> { } impl<'a, 'b: 'a> Cascade<'a, 'b> { - fn new( - context: &'a mut computed::Context<'b>, - cascade_mode: CascadeMode<'a>, - referenced: &'a LonghandIdSet, - ) -> Self { + fn new(context: &'a mut computed::Context<'b>, cascade_mode: CascadeMode<'a>) -> Self { Self { context, cascade_mode, - referenced, seen: LonghandIdSet::default(), author_specified: LonghandIdSet::default(), reverted_set: Default::default(), @@ -590,21 +575,23 @@ impl<'a, 'b: 'a> Cascade<'a, 'b> { (CASCADE_PROPERTY[discriminant])(declaration, &mut self.context); } - fn apply_properties<'decls, I>( + fn apply_properties<'decls, Phase, I>( &mut self, - can_have_logical_properties: CanHaveLogicalProperties, - properties_to_apply: &'a LonghandIdSet, + apply_reset: ApplyResetProperties, declarations: I, mut shorthand_cache: &mut ShorthandsWithPropertyReferencesCache, - ) -> bool - where + ) where + Phase: CascadePhase, I: Iterator, { - if !self.referenced.contains_any(properties_to_apply) { - return false; - } + let apply_reset = apply_reset == ApplyResetProperties::Yes; - let can_have_logical_properties = can_have_logical_properties == CanHaveLogicalProperties::Yes; + debug_assert!( + !Phase::is_early() || apply_reset, + "Should always apply reset properties in the early phase, since we \ + need to know font-size / writing-mode to decide whether to use the \ + cached reset properties" + ); let ignore_colors = !self.context.builder.device.use_document_colors(); let mut declarations_to_apply_unless_overriden = DeclarationsToApplyUnlessOverriden::new(); @@ -618,15 +605,20 @@ impl<'a, 'b: 'a> Cascade<'a, 'b> { PropertyDeclarationId::Custom(..) => continue, }; - if !properties_to_apply.contains(longhand_id) { + let inherited = longhand_id.inherited(); + if !apply_reset && !inherited { continue; } - debug_assert!(can_have_logical_properties || !longhand_id.is_logical()); - let physical_longhand_id = if can_have_logical_properties { - longhand_id.to_physical(self.context.builder.writing_mode) - } else { + if Phase::is_early() != longhand_id.is_early_property() { + continue; + } + + debug_assert!(!Phase::is_early() || !longhand_id.is_logical()); + let physical_longhand_id = if Phase::is_early() { longhand_id + } else { + longhand_id.to_physical(self.context.builder.writing_mode) }; if self.seen.contains(physical_longhand_id) { @@ -641,6 +633,15 @@ impl<'a, 'b: 'a> Cascade<'a, 'b> { } } + // Only a few properties are allowed to depend on the visited state + // of links. When cascading visited styles, we can save time by + // only processing these properties. + if matches!(self.cascade_mode, CascadeMode::Visited { .. }) && + !physical_longhand_id.is_visited_dependent() + { + continue; + } + let mut declaration = self.substitute_variables_if_needed(declaration, &mut shorthand_cache); @@ -674,8 +675,8 @@ impl<'a, 'b: 'a> Cascade<'a, 'b> { continue; }, CSSWideKeyword::Unset => true, - CSSWideKeyword::Inherit => longhand_id.inherited(), - CSSWideKeyword::Initial => !longhand_id.inherited(), + CSSWideKeyword::Inherit => inherited, + CSSWideKeyword::Initial => !inherited, }, None => false, }; @@ -709,13 +710,22 @@ impl<'a, 'b: 'a> Cascade<'a, 'b> { } } - true + if Phase::is_early() { + self.fixup_font_stuff(); + self.compute_writing_mode(); + } else { + self.finished_applying_properties(); + } } fn compute_writing_mode(&mut self) { - debug_assert!(matches!(self.cascade_mode, CascadeMode::Unvisited { .. })); - self.context.builder.writing_mode = - WritingMode::new(self.context.builder.get_inherited_box()) + let writing_mode = match self.cascade_mode { + CascadeMode::Unvisited { .. } => { + WritingMode::new(self.context.builder.get_inherited_box()) + }, + CascadeMode::Visited { writing_mode } => writing_mode, + }; + self.context.builder.writing_mode = writing_mode; } fn compute_visited_style_if_needed( @@ -724,12 +734,20 @@ impl<'a, 'b: 'a> Cascade<'a, 'b> { parent_style: Option<&ComputedValues>, parent_style_ignoring_first_line: Option<&ComputedValues>, layout_parent_style: Option<&ComputedValues>, - visited_rules: &StrongRuleNode, guards: &StylesheetGuards, ) where E: TElement, { - debug_assert!(matches!(self.cascade_mode, CascadeMode::Unvisited { .. })); + let visited_rules = match self.cascade_mode { + CascadeMode::Unvisited { visited_rules } => visited_rules, + CascadeMode::Visited { .. } => return, + }; + + let visited_rules = match visited_rules { + Some(rules) => rules, + None => return, + }; + let is_link = self.context.builder.pseudo.is_none() && element.unwrap().is_link(); macro_rules! visited_parent { diff --git a/components/style/properties/counted_unknown_properties.py b/components/style/properties/counted_unknown_properties.py index 997fd167a07..473f2f599d2 100644 --- a/components/style/properties/counted_unknown_properties.py +++ b/components/style/properties/counted_unknown_properties.py @@ -73,6 +73,7 @@ COUNTED_UNKNOWN_PROPERTIES = [ "-webkit-perspective-origin-y", "-webkit-margin-before-collapse", "-webkit-border-before-style", + "scroll-snap-stop", "-webkit-margin-bottom-collapse", "-webkit-ruby-position", "-webkit-column-break-after", diff --git a/components/style/properties/data.py b/components/style/properties/data.py index ac1de39f877..84e0b482569 100644 --- a/components/style/properties/data.py +++ b/components/style/properties/data.py @@ -444,7 +444,6 @@ class Longhand(Property): "ColumnCount", "Contain", "ContentVisibility", - "ContainerType", "Display", "FillRule", "Float", @@ -492,7 +491,6 @@ class Longhand(Property): "ScrollbarGutter", "ScrollSnapAlign", "ScrollSnapAxis", - "ScrollSnapStop", "ScrollSnapStrictness", "ScrollSnapType", "TextAlign", diff --git a/components/style/properties/gecko.mako.rs b/components/style/properties/gecko.mako.rs index 0edfbf84451..bc0a78183a8 100644 --- a/components/style/properties/gecko.mako.rs +++ b/components/style/properties/gecko.mako.rs @@ -755,6 +755,7 @@ fn static_assert() { <%self:impl_trait style_struct_name="Margin" skip_longhands="${skip_margin_longhands} ${skip_scroll_margin_longhands}"> + % for side in SIDES: <% impl_split_style_coord("margin_%s" % side.ident, "mMargin", @@ -1180,7 +1181,11 @@ fn static_assert() { <% skip_box_longhands= """display - clear + animation-name animation-delay animation-duration + animation-direction animation-fill-mode animation-play-state + animation-iteration-count animation-timeline animation-timing-function + clear transition-duration transition-delay + transition-timing-function transition-property -webkit-line-clamp""" %> <%self:impl_trait style_struct_name="Box" skip_longhands="${skip_box_longhands}"> #[inline] @@ -1222,6 +1227,245 @@ fn static_assert() { ) %> ${impl_keyword('clear', 'mBreakType', clear_keyword)} + ${impl_transition_time_value('delay', 'Delay')} + ${impl_transition_time_value('duration', 'Duration')} + ${impl_animation_or_transition_timing_function('transition')} + + pub fn transition_combined_duration_at(&self, index: usize) -> f32 { + // https://drafts.csswg.org/css-transitions/#transition-combined-duration + self.gecko.mTransitions[index % self.gecko.mTransitionDurationCount as usize].mDuration.max(0.0) + + self.gecko.mTransitions[index % self.gecko.mTransitionDelayCount as usize].mDelay + } + + pub fn set_transition_property(&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 { @@ -1776,247 +2020,7 @@ mask-mode mask-repeat mask-clip mask-origin mask-composite mask-position-x mask- } -<% 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="UI"> <%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 b62ddb25ce4..5ffea97ba50 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_ui().transition_property_count(), + index_range: 0..style.get_box().transition_property_count(), longhand_iterator: None, } } @@ -832,7 +832,7 @@ impl<'a> Iterator for TransitionPropertyIterator<'a> { } let index = self.index_range.next()?; - match self.style.get_ui().transition_property_at(index) { + match self.style.get_box().transition_property_at(index) { TransitionProperty::Longhand(longhand_id) => { return Some(TransitionPropertyIteration { longhand_id, diff --git a/components/style/properties/longhands/box.mako.rs b/components/style/properties/longhands/box.mako.rs index 6598885c3a1..1fccedcc652 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, Keyword, Method, to_rust_ident, to_camel_case%> +<% from data import ALL_AXES, DEFAULT_RULES_EXCEPT_KEYFRAME, Keyword, Method, to_rust_ident, to_camel_case%> <% data.new_style_struct("Box", inherited=False, @@ -150,6 +150,193 @@ ${helpers.predefined_type( animation_value_type="discrete", )} +<% transition_extra_prefixes = "moz:layout.css.prefixes.transitions webkit" %> + +${helpers.predefined_type( + "transition-duration", + "Time", + "computed::Time::zero()", + engines="gecko servo", + initial_specified_value="specified::Time::zero()", + parse_method="parse_non_negative", + vector=True, + need_index=True, + animation_value_type="none", + extra_prefixes=transition_extra_prefixes, + spec="https://drafts.csswg.org/css-transitions/#propdef-transition-duration", +)} + +${helpers.predefined_type( + "transition-timing-function", + "TimingFunction", + "computed::TimingFunction::ease()", + engines="gecko servo", + initial_specified_value="specified::TimingFunction::ease()", + vector=True, + need_index=True, + animation_value_type="none", + extra_prefixes=transition_extra_prefixes, + spec="https://drafts.csswg.org/css-transitions/#propdef-transition-timing-function", +)} + +${helpers.predefined_type( + "transition-property", + "TransitionProperty", + "computed::TransitionProperty::all()", + engines="gecko servo", + initial_specified_value="specified::TransitionProperty::all()", + vector=True, + allow_empty="NotInitial", + need_index=True, + animation_value_type="none", + extra_prefixes=transition_extra_prefixes, + spec="https://drafts.csswg.org/css-transitions/#propdef-transition-property", +)} + +${helpers.predefined_type( + "transition-delay", + "Time", + "computed::Time::zero()", + engines="gecko servo", + initial_specified_value="specified::Time::zero()", + vector=True, + need_index=True, + animation_value_type="none", + extra_prefixes=transition_extra_prefixes, + spec="https://drafts.csswg.org/css-transitions/#propdef-transition-delay", +)} + +<% animation_extra_prefixes = "moz:layout.css.prefixes.animations webkit" %> + +${helpers.predefined_type( + "animation-name", + "AnimationName", + "computed::AnimationName::none()", + engines="gecko servo", + initial_specified_value="specified::AnimationName::none()", + vector=True, + need_index=True, + animation_value_type="none", + extra_prefixes=animation_extra_prefixes, + rule_types_allowed=DEFAULT_RULES_EXCEPT_KEYFRAME, + spec="https://drafts.csswg.org/css-animations/#propdef-animation-name", +)} + +${helpers.predefined_type( + "animation-duration", + "Time", + "computed::Time::zero()", + engines="gecko servo", + initial_specified_value="specified::Time::zero()", + parse_method="parse_non_negative", + vector=True, + need_index=True, + animation_value_type="none", + extra_prefixes=animation_extra_prefixes, + spec="https://drafts.csswg.org/css-transitions/#propdef-transition-duration", +)} + +// animation-timing-function is the exception to the rule for allowed_in_keyframe_block: +// https://drafts.csswg.org/css-animations/#keyframes +${helpers.predefined_type( + "animation-timing-function", + "TimingFunction", + "computed::TimingFunction::ease()", + engines="gecko servo", + initial_specified_value="specified::TimingFunction::ease()", + vector=True, + need_index=True, + animation_value_type="none", + extra_prefixes=animation_extra_prefixes, + spec="https://drafts.csswg.org/css-transitions/#propdef-animation-timing-function", +)} + +${helpers.predefined_type( + "animation-iteration-count", + "AnimationIterationCount", + "computed::AnimationIterationCount::one()", + engines="gecko servo", + initial_specified_value="specified::AnimationIterationCount::one()", + vector=True, + need_index=True, + animation_value_type="none", + extra_prefixes=animation_extra_prefixes, + rule_types_allowed=DEFAULT_RULES_EXCEPT_KEYFRAME, + spec="https://drafts.csswg.org/css-animations/#propdef-animation-iteration-count", +)} + +<% animation_direction_custom_consts = { "alternate-reverse": "Alternate_reverse" } %> +${helpers.single_keyword( + "animation-direction", + "normal reverse alternate alternate-reverse", + engines="gecko servo", + need_index=True, + animation_value_type="none", + vector=True, + gecko_enum_prefix="PlaybackDirection", + custom_consts=animation_direction_custom_consts, + extra_prefixes=animation_extra_prefixes, + gecko_inexhaustive=True, + spec="https://drafts.csswg.org/css-animations/#propdef-animation-direction", + rule_types_allowed=DEFAULT_RULES_EXCEPT_KEYFRAME, +)} + +${helpers.single_keyword( + "animation-play-state", + "running paused", + engines="gecko servo", + need_index=True, + animation_value_type="none", + vector=True, + extra_prefixes=animation_extra_prefixes, + gecko_enum_prefix="StyleAnimationPlayState", + spec="https://drafts.csswg.org/css-animations/#propdef-animation-play-state", + rule_types_allowed=DEFAULT_RULES_EXCEPT_KEYFRAME, +)} + +${helpers.single_keyword( + "animation-fill-mode", + "none forwards backwards both", + engines="gecko servo", + need_index=True, + animation_value_type="none", + vector=True, + gecko_enum_prefix="FillMode", + extra_prefixes=animation_extra_prefixes, + gecko_inexhaustive=True, + spec="https://drafts.csswg.org/css-animations/#propdef-animation-fill-mode", + rule_types_allowed=DEFAULT_RULES_EXCEPT_KEYFRAME, +)} + +${helpers.predefined_type( + "animation-delay", + "Time", + "computed::Time::zero()", + engines="gecko servo", + initial_specified_value="specified::Time::zero()", + vector=True, + need_index=True, + animation_value_type="none", + extra_prefixes=animation_extra_prefixes, + spec="https://drafts.csswg.org/css-animations/#propdef-animation-delay", + rule_types_allowed=DEFAULT_RULES_EXCEPT_KEYFRAME, +)} + +${helpers.predefined_type( + "animation-timeline", + "AnimationTimeline", + "computed::AnimationTimeline::auto()", + engines="gecko servo", + servo_pref="layout.unimplemented", + initial_specified_value="specified::AnimationTimeline::auto()", + vector=True, + need_index=True, + animation_value_type="none", + gecko_pref="layout.css.scroll-linked-animations.enabled", + spec="https://drafts.csswg.org/css-animations-2/#propdef-animation-timeline", + rule_types_allowed=DEFAULT_RULES_EXCEPT_KEYFRAME, +)} + <% transform_extra_prefixes = "moz:layout.css.prefixes.transforms webkit" %> ${helpers.predefined_type( @@ -285,15 +472,6 @@ ${helpers.predefined_type( animation_value_type="discrete", )} -${helpers.predefined_type( - "scroll-snap-stop", - "ScrollSnapStop", - "computed::ScrollSnapStop::Normal", - engines="gecko", - spec="https://drafts.csswg.org/css-scroll-snap-1/#scroll-snap-stop", - animation_value_type="discrete", -)} - % for (axis, logical) in ALL_AXES: ${helpers.predefined_type( "overscroll-behavior-" + axis, @@ -445,28 +623,6 @@ ${helpers.predefined_type( animation_value_type="none", )} -${helpers.predefined_type( - "container-type", - "ContainerType", - "computed::ContainerType::NONE", - engines="gecko servo", - animation_value_type="none", - gecko_pref="layout.css.container-queries.enabled", - servo_pref="layout.container-queries.enabled", - spec="https://drafts.csswg.org/css-contain-3/#container-type", -)} - -${helpers.predefined_type( - "container-name", - "ContainerName", - "computed::ContainerName::none()", - engines="gecko servo", - animation_value_type="none", - gecko_pref="layout.css.container-queries.enabled", - servo_pref="layout.container-queries.enabled", - spec="https://drafts.csswg.org/css-contain-3/#container-name", -)} - ${helpers.predefined_type( "appearance", "Appearance", diff --git a/components/style/properties/longhands/margin.mako.rs b/components/style/properties/longhands/margin.mako.rs index 251ee85f265..b2d3ff3949a 100644 --- a/components/style/properties/longhands/margin.mako.rs +++ b/components/style/properties/longhands/margin.mako.rs @@ -28,16 +28,6 @@ )} % endfor -${helpers.predefined_type( - "overflow-clip-margin", - "Length", - "computed::Length::zero()", - parse_method="parse_non_negative", - engines="gecko", - spec="https://drafts.csswg.org/css-overflow/#propdef-overflow-clip-margin", - animation_value_type="ComputedValue", -)} - % for side in ALL_SIDES: ${helpers.predefined_type( "scroll-margin-%s" % side[0], diff --git a/components/style/properties/longhands/svg.mako.rs b/components/style/properties/longhands/svg.mako.rs index 13704e5953b..3d711462ff3 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", - "computed::Color::black()", + "RGBA::new(0, 0, 0, 255).into()", engines="gecko", animation_value_type="AnimatedRGBA", spec="https://www.w3.org/TR/SVGTiny12/painting.html#StopColorProperty", @@ -40,7 +40,7 @@ ${helpers.predefined_type( ${helpers.predefined_type( "flood-color", "Color", - "computed::Color::black()", + "RGBA::new(0, 0, 0, 255).into()", engines="gecko", animation_value_type="AnimatedColor", spec="https://www.w3.org/TR/SVG/filters.html#FloodColorProperty", @@ -58,7 +58,7 @@ ${helpers.predefined_type( ${helpers.predefined_type( "lighting-color", "Color", - "computed::Color::white()", + "RGBA::new(255, 255, 255, 255).into()", engines="gecko", animation_value_type="AnimatedColor", spec="https://www.w3.org/TR/SVG/filters.html#LightingColorProperty", diff --git a/components/style/properties/longhands/ui.mako.rs b/components/style/properties/longhands/ui.mako.rs index 8d4bbab038c..400eaedf577 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 DEFAULT_RULES_EXCEPT_KEYFRAME, Method %> +<% from data import Method %> // CSS Basic User Interface Module Level 1 // https://drafts.csswg.org/css-ui-3/ @@ -51,15 +51,12 @@ ${helpers.single_keyword( spec="None (Nonstandard Firefox-only property)", )} -// TODO(emilio): Maybe make shadow behavior on macOS match Linux / Windows, and remove this -// property. ${helpers.single_keyword( "-moz-window-shadow", - "default none", + "default none menu tooltip sheet cliprounded", engines="gecko", gecko_ffi_name="mWindowShadow", gecko_enum_prefix="StyleWindowShadow", - gecko_inexhaustive=True, animation_value_type="discrete", enabled_in="chrome", spec="None (Nonstandard internal property)", @@ -98,16 +95,6 @@ ${helpers.predefined_type( enabled_in="chrome", )} -${helpers.predefined_type( - "-moz-window-input-region-margin", - "Length", - "computed::Length::zero()", - engines="gecko", - animation_value_type="ComputedValue", - spec="None (Nonstandard internal property)", - enabled_in="chrome", -)} - // TODO(emilio): Probably also should be hidden from content. ${helpers.predefined_type( "-moz-force-broken-image-icon", @@ -117,190 +104,3 @@ ${helpers.predefined_type( animation_value_type="discrete", spec="None (Nonstandard Firefox-only property)", )} - -<% transition_extra_prefixes = "moz:layout.css.prefixes.transitions webkit" %> - -${helpers.predefined_type( - "transition-duration", - "Time", - "computed::Time::zero()", - engines="gecko servo", - initial_specified_value="specified::Time::zero()", - parse_method="parse_non_negative", - vector=True, - need_index=True, - animation_value_type="none", - extra_prefixes=transition_extra_prefixes, - spec="https://drafts.csswg.org/css-transitions/#propdef-transition-duration", -)} - -${helpers.predefined_type( - "transition-timing-function", - "TimingFunction", - "computed::TimingFunction::ease()", - engines="gecko servo", - initial_specified_value="specified::TimingFunction::ease()", - vector=True, - need_index=True, - animation_value_type="none", - extra_prefixes=transition_extra_prefixes, - spec="https://drafts.csswg.org/css-transitions/#propdef-transition-timing-function", -)} - -${helpers.predefined_type( - "transition-property", - "TransitionProperty", - "computed::TransitionProperty::all()", - engines="gecko servo", - initial_specified_value="specified::TransitionProperty::all()", - vector=True, - allow_empty="NotInitial", - need_index=True, - animation_value_type="none", - extra_prefixes=transition_extra_prefixes, - spec="https://drafts.csswg.org/css-transitions/#propdef-transition-property", -)} - -${helpers.predefined_type( - "transition-delay", - "Time", - "computed::Time::zero()", - engines="gecko servo", - initial_specified_value="specified::Time::zero()", - vector=True, - need_index=True, - animation_value_type="none", - extra_prefixes=transition_extra_prefixes, - spec="https://drafts.csswg.org/css-transitions/#propdef-transition-delay", -)} - -<% animation_extra_prefixes = "moz:layout.css.prefixes.animations webkit" %> - -${helpers.predefined_type( - "animation-name", - "AnimationName", - "computed::AnimationName::none()", - engines="gecko servo", - initial_specified_value="specified::AnimationName::none()", - vector=True, - need_index=True, - animation_value_type="none", - extra_prefixes=animation_extra_prefixes, - rule_types_allowed=DEFAULT_RULES_EXCEPT_KEYFRAME, - spec="https://drafts.csswg.org/css-animations/#propdef-animation-name", -)} - -${helpers.predefined_type( - "animation-duration", - "Time", - "computed::Time::zero()", - engines="gecko servo", - initial_specified_value="specified::Time::zero()", - parse_method="parse_non_negative", - vector=True, - need_index=True, - animation_value_type="none", - extra_prefixes=animation_extra_prefixes, - spec="https://drafts.csswg.org/css-transitions/#propdef-transition-duration", -)} - -// animation-timing-function is the exception to the rule for allowed_in_keyframe_block: -// https://drafts.csswg.org/css-animations/#keyframes -${helpers.predefined_type( - "animation-timing-function", - "TimingFunction", - "computed::TimingFunction::ease()", - engines="gecko servo", - initial_specified_value="specified::TimingFunction::ease()", - vector=True, - need_index=True, - animation_value_type="none", - extra_prefixes=animation_extra_prefixes, - spec="https://drafts.csswg.org/css-transitions/#propdef-animation-timing-function", -)} - -${helpers.predefined_type( - "animation-iteration-count", - "AnimationIterationCount", - "computed::AnimationIterationCount::one()", - engines="gecko servo", - initial_specified_value="specified::AnimationIterationCount::one()", - vector=True, - need_index=True, - animation_value_type="none", - extra_prefixes=animation_extra_prefixes, - rule_types_allowed=DEFAULT_RULES_EXCEPT_KEYFRAME, - spec="https://drafts.csswg.org/css-animations/#propdef-animation-iteration-count", -)} - -<% animation_direction_custom_consts = { "alternate-reverse": "Alternate_reverse" } %> -${helpers.single_keyword( - "animation-direction", - "normal reverse alternate alternate-reverse", - engines="gecko servo", - need_index=True, - animation_value_type="none", - vector=True, - gecko_enum_prefix="PlaybackDirection", - custom_consts=animation_direction_custom_consts, - extra_prefixes=animation_extra_prefixes, - gecko_inexhaustive=True, - spec="https://drafts.csswg.org/css-animations/#propdef-animation-direction", - rule_types_allowed=DEFAULT_RULES_EXCEPT_KEYFRAME, -)} - -${helpers.single_keyword( - "animation-play-state", - "running paused", - engines="gecko servo", - need_index=True, - animation_value_type="none", - vector=True, - extra_prefixes=animation_extra_prefixes, - gecko_enum_prefix="StyleAnimationPlayState", - spec="https://drafts.csswg.org/css-animations/#propdef-animation-play-state", - rule_types_allowed=DEFAULT_RULES_EXCEPT_KEYFRAME, -)} - -${helpers.single_keyword( - "animation-fill-mode", - "none forwards backwards both", - engines="gecko servo", - need_index=True, - animation_value_type="none", - vector=True, - gecko_enum_prefix="FillMode", - extra_prefixes=animation_extra_prefixes, - gecko_inexhaustive=True, - spec="https://drafts.csswg.org/css-animations/#propdef-animation-fill-mode", - rule_types_allowed=DEFAULT_RULES_EXCEPT_KEYFRAME, -)} - -${helpers.predefined_type( - "animation-delay", - "Time", - "computed::Time::zero()", - engines="gecko servo", - initial_specified_value="specified::Time::zero()", - vector=True, - need_index=True, - animation_value_type="none", - extra_prefixes=animation_extra_prefixes, - spec="https://drafts.csswg.org/css-animations/#propdef-animation-delay", - rule_types_allowed=DEFAULT_RULES_EXCEPT_KEYFRAME, -)} - -${helpers.predefined_type( - "animation-timeline", - "AnimationTimeline", - "computed::AnimationTimeline::auto()", - engines="gecko servo", - servo_pref="layout.unimplemented", - initial_specified_value="specified::AnimationTimeline::auto()", - vector=True, - need_index=True, - animation_value_type="none", - gecko_pref="layout.css.scroll-linked-animations.enabled", - spec="https://drafts.csswg.org/css-animations-2/#propdef-animation-timeline", - rule_types_allowed=DEFAULT_RULES_EXCEPT_KEYFRAME, -)} diff --git a/components/style/properties/properties.mako.rs b/components/style/properties/properties.mako.rs index 4327a739001..fd2bc2cb373 100644 --- a/components/style/properties/properties.mako.rs +++ b/components/style/properties/properties.mako.rs @@ -267,9 +267,6 @@ pub enum PropertyDeclaration { % endfor } -// There's one of these for each parsed declaration so it better be small. -size_of_test!(PropertyDeclaration, 32); - #[repr(C)] struct PropertyDeclarationVariantRepr { tag: u16, @@ -477,10 +474,9 @@ impl NonCustomPropertyId { self.0 } - /// Convert a `NonCustomPropertyId` into a `nsCSSPropertyID`. #[cfg(feature = "gecko")] #[inline] - pub fn to_nscsspropertyid(self) -> nsCSSPropertyID { + fn to_nscsspropertyid(self) -> nsCSSPropertyID { // unsafe: guaranteed by static_assert_nscsspropertyid above. unsafe { std::mem::transmute(self.0 as i32) } } @@ -896,72 +892,6 @@ impl<'a> Iterator for LonghandIdSetIterator<'a> { } } -<% - -CASCADE_GROUPS = { - # The writing-mode group has the most priority of all property groups, as - # sizes like font-size can depend on it. - "writing_mode": [ - "writing-mode", - "direction", - "text-orientation", - ], - # The fonts and colors group has the second priority, as all other lengths - # and colors depend on them. - # - # There are some interdependencies between these, but we fix them up in - # Cascade::fixup_font_stuff. - "fonts_and_color": [ - # Needed to properly compute the zoomed font-size. - # FIXME(emilio): This could probably just be a cascade flag - # like IN_SVG_SUBTREE or such, and we could nuke this property. - "-x-text-zoom", - # Needed to do font-size computation in a language-dependent way. - "-x-lang", - # Needed for ruby to respect language-dependent min-font-size - # preferences properly, see bug 1165538. - "-moz-min-font-size-ratio", - # font-size depends on math-depth's computed value. - "math-depth", - # Needed to compute the first available font, in order to - # compute font-relative units correctly. - "font-size", - "font-weight", - "font-stretch", - "font-style", - "font-family", - # color-scheme affects how system colors resolve. - "color-scheme", - ], -} -def in_late_group(p): - return p.name not in CASCADE_GROUPS["writing_mode"] and p.name not in CASCADE_GROUPS["fonts_and_color"] - -def is_visited_dependent(p): - return p.name in [ - "column-rule-color", - "text-emphasis-color", - "-webkit-text-fill-color", - "-webkit-text-stroke-color", - "text-decoration-color", - "fill", - "stroke", - "caret-color", - "background-color", - "border-top-color", - "border-right-color", - "border-bottom-color", - "border-left-color", - "border-block-start-color", - "border-inline-end-color", - "border-block-end-color", - "border-inline-start-color", - "outline-color", - "color", - ] - -%> - impl LonghandIdSet { #[inline] fn reset() -> &'static Self { @@ -990,7 +920,7 @@ impl LonghandIdSet { /// Returns the set of longhands that are ignored when document colors are /// disabled. #[inline] - fn ignored_when_colors_disabled() -> &'static Self { + pub fn ignored_when_colors_disabled() -> &'static Self { ${static_longhand_id_set( "IGNORED_WHEN_COLORS_DISABLED", lambda p: p.ignored_when_colors_disabled @@ -998,48 +928,6 @@ impl LonghandIdSet { &IGNORED_WHEN_COLORS_DISABLED } - /// Only a few properties are allowed to depend on the visited state of - /// links. When cascading visited styles, we can save time by only - /// processing these properties. - fn visited_dependent() -> &'static Self { - ${static_longhand_id_set( - "VISITED_DEPENDENT", - lambda p: is_visited_dependent(p) - )} - debug_assert!(Self::late_group().contains_all(&VISITED_DEPENDENT)); - &VISITED_DEPENDENT - } - - #[inline] - fn writing_mode_group() -> &'static Self { - ${static_longhand_id_set( - "WRITING_MODE_GROUP", - lambda p: p.name in CASCADE_GROUPS["writing_mode"] - )} - &WRITING_MODE_GROUP - } - - #[inline] - fn fonts_and_color_group() -> &'static Self { - ${static_longhand_id_set( - "FONTS_AND_COLOR_GROUP", - lambda p: p.name in CASCADE_GROUPS["fonts_and_color"] - )} - &FONTS_AND_COLOR_GROUP - } - - #[inline] - fn late_group_only_inherited() -> &'static Self { - ${static_longhand_id_set("LATE_GROUP_ONLY_INHERITED", lambda p: p.style_struct.inherited and in_late_group(p))} - &LATE_GROUP_ONLY_INHERITED - } - - #[inline] - fn late_group() -> &'static Self { - ${static_longhand_id_set("LATE_GROUP", lambda p: in_late_group(p))} - &LATE_GROUP - } - /// Returns the set of properties that are declared as having no effect on /// Gecko elements or their descendant scrollbar parts. #[cfg(debug_assertions)] @@ -1442,12 +1330,91 @@ impl LonghandId { PropertyFlags::from_bits_truncate(FLAGS[self as usize]) } + /// Only a few properties are allowed to depend on the visited state of + /// links. When cascading visited styles, we can save time by only + /// processing these properties. + fn is_visited_dependent(&self) -> bool { + matches!(*self, + % if engine == "gecko": + LonghandId::ColumnRuleColor | + LonghandId::TextEmphasisColor | + LonghandId::WebkitTextFillColor | + LonghandId::WebkitTextStrokeColor | + LonghandId::TextDecorationColor | + LonghandId::Fill | + LonghandId::Stroke | + LonghandId::CaretColor | + % endif + LonghandId::BackgroundColor | + LonghandId::BorderTopColor | + LonghandId::BorderRightColor | + LonghandId::BorderBottomColor | + LonghandId::BorderLeftColor | + LonghandId::OutlineColor | + LonghandId::Color + ) + } + /// Returns true if the property is one that is ignored when document /// colors are disabled. #[inline] fn ignored_when_document_colors_disabled(self) -> bool { LonghandIdSet::ignored_when_colors_disabled().contains(self) } + + /// The computed value of some properties depends on the (sometimes + /// computed) value of *other* properties. + /// + /// So we classify properties into "early" and "other", such that the only + /// dependencies can be from "other" to "early". + /// + /// Unfortunately, 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 @@ -2593,11 +2560,9 @@ impl PropertyDeclaration { } } -const SUB_PROPERTIES_ARRAY_CAP: usize = - ${max(len(s.sub_properties) for s in data.shorthands_except_all()) \ - if data.shorthands_except_all() else 0}; - -type SubpropertiesVec = ArrayVec; +type SubpropertiesVec = ArrayVec; /// A stack-allocated vector of `PropertyDeclaration` /// large enough to parse one CSS `key: value` declaration. @@ -2609,10 +2574,6 @@ pub struct SourcePropertyDeclaration { all_shorthand: AllShorthand, } -// This is huge, but we allocate it on the stack and then never move it, -// we only pass `&mut SourcePropertyDeclaration` references around. -size_of_test!(SourcePropertyDeclaration, 568); - impl SourcePropertyDeclaration { /// Create one. It’s big, try not to move it around. #[inline] @@ -2657,7 +2618,11 @@ impl SourcePropertyDeclaration { /// Return type of SourcePropertyDeclaration::drain pub struct SourcePropertyDeclarationDrain<'a> { - declarations: ArrayVecDrain<'a, PropertyDeclaration, SUB_PROPERTIES_ARRAY_CAP>, + declarations: ArrayVecDrain< + 'a, PropertyDeclaration, + ${max(len(s.sub_properties) for s in data.shorthands_except_all()) \ + if data.shorthands_except_all() else 0} + >, all_shorthand: AllShorthand, } @@ -2938,11 +2903,11 @@ pub mod style_structs { % endif % endfor - % if style_struct.name == "UI": + % if style_struct.name == "Box": /// Returns whether there is any animation specified with /// animation-name other than `none`. pub fn specifies_animations(&self) -> bool { - self.animation_name_iter().any(|name| !name.is_none()) + self.animation_name_iter().any(|name| name.0.is_some()) } /// Returns whether there are any transitions specified. @@ -3070,7 +3035,7 @@ impl ComputedValues { /// Returns whether this style's display value is equal to contents. pub fn is_display_contents(&self) -> bool { - self.clone_display().is_contents() + self.get_box().clone_display().is_contents() } /// Gets a reference to the rule node. Panic if no rule node exists. @@ -3219,7 +3184,7 @@ impl ComputedValues { /// style.resolve_color(style.get_border().clone_border_top_color()); #[inline] pub fn resolve_color(&self, color: computed::Color) -> RGBA { - color.into_rgba(self.get_inherited_text().clone_color()) + color.to_rgba(self.get_inherited_text().clone_color()) } /// Returns which longhand properties have different values in the two @@ -4223,7 +4188,7 @@ macro_rules! css_properties_accessors { /// Call the given macro with tokens like this for each longhand properties: /// /// ``` -/// { snake_case_ident } +/// { snake_case_ident, true } /// ``` /// /// … where the boolean indicates whether the property value type @@ -4233,39 +4198,12 @@ macro_rules! longhand_properties_idents { ($macro_name: ident) => { $macro_name! { % for property in data.longhands: - { ${property.ident} } + { ${property.ident}, ${"true" if property.boxed else "false"} } % endfor } } } -// Large pages generate tens of thousands of ComputedValues. -size_of_test!(ComputedValues, 192); -// FFI relies on this. -size_of_test!(Option>, 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 a05f7fd461c..a0bc082caeb 100644 --- a/components/style/properties/shorthands/box.mako.rs +++ b/components/style/properties/shorthands/box.mako.rs @@ -24,6 +24,318 @@ ${helpers.two_properties_shorthand( "(https://developer.mozilla.org/en-US/docs/Web/CSS/overflow-clip-box)", )} +macro_rules! try_parse_one { + ($context: expr, $input: expr, $var: ident, $prop_module: ident) => { + if $var.is_none() { + if let Ok(value) = $input.try_parse(|i| { + $prop_module::single_value::parse($context, i) + }) { + $var = Some(value); + continue; + } + } + }; +} + +<%helpers:shorthand name="transition" + engines="gecko servo" + extra_prefixes="moz:layout.css.prefixes.transitions webkit" + sub_properties="transition-property transition-duration + transition-timing-function + transition-delay" + spec="https://drafts.csswg.org/css-transitions/#propdef-transition"> + use crate::parser::Parse; + % for prop in "delay duration property timing_function".split(): + use crate::properties::longhands::transition_${prop}; + % endfor + use crate::values::specified::TransitionProperty; + + pub fn parse_value<'i, 't>( + context: &ParserContext, + input: &mut Parser<'i, 't>, + ) -> Result> { + 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", @@ -33,45 +345,6 @@ ${helpers.two_properties_shorthand( spec="https://wicg.github.io/overscroll-behavior/#overscroll-behavior-properties", )} -<%helpers:shorthand - engines="gecko" - name="container" - sub_properties="container-name container-type" - gecko_pref="layout.css.container-queries.enabled", - spec="https://drafts.csswg.org/css-contain-3/#container-shorthand" -> - pub fn parse_value<'i>( - context: &ParserContext, - input: &mut Parser<'i, '_>, - ) -> Result> { - 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 deleted file mode 100644 index e2eb8d979a0..00000000000 --- a/components/style/properties/shorthands/ui.mako.rs +++ /dev/null @@ -1,317 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ - -<%namespace name="helpers" file="/helpers.mako.rs" /> - -macro_rules! try_parse_one { - ($context: expr, $input: expr, $var: ident, $prop_module: ident) => { - if $var.is_none() { - if let Ok(value) = $input.try_parse(|i| { - $prop_module::single_value::parse($context, i) - }) { - $var = Some(value); - continue; - } - } - }; -} - -<%helpers:shorthand name="transition" - engines="gecko servo" - extra_prefixes="moz:layout.css.prefixes.transitions webkit" - sub_properties="transition-property transition-duration - transition-timing-function - transition-delay" - spec="https://drafts.csswg.org/css-transitions/#propdef-transition"> - use crate::parser::Parse; - % for prop in "delay duration property timing_function".split(): - use crate::properties::longhands::transition_${prop}; - % endfor - use crate::values::specified::TransitionProperty; - - pub fn parse_value<'i, 't>( - context: &ParserContext, - input: &mut Parser<'i, 't>, - ) -> Result> { - 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/queries/feature_expression.rs b/components/style/queries/feature_expression.rs deleted file mode 100644 index aa2e4a04582..00000000000 --- a/components/style/queries/feature_expression.rs +++ /dev/null @@ -1,711 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ - -//! Parsing for query feature expressions, like `(foo: bar)` or -//! `(width >= 400px)`. - -use super::feature::{Evaluator, QueryFeatureDescription}; -use super::feature::{KeywordDiscriminant, FeatureFlags}; -use crate::parser::{Parse, ParserContext}; -use crate::str::{starts_with_ignore_ascii_case, string_as_ascii_lowercase}; -use crate::values::computed::{self, Ratio, ToComputedValue}; -use crate::values::specified::{Integer, Length, Number, Resolution}; -use crate::values::CSSFloat; -use crate::{Atom, Zero}; -use cssparser::{Parser, Token}; -use std::cmp::{Ordering, PartialOrd}; -use std::fmt::{self, Write}; -use style_traits::{CssWriter, ParseError, StyleParseErrorKind, ToCss}; - -/// Whether we're parsing a media or container query feature. -#[derive(Clone, Copy, Debug, Eq, MallocSizeOf, PartialEq, ToShmem)] -pub enum FeatureType { - /// We're parsing a media feature. - Media, - /// We're parsing a container feature. - Container, -} - -impl FeatureType { - fn features(&self) -> &'static [QueryFeatureDescription] { - #[cfg(feature = "gecko")] - let media_features = &crate::gecko::media_features::MEDIA_FEATURES; - #[cfg(feature = "servo")] - let media_features = &*crate::servo::media_queries::MEDIA_FEATURES; - - use crate::stylesheets::container_rule::CONTAINER_FEATURES; - - match *self { - FeatureType::Media => media_features, - FeatureType::Container => &CONTAINER_FEATURES, - } - } - - fn find_feature(&self, name: &Atom) -> Option<(usize, &'static QueryFeatureDescription)> { - self.features().iter().enumerate().find(|(_, f)| f.name == *name) - } -} - -/// The kind of matching that should be performed on a feature value. -#[derive(Clone, Copy, Debug, Eq, MallocSizeOf, PartialEq, ToShmem)] -enum LegacyRange { - /// At least the specified value. - Min, - /// At most the specified value. - Max, -} - -/// The operator that was specified in this feature. -#[derive(Clone, Copy, Debug, Eq, MallocSizeOf, PartialEq, ToShmem)] -enum Operator { - /// = - Equal, - /// > - GreaterThan, - /// >= - GreaterThanEqual, - /// < - LessThan, - /// <= - LessThanEqual, -} - -impl ToCss for Operator { - fn to_css(&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 deleted file mode 100644 index 6bbf197c43b..00000000000 --- a/components/style/queries/mod.rs +++ /dev/null @@ -1,19 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ - -//! Code shared between [media queries][mq] and [container queries][cq]. -//! -//! [mq]: https://drafts.csswg.org/mediaqueries/ -//! [cq]: https://drafts.csswg.org/css-contain-3/#container-rule - -mod condition; - -#[macro_use] -pub mod feature; -pub mod feature_expression; -pub mod values; - -pub use self::condition::QueryCondition; -pub use self::feature::FeatureFlags; -pub use self::feature_expression::{QueryFeatureExpression, FeatureType}; diff --git a/components/style/queries/values.rs b/components/style/queries/values.rs deleted file mode 100644 index 5bd1cede48b..00000000000 --- a/components/style/queries/values.rs +++ /dev/null @@ -1,36 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ - -//! Common feature values between media and container features. - -/// The orientation media / container feature. -/// https://drafts.csswg.org/mediaqueries-5/#orientation -/// https://drafts.csswg.org/css-contain-3/#orientation -#[derive(Clone, Copy, Debug, FromPrimitive, Parse, ToCss)] -#[repr(u8)] -#[allow(missing_docs)] -pub enum Orientation { - Portrait, - Landscape, -} - -impl Orientation { - /// A helper to evaluate a orientation query given a generic size getter. - pub fn eval(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 0e18383455c..dfd4d2bd316 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::{MatchingContext, MatchingMode}; +use selectors::matching::{ElementSelectorFlags, MatchingContext, MatchingMode}; use servo_arc::ArcBorrow; use smallvec::SmallVec; @@ -59,7 +59,7 @@ pub fn containing_shadow_ignoring_svg_use( /// /// This is done basically to be able to organize the cascade in smaller /// functions, and be able to reason about it easily. -pub struct RuleCollector<'a, 'b: 'a, E> +pub struct RuleCollector<'a, 'b: 'a, E, F: 'a> where E: TElement, { @@ -73,14 +73,16 @@ where rule_inclusion: RuleInclusion, rules: &'a mut ApplicableDeclarationList, context: &'a mut MatchingContext<'b, E::Impl>, + flags_setter: &'a mut F, matches_user_and_author_rules: bool, matches_document_author_rules: bool, in_sort_scope: bool, } -impl<'a, 'b: 'a, E> RuleCollector<'a, 'b, E> +impl<'a, 'b: 'a, E, F: 'a> RuleCollector<'a, 'b, E, F> where E: TElement, + F: FnMut(&E, ElementSelectorFlags), { /// Trivially construct a new collector. pub fn new( @@ -93,6 +95,7 @@ where rule_inclusion: RuleInclusion, rules: &'a mut ApplicableDeclarationList, context: &'a mut MatchingContext<'b, E::Impl>, + flags_setter: &'a mut F, ) -> Self { // When we're matching with matching_mode = // `ForStatelessPseudoeElement`, the "target" for the rule hash is the @@ -122,6 +125,7 @@ where animation_declarations, rule_inclusion, context, + flags_setter, rules, matches_user_and_author_rules, matches_document_author_rules: matches_user_and_author_rules, @@ -223,9 +227,9 @@ where part_rules, &mut self.rules, &mut self.context, + &mut self.flags_setter, cascade_level, cascade_data, - &self.stylist, ); } @@ -242,9 +246,9 @@ where self.rule_hash_target, &mut self.rules, &mut self.context, + &mut self.flags_setter, cascade_level, cascade_data, - &self.stylist, ); } diff --git a/components/style/rule_tree/core.rs b/components/style/rule_tree/core.rs index 85584a0e224..e4632ffa711 100644 --- a/components/style/rule_tree/core.rs +++ b/components/style/rule_tree/core.rs @@ -203,6 +203,9 @@ impl RuleTree { /// where it likely did not result from a rigorous performance analysis.) const RULE_TREE_GC_INTERVAL: usize = 300; +/// Used for some size assertions. +pub const RULE_NODE_SIZE: usize = std::mem::size_of::(); + /// A node in the rule tree. struct RuleNode { /// The root node. Only the root has no root pointer, for obvious reasons. @@ -765,8 +768,3 @@ impl hash::Hash for StrongRuleNode { (&*self.p as *const RuleNode).hash(state) } } - -// Large pages generate thousands of RuleNode objects. -size_of_test!(RuleNode, 80); -// StrongRuleNode should be pointer-sized even inside an option. -size_of_test!(Option, 8); diff --git a/components/style/rule_tree/mod.rs b/components/style/rule_tree/mod.rs index 01510e62070..c2339ee9907 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}; +pub use self::core::{RuleTree, StrongRuleNode, RULE_NODE_SIZE}; 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 b2cd8d918ca..8a575ca8386 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::{Stylist, CascadeData, Rule, ContainerConditionId}; +use crate::stylist::{CascadeData, Rule}; use crate::AllocErr; use crate::{Atom, LocalName, Namespace, ShrinkIfNeeded, WeakAtom}; use precomputed_hash::PrecomputedHash; -use selectors::matching::{matches_selector, MatchingContext}; +use selectors::matching::{matches_selector, ElementSelectorFlags, MatchingContext}; use selectors::parser::{Combinator, Component, SelectorIter}; use smallvec::SmallVec; use std::collections::hash_map; @@ -186,33 +186,34 @@ impl SelectorMap { /// /// 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, - matching_context: &mut MatchingContext, + context: &mut MatchingContext, + flags_setter: &mut F, cascade_level: CascadeLevel, cascade_data: &CascadeData, - stylist: &Stylist, ) where E: TElement, + F: FnMut(&E, ElementSelectorFlags), { if self.is_empty() { return; } - let quirks_mode = matching_context.quirks_mode(); + let quirks_mode = context.quirks_mode(); if rule_hash_target.is_root() { SelectorMap::get_matching_rules( element, &self.root, matching_rules_list, - matching_context, + context, + flags_setter, cascade_level, cascade_data, - stylist, ); } @@ -222,10 +223,10 @@ impl SelectorMap { element, rules, matching_rules_list, - matching_context, + context, + flags_setter, cascade_level, cascade_data, - stylist, ) } } @@ -236,10 +237,10 @@ impl SelectorMap { element, rules, matching_rules_list, - matching_context, + context, + flags_setter, cascade_level, cascade_data, - stylist, ) } }); @@ -251,10 +252,10 @@ impl SelectorMap { element, rules, matching_rules_list, - matching_context, + context, + flags_setter, cascade_level, cascade_data, - stylist, ) } }); @@ -265,10 +266,10 @@ impl SelectorMap { element, rules, matching_rules_list, - matching_context, + context, + flags_setter, cascade_level, cascade_data, - stylist, ) } @@ -277,10 +278,10 @@ impl SelectorMap { element, rules, matching_rules_list, - matching_context, + context, + flags_setter, cascade_level, cascade_data, - stylist, ) } @@ -288,44 +289,38 @@ impl SelectorMap { element, &self.other, matching_rules_list, - matching_context, + context, + flags_setter, cascade_level, cascade_data, - stylist, ); } /// Adds rules in `rules` that match `element` to the `matching_rules` list. - pub(crate) fn get_matching_rules( + pub(crate) fn get_matching_rules( element: E, rules: &[Rule], matching_rules: &mut ApplicableDeclarationList, - matching_context: &mut MatchingContext, + context: &mut MatchingContext, + flags_setter: &mut F, cascade_level: CascadeLevel, cascade_data: &CascadeData, - stylist: &Stylist, ) where E: TElement, + F: FnMut(&E, ElementSelectorFlags), { for rule in rules { - if !matches_selector( + if matches_selector( &rule.selector, 0, Some(&rule.hashes), &element, - matching_context, + context, + flags_setter, ) { - continue; + matching_rules + .push(rule.to_applicable_declaration_block(cascade_level, cascade_data)); } - - if rule.container_condition_id != ContainerConditionId::none() { - if !cascade_data.container_condition_matches(rule.container_condition_id, stylist, element) { - continue; - } - } - - matching_rules - .push(rule.to_applicable_declaration_block(cascade_level, cascade_data)); } } } diff --git a/components/style/servo/media_queries.rs b/components/style/servo/media_queries.rs index d4a4d581b1f..e1822332ff1 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::queries::feature::{AllowsRanges, Evaluator, FeatureFlags, QueryFeatureDescription}; +use crate::media_queries::media_feature::{AllowsRanges, ParsingRequirements}; +use crate::media_queries::media_feature::{Evaluator, MediaFeatureDescription}; +use crate::media_queries::media_feature_expression::RangeOrOperator; use crate::media_queries::MediaType; use crate::properties::ComputedValues; use crate::values::computed::CSSPixelLength; -use crate::values::computed::Context; use crate::values::specified::font::FONT_MEDIUM_PX; -use crate::values::specified::ViewportVariant; use crate::values::KeyframesName; use app_units::Au; use cssparser::RGBA; @@ -143,13 +143,8 @@ impl Device { } /// Like the above, but records that we've used viewport units. - pub fn au_viewport_size_for_viewport_unit_resolution( - &self, - _: ViewportVariant, - ) -> UntypedSize2D { + pub fn au_viewport_size_for_viewport_unit_resolution(&self) -> 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() } @@ -234,8 +229,16 @@ impl Device { } /// https://drafts.csswg.org/mediaqueries-4/#width -fn eval_width(context: &Context) -> CSSPixelLength { - CSSPixelLength::new(context.device().au_viewport_size().width.to_f32_px()) +fn eval_width( + device: &Device, + value: Option, + range_or_operator: Option, +) -> bool { + RangeOrOperator::evaluate( + range_or_operator, + value.map(Au::from), + device.au_viewport_size().width, + ) } #[derive(Clone, Copy, Debug, FromPrimitive, Parse, ToCss)] @@ -246,7 +249,7 @@ enum Scan { } /// https://drafts.csswg.org/mediaqueries-4/#scan -fn eval_scan(_: &Context, _: Option) -> bool { +fn eval_scan(_: &Device, _: Option) -> bool { // Since we doesn't support the 'tv' media type, the 'scan' feature never // matches. false @@ -254,18 +257,18 @@ fn eval_scan(_: &Context, _: Option) -> bool { lazy_static! { /// A list with all the media features that Servo supports. - pub static ref MEDIA_FEATURES: [QueryFeatureDescription; 2] = [ + pub static ref MEDIA_FEATURES: [MediaFeatureDescription; 2] = [ feature!( atom!("width"), AllowsRanges::Yes, Evaluator::Length(eval_width), - FeatureFlags::empty(), + ParsingRequirements::empty(), ), feature!( atom!("scan"), AllowsRanges::No, keyword_evaluator!(eval_scan, Scan), - FeatureFlags::empty(), + ParsingRequirements::empty(), ), ]; } diff --git a/components/style/sharing/checks.rs b/components/style/sharing/checks.rs index 2f8f410190f..5e8350e78d3 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::SharedStyleContext; +use crate::context::{SelectorFlagsMap, SharedStyleContext}; use crate::dom::TElement; use crate::sharing::{StyleSharingCandidate, StyleSharingTarget}; use selectors::NthIndexCache; @@ -120,6 +120,7 @@ pub fn revalidate( shared_context: &SharedStyleContext, bloom: &StyleBloom, nth_index_cache: &mut NthIndexCache, + selector_flags_map: &mut SelectorFlagsMap, ) -> bool where E: TElement, @@ -127,7 +128,7 @@ where let stylist = &shared_context.stylist; let for_element = - target.revalidation_match_results(stylist, bloom, nth_index_cache); + target.revalidation_match_results(stylist, bloom, nth_index_cache, selector_flags_map); let for_candidate = candidate.revalidation_match_results(stylist, bloom, nth_index_cache); diff --git a/components/style/sharing/mod.rs b/components/style/sharing/mod.rs index 91e4f02e084..50eb51fba35 100644 --- a/components/style/sharing/mod.rs +++ b/components/style/sharing/mod.rs @@ -66,8 +66,9 @@ use crate::applicable_declarations::ApplicableDeclarationBlock; use crate::bloom::StyleBloom; -use crate::context::{SharedStyleContext, StyleContext}; +use crate::context::{SelectorFlagsMap, SharedStyleContext, StyleContext}; use crate::dom::{SendElement, TElement}; +use crate::matching::MatchMethods; use crate::properties::ComputedValues; use crate::rule_tree::StrongRuleNode; use crate::style_resolver::{PrimaryStyle, ResolvedElementStyles}; @@ -75,7 +76,7 @@ use crate::stylist::Stylist; use crate::values::AtomIdent; use atomic_refcell::{AtomicRefCell, AtomicRefMut}; use owning_ref::OwningHandle; -use selectors::matching::{VisitedHandlingMode, NeedsSelectorFlags}; +use selectors::matching::{ElementSelectorFlags, VisitedHandlingMode}; use selectors::NthIndexCache; use servo_arc::Arc; use smallbitvec::SmallBitVec; @@ -222,17 +223,18 @@ impl ValidationData { /// Computes the revalidation results if needed, and returns it. /// Inline so we know at compile time what bloom_known_valid is. #[inline] - fn revalidation_match_results( + fn revalidation_match_results( &mut self, element: E, stylist: &Stylist, bloom: &StyleBloom, nth_index_cache: &mut NthIndexCache, bloom_known_valid: bool, - needs_selector_flags: NeedsSelectorFlags, + flags_setter: &mut F, ) -> &SmallBitVec where E: TElement, + F: FnMut(&E, ElementSelectorFlags), { self.revalidation_match_results.get_or_insert_with(|| { // The bloom filter may already be set up for our element. @@ -255,7 +257,7 @@ impl ValidationData { element, bloom_to_use, nth_index_cache, - needs_selector_flags, + flags_setter, ) }) } @@ -325,9 +327,7 @@ impl StyleSharingCandidate { bloom, nth_index_cache, /* bloom_known_valid = */ false, - // The candidate must already have the right bits already, if - // needed. - NeedsSelectorFlags::No, + &mut |_, _| {}, ) } } @@ -384,6 +384,7 @@ impl 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 @@ -400,13 +401,18 @@ 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, - NeedsSelectorFlags::Yes, + &mut set_selector_flags, ) } @@ -417,6 +423,7 @@ 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; @@ -436,6 +443,7 @@ impl StyleSharingTarget { cache.share_style_if_possible( shared_context, + selector_flags_map, bloom_filter, nth_index_cache, self, @@ -623,13 +631,13 @@ impl StyleSharingCache { // // These are things we don't check in the candidate match because they // are either uncommon or expensive. - let ui_style = style.style().get_ui(); - if ui_style.specifies_transitions() { + let box_style = style.style().get_box(); + if box_style.specifies_transitions() { debug!("Failing to insert to the cache: transitions"); return; } - if ui_style.specifies_animations() { + if box_style.specifies_animations() { debug!("Failing to insert to the cache: animations"); return; } @@ -659,6 +667,7 @@ impl 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, @@ -691,6 +700,7 @@ impl StyleSharingCache { &shared_context, bloom_filter, nth_index_cache, + selector_flags_map, shared_context, ) }) @@ -702,6 +712,7 @@ 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()); @@ -806,6 +817,7 @@ 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 7d6135b876f..81517f6cd64 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::{NeedsSelectorFlags, MatchingContext}; +use selectors::matching::{ElementSelectorFlags, MatchingContext}; use selectors::matching::{MatchingMode, VisitedHandlingMode}; use servo_arc::Arc; @@ -451,6 +451,7 @@ where ); let mut applicable_declarations = ApplicableDeclarationList::new(); + let map = &mut self.context.thread_local.selector_flags; let bloom_filter = self.context.thread_local.bloom_filter.filter(); let nth_index_cache = &mut self.context.thread_local.nth_index_cache; let mut matching_context = MatchingContext::new_for_visited( @@ -459,22 +460,29 @@ where Some(nth_index_cache), visited_handling, self.context.shared.quirks_mode(), - NeedsSelectorFlags::Yes, ); let stylist = &self.context.shared.stylist; let implemented_pseudo = self.element.implemented_pseudo_element(); - // Compute the primary rule node. - stylist.push_applicable_declarations( - self.element, - implemented_pseudo.as_ref(), - self.element.style_attribute(), - self.element.smil_override(), - self.element.animation_declarations(self.context.shared), - self.rule_inclusion, - &mut applicable_declarations, - &mut matching_context, - ); + { + let resolving_element = self.element; + let mut set_selector_flags = |element: &E, flags: ElementSelectorFlags| { + resolving_element.apply_selector_flags(map, element, flags); + }; + + // Compute the primary rule node. + stylist.push_applicable_declarations( + self.element, + implemented_pseudo.as_ref(), + self.element.style_attribute(), + self.element.smil_override(), + self.element.animation_declarations(self.context.shared), + self.rule_inclusion, + &mut applicable_declarations, + &mut matching_context, + &mut set_selector_flags, + ); + } // FIXME(emilio): This is a hack for animations, and should go away. self.element.unset_dirty_style_attribute(); @@ -532,9 +540,14 @@ where Some(nth_index_cache), visited_handling, self.context.shared.quirks_mode(), - NeedsSelectorFlags::Yes, ); + let map = &mut self.context.thread_local.selector_flags; + let resolving_element = self.element; + let mut set_selector_flags = |element: &E, flags: ElementSelectorFlags| { + resolving_element.apply_selector_flags(map, element, flags); + }; + // NB: We handle animation rules for ::before and ::after when // traversing them. stylist.push_applicable_declarations( @@ -546,6 +559,7 @@ where self.rule_inclusion, &mut applicable_declarations, &mut matching_context, + &mut set_selector_flags, ); if applicable_declarations.is_empty() { diff --git a/components/style/stylesheets/container_rule.rs b/components/style/stylesheets/container_rule.rs deleted file mode 100644 index fe103d8986d..00000000000 --- a/components/style/stylesheets/container_rule.rs +++ /dev/null @@ -1,281 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ - -//! A [`@container`][container] rule. -//! -//! [container]: https://drafts.csswg.org/css-contain-3/#container-rule - -use crate::logical_geometry::{WritingMode, LogicalSize}; -use crate::dom::TElement; -use crate::media_queries::Device; -use crate::parser::ParserContext; -use crate::queries::{QueryCondition, FeatureType}; -use crate::queries::feature::{AllowsRanges, Evaluator, FeatureFlags, QueryFeatureDescription}; -use crate::queries::values::Orientation; -use crate::str::CssStringWriter; -use crate::shared_lock::{ - DeepCloneParams, DeepCloneWithLock, Locked, SharedRwLock, SharedRwLockReadGuard, ToCssWithGuard, -}; -use crate::values::specified::ContainerName; -use crate::values::computed::{Context, CSSPixelLength, Ratio}; -use crate::properties::ComputedValues; -use crate::stylesheets::CssRules; -use app_units::Au; -use cssparser::{SourceLocation, Parser}; -use euclid::default::Size2D; -#[cfg(feature = "gecko")] -use malloc_size_of::{MallocSizeOfOps, MallocUnconditionalShallowSizeOf}; -use servo_arc::Arc; -use std::fmt::{self, Write}; -use style_traits::{CssWriter, ToCss, ParseError}; - -/// A container rule. -#[derive(Debug, ToShmem)] -pub struct ContainerRule { - /// The container query and name. - pub condition: Arc, - /// 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 c724fd81f1a..ebff5bb9add 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] rule. +//! A [`@layer`][layer] urle. //! //! [layer]: https://drafts.csswg.org/css-cascade-5/#layering @@ -61,6 +61,17 @@ impl LayerOrder { } } +/// The id of a given layer, a sequentially-increasing identifier. +#[derive(Clone, Copy, Debug, Eq, MallocSizeOf, PartialEq, PartialOrd, Ord)] +pub struct LayerId(pub u32); + +impl LayerId { + /// The id of the root layer. + pub const fn root() -> Self { + Self(0) + } +} + /// A ``: 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 cde60a16bf7..ea7441a5c09 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] rule. +//! An [`@media`][media] urle. //! //! [media]: https://drafts.csswg.org/css-conditional/#at-ruledef-media @@ -18,7 +18,7 @@ use servo_arc::Arc; use std::fmt::{self, Write}; use style_traits::{CssWriter, ToCss}; -/// An [`@media`][media] rule. +/// An [`@media`][media] urle. /// /// [media]: https://drafts.csswg.org/css-conditional/#at-ruledef-media #[derive(Debug, ToShmem)] diff --git a/components/style/stylesheets/mod.rs b/components/style/stylesheets/mod.rs index 344fda5ef3c..d48b7504797 100644 --- a/components/style/stylesheets/mod.rs +++ b/components/style/stylesheets/mod.rs @@ -12,7 +12,6 @@ pub mod font_feature_values_rule; pub mod import_rule; pub mod keyframes_rule; pub mod layer_rule; -pub mod container_rule; mod loader; mod media_rule; mod namespace_rule; @@ -54,7 +53,6 @@ pub use self::import_rule::ImportRule; pub use self::keyframes_rule::KeyframesRule; pub use self::layer_rule::{LayerBlockRule, LayerStatementRule}; pub use self::loader::StylesheetLoader; -pub use self::container_rule::ContainerRule; pub use self::media_rule::MediaRule; pub use self::namespace_rule::NamespaceRule; pub use self::origin::{Origin, OriginSet, OriginSetIterator, PerOrigin, PerOriginIter}; @@ -255,7 +253,6 @@ pub enum CssRule { Import(Arc>), Style(Arc>), Media(Arc>), - Container(Arc>), FontFace(Arc>), FontFeatureValues(Arc>), CounterStyle(Arc>), @@ -290,10 +287,6 @@ impl CssRule { lock.unconditional_shallow_size_of(ops) + lock.read_with(guard).size_of(guard, ops) }, - CssRule::Container(ref lock) => { - lock.unconditional_shallow_size_of(ops) + lock.read_with(guard).size_of(guard, ops) - }, - CssRule::FontFace(_) => 0, CssRule::FontFeatureValues(_) => 0, CssRule::CounterStyle(_) => 0, @@ -351,7 +344,6 @@ pub enum CssRuleType { LayerBlock = 16, LayerStatement = 17, ScrollTimeline = 18, - Container = 19, } #[allow(missing_docs)] @@ -381,7 +373,16 @@ impl CssRule { CssRule::LayerBlock(_) => CssRuleType::LayerBlock, CssRule::LayerStatement(_) => CssRuleType::LayerStatement, CssRule::ScrollTimeline(_) => CssRuleType::ScrollTimeline, - CssRule::Container(_) => CssRuleType::Container, + } + } + + fn rule_state(&self) -> State { + match *self { + // CssRule::Charset(..) => State::Start, + CssRule::Import(..) => State::Imports, + CssRule::Namespace(..) => State::Namespaces, + // TODO(emilio): Do we need something for EarlyLayers? + _ => State::Body, } } @@ -459,12 +460,6 @@ impl DeepCloneWithLock for CssRule { lock.wrap(rule.deep_clone_with_lock(lock, guard, params)), )) }, - CssRule::Container(ref arc) => { - let rule = arc.read_with(guard); - CssRule::Container(Arc::new( - lock.wrap(rule.deep_clone_with_lock(lock, guard, params)), - )) - }, CssRule::Media(ref arc) => { let rule = arc.read_with(guard); CssRule::Media(Arc::new( @@ -550,7 +545,6 @@ impl ToCssWithGuard for CssRule { CssRule::LayerBlock(ref lock) => lock.read_with(guard).to_css(guard, dest), CssRule::LayerStatement(ref lock) => lock.read_with(guard).to_css(guard, dest), CssRule::ScrollTimeline(ref lock) => lock.read_with(guard).to_css(guard, dest), - CssRule::Container(ref lock) => lock.read_with(guard).to_css(guard, dest), } } } diff --git a/components/style/stylesheets/rule_list.rs b/components/style/stylesheets/rule_list.rs index c246d7ae6bb..d84a738bca0 100644 --- a/components/style/stylesheets/rule_list.rs +++ b/components/style/stylesheets/rule_list.rs @@ -153,17 +153,21 @@ 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 { - insert_rule_context.max_rule_state_at_index(index - 1) + rules + .0 + .get(index - 1) + .map(CssRule::rule_state) + .unwrap_or(State::Body) + }; + + let insert_rule_context = InsertRuleContext { + rule_list: &rules.0, + index, }; // Steps 3, 4, 5, 6 diff --git a/components/style/stylesheets/rule_parser.rs b/components/style/stylesheets/rule_parser.rs index 646a7dbbfd2..9c0095bc32d 100644 --- a/components/style/stylesheets/rule_parser.rs +++ b/components/style/stylesheets/rule_parser.rs @@ -13,7 +13,6 @@ use crate::properties::parse_property_declaration_list; use crate::selector_parser::{SelectorImpl, SelectorParser}; use crate::shared_lock::{Locked, SharedRwLock}; use crate::str::starts_with_ignore_ascii_case; -use crate::stylesheets::container_rule::{ContainerRule, ContainerCondition}; use crate::stylesheets::document_rule::DocumentCondition; use crate::stylesheets::font_feature_values_rule::parse_family_name_list; use crate::stylesheets::import_rule::ImportLayer; @@ -46,36 +45,6 @@ pub struct InsertRuleContext<'a> { pub index: usize, } -impl<'a> InsertRuleContext<'a> { - /// Returns the max rule state allowable for insertion at a given index in - /// the rule list. - pub fn max_rule_state_at_index(&self, index: usize) -> State { - let rule = match self.rule_list.get(index) { - Some(rule) => rule, - None => return State::Body, - }; - match rule { - CssRule::Import(..) => State::Imports, - CssRule::Namespace(..) => State::Namespaces, - CssRule::LayerStatement(..) => { - // If there are @import / @namespace after this layer, then - // we're in the early-layers phase, otherwise we're in the body - // and everything is fair game. - let next_non_layer_statement_rule = self.rule_list[index + 1..] - .iter() - .find(|r| !matches!(*r, CssRule::LayerStatement(..))); - if let Some(non_layer) = next_non_layer_statement_rule { - if matches!(*non_layer, CssRule::Import(..) | CssRule::Namespace(..)) { - return State::EarlyLayers; - } - } - State::Body - } - _ => State::Body, - } - } -} - /// The parser for the top-level rules in a stylesheet. pub struct TopLevelRuleParser<'a> { /// A reference to the lock we need to use to create rules. @@ -133,8 +102,12 @@ impl<'b> TopLevelRuleParser<'b> { None => return true, }; - let max_rule_state = ctx.max_rule_state_at_index(ctx.index); - if new_state > max_rule_state { + let next_rule_state = match ctx.rule_list.get(ctx.index) { + None => return true, + Some(rule) => rule.rule_state(), + }; + + if new_state > next_rule_state { self.dom_error = Some(RulesMutateError::HierarchyRequest); return false; } @@ -189,8 +162,6 @@ pub enum AtRulePrelude { CounterStyle(CustomIdent), /// A @media rule prelude, with its media queries. Media(Arc>), - /// A @container rule prelude. - Container(Arc), /// An @supports rule, with its conditional Supports(SupportsCondition), /// A @viewport rule prelude. @@ -291,23 +262,11 @@ impl<'a, 'i> AtRuleParser<'i> for TopLevelRuleParser<'a> { self.dom_error = Some(RulesMutateError::HierarchyRequest); return Err(input.new_custom_error(StyleParseErrorKind::UnexpectedCharsetRule)) }, - "layer" => { - let state_to_check = if self.state <= State::EarlyLayers { - // The real state depends on whether there's a block or not. - // We don't know that yet, but the parse_block check deals - // with that. - State::EarlyLayers - } else { - State::Body - }; - if !self.check_state(state_to_check) { - return Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError)); - } - }, - _ => { - // All other rules have blocks, so we do this check early in - // parse_block instead. - } + _ => {} + } + + if !self.check_state(State::Body) { + return Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError)); } AtRuleParser::parse_prelude(&mut self.nested(), name, input) @@ -320,9 +279,6 @@ impl<'a, 'i> AtRuleParser<'i> for TopLevelRuleParser<'a> { start: &ParserState, input: &mut Parser<'i, 't>, ) -> Result> { - if !self.check_state(State::Body) { - return Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError)); - } let rule = AtRuleParser::parse_block(&mut self.nested(), prelude, start, input)?; self.state = State::Body; Ok((start.position(), rule)) @@ -454,16 +410,6 @@ impl<'a, 'b> NestedRuleParser<'a, 'b> { } } -fn container_queries_enabled() -> bool { - #[cfg(feature = "gecko")] - return static_prefs::pref!("layout.css.container-queries.enabled"); - #[cfg(feature = "servo")] - return servo_config::prefs::pref_map() - .get("layout.container-queries.enabled") - .as_bool() - .unwrap_or(false); -} - impl<'a, 'b, 'i> AtRuleParser<'i> for NestedRuleParser<'a, 'b> { type Prelude = AtRulePrelude; type AtRule = CssRule; @@ -487,10 +433,6 @@ impl<'a, 'b, 'i> AtRuleParser<'i> for NestedRuleParser<'a, 'b> { "font-face" => { AtRulePrelude::FontFace }, - "container" if container_queries_enabled() => { - let condition = Arc::new(ContainerCondition::parse(self.context, input)?); - AtRulePrelude::Container(condition) - }, "layer" => { let names = input.try_parse(|input| { input.parse_comma_separated(|input| { @@ -672,15 +614,6 @@ impl<'a, 'b, 'i> AtRuleParser<'i> for NestedRuleParser<'a, 'b> { }, )))) }, - AtRulePrelude::Container(condition) => { - Ok(CssRule::Container(Arc::new(self.shared_lock.wrap( - ContainerRule { - condition, - rules: self.parse_nested_rules(input, CssRuleType::Container), - source_location: start.source_location(), - }, - )))) - }, AtRulePrelude::Layer(names) => { let name = match names.len() { 0 | 1 => names.into_iter().next(), diff --git a/components/style/stylesheets/rules_iterator.rs b/components/style/stylesheets/rules_iterator.rs index c86d4dc96c3..417185953a0 100644 --- a/components/style/stylesheets/rules_iterator.rs +++ b/components/style/stylesheets/rules_iterator.rs @@ -88,10 +88,6 @@ where } Some(doc_rule.rules.read_with(guard).0.iter()) }, - CssRule::Container(ref lock) => { - let container_rule = lock.read_with(guard); - Some(container_rule.rules.read_with(guard).0.iter()) - }, CssRule::Media(ref lock) => { let media_rule = lock.read_with(guard); if !C::process_media(guard, device, quirks_mode, media_rule) { diff --git a/components/style/stylesheets/scroll_timeline_rule.rs b/components/style/stylesheets/scroll_timeline_rule.rs index f4796ca77aa..12b0d2013a6 100644 --- a/components/style/stylesheets/scroll_timeline_rule.rs +++ b/components/style/stylesheets/scroll_timeline_rule.rs @@ -206,6 +206,7 @@ impl Default for ScrollDirection { } } +// Avoid name collision in cbindgen with StyleOrientation. pub use self::ScrollDirection as Orientation; /// Scroll-timeline offsets. We treat None as an empty vector. diff --git a/components/style/stylesheets/stylesheet.rs b/components/style/stylesheets/stylesheet.rs index 1410634c281..aaf350b684d 100644 --- a/components/style/stylesheets/stylesheet.rs +++ b/components/style/stylesheets/stylesheet.rs @@ -370,7 +370,6 @@ impl SanitizationKind { CssRule::Media(..) | CssRule::Supports(..) | CssRule::Import(..) | - CssRule::Container(..) | // TODO(emilio): Perhaps Layer should not be always sanitized? But // we sanitize @media and co, so this seems safer for now. CssRule::LayerStatement(..) | diff --git a/components/style/stylesheets/viewport_rule.rs b/components/style/stylesheets/viewport_rule.rs index 10e6af5715b..89f604be01b 100644 --- a/components/style/stylesheets/viewport_rule.rs +++ b/components/style/stylesheets/viewport_rule.rs @@ -671,8 +671,7 @@ impl MaybeNew for ViewportConstraints { builder: StyleBuilder::for_inheritance(device, None, None), cached_system_font: None, in_media_query: false, - quirks_mode, - container_info: None, + quirks_mode: quirks_mode, for_smil_animation: false, for_non_inherited_property: None, rule_cache_conditions: RefCell::new(&mut conditions), diff --git a/components/style/stylist.rs b/components/style/stylist.rs index 21b975c9e92..a1ad4aab659 100644 --- a/components/style/stylist.rs +++ b/components/style/stylist.rs @@ -28,9 +28,8 @@ use crate::selector_parser::{PerPseudoElementMap, PseudoElement, SelectorImpl, S use crate::shared_lock::{Locked, SharedRwLockReadGuard, StylesheetGuards}; use crate::stylesheet_set::{DataValidity, DocumentStylesheetSet, SheetRebuildKind}; use crate::stylesheet_set::{DocumentStylesheetFlusher, SheetCollectionFlusher}; -use crate::stylesheets::container_rule::ContainerCondition; use crate::stylesheets::keyframes_rule::KeyframesAnimation; -use crate::stylesheets::layer_rule::{LayerName, LayerOrder}; +use crate::stylesheets::layer_rule::{LayerId, LayerName, LayerOrder}; use crate::stylesheets::viewport_rule::{self, MaybeNew, ViewportRule}; #[cfg(feature = "gecko")] use crate::stylesheets::{ @@ -40,6 +39,7 @@ use crate::stylesheets::{ CssRule, EffectiveRulesIterator, Origin, OriginSet, PageRule, PerOrigin, PerOriginIter, }; use crate::stylesheets::{StyleRule, StylesheetContents, StylesheetInDocument}; +use crate::thread_state::{self, ThreadState}; use crate::AllocErr; use crate::{Atom, LocalName, Namespace, ShrinkIfNeeded, WeakAtom}; use fxhash::FxHashMap; @@ -49,7 +49,7 @@ use malloc_size_of::MallocUnconditionalShallowSizeOf; use selectors::attr::{CaseSensitivity, NamespaceConstraint}; use selectors::bloom::BloomFilter; use selectors::matching::VisitedHandlingMode; -use selectors::matching::{matches_selector, MatchingContext, MatchingMode, NeedsSelectorFlags}; +use selectors::matching::{matches_selector, ElementSelectorFlags, MatchingContext, MatchingMode}; use selectors::parser::{AncestorHashes, Combinator, Component, Selector, SelectorIter}; use selectors::visitor::SelectorVisitor; use selectors::NthIndexCache; @@ -549,47 +549,6 @@ impl From for RuleInclusion { } } -/// A struct containing state from ancestor rules like @layer / @import / -/// @container. -struct ContainingRuleState { - layer_name: LayerName, - layer_id: LayerId, - container_condition_id: ContainerConditionId, -} - -impl Default for ContainingRuleState { - fn default() -> Self { - Self { - layer_name: LayerName::new_empty(), - layer_id: LayerId::root(), - container_condition_id: ContainerConditionId::none(), - } - } -} - -struct SavedContainingRuleState { - layer_name_len: usize, - layer_id: LayerId, - container_condition_id: ContainerConditionId, -} - -impl ContainingRuleState { - fn save(&self) -> SavedContainingRuleState { - SavedContainingRuleState { - layer_name_len: self.layer_name.0.len(), - layer_id: self.layer_id, - container_condition_id: self.container_condition_id, - } - } - - fn restore(&mut self, saved: &SavedContainingRuleState) { - debug_assert!(self.layer_name.0.len() >= saved.layer_name_len); - self.layer_name.0.truncate(saved.layer_name_len); - self.layer_id = saved.layer_id; - self.container_condition_id = saved.container_condition_id; - } -} - impl Stylist { /// Construct a new `Stylist`, using given `Device` and `QuirksMode`. /// If more members are added here, think about whether they should @@ -1114,12 +1073,38 @@ impl Stylist { { debug_assert!(pseudo.is_lazy()); - // No need to bother setting the selector flags when we're computing - // default styles. - let needs_selector_flags = if rule_inclusion == RuleInclusion::DefaultOnly { - NeedsSelectorFlags::No - } else { - NeedsSelectorFlags::Yes + // Apply the selector flags. We should be in sequential mode + // already, so we can directly apply the parent flags. + let mut set_selector_flags = |element: &E, flags: ElementSelectorFlags| { + if cfg!(feature = "servo") { + // Servo calls this function from the worker, but only for internal + // pseudos, so we should never generate selector flags here. + unreachable!("internal pseudo generated slow selector flags?"); + } + + // No need to bother setting the selector flags when we're computing + // default styles. + if rule_inclusion == RuleInclusion::DefaultOnly { + return; + } + + // Gecko calls this from sequential mode, so we can directly apply + // the flags. + debug_assert_eq!(thread_state::get(), ThreadState::LAYOUT); + let self_flags = flags.for_self(); + if !self_flags.is_empty() { + unsafe { + element.set_selector_flags(self_flags); + } + } + let parent_flags = flags.for_parent(); + if !parent_flags.is_empty() { + if let Some(p) = element.parent_element() { + unsafe { + p.set_selector_flags(parent_flags); + } + } + } }; let mut declarations = ApplicableDeclarationList::new(); @@ -1128,7 +1113,6 @@ impl Stylist { None, None, self.quirks_mode, - needs_selector_flags, ); matching_context.pseudo_element_matching_fn = matching_fn; @@ -1142,6 +1126,7 @@ impl Stylist { rule_inclusion, &mut declarations, &mut matching_context, + &mut set_selector_flags, ); if declarations.is_empty() && is_probe { @@ -1159,7 +1144,6 @@ impl Stylist { None, VisitedHandlingMode::RelevantLinkVisited, self.quirks_mode, - needs_selector_flags, ); matching_context.pseudo_element_matching_fn = matching_fn; @@ -1172,6 +1156,7 @@ impl Stylist { rule_inclusion, &mut declarations, &mut matching_context, + &mut set_selector_flags, ); if !declarations.is_empty() { let rule_node = self.rule_tree.insert_ordered_rules_with_important( @@ -1284,7 +1269,7 @@ impl Stylist { } /// Returns the applicable CSS declarations for the given element. - pub fn push_applicable_declarations( + pub fn push_applicable_declarations( &self, element: E, pseudo_element: Option<&PseudoElement>, @@ -1294,8 +1279,10 @@ 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, @@ -1307,6 +1294,7 @@ impl Stylist { rule_inclusion, applicable_declarations, context, + flags_setter, ) .collect_all(); } @@ -1386,15 +1374,16 @@ impl Stylist { /// Computes the match results of a given element against the set of /// revalidation selectors. - pub fn match_revalidation_selectors( + pub fn match_revalidation_selectors( &self, element: E, bloom: Option<&BloomFilter>, nth_index_cache: &mut NthIndexCache, - needs_selector_flags: NeedsSelectorFlags, + flags_setter: &mut F, ) -> SmallBitVec where E: TElement, + F: FnMut(&E, ElementSelectorFlags), { // NB: `MatchingMode` doesn't really matter, given we don't share style // between pseudos. @@ -1403,7 +1392,6 @@ impl Stylist { bloom, Some(nth_index_cache), self.quirks_mode, - needs_selector_flags, ); // Note that, by the time we're revalidating, we're guaranteed that the @@ -1426,6 +1414,7 @@ impl Stylist { Some(&selector_and_hashes.hashes), &element, matching_context, + flags_setter, )); true }, @@ -1448,6 +1437,7 @@ impl Stylist { Some(&selector_and_hashes.hashes), &element, &mut matching_context, + flags_setter, )); true }, @@ -2071,17 +2061,6 @@ impl PartElementAndPseudoRules { } } -/// The id of a given layer, a sequentially-increasing identifier. -#[derive(Clone, Copy, Debug, Eq, MallocSizeOf, PartialEq, PartialOrd, Ord)] -pub struct LayerId(u16); - -impl LayerId { - /// The id of the root layer. - pub const fn root() -> Self { - Self(0) - } -} - #[derive(Clone, Debug, MallocSizeOf)] struct CascadeLayer { id: LayerId, @@ -2099,35 +2078,6 @@ impl CascadeLayer { } } -/// The id of a given container condition, a sequentially-increasing identifier -/// for a given style set. -#[derive(Clone, Copy, Debug, Eq, MallocSizeOf, PartialEq, PartialOrd, Ord)] -pub struct ContainerConditionId(u16); - -impl ContainerConditionId { - /// A special id that represents no container rule all. - pub const fn none() -> Self { - Self(0) - } -} - - -#[derive(Clone, Debug, MallocSizeOf)] -struct ContainerConditionReference { - parent: ContainerConditionId, - #[ignore_malloc_size_of = "Arc"] - condition: Option>, -} - -impl ContainerConditionReference { - const fn none() -> Self { - Self { - parent: ContainerConditionId::none(), - condition: None, - } - } -} - /// Data resulting from performing the CSS cascade that is specific to a given /// origin. /// @@ -2199,9 +2149,6 @@ pub struct CascadeData { /// The list of cascade layers, indexed by their layer id. layers: SmallVec<[CascadeLayer; 1]>, - /// The list of container conditions, indexed by their id. - container_conditions: SmallVec<[ContainerConditionReference; 1]>, - /// Effective media query results cached from the last rebuild. effective_media_query_results: EffectiveMediaQueryResults, @@ -2244,7 +2191,6 @@ impl CascadeData { animations: Default::default(), layer_id: Default::default(), layers: smallvec::smallvec![CascadeLayer::root()], - container_conditions: smallvec::smallvec![ContainerConditionReference::none()], extra_data: ExtraStyleData::default(), effective_media_query_results: EffectiveMediaQueryResults::new(), rules_source_order: 0, @@ -2359,23 +2305,6 @@ impl CascadeData { self.layers[id.0 as usize].order } - pub(crate) fn container_condition_matches(&self, mut id: ContainerConditionId, stylist: &Stylist, element: E) -> bool - where - E: TElement, - { - loop { - let condition_ref = &self.container_conditions[id.0 as usize]; - let condition = match condition_ref.condition { - None => return true, - Some(ref c) => c, - }; - if !condition.matches(stylist.device(), element) { - return false; - } - id = condition_ref.parent; - } - } - fn did_finish_rebuild(&mut self) { self.shrink_maps_if_needed(); self.compute_layer_order(); @@ -2497,7 +2426,8 @@ impl CascadeData { stylesheet: &S, guard: &SharedRwLockReadGuard, rebuild_kind: SheetRebuildKind, - containing_rule_state: &mut ContainingRuleState, + mut current_layer: &mut LayerName, + current_layer_id: LayerId, mut precomputed_pseudo_element_decls: Option<&mut PrecomputedPseudoElementDeclarations>, ) -> Result<(), AllocErr> where @@ -2520,7 +2450,7 @@ impl CascadeData { if pseudo.is_precomputed() { debug_assert!(selector.is_universal()); debug_assert_eq!(stylesheet.contents().origin, Origin::UserAgent); - debug_assert_eq!(containing_rule_state.layer_id, LayerId::root()); + debug_assert_eq!(current_layer_id, LayerId::root()); precomputed_pseudo_element_decls .as_mut() @@ -2547,8 +2477,7 @@ impl CascadeData { hashes, locked.clone(), self.rules_source_order, - containing_rule_state.layer_id, - containing_rule_state.container_condition_id, + current_layer_id, ); if rebuild_kind.should_rebuild_invalidation() { @@ -2626,7 +2555,7 @@ impl CascadeData { self.animations.try_insert_with( name, animation, - containing_rule_state.layer_id, + current_layer_id, compare_keyframes_in_same_layer, )?; }, @@ -2635,35 +2564,25 @@ impl CascadeData { // Note: Bug 1733260: we may drop @scroll-timeline rule once this spec issue // https://github.com/w3c/csswg-drafts/issues/6674 gets landed. self.extra_data - .add_scroll_timeline(guard, rule, containing_rule_state.layer_id)?; + .add_scroll_timeline(guard, rule, current_layer_id)?; }, #[cfg(feature = "gecko")] CssRule::FontFace(ref rule) => { - // NOTE(emilio): We don't care about container_condition_id - // because: - // - // Global, name-defining at-rules such as @keyframes or - // @font-face or @layer that are defined inside container - // queries are not constrained by the container query - // conditions. - // - // https://drafts.csswg.org/css-contain-3/#container-rule - // (Same elsewhere) - self.extra_data.add_font_face(rule, containing_rule_state.layer_id); + self.extra_data.add_font_face(rule, current_layer_id); }, #[cfg(feature = "gecko")] CssRule::FontFeatureValues(ref rule) => { self.extra_data - .add_font_feature_values(rule, containing_rule_state.layer_id); + .add_font_feature_values(rule, current_layer_id); }, #[cfg(feature = "gecko")] CssRule::CounterStyle(ref rule) => { self.extra_data - .add_counter_style(guard, rule, containing_rule_state.layer_id)?; + .add_counter_style(guard, rule, current_layer_id)?; }, #[cfg(feature = "gecko")] CssRule::Page(ref rule) => { - self.extra_data.add_page(guard, rule, containing_rule_state.layer_id)?; + self.extra_data.add_page(guard, rule, current_layer_id)?; }, CssRule::Viewport(..) => {}, _ => { @@ -2704,7 +2623,7 @@ impl CascadeData { if let Some(id) = data.layer_id.get(layer) { return *id; } - let id = LayerId(data.layers.len() as u16); + let id = LayerId(data.layers.len() as u32); let parent_layer_id = if layer.layer_names().len() > 1 { let mut parent = layer.clone(); @@ -2735,8 +2654,9 @@ impl CascadeData { fn maybe_register_layers( data: &mut CascadeData, name: Option<&LayerName>, - containing_rule_state: &mut ContainingRuleState, - ) { + current_layer: &mut LayerName, + pushed_layers: &mut usize, + ) -> LayerId { let anon_name; let name = match name { Some(name) => name, @@ -2745,14 +2665,19 @@ impl CascadeData { &anon_name }, }; + + let mut id = LayerId::root(); for name in name.layer_names() { - containing_rule_state.layer_name.0.push(name.clone()); - containing_rule_state.layer_id = maybe_register_layer(data, &containing_rule_state.layer_name); + current_layer.0.push(name.clone()); + id = maybe_register_layer(data, ¤t_layer); + *pushed_layers += 1; } - debug_assert_ne!(containing_rule_state.layer_id, LayerId::root()); + debug_assert_ne!(id, LayerId::root()); + id } - let saved_containing_rule_state = containing_rule_state.save(); + let mut layer_names_to_pop = 0; + let mut children_layer_id = current_layer_id; match *rule { CssRule::Import(ref lock) => { let import_rule = lock.read_with(guard); @@ -2761,10 +2686,11 @@ impl CascadeData { .saw_effective(import_rule); } if let Some(ref layer) = import_rule.layer { - maybe_register_layers( + children_layer_id = maybe_register_layers( self, layer.name.as_ref(), - containing_rule_state + &mut current_layer, + &mut layer_names_to_pop, ); } }, @@ -2776,29 +2702,25 @@ impl CascadeData { }, CssRule::LayerBlock(ref lock) => { let layer_rule = lock.read_with(guard); - maybe_register_layers( + children_layer_id = maybe_register_layers( self, layer_rule.name.as_ref(), - containing_rule_state, + &mut current_layer, + &mut layer_names_to_pop, ); }, CssRule::LayerStatement(ref lock) => { let layer_rule = lock.read_with(guard); for name in &*layer_rule.names { - maybe_register_layers(self, Some(name), containing_rule_state); - // Register each layer individually. - containing_rule_state.restore(&saved_containing_rule_state); + let mut pushed = 0; + // There are no children, so we can ignore the + // return value. + maybe_register_layers(self, Some(name), &mut current_layer, &mut pushed); + for _ in 0..pushed { + current_layer.0.pop(); + } } }, - CssRule::Container(ref lock) => { - let container_rule = lock.read_with(guard); - let id = ContainerConditionId(self.container_conditions.len() as u16); - self.container_conditions.push(ContainerConditionReference { - parent: containing_rule_state.container_condition_id, - condition: Some(container_rule.condition.clone()), - }); - containing_rule_state.container_condition_id = id; - }, // We don't care about any other rule. _ => {}, } @@ -2811,12 +2733,15 @@ impl CascadeData { stylesheet, guard, rebuild_kind, - containing_rule_state, + current_layer, + children_layer_id, precomputed_pseudo_element_decls.as_deref_mut(), )?; } - containing_rule_state.restore(&saved_containing_rule_state); + for _ in 0..layer_names_to_pop { + current_layer.0.pop(); + } } Ok(()) @@ -2845,7 +2770,7 @@ impl CascadeData { self.effective_media_query_results.saw_effective(contents); } - let mut state = ContainingRuleState::default(); + let mut current_layer = LayerName::new_empty(); self.add_rule_list( contents.rules(guard).iter(), device, @@ -2853,7 +2778,8 @@ impl CascadeData { stylesheet, guard, rebuild_kind, - &mut state, + &mut current_layer, + LayerId::root(), precomputed_pseudo_element_decls.as_deref_mut(), )?; @@ -2901,7 +2827,6 @@ impl CascadeData { CssRule::Style(..) | CssRule::Namespace(..) | CssRule::FontFace(..) | - CssRule::Container(..) | CssRule::CounterStyle(..) | CssRule::Supports(..) | CssRule::Keyframes(..) | @@ -2979,8 +2904,6 @@ impl CascadeData { self.layer_id.clear(); self.layers.clear(); self.layers.push(CascadeLayer::root()); - self.container_conditions.clear(); - self.container_conditions.push(ContainerConditionReference::none()); #[cfg(feature = "gecko")] { self.extra_data.clear(); @@ -3075,9 +2998,6 @@ pub struct Rule { /// The current layer id of this style rule. pub layer_id: LayerId, - /// The current @container rule id. - pub container_condition_id: ContainerConditionId, - /// The actual style rule. #[cfg_attr( feature = "gecko", @@ -3123,7 +3043,6 @@ impl Rule { style_rule: Arc>, source_order: u32, layer_id: LayerId, - container_condition_id: ContainerConditionId, ) -> Self { Rule { selector, @@ -3131,17 +3050,10 @@ impl Rule { style_rule, source_order, layer_id, - container_condition_id, } } } -// The size of this is critical to performance on the bloom-basic -// microbenchmark. -// When iterating over a large Rule array, we want to be able to fast-reject -// selectors (with the inline hashes) with as few cache misses as possible. -size_of_test!(Rule, 40); - /// A function to be able to test the revalidation stuff. pub fn needs_revalidation_for_testing(s: &Selector) -> bool { let mut attribute_dependencies = Default::default(); diff --git a/components/style/traversal.rs b/components/style/traversal.rs index fcaeb6e9c57..bf0b963c709 100644 --- a/components/style/traversal.rs +++ b/components/style/traversal.rs @@ -16,13 +16,6 @@ use crate::stylist::RuleInclusion; use crate::traversal_flags::TraversalFlags; use selectors::NthIndexCache; use smallvec::SmallVec; -use std::collections::HashMap; - -/// A cache from element reference to known-valid computed style. -pub type UndisplayedStyleCache = HashMap< - selectors::OpaqueElement, - servo_arc::Arc, ->; /// A per-traversal-level chunk of data. This is sent down by the traversal, and /// currently only holds the dom depth for the bloom filter. @@ -301,7 +294,6 @@ pub fn resolve_style( element: E, rule_inclusion: RuleInclusion, pseudo: Option<&PseudoElement>, - mut undisplayed_style_cache: Option<&mut UndisplayedStyleCache>, ) -> ElementStyles where E: TElement, @@ -312,11 +304,6 @@ where element.borrow_data().map_or(true, |d| !d.has_styles()), "Why are we here?" ); - debug_assert!( - rule_inclusion == RuleInclusion::All || undisplayed_style_cache.is_none(), - "can't use the cache for default styles only" - ); - let mut ancestors_requiring_style_resolution = SmallVec::<[E; 16]>::new(); // Clear the bloom filter, just in case the caller is reusing TLS. @@ -333,12 +320,6 @@ where } } } - if let Some(ref mut cache) = undisplayed_style_cache { - if let Some(s) = cache.get(¤t.opaque()) { - style = Some(s.clone()); - break; - } - } ancestors_requiring_style_resolution.push(current); ancestor = current.traversal_parent(); } @@ -356,9 +337,7 @@ where } ancestor = ancestor.unwrap().traversal_parent(); - layout_parent_style = ancestor.and_then(|a| { - a.borrow_data().map(|data| data.styles.primary().clone()) - }); + layout_parent_style = ancestor.map(|a| a.borrow_data().unwrap().styles.primary().clone()); } for ancestor in ancestors_requiring_style_resolution.iter().rev() { @@ -381,27 +360,18 @@ where layout_parent_style = style.clone(); } - if let Some(ref mut cache) = undisplayed_style_cache { - cache.insert(ancestor.opaque(), style.clone().unwrap()); - } context.thread_local.bloom_filter.push(*ancestor); } context.thread_local.bloom_filter.assert_complete(element); - let styles: ElementStyles = StyleResolverForElement::new( + StyleResolverForElement::new( element, context, rule_inclusion, PseudoElementResolution::Force, ) .resolve_style(style.as_deref(), layout_parent_style.as_deref()) - .into(); - - if let Some(ref mut cache) = undisplayed_style_cache { - cache.insert(element.opaque(), styles.primary().clone()); - } - - styles + .into() } /// Calculates the style for a single node. diff --git a/components/style/values/animated/color.rs b/components/style/values/animated/color.rs index cdfa45dc4e7..f99e344a57a 100644 --- a/components/style/values/animated/color.rs +++ b/components/style/values/animated/color.rs @@ -5,20 +5,16 @@ //! 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::{ - GenericColor, GenericColorMix, ColorInterpolationMethod, ColorSpace, HueInterpolationMethod, -}; +use crate::values::generics::color::{Color as GenericColor, ComplexColorRatios}; +use crate::values::specified::color::{ColorSpaceKind, HueAdjuster}; 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, ToAnimatedValue)] -#[repr(C)] +#[derive(Clone, Copy, Debug, MallocSizeOf, PartialEq, ToAnimatedZero)] pub struct RGBA { /// The red component. pub red: f32, @@ -30,9 +26,6 @@ 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] @@ -50,20 +43,44 @@ 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 (left_weight, right_weight) = procedure.weights(); - Ok(Color::mix( - &ColorInterpolationMethod::srgb(), - self, - left_weight as f32, - other, - right_weight as f32, - /* normalize_weights = */ false, - )) + 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)) } } @@ -91,122 +108,253 @@ impl ComputeSquaredDistance for RGBA { } /// An animated value for ``. -pub type Color = GenericColor; - -/// An animated value for ``. -pub type ColorMix = GenericColorMix; +pub type Color = GenericColor; impl Color { - fn to_rgba(&self, current_color: RGBA) -> RGBA { - let mut clone = self.clone(); - clone.simplify(Some(¤t_color)); - *clone.as_numeric().unwrap() + 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 + } } /// Mix two colors into one. pub fn mix( - 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; - } - } + 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, + ), } - - 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: &RGBA, + left_color: &Color, left_weight: f32, - right_color: &RGBA, + right_color: &Color, right_weight: f32, - hue_interpolation: HueInterpolationMethod, - alpha_multiplier: f32, - ) -> RGBA + hue_adjuster: HueAdjuster, + ) -> Self where S: ModelledColor, { - let left = S::from(*left_color); - let right = S::from(*right_color); + let left_bg = S::from(left_color.scaled_rgba()); + let right_bg = S::from(right_color.scaled_rgba()); - 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; + 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(); } - rgba + 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, + } } } impl Animate for Color { #[inline] fn animate(&self, other: &Self, procedure: Procedure) -> Result { - 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) + 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 })) } } impl ComputeSquaredDistance for Color { #[inline] fn compute_squared_distance(&self, other: &Self) -> Result { - let current_color = RGBA::transparent(); - self.to_rgba(current_color) - .compute_squared_distance(&other.to_rgba(current_color)) + // 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)?) } } impl ToAnimatedZero for Color { #[inline] fn to_animated_zero(&self) -> Result { - Ok(Color::rgba(RGBA::transparent())) + Ok(RGBA::transparent().into()) } } @@ -217,347 +365,327 @@ impl ToAnimatedZero for Color { trait ModelledColor: Clone + Copy + From + Into { /// Linearly interpolate between the left and right colors. /// - /// The HueInterpolationMethod parameter is only for color spaces where the hue is + /// The HueAdjuster 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_interpolation: HueInterpolationMethod, + hue_adjuster: HueAdjuster, ) -> Self; } -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 -} +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.; -// Normalize hue into [0, 360) -fn normalize_hue(v: f32) -> f32 { - v - 360. * (v / 360.).floor() -} - -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 { - *left = *right; + // 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; } - } 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"), - } -} - -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 -} - -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, - ) + let alpha = (left_bg.alpha * left_weight + right_bg.alpha * right_weight).min(1.); + if alpha <= 0. { + RGBA::transparent() } else { - interpolate_premultiplied_component( - left[i], - left_weight, - left_alpha, - right[i], - right_weight, - right_alpha, - inverse_of_result_alpha, - ) - }; + let inv = 1. / alpha; + RGBA::new(red * inv, green * inv, blue * inv, alpha) + } } - result[3] = result_alpha; - - result } -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 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, +} + +impl XYZA { + /// Returns a transparent color. + #[inline] + pub fn transparent() -> Self { + Self { + x: 0., + y: 0., + z: 0., + alpha: 0., + } + } +} + +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, } } - }; + } } -impl_lerp!(RGBA, None); - +/// An animated LABA colour. #[derive(Clone, Copy, Debug)] -#[repr(C)] -struct LinearRGBA { - red: f32, - green: f32, - blue: f32, - alpha: f32, +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, } -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 LABA { + /// Returns a transparent color. + #[inline] + pub fn transparent() -> Self { + Self { + lightness: 0., + a: 0., + b: 0., + alpha: 0., + } + } } -impl_lerp!(XYZD65A, None); +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.; -/// An animated XYZ D50 colour. -#[derive(Clone, Copy, Debug)] -#[repr(C)] -struct XYZD50A { - x: f32, - y: f32, - z: f32, - alpha: f32, + // 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, + } + } + } } -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)] -#[repr(C)] -struct LCHA { - lightness: f32, - chroma: f32, - hue: f32, - alpha: f32, +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, } -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_lerp!(HWBA, Some(0)); - -#[derive(Clone, Copy, Debug)] -#[repr(C)] -struct HSLA { - hue: f32, - sat: f32, - light: f32, - alpha: f32, -} - -impl_lerp!(HSLA, Some(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; - - if d != 0. { - sat = if light == 0.0 || light == 1.0 { - 0. - } else { - (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); +impl LCHA { + /// Returns a transparent color. + #[inline] + pub fn transparent() -> Self { Self { - hue: hsl.hue, - white: min, - black: 1. - max, - alpha: rgba.alpha, + lightness: 0., + chroma: 0., + hue: 0., + alpha: 0., } } } -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 LCHA { + fn adjust(left_bg: Self, right_bg: Self, hue_adjuster: HueAdjuster) -> (Self, Self) { + use std::f32::consts::{PI, TAU}; + + 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) } } -impl From for LinearRGBA { +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); + + let mut lightness = 0.; + let mut chroma = 0.; + let mut hue = 0.; + + // 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() + } else { + let inv = 1. / alpha; + Self { + lightness: lightness * inv, + chroma: chroma * inv, + hue, + 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 fn from(rgba: RGBA) -> Self { fn linearize(value: f32) -> f32 { let sign = if value < 0. { -1. } else { 1. }; @@ -568,39 +696,15 @@ impl From for LinearRGBA { 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, - } - } -} -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(); + #[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., + ); - 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., @@ -608,100 +712,34 @@ impl From for XYZD50A { -0.05019222954313557, -0.01707382502938514, 0.7518742899580008, 0., 0., 0., 0., 1., ); - 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 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., + // 1. Convert from sRGB to linear-light sRGB (undo gamma encoding). + let rgb = Vector3D::new( + linearize(rgba.red), + linearize(rgba.green), + linearize(rgba.blue), ); - 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 { + // 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: lrgba.alpha, + alpha: rgba.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 { +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: XYZD50A) -> Self { + fn from(xyza: XYZA) -> Self { const WHITE: [f32; 3] = [0.96422, 1., 0.82521]; fn compute_f(value: f32) -> f32 { @@ -740,7 +778,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) * DEG_PER_RAD; + let hue = laba.b.atan2(laba.a); let chroma = (laba.a * laba.a + laba.b * laba.b).sqrt(); LCHA { lightness: laba.lightness, @@ -756,9 +794,8 @@ impl From for LABA { /// /// [1]: https://drafts.csswg.org/css-color/#color-conversion-code fn from(lcha: LCHA) -> Self { - let hue_radians = lcha.hue * RAD_PER_DEG; - let a = lcha.chroma * hue_radians.cos(); - let b = lcha.chroma * hue_radians.sin(); + let a = lcha.chroma * lcha.hue.cos(); + let b = lcha.chroma * lcha.hue.sin(); LABA { lightness: lcha.lightness, a, @@ -768,7 +805,7 @@ impl From for LABA { } } -impl From for XYZD50A { +impl From for XYZA { /// Convert a CIELAB color to XYZ as specified in [1] and [2]. /// /// [1]: https://drafts.csswg.org/css-color/#lab-to-predefined @@ -799,7 +836,7 @@ impl From for XYZD50A { (116. * f2 - 16.) / KAPPA }; - Self { + XYZA { x: x * WHITE[0], y: y * WHITE[1], z: z * WHITE[2], @@ -808,38 +845,85 @@ impl From for XYZD50A { } } -impl From for RGBA { - fn from(d50: XYZD50A) -> Self { - Self::from(XYZD65A::from(d50)) - } -} +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 XYZD50A { - fn from(rgba: RGBA) -> Self { - Self::from(XYZD65A::from(rgba)) + #[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 LABA { fn from(rgba: RGBA) -> Self { - Self::from(XYZD50A::from(rgba)) + let xyza: XYZA = rgba.into(); + xyza.into() } } impl From for RGBA { fn from(laba: LABA) -> Self { - Self::from(XYZD50A::from(laba)) + let xyza: XYZA = laba.into(); + xyza.into() } } impl From for LCHA { fn from(rgba: RGBA) -> Self { - Self::from(LABA::from(rgba)) + let xyza: XYZA = rgba.into(); + let laba: LABA = xyza.into(); + laba.into() } } impl From for RGBA { fn from(lcha: LCHA) -> Self { - Self::from(LABA::from(lcha)) + let laba: LABA = lcha.into(); + let xyza: XYZA = laba.into(); + xyza.into() } } diff --git a/components/style/values/computed/box.rs b/components/style/values/computed/box.rs index f05870d6ead..dd1e4900672 100644 --- a/components/style/values/computed/box.rs +++ b/components/style/values/computed/box.rs @@ -13,11 +13,9 @@ use crate::values::specified::box_ as specified; pub use crate::values::specified::box_::{ AnimationName, AnimationTimeline, Appearance, BreakBetween, BreakWithin, - Clear as SpecifiedClear, Contain, ContainerName, ContainerType, ContentVisibility, Display, - Float as SpecifiedFloat, Overflow, OverflowAnchor, OverflowClipBox, - OverscrollBehavior, ScrollSnapAlign, ScrollSnapAxis, ScrollSnapStop, - ScrollSnapStrictness, ScrollSnapType, ScrollbarGutter, TouchAction, - TransitionProperty, WillChange, + Clear as SpecifiedClear, Contain, ContentVisibility, Display, Float as SpecifiedFloat, Overflow, + OverflowAnchor, OverflowClipBox, OverscrollBehavior, ScrollSnapAlign, ScrollSnapAxis, + ScrollSnapStrictness, ScrollSnapType, ScrollbarGutter, TouchAction, TransitionProperty, WillChange, }; /// A computed value for the `vertical-align` property. diff --git a/components/style/values/computed/color.rs b/components/style/values/computed/color.rs index 573cb6fe500..7610bfbba3b 100644 --- a/components/style/values/computed/color.rs +++ b/components/style/values/computed/color.rs @@ -7,7 +7,6 @@ use crate::values::animated::color::RGBA as AnimatedRGBA; use crate::values::animated::ToAnimatedValue; use crate::values::generics::color::{GenericCaretColor, GenericColor, GenericColorOrAuto}; -use crate::values::computed::percentage::Percentage; use cssparser::{Color as CSSParserColor, RGBA}; use std::fmt; use style_traits::{CssWriter, ToCss}; @@ -21,20 +20,7 @@ pub type ColorPropertyValue = RGBA; pub type MozFontSmoothingBackgroundColor = RGBA; /// A computed value for ``. -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), - } - } -} +pub type Color = GenericColor; impl Color { /// Returns a complex color value representing transparent. @@ -42,21 +28,67 @@ impl Color { Color::rgba(RGBA::transparent()) } - /// Returns opaque black. - pub fn black() -> Color { - Color::rgba(RGBA::new(0, 0, 0, 255)) - } - - /// Returns opaque white. - pub fn white() -> Color { - Color::rgba(RGBA::new(255, 255, 255, 255)) - } - /// Combine this complex color with the given foreground color into - /// a numeric RGBA color. - pub fn into_rgba(mut self, current_color: RGBA) -> RGBA { - self.simplify(Some(¤t_color)); - *self.as_numeric().unwrap() + /// a numeric RGBA color. It currently uses linear blending. + pub fn to_rgba(&self, fg_color: RGBA) -> RGBA { + // Common cases that the complex color is either pure numeric color or + // pure currentcolor. + if self.is_numeric() { + return self.color; + } + + if self.is_currentcolor() { + return fg_color; + } + + let ratios = &self.ratios; + let color = &self.color; + + // For the more complicated case that the alpha value differs, + // we use the following formula to compute the components: + // alpha = self_alpha * bg_ratio + fg_alpha * fg_ratio + // color = (self_color * self_alpha * bg_ratio + + // fg_color * fg_alpha * fg_ratio) / alpha + + let p1 = ratios.bg; + let a1 = color.alpha_f32(); + let r1 = a1 * color.red_f32(); + let g1 = a1 * color.green_f32(); + let b1 = a1 * color.blue_f32(); + + let p2 = ratios.fg; + let a2 = fg_color.alpha_f32(); + let r2 = a2 * fg_color.red_f32(); + let g2 = a2 * fg_color.green_f32(); + let b2 = a2 * fg_color.blue_f32(); + + let a = p1 * a1 + p2 * a2; + if a <= 0. { + return RGBA::transparent(); + } + let a = a.min(1.); + + let inv = 1. / a; + + let r = (p1 * r1 + p2 * r2) * inv; + let g = (p1 * g1 + p2 * g2) * inv; + let b = (p1 * b1 + p2 * b2) * inv; + RGBA::from_floats(r, g, b, a) + } +} + +impl ToCss for Color { + fn to_css(&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(()) } } diff --git a/components/style/values/computed/image.rs b/components/style/values/computed/image.rs index 5f650631c89..980017b2822 100644 --- a/components/style/values/computed/image.rs +++ b/components/style/values/computed/image.rs @@ -31,9 +31,6 @@ 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< @@ -50,6 +47,8 @@ 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 896001cbcd8..b8ff80587e6 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) + length.to_computed_value(context.viewport_size_for_viewport_unit_resolution()) }, specified::NoCalcLength::ServoCharacterWidth(length) => { length.to_computed_value(context.style().get_font().clone_font_size().size()) diff --git a/components/style/values/computed/mod.rs b/components/style/values/computed/mod.rs index 68256cdd20f..68194eb733a 100644 --- a/components/style/values/computed/mod.rs +++ b/components/style/values/computed/mod.rs @@ -16,7 +16,6 @@ use super::specified; use super::{CSSFloat, CSSInteger}; use crate::computed_value_flags::ComputedValueFlags; use crate::context::QuirksMode; -use crate::stylesheets::container_rule::ContainerInfo; use crate::font_metrics::{FontMetrics, FontMetricsOrientation}; use crate::media_queries::Device; #[cfg(feature = "gecko")] @@ -45,12 +44,11 @@ pub use self::basic_shape::FillRule; pub use self::border::{BorderCornerRadius, BorderRadius, BorderSpacing}; pub use self::border::{BorderImageRepeat, BorderImageSideWidth}; pub use self::border::{BorderImageSlice, BorderImageWidth}; -pub use self::box_::{AnimationIterationCount, AnimationName, AnimationTimeline, Contain, ContainerName, ContainerType}; +pub use self::box_::{AnimationIterationCount, AnimationName, AnimationTimeline, Contain}; pub use self::box_::{Appearance, BreakBetween, BreakWithin, Clear, ContentVisibility, Float}; pub use self::box_::{Display, Overflow, OverflowAnchor, TransitionProperty}; pub use self::box_::{OverflowClipBox, OverscrollBehavior, Perspective, Resize, ScrollbarGutter}; -pub use self::box_::{ScrollSnapAlign, ScrollSnapAxis, ScrollSnapStop}; -pub use self::box_::{ScrollSnapStrictness, ScrollSnapType}; +pub use self::box_::{ScrollSnapAlign, ScrollSnapAxis, ScrollSnapStrictness, ScrollSnapType}; pub use self::box_::{TouchAction, VerticalAlign, WillChange}; pub use self::color::{Color, ColorOrAuto, ColorPropertyValue, ColorScheme, PrintColorAdjust}; pub use self::column::ColumnCount; @@ -74,7 +72,7 @@ pub use self::list::ListStyleType; pub use self::list::Quotes; pub use self::motion::{OffsetPath, OffsetRotate}; pub use self::outline::OutlineStyle; -pub use self::page::{PageOrientation, PageName, PageSize, PaperSize}; +pub use self::page::{Orientation, PageName, PageSize, PaperSize}; pub use self::percentage::{NonNegativePercentage, Percentage}; pub use self::position::AspectRatio; pub use self::position::{ @@ -99,7 +97,6 @@ pub use self::transform::{TransformOrigin, TransformStyle, Translate}; pub use self::ui::CursorImage; pub use self::ui::{Cursor, MozForceBrokenImageIcon, UserSelect}; pub use super::specified::TextTransform; -pub use super::specified::ViewportVariant; pub use super::specified::{BorderStyle, TextDecorationLine}; pub use super::{Auto, Either, None_}; pub use app_units::Au; @@ -172,9 +169,6 @@ pub struct Context<'a> { /// values, which SMIL allows. pub for_smil_animation: bool, - /// Returns the container information to evaluate a given container query. - pub container_info: Option, - /// The property we are computing a value for, if it is a non-inherited /// property. None if we are computed a value for an inherited property /// or not computing for a property at all (e.g. in a media query @@ -203,40 +197,6 @@ impl<'a> Context<'a> { in_media_query: true, quirks_mode, for_smil_animation: false, - container_info: None, - for_non_inherited_property: None, - rule_cache_conditions: RefCell::new(&mut conditions), - }; - - f(&context) - } - - /// Creates a suitable context for container query evaluation for the style - /// specified. - pub fn for_container_query_evaluation( - 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), }; @@ -293,13 +253,10 @@ impl<'a> Context<'a> { } /// The current viewport size, used to resolve viewport units. - pub fn viewport_size_for_viewport_unit_resolution( - &self, - variant: ViewportVariant, - ) -> default::Size2D { + pub fn viewport_size_for_viewport_unit_resolution(&self) -> default::Size2D { self.builder .device - .au_viewport_size_for_viewport_unit_resolution(variant) + .au_viewport_size_for_viewport_unit_resolution() } /// The default computed style we're getting our reset style from. @@ -343,8 +300,8 @@ impl<'a, 'cx, 'cx_a: 'cx, S: ToComputedValue + 'a> ComputedVecIter<'a, 'cx, 'cx_ /// Construct an iterator from a slice of specified values and a context pub fn new(cx: &'cx Context<'cx_a>, values: &'a [S]) -> Self { ComputedVecIter { - cx, - values, + cx: cx, + values: values, } } } diff --git a/components/style/values/computed/page.rs b/components/style/values/computed/page.rs index 5daf6bbcde8..080681e008f 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::PageOrientation; +pub use generics::page::Orientation; pub use generics::page::PaperSize; pub use specified::PageName; @@ -26,7 +26,7 @@ pub enum PageSize { /// Specified size, paper size, or paper size and orientation. Size(Size2D), /// `landscape` or `portrait` value, no specified size. - Orientation(PageOrientation), + Orientation(Orientation), /// `auto` value Auto, } @@ -37,11 +37,11 @@ impl ToComputedValue for specified::PageSize { fn to_computed_value(&self, ctx: &Context) -> Self::ComputedValue { match &*self { Self::Size(s) => PageSize::Size(s.to_computed_value(ctx)), - Self::PaperSize(p, PageOrientation::Landscape) => PageSize::Size(Size2D { + Self::PaperSize(p, Orientation::Landscape) => PageSize::Size(Size2D { width: p.long_edge().to_computed_value(ctx), height: p.short_edge().to_computed_value(ctx), }), - Self::PaperSize(p, PageOrientation::Portrait) => PageSize::Size(Size2D { + Self::PaperSize(p, Orientation::Portrait) => PageSize::Size(Size2D { width: p.short_edge().to_computed_value(ctx), height: p.long_edge().to_computed_value(ctx), }), diff --git a/components/style/values/computed/percentage.rs b/components/style/values/computed/percentage.rs index 4e9732ade2c..7430d82d471 100644 --- a/components/style/values/computed/percentage.rs +++ b/components/style/values/computed/percentage.rs @@ -6,7 +6,6 @@ use crate::values::animated::ToAnimatedValue; use crate::values::generics::NonNegative; -use crate::values::specified::percentage::ToPercentage; use crate::values::{serialize_percentage, CSSFloat}; use crate::Zero; use std::fmt; @@ -65,12 +64,6 @@ impl Zero for Percentage { } } -impl ToPercentage for Percentage { - fn to_percentage(&self) -> CSSFloat { - self.0 - } -} - impl std::ops::AddAssign for Percentage { fn add_assign(&mut self, other: Self) { self.0 += other.0 diff --git a/components/style/values/computed/ratio.rs b/components/style/values/computed/ratio.rs index ae8997cfc06..ba40039eae1 100644 --- a/components/style/values/computed/ratio.rs +++ b/components/style/values/computed/ratio.rs @@ -72,16 +72,6 @@ impl ComputeSquaredDistance for Ratio { } } -impl Zero for Ratio { - fn zero() -> Self { - Self::new(Zero::zero(), One::one()) - } - - fn is_zero(&self) -> bool { - self.0.is_zero() - } -} - impl Ratio { /// Returns a new Ratio. #[inline] diff --git a/components/style/values/computed/svg.rs b/components/style/values/computed/svg.rs index 640c3bfda70..344f1d83518 100644 --- a/components/style/values/computed/svg.rs +++ b/components/style/values/computed/svg.rs @@ -8,6 +8,7 @@ use crate::values::computed::color::Color; use crate::values::computed::url::ComputedUrl; use crate::values::computed::{LengthPercentage, NonNegativeLengthPercentage, Opacity}; use crate::values::generics::svg as generic; +use crate::values::RGBA; use crate::Zero; pub use crate::values::specified::{DProperty, MozContextProperties, SVGPaintOrder}; @@ -21,8 +22,9 @@ pub type SVGPaintKind = generic::GenericSVGPaintKind; impl SVGPaint { /// Opaque black color pub fn black() -> Self { + let rgba = RGBA::from_floats(0., 0., 0., 1.).into(); SVGPaint { - kind: generic::SVGPaintKind::Color(Color::black()), + kind: generic::SVGPaintKind::Color(rgba), fallback: generic::SVGPaintFallback::Unset, } } diff --git a/components/style/values/generics/calc.rs b/components/style/values/generics/calc.rs index 5d52cdf5f5f..d9044bbb818 100644 --- a/components/style/values/generics/calc.rs +++ b/components/style/values/generics/calc.rs @@ -45,33 +45,13 @@ pub enum SortKey { Cap, Ch, Deg, - Dvb, - Dvh, - Dvi, - Dvmax, - Dvmin, - Dvw, Em, Ex, Ic, - Lvb, - Lvh, - Lvi, - Lvmax, - Lvmin, - Lvw, Px, Rem, Sec, - Svb, - Svh, - Svi, - Svmax, - Svmin, - Svw, - Vb, Vh, - Vi, Vmax, Vmin, Vw, diff --git a/components/style/values/generics/color.rs b/components/style/values/generics/color.rs index 7ab3880bc1a..5b477dee60d 100644 --- a/components/style/values/generics/color.rs +++ b/components/style/values/generics/color.rs @@ -4,268 +4,81 @@ //! Generic types for color properties. -use std::fmt::{self, Write}; -use style_traits::{CssWriter, ParseError, ToCss}; -use crate::values::{Parse, ParserContext, Parser}; -use crate::values::specified::percentage::ToPercentage; -use crate::values::animated::ToAnimatedValue; -use crate::values::animated::color::RGBA as AnimatedRGBA; +/// Ratios representing the contribution of color and currentcolor to +/// the final color value. +/// +/// NOTE(emilio): For animated colors, the sum of these two might be more than +/// one (because the background color would've been scaled down already). So +/// beware that it is not generally safe to assume that if bg is 1 then fg is 0, +/// for example. +#[derive(Clone, Copy, Debug, MallocSizeOf, PartialEq, ToAnimatedValue, ToShmem)] +#[repr(C)] +pub struct ComplexColorRatios { + /// Numeric color contribution. + pub bg: f32, + /// currentcolor contribution. + pub fg: f32, +} + +impl ComplexColorRatios { + /// Ratios representing a `Numeric` color. + pub const NUMERIC: ComplexColorRatios = ComplexColorRatios { bg: 1., fg: 0. }; + /// Ratios representing the `CurrentColor` color. + pub const CURRENT_COLOR: ComplexColorRatios = ComplexColorRatios { bg: 0., fg: 1. }; +} /// This struct represents a combined color from a numeric color and /// the current foreground color (currentcolor keyword). -#[derive(Clone, Debug, MallocSizeOf, PartialEq, ToAnimatedValue, ToShmem)] +#[derive(Clone, Copy, Debug, MallocSizeOf, PartialEq, ToAnimatedValue, ToShmem)] #[repr(C)] -pub enum GenericColor { +pub struct GenericColor { /// The actual numeric color. - 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 color: RGBA, + /// The ratios of mixing between numeric and currentcolor. + /// The formula is: `color * ratios.bg + currentcolor * ratios.fg`. + pub ratios: ComplexColorRatios, } pub use self::GenericColor as Color; -impl 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, - } - } - - /// 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); - } - }, - } - } - +impl Color { /// Returns a color value representing currentcolor. pub fn currentcolor() -> Self { - Self::CurrentColor + Color { + color: cssparser::RGBA::transparent(), + ratios: ComplexColorRatios::CURRENT_COLOR, + } + } +} + +impl Color { + /// Create a color based upon the specified ratios. + pub fn new(color: RGBA, ratios: ComplexColorRatios) -> Self { + Self { color, ratios } } /// Returns a numeric color representing the given RGBA value. pub fn rgba(color: RGBA) -> Self { - Self::Numeric(color) - } - - /// Whether it is a currentcolor value (no numeric color component). - pub fn is_currentcolor(&self) -> bool { - matches!(*self, Self::CurrentColor) + Self { + color, + ratios: ComplexColorRatios::NUMERIC, + } } /// Whether it is a numeric color (no currentcolor component). pub fn is_numeric(&self) -> bool { - matches!(*self, Self::Numeric(..)) + self.ratios == ComplexColorRatios::NUMERIC + } + + /// Whether it is a currentcolor value (no numeric color component). + pub fn is_currentcolor(&self) -> bool { + self.ratios == ComplexColorRatios::CURRENT_COLOR + } +} + +impl From for Color { + fn from(color: RGBA) -> Self { + Self::rgba(color) } } diff --git a/components/style/values/generics/image.rs b/components/style/values/generics/image.rs index d5e830810a8..dd504a07d24 100644 --- a/components/style/values/generics/image.rs +++ b/components/style/values/generics/image.rs @@ -8,7 +8,6 @@ use crate::custom_properties; use crate::values::generics::position::PositionComponent; -use crate::values::generics::Optional; use crate::values::serialize_atom_identifier; use crate::Atom; use crate::Zero; @@ -72,6 +71,20 @@ pub struct GenericCrossFade { 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, @@ -79,7 +92,7 @@ pub struct GenericCrossFade { #[repr(C)] pub struct GenericCrossFadeElement { /// The percent of the final image that `image` will be. - pub percent: Optional, + pub percent: PercentOrNone, /// 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 8c10085af18..b15d4f01887 100644 --- a/components/style/values/generics/mod.rs +++ b/components/style/values/generics/mod.rs @@ -310,74 +310,3 @@ 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 efb67e0812d..1de1a8e912c 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 PageOrientation { +pub enum Orientation { /// Portrait orientation Portrait, /// Landscape orientation @@ -95,8 +95,8 @@ pub enum PageOrientation { } #[inline] -fn is_portrait(orientation: &PageOrientation) -> bool { - *orientation == PageOrientation::Portrait +fn is_portrait(orientation: &Orientation) -> bool { + *orientation == Orientation::Portrait } /// Page size property @@ -110,9 +110,9 @@ pub enum GenericPageSize { /// Page dimensions. Size(S), /// An orientation with no size. - Orientation(PageOrientation), + Orientation(Orientation), /// Paper size by name - PaperSize(PaperSize, #[css(skip_if = "is_portrait")] PageOrientation), + PaperSize(PaperSize, #[css(skip_if = "is_portrait")] Orientation), } pub use self::GenericPageSize as PageSize; diff --git a/components/style/values/mod.rs b/components/style/values/mod.rs index e221619c843..9d7b7d74acc 100644 --- a/components/style/values/mod.rs +++ b/components/style/values/mod.rs @@ -16,6 +16,7 @@ pub use cssparser::{SourceLocation, Token, RGBA}; use precomputed_hash::PrecomputedHash; use selectors::parser::SelectorParseErrorKind; use std::fmt::{self, Debug, Write}; +use std::hash; use style_traits::{CssWriter, ParseError, StyleParseErrorKind, ToCss}; use to_shmem::impl_trivial_to_shmem; @@ -457,32 +458,27 @@ impl CustomIdent { ident: &CowRcStr<'i>, excluding: &[&str], ) -> Result> { - if !Self::is_valid(ident, excluding) { - return Err( - location.new_custom_error(SelectorParseErrorKind::UnexpectedIdent(ident.clone())) - ); - } - if excluding.iter().any(|s| ident.eq_ignore_ascii_case(s)) { - Err(location.new_custom_error(StyleParseErrorKind::UnspecifiedError)) - } else { - Ok(CustomIdent(Atom::from(ident.as_ref()))) - } - } - - fn is_valid(ident: &str, excluding: &[&str]) -> bool { use crate::properties::CSSWideKeyword; // https://drafts.csswg.org/css-values-4/#custom-idents: // // The CSS-wide keywords are not valid 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; + return Err( + location.new_custom_error(SelectorParseErrorKind::UnexpectedIdent(ident.clone())) + ); } // https://drafts.csswg.org/css-values-4/#custom-idents: // // Excluded keywords are excluded in all ASCII case permutations. - !excluding.iter().any(|s| ident.eq_ignore_ascii_case(s)) + // + if excluding.iter().any(|s| ident.eq_ignore_ascii_case(s)) { + Err(location.new_custom_error(StyleParseErrorKind::UnspecifiedError)) + } else { + Ok(CustomIdent(Atom::from(ident.as_ref()))) + } } } @@ -500,72 +496,45 @@ impl ToCss for CustomIdent { /// /// /// -/// -/// We use a single atom for these. Empty atom represents `none` animation. -#[repr(transparent)] #[derive( - Clone, Debug, Hash, PartialEq, MallocSizeOf, SpecifiedValueInfo, ToComputedValue, ToResolvedValue, ToShmem, + Clone, Debug, MallocSizeOf, SpecifiedValueInfo, ToComputedValue, ToResolvedValue, ToShmem, )] -pub struct TimelineOrKeyframesName(Atom); +#[repr(C, u8)] +pub enum TimelineOrKeyframesName { + /// + Ident(CustomIdent), + /// + QuotedString(Atom), +} impl TimelineOrKeyframesName { /// pub fn from_ident(value: &str) -> Self { - Self(Atom::from(value)) - } - - /// Returns the `none` value. - pub fn none() -> Self { - Self(atom!("")) - } - - /// Returns whether this is the special `none` value. - pub fn is_none(&self) -> bool { - self.0 == atom!("") + let location = SourceLocation { line: 0, column: 0 }; + let custom_ident = CustomIdent::from_ident(location, &value.into(), &["none"]).ok(); + match custom_ident { + Some(ident) => Self::Ident(ident), + None => Self::QuotedString(value.into()), + } } /// Create a new TimelineOrKeyframesName from Atom. #[cfg(feature = "gecko")] pub fn from_atom(atom: Atom) -> Self { - Self(atom) + debug_assert_ne!(atom, atom!("")); + + // FIXME: We might want to preserve , but currently Gecko + // stores both of and into nsAtom, so + // we can't tell it. + Self::Ident(CustomIdent(atom)) } /// The name as an Atom pub fn as_atom(&self) -> &Atom { - &self.0 - } - - fn parse<'i, 't>(input: &mut Parser<'i, 't>, invalid: &[&str]) -> Result> { - 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") + match *self { + Self::Ident(ref ident) => &ident.0, + Self::QuotedString(ref atom) => atom, } - - let mut serialize = |s: &_| { - if CustomIdent::is_valid(s, invalid) { - serialize_identifier(s, dest) - } else { - s.to_css(dest) - } - }; - #[cfg(feature = "gecko")] - return self.0.with_str(|s| serialize(s)); - #[cfg(feature = "servo")] - return serialize(self.0.as_ref()); } } @@ -578,70 +547,53 @@ pub trait IsAuto { fn is_auto(&self) -> bool; } -/// The typedef of . -#[repr(transparent)] -#[derive( - Clone, Debug, Deref, Hash, Eq, PartialEq, MallocSizeOf, SpecifiedValueInfo, ToComputedValue, ToResolvedValue, ToShmem, -)] -pub struct TimelineName(TimelineOrKeyframesName); - -impl TimelineName { - /// Returns the `none` value. - pub fn none() -> Self { - Self(TimelineOrKeyframesName::none()) +impl PartialEq for TimelineOrKeyframesName { + fn eq(&self, other: &Self) -> bool { + self.as_atom() == other.as_atom() } } -impl Parse for TimelineName { - fn parse<'i, 't>(_: &ParserContext, input: &mut Parser<'i, 't>) -> Result> { - Ok(Self(TimelineOrKeyframesName::parse(input, &["none", "auto"])?)) +impl hash::Hash for TimelineOrKeyframesName { + fn hash(&self, state: &mut H) + where + H: hash::Hasher, + { + self.as_atom().hash(state) } } -impl ToCss for TimelineName { +impl Parse for TimelineOrKeyframesName { + fn parse<'i, 't>( + _context: &ParserContext, + input: &mut Parser<'i, 't>, + ) -> Result> { + let location = input.current_source_location(); + match *input.next()? { + Token::Ident(ref s) => Ok(Self::Ident(CustomIdent::from_ident( + location, + s, + &["none"], + )?)), + Token::QuotedString(ref s) => Ok(Self::QuotedString(Atom::from(s.as_ref()))), + ref t => Err(location.new_unexpected_token_error(t.clone())), + } + } +} + +impl ToCss for TimelineOrKeyframesName { fn to_css(&self, dest: &mut CssWriter) -> fmt::Result where W: Write, { - self.0.to_css(dest, &["none", "auto"]) + match *self { + Self::Ident(ref ident) => ident.to_css(dest), + Self::QuotedString(ref atom) => atom.to_string().to_css(dest), + } } } +/// The typedef of . +pub type TimelineName = TimelineOrKeyframesName; + /// The typedef of . -#[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"]) - } -} +pub type KeyframesName = TimelineOrKeyframesName; diff --git a/components/style/values/specified/box.rs b/components/style/values/specified/box.rs index 3a6677de42a..35b1b3ff648 100644 --- a/components/style/values/specified/box.rs +++ b/components/style/values/specified/box.rs @@ -17,6 +17,7 @@ use crate::values::{CustomIdent, KeyframesName, TimelineName}; use crate::Atom; use cssparser::Parser; use num_traits::FromPrimitive; +use selectors::parser::SelectorParseErrorKind; use std::fmt::{self, Debug, Formatter, Write}; use style_traits::{CssWriter, KeywordsCollectFn, ParseError}; use style_traits::{SpecifiedValueInfo, StyleParseErrorKind, ToCss}; @@ -679,30 +680,33 @@ impl AnimationIterationCount { PartialEq, SpecifiedValueInfo, ToComputedValue, - ToCss, ToResolvedValue, ToShmem, )] #[value_info(other_values = "none")] -pub struct AnimationName(pub KeyframesName); +pub struct AnimationName(pub Option); impl AnimationName { /// Get the name of the animation as an `Atom`. pub fn as_atom(&self) -> Option<&Atom> { - if self.is_none() { - return None; - } - Some(self.0.as_atom()) + self.0.as_ref().map(|n| n.as_atom()) } /// Returns the `none` value. pub fn none() -> Self { - AnimationName(KeyframesName::none()) + AnimationName(None) } +} - /// Returns whether this is the none value. - pub fn is_none(&self) -> bool { - self.0.is_none() +impl ToCss for AnimationName { + fn to_css(&self, dest: &mut CssWriter) -> fmt::Result + where + W: Write, + { + match self.0 { + Some(ref name) => name.to_css(dest), + None => dest.write_str("none"), + } } } @@ -712,88 +716,14 @@ impl Parse for AnimationName { input: &mut Parser<'i, 't>, ) -> Result> { if let Ok(name) = input.try_parse(|input| KeyframesName::parse(context, input)) { - return Ok(AnimationName(name)); + return Ok(AnimationName(Some(name))); } input.expect_ident_matching("none")?; - Ok(AnimationName(KeyframesName::none())) + Ok(AnimationName(None)) } } -/// A value for the 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 @@ -815,15 +745,10 @@ fn is_default(value: &T) -> bool { 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 { @@ -844,30 +769,16 @@ 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 the "none" keyword). - // + // accepts "auto", so need to manually parse this. (We can not derive Parse because + // TimelineName excludes only "none" keyword.) // FIXME: Bug 1733260: we may drop None based on the spec issue: - // https://github.com/w3c/csswg-drafts/issues/6674 - // - // If `none` is removed, then we could potentially shrink this the same - // way we deal with animation-name. + // Note: https://github.com/w3c/csswg-drafts/issues/6674. if input.try_parse(|i| i.expect_ident_matching("auto")).is_ok() { return Ok(Self::Auto); } if input.try_parse(|i| i.expect_ident_matching("none")).is_ok() { - return Ok(AnimationTimeline::Timeline(TimelineName::none())); - } - - // https://drafts.csswg.org/scroll-animations-1/rewrite#scroll-notation - if input.try_parse(|i| i.expect_function_matching("scroll")).is_ok() { - return input.parse_nested_block(|i| { - Ok(Self::Scroll( - i.try_parse(ScrollAxis::parse).unwrap_or(ScrollAxis::Block), - i.try_parse(Scroller::parse).unwrap_or(Scroller::Nearest), - )) - }); + return Ok(Self::None); } TimelineName::parse(context, input).map(AnimationTimeline::Timeline) @@ -1080,28 +991,6 @@ impl ToCss for ScrollSnapAlign { } } -#[allow(missing_docs)] -#[cfg_attr(feature = "servo", derive(Deserialize, Serialize))] -#[derive( - Clone, - Copy, - Debug, - Eq, - MallocSizeOf, - Parse, - PartialEq, - SpecifiedValueInfo, - ToComputedValue, - ToCss, - ToResolvedValue, - ToShmem, -)] -#[repr(u8)] -pub enum ScrollSnapStop { - Normal, - Always, -} - #[allow(missing_docs)] #[cfg_attr(feature = "servo", derive(Deserialize, Serialize))] #[derive( @@ -1307,9 +1196,7 @@ impl Parse for WillChange { &["will-change", "none", "all", "auto"], )?; - if context.in_ua_sheet() && ident.0 == atom!("-moz-fixed-pos-containing-block") { - bits |= WillChangeBits::FIXPOS_CB_NON_SVG; - } else if ident.0 == atom!("scroll-position") { + if ident.0 == atom!("scroll-position") { bits |= WillChangeBits::SCROLL; } else { bits |= change_bits_for_maybe_property(&parser_ident, context); @@ -1326,8 +1213,9 @@ impl Parse for WillChange { bitflags! { /// Values for the `touch-action` property. - #[derive(MallocSizeOf, SpecifiedValueInfo, ToComputedValue, ToCss, ToResolvedValue, ToShmem, Parse)] - #[css(bitflags(single = "none,auto,manipulation", mixed = "pan-x,pan-y,pinch-zoom"))] + #[derive(MallocSizeOf, SpecifiedValueInfo, ToComputedValue, ToResolvedValue, ToShmem)] + /// These constants match Gecko's `NS_STYLE_TOUCH_ACTION_*` constants. + #[value_info(other_values = "auto,none,manipulation,pan-x,pan-y,pinch-zoom")] #[repr(C)] pub struct TouchAction: u8 { /// `none` variant @@ -1353,32 +1241,178 @@ impl TouchAction { } } +impl ToCss for TouchAction { + fn to_css(&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, Parse, SpecifiedValueInfo, ToComputedValue, ToCss, ToResolvedValue, ToShmem)] - #[css(bitflags(single = "none,strict,content", mixed="size,layout,paint,inline-size", overlapping_bits))] + #[derive(MallocSizeOf, SpecifiedValueInfo, ToComputedValue, ToResolvedValue, ToShmem)] + #[value_info(other_values = "none,strict,content,size,layout,paint")] #[repr(C)] /// Constants for contain: https://drafts.csswg.org/css-contain/#contain-property pub struct Contain: u8 { /// `none` variant, just for convenience. const NONE = 0; - /// `inline-size` variant, turns on single-axis inline size containment - const INLINE_SIZE = 1 << 0; - /// `block-size` variant, turns on single-axis block size containment, internal only - const BLOCK_SIZE = 1 << 1; - /// `layout` variant, turns on layout containment - const LAYOUT = 1 << 2; - /// `paint` variant, turns on paint containment - const PAINT = 1 << 3; /// 'size' variant, turns on size containment - const SIZE = 1 << 4 | Contain::INLINE_SIZE.bits | Contain::BLOCK_SIZE.bits; - /// `content` variant, turns on layout and paint containment - const CONTENT = 1 << 5 | Contain::LAYOUT.bits | Contain::PAINT.bits; + const SIZE = 1 << 0; + /// `layout` variant, turns on layout containment + const LAYOUT = 1 << 1; + /// `paint` variant, turns on paint containment + const PAINT = 1 << 2; /// `strict` variant, turns on all types of containment - const STRICT = 1 << 6 | Contain::LAYOUT.bits | Contain::PAINT.bits | Contain::SIZE.bits; + const STRICT = 1 << 3; + /// 'content' variant, turns on layout and paint containment + const CONTENT = 1 << 4; + /// variant with all the bits that contain: strict turns on + const STRICT_BITS = Contain::LAYOUT.bits | Contain::PAINT.bits | Contain::SIZE.bits; + /// variant with all the bits that contain: content turns on + const CONTENT_BITS = Contain::LAYOUT.bits | Contain::PAINT.bits; } } -/// https://drafts.csswg.org/css-contain-2/#content-visibility +impl ToCss for Contain { + fn to_css(&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)] #[cfg_attr(feature = "servo", derive(Deserialize, Serialize))] #[derive( Clone, @@ -1406,61 +1440,6 @@ pub enum ContentVisibility { Visible, } -bitflags! { - #[derive(MallocSizeOf, SpecifiedValueInfo, ToComputedValue, ToCss, Parse, ToResolvedValue, ToShmem)] - #[repr(C)] - #[allow(missing_docs)] - #[css(bitflags(single="none", mixed="style,size,inline-size", overlapping_bits))] - /// https://drafts.csswg.org/css-contain-3/#container-type - /// - /// TODO: block-size is on the spec but it seems it was removed? WPTs don't - /// support it, see https://github.com/w3c/csswg-drafts/issues/7179. - pub struct ContainerType: u8 { - /// The `none` variant. - const NONE = 0; - /// The `style` variant. - const STYLE = 1 << 0; - /// The `inline-size` variant. - const INLINE_SIZE = 1 << 1; - /// The `size` variant, exclusive with `inline-size` (they sharing bits - /// guarantees this). - const SIZE = 1 << 2 | Self::INLINE_SIZE.bits; - } -} - -/// https://drafts.csswg.org/css-contain-3/#container-name -#[repr(transparent)] -#[derive(Clone, Debug, MallocSizeOf, PartialEq, SpecifiedValueInfo, ToComputedValue, ToCss, ToResolvedValue, ToShmem)] -pub struct ContainerName(#[css(iterable, if_empty = "none")] pub crate::OwnedSlice); - -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; @@ -1911,6 +1890,10 @@ pub enum Appearance { MozMacSourceList, #[parse(condition = "ParserContext::in_ua_or_chrome_sheet")] MozMacSourceListSelection, + #[parse(condition = "ParserContext::in_ua_or_chrome_sheet")] + MozMacVibrantTitlebarDark, + #[parse(condition = "ParserContext::in_ua_or_chrome_sheet")] + MozMacVibrantTitlebarLight, /// A themed focus outline (for outline:auto). /// @@ -2118,9 +2101,9 @@ impl Overflow { } bitflags! { - #[derive(MallocSizeOf, SpecifiedValueInfo, ToCss, ToComputedValue, ToResolvedValue, ToShmem, Parse)] + #[derive(MallocSizeOf, SpecifiedValueInfo, ToComputedValue, ToResolvedValue, ToShmem)] + #[value_info(other_values = "auto,stable,both-edges")] #[repr(C)] - #[css(bitflags(single = "auto", mixed = "stable,both-edges", validate_mixed="Self::has_stable"))] /// Values for scrollbar-gutter: /// pub struct ScrollbarGutter: u8 { @@ -2133,9 +2116,56 @@ bitflags! { } } -impl ScrollbarGutter { - #[inline] - fn has_stable(self) -> bool { - self.intersects(Self::STABLE) +impl ToCss for ScrollbarGutter { + fn to_css(&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)) + } } } diff --git a/components/style/values/specified/calc.rs b/components/style/values/specified/calc.rs index c3abb9170d5..f5448591f21 100644 --- a/components/style/values/specified/calc.rs +++ b/components/style/values/specified/calc.rs @@ -190,29 +190,9 @@ impl generic::CalcNodeLeaf for Leaf { }, NoCalcLength::ViewportPercentage(ref vp) => match *vp { ViewportPercentageLength::Vh(..) => SortKey::Vh, - ViewportPercentageLength::Svh(..) => SortKey::Svh, - ViewportPercentageLength::Lvh(..) => SortKey::Lvh, - ViewportPercentageLength::Dvh(..) => SortKey::Dvh, ViewportPercentageLength::Vw(..) => SortKey::Vw, - ViewportPercentageLength::Svw(..) => SortKey::Svw, - ViewportPercentageLength::Lvw(..) => SortKey::Lvw, - ViewportPercentageLength::Dvw(..) => SortKey::Dvw, ViewportPercentageLength::Vmax(..) => SortKey::Vmax, - ViewportPercentageLength::Svmax(..) => SortKey::Svmax, - ViewportPercentageLength::Lvmax(..) => SortKey::Lvmax, - ViewportPercentageLength::Dvmax(..) => SortKey::Dvmax, ViewportPercentageLength::Vmin(..) => SortKey::Vmin, - ViewportPercentageLength::Svmin(..) => SortKey::Svmin, - ViewportPercentageLength::Lvmin(..) => SortKey::Lvmin, - ViewportPercentageLength::Dvmin(..) => SortKey::Dvmin, - ViewportPercentageLength::Vb(..) => SortKey::Vb, - ViewportPercentageLength::Svb(..) => SortKey::Svb, - ViewportPercentageLength::Lvb(..) => SortKey::Lvb, - ViewportPercentageLength::Dvb(..) => SortKey::Dvb, - ViewportPercentageLength::Vi(..) => SortKey::Vi, - ViewportPercentageLength::Svi(..) => SortKey::Svi, - ViewportPercentageLength::Lvi(..) => SortKey::Lvi, - ViewportPercentageLength::Dvi(..) => SortKey::Dvi, }, NoCalcLength::ServoCharacterWidth(..) => unreachable!(), }, diff --git a/components/style/values/specified/color.rs b/components/style/values/specified/color.rs index 493c2cf8c85..4c0683dbce0 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::{ColorInterpolationMethod, GenericColorMix, GenericCaretColor, GenericColorOrAuto}; +use crate::values::generics::color::{GenericCaretColor, GenericColorOrAuto}; use crate::values::specified::calc::CalcNode; use crate::values::specified::Percentage; use crate::values::CustomIdent; @@ -19,8 +19,52 @@ use std::io::Write as IoWrite; use style_traits::{CssType, CssWriter, KeywordsCollectFn, ParseError, StyleParseErrorKind}; use style_traits::{SpecifiedValueInfo, ToCss, ValueParseErrorKind}; -/// A specified color-mix(). -pub type ColorMix = GenericColorMix; +/// A color space as defined in [1]. +/// +/// [1]: https://drafts.csswg.org/css-color-5/#typedef-colorspace +#[derive(Clone, Copy, Debug, Eq, MallocSizeOf, Parse, PartialEq, ToCss, ToShmem)] +pub enum ColorSpaceKind { + /// The sRGB color space. + Srgb, + /// The CIEXYZ color space. + Xyz, + /// The CIELAB color space. + Lab, + /// The CIELAB color space, expressed in cylindrical coordinates. + Lch, +} + +/// A hue adjuster as defined in [1]. +/// +/// [1]: https://drafts.csswg.org/css-color-5/#typedef-hue-adjuster +#[derive(Clone, Copy, Debug, Eq, MallocSizeOf, Parse, PartialEq, ToCss, ToShmem)] +pub enum HueAdjuster { + /// The "shorter" angle adjustment. + Shorter, + /// The "longer" angle adjustment. + Longer, + /// The "increasing" angle adjustment. + Increasing, + /// The "decreasing" angle adjustment. + Decreasing, + /// The "specified" angle adjustment. + Specified, +} + +/// A restricted version of the css `color-mix()` function, which only supports +/// percentages. +/// +/// https://drafts.csswg.org/css-color-5/#color-mix +#[derive(Clone, Debug, MallocSizeOf, PartialEq, ToShmem)] +#[allow(missing_docs)] +pub struct ColorMix { + pub color_space: ColorSpaceKind, + pub left: Color, + pub left_percentage: Percentage, + pub right: Color, + pub right_percentage: Percentage, + pub hue_adjuster: HueAdjuster, +} #[inline] fn allow_color_mix() -> bool { @@ -30,6 +74,18 @@ fn allow_color_mix() -> bool { return false; } +#[inline] +fn allow_color_mix_color_spaces() -> bool { + #[cfg(feature = "gecko")] + return static_prefs::pref!("layout.css.color-mix.color-spaces.enabled"); + #[cfg(feature = "servo")] + return false; +} + +// NOTE(emilio): Syntax is still a bit in-flux, since [1] doesn't seem +// particularly complete, and disagrees with the examples. +// +// [1]: https://github.com/w3c/csswg-drafts/commit/a4316446112f9e814668c2caff7f826f512f8fed impl Parse for ColorMix { fn parse<'i, 't>( context: &ParserContext, @@ -42,58 +98,100 @@ impl Parse for ColorMix { return Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError)); } + let color_spaces_enabled = context.chrome_rules_enabled() || + allow_color_mix_color_spaces(); + input.expect_function_matching("color-mix")?; + // NOTE(emilio): This implements the syntax described here for now, + // might need to get updated in the future. + // + // https://github.com/w3c/csswg-drafts/issues/6066#issuecomment-789836765 input.parse_nested_block(|input| { - let interpolation = ColorInterpolationMethod::parse(context, input)?; - input.expect_comma()?; - - let try_parse_percentage = |input: &mut Parser| -> Option { - input - .try_parse(|input| Percentage::parse_zero_to_a_hundred(context, input)) - .ok() + input.expect_ident_matching("in")?; + let color_space = if color_spaces_enabled { + ColorSpaceKind::parse(input)? + } else { + input.expect_ident_matching("srgb")?; + ColorSpaceKind::Srgb }; - - let mut left_percentage = try_parse_percentage(input); + input.expect_comma()?; let left = Color::parse(context, input)?; - if left_percentage.is_none() { - left_percentage = try_parse_percentage(input); - } + let left_percentage = input + .try_parse(|input| Percentage::parse(context, input)) + .ok(); input.expect_comma()?; - let mut right_percentage = try_parse_percentage(input); - let right = Color::parse(context, input)?; - - if right_percentage.is_none() { - right_percentage = try_parse_percentage(input); - } - - let right_percentage = right_percentage - .unwrap_or_else(|| Percentage::new(1.0 - left_percentage.map_or(0.5, |p| p.get()))); + let right_percentage = input + .try_parse(|input| Percentage::parse(context, input)) + .unwrap_or_else(|_| { + Percentage::new(1.0 - left_percentage.map_or(0.5, |p| p.get())) + }); let left_percentage = left_percentage.unwrap_or_else(|| Percentage::new(1.0 - right_percentage.get())); - if left_percentage.get() + right_percentage.get() <= 0.0 { - // If the percentages sum to zero, the function is invalid. - return Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError)); - } + let hue_adjuster = input + .try_parse(|input| HueAdjuster::parse(input)) + .unwrap_or(HueAdjuster::Shorter); Ok(ColorMix { - interpolation, + color_space, left, left_percentage, right, right_percentage, - normalize_weights: true, + hue_adjuster, }) }) } } +impl ToCss for ColorMix { + fn to_css(&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 { @@ -106,6 +204,8 @@ pub enum Color { /// Authored representation authored: Option>, }, + /// A complex color value from computed value + Complex(ComputedColor), /// A system color. #[cfg(feature = "gecko")] System(SystemColor), @@ -287,6 +387,10 @@ pub enum SystemColor { /// Inactive light hightlight MozMacSecondaryhighlight, + /// Font smoothing background colors needed by the Mac OS X theme, based on + /// -moz-appearance names. + MozMacVibrantTitlebarLight, + MozMacVibrantTitlebarDark, MozMacMenupopup, MozMacMenuitem, MozMacActiveMenuitem, @@ -493,6 +597,8 @@ impl ToCss for Color { Color::Numeric { parsed: ref rgba, .. } => rgba.to_css(dest), + // TODO: Could represent this as a color-mix() instead. + Color::Complex(_) => Ok(()), Color::ColorMix(ref mix) => mix.to_css(dest), #[cfg(feature = "gecko")] Color::System(system) => system.to_css(dest), @@ -638,23 +744,23 @@ impl Color { /// the context to resolve, then `None` is returned. pub fn to_computed_color(&self, context: Option<&Context>) -> Option { Some(match *self { - Color::CurrentColor => ComputedColor::CurrentColor, - Color::Numeric { ref parsed, .. } => ComputedColor::Numeric(*parsed), + Color::CurrentColor => ComputedColor::currentcolor(), + Color::Numeric { ref parsed, .. } => ComputedColor::rgba(*parsed), + Color::Complex(ref complex) => *complex, Color::ColorMix(ref mix) => { - use crate::values::computed::percentage::Percentage; + use crate::values::animated::color::Color as AnimatedColor; + use crate::values::animated::ToAnimatedValue; - let left = mix.left.to_computed_color(context)?; - let right = mix.right.to_computed_color(context)?; - let mut color = ComputedColor::ColorMix(Box::new(GenericColorMix { - interpolation: mix.interpolation, - left, - left_percentage: Percentage(mix.left_percentage.get()), - right, - right_percentage: Percentage(mix.right_percentage.get()), - normalize_weights: mix.normalize_weights, - })); - color.simplify(None); - color + let left = mix.left.to_computed_color(context)?.to_animated_value(); + let right = mix.right.to_computed_color(context)?.to_animated_value(); + ToAnimatedValue::from_animated_value(AnimatedColor::mix( + mix.color_space, + &left, + mix.left_percentage.get(), + &right, + mix.right_percentage.get(), + mix.hue_adjuster, + )) }, #[cfg(feature = "gecko")] Color::System(system) => system.compute(context?), @@ -672,13 +778,13 @@ impl ToComputedValue for Color { } fn from_computed_value(computed: &ComputedColor) -> Self { - match *computed { - ComputedColor::Numeric(ref color) => Color::rgba(*color), - ComputedColor::CurrentColor => Color::CurrentColor, - ComputedColor::ColorMix(ref mix) => { - Color::ColorMix(Box::new(ToComputedValue::from_computed_value(&**mix))) - }, + if computed.is_numeric() { + return Color::rgba(computed.color); } + if computed.is_currentcolor() { + return Color::currentcolor(); + } + Color::Complex(*computed) } } @@ -706,7 +812,7 @@ impl ToComputedValue for MozFontSmoothingBackgroundColor { fn to_computed_value(&self, context: &Context) -> RGBA { self.0 .to_computed_value(context) - .into_rgba(RGBA::transparent()) + .to_rgba(RGBA::transparent()) } fn from_computed_value(computed: &RGBA) -> Self { @@ -723,15 +829,7 @@ impl SpecifiedValueInfo for Color { // should probably be handled that way as well. // XXX `currentColor` should really be `currentcolor`. But let's // keep it consistent with the old system for now. - f(&[ - "rgb", - "rgba", - "hsl", - "hsla", - "hwb", - "currentColor", - "transparent", - ]); + f(&["rgb", "rgba", "hsl", "hsla", "hwb", "currentColor", "transparent"]); } } @@ -748,7 +846,7 @@ impl ToComputedValue for ColorPropertyValue { fn to_computed_value(&self, context: &Context) -> RGBA { self.0 .to_computed_value(context) - .into_rgba(context.builder.get_parent_inherited_text().clone_color()) + .to_rgba(context.builder.get_parent_inherited_text().clone_color()) } #[inline] @@ -910,19 +1008,7 @@ impl ToCss for ColorScheme { } /// https://drafts.csswg.org/css-color-adjust/#print-color-adjust -#[derive( - Clone, - Copy, - Debug, - MallocSizeOf, - Parse, - PartialEq, - SpecifiedValueInfo, - ToCss, - ToComputedValue, - ToResolvedValue, - ToShmem, -)] +#[derive(Clone, Copy, Debug, MallocSizeOf, Parse, PartialEq, SpecifiedValueInfo, ToCss, ToComputedValue, ToResolvedValue, ToShmem)] #[repr(u8)] pub enum PrintColorAdjust { /// Ignore backgrounds and darken text. diff --git a/components/style/values/specified/image.rs b/components/style/values/specified/image.rs index 1c1d1c88725..f4cf417a044 100644 --- a/components/style/values/specified/image.rs +++ b/components/style/values/specified/image.rs @@ -39,9 +39,6 @@ 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< @@ -63,6 +60,8 @@ 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; @@ -316,16 +315,6 @@ 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, @@ -333,17 +322,14 @@ impl CrossFadeElement { cors_mode: CorsMode, ) -> Result> { // Try and parse a leading percent sign. - let mut percent = Self::parse_percentage(context, input); + let mut percent = PercentOrNone::parse_or_none(context, input); // Parse the image let image = CrossFadeImage::parse(context, input, cors_mode)?; // Try and parse a trailing percent sign. - if percent.is_none() { - percent = Self::parse_percentage(context, input); + if percent == PercentOrNone::None { + percent = PercentOrNone::parse_or_none(context, input); } - Ok(Self { - percent: percent.into(), - image, - }) + Ok(Self { percent, image }) } } @@ -365,6 +351,22 @@ impl CrossFadeImage { } } +impl PercentOrNone { + fn parse_or_none<'i, 't>(context: &ParserContext, input: &mut Parser<'i, 't>) -> Self { + // We clamp our values here as this is the way that Safari and + // Chrome's implementation handle out-of-bounds percentages + // but whether or not this behavior follows the specification + // is still being discussed. See: + // + if let Ok(percent) = input.try_parse(|input| Percentage::parse_non_negative(context, input)) + { + Self::Percent(percent.clamp_to_hundred()) + } else { + Self::None + } + } +} + impl ImageSet { fn parse<'i, 't>( context: &ParserContext, @@ -733,12 +735,12 @@ impl Gradient { if items.is_empty() { items = vec![ generic::GradientItem::ComplexColorStop { - color: Color::transparent(), - position: LengthPercentage::zero_percent(), + color: Color::transparent().into(), + position: Percentage::zero().into(), }, generic::GradientItem::ComplexColorStop { - color: Color::transparent(), - position: LengthPercentage::hundred_percent(), + color: Color::transparent().into(), + position: Percentage::hundred().into(), }, ]; } else if items.len() == 1 { diff --git a/components/style/values/specified/length.rs b/components/style/values/specified/length.rs index 159eb996364..e092f254bd4 100644 --- a/components/style/values/specified/length.rs +++ b/components/style/values/specified/length.rs @@ -21,6 +21,7 @@ use crate::values::CSSFloat; use crate::Zero; use app_units::Au; use cssparser::{Parser, Token}; +use euclid::default::Size2D; use std::cmp; use std::ops::{Add, Mul}; use style_traits::values::specified::AllowedNumericType; @@ -277,249 +278,45 @@ impl FontRelativeLength { } } -/// https://drafts.csswg.org/css-values/#viewport-variants -pub enum ViewportVariant { - /// https://drafts.csswg.org/css-values/#ua-default-viewport-size - UADefault, - /// https://drafts.csswg.org/css-values/#small-viewport-percentage-units - Small, - /// https://drafts.csswg.org/css-values/#large-viewport-percentage-units - Large, - /// https://drafts.csswg.org/css-values/#dynamic-viewport-percentage-units - Dynamic, -} - -/// https://drafts.csswg.org/css-values/#viewport-relative-units -#[derive(PartialEq)] -enum ViewportUnit { - /// *vw units. - Vw, - /// *vh units. - Vh, - /// *vmin units. - Vmin, - /// *vmax units. - Vmax, - /// *vb units. - Vb, - /// *vi units. - Vi, -} - /// A viewport-relative length. /// /// #[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), - /// - #[css(dimension)] - Svw(CSSFloat), - /// - #[css(dimension)] - Lvw(CSSFloat), - /// - #[css(dimension)] - Dvw(CSSFloat), - /// + /// A vh unit: https://drafts.csswg.org/css-values/#vh #[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 { - let (_, _, v) = self.unpack(); - v == 0. + match *self { + ViewportPercentageLength::Vw(v) | + ViewportPercentageLength::Vh(v) | + ViewportPercentageLength::Vmin(v) | + ViewportPercentageLength::Vmax(v) => v == 0., + } } fn is_negative(&self) -> bool { - let (_, _, v) = self.unpack(); - v < 0. - } - - fn unpack(&self) -> (ViewportVariant, ViewportUnit, CSSFloat) { match *self { - ViewportPercentageLength::Vw(v) => ( - ViewportVariant::UADefault, - ViewportUnit::Vw, - v, - ), - ViewportPercentageLength::Svw(v) => ( - ViewportVariant::Small, - ViewportUnit::Vw, - v, - ), - ViewportPercentageLength::Lvw(v) => ( - ViewportVariant::Large, - ViewportUnit::Vw, - v, - ), - ViewportPercentageLength::Dvw(v) => ( - ViewportVariant::Dynamic, - ViewportUnit::Vw, - v, - ), - ViewportPercentageLength::Vh(v) => ( - ViewportVariant::UADefault, - ViewportUnit::Vh, - v, - ), - ViewportPercentageLength::Svh(v) => ( - ViewportVariant::Small, - ViewportUnit::Vh, - v, - ), - ViewportPercentageLength::Lvh(v) => ( - ViewportVariant::Large, - ViewportUnit::Vh, - v), - ViewportPercentageLength::Dvh(v) => ( - ViewportVariant::Dynamic, - ViewportUnit::Vh, - v, - ), - ViewportPercentageLength::Vmin(v) => ( - ViewportVariant::UADefault, - ViewportUnit::Vmin, - v, - ), - ViewportPercentageLength::Svmin(v) => ( - ViewportVariant::Small, - ViewportUnit::Vmin, - v, - ), - ViewportPercentageLength::Lvmin(v) => ( - ViewportVariant::Large, - ViewportUnit::Vmin, - v, - ), - ViewportPercentageLength::Dvmin(v) => ( - ViewportVariant::Dynamic, - ViewportUnit::Vmin, - v, - ), - ViewportPercentageLength::Vmax(v) => ( - ViewportVariant::UADefault, - ViewportUnit::Vmax, - v, - ), - ViewportPercentageLength::Svmax(v) => ( - ViewportVariant::Small, - ViewportUnit::Vmax, - v, - ), - ViewportPercentageLength::Lvmax(v) => ( - ViewportVariant::Large, - ViewportUnit::Vmax, - v, - ), - ViewportPercentageLength::Dvmax(v) => ( - ViewportVariant::Dynamic, - ViewportUnit::Vmax, - v, - ), - ViewportPercentageLength::Vb(v) => ( - ViewportVariant::UADefault, - ViewportUnit::Vb, - v, - ), - ViewportPercentageLength::Svb(v) => ( - ViewportVariant::Small, - ViewportUnit::Vb, - v, - ), - ViewportPercentageLength::Lvb(v) => ( - ViewportVariant::Large, - ViewportUnit::Vb, - v, - ), - ViewportPercentageLength::Dvb(v) => ( - ViewportVariant::Dynamic, - ViewportUnit::Vb, - v, - ), - ViewportPercentageLength::Vi(v) => ( - ViewportVariant::UADefault, - ViewportUnit::Vi, - v, - ), - ViewportPercentageLength::Svi(v) => ( - ViewportVariant::Small, - ViewportUnit::Vi, - v, - ), - ViewportPercentageLength::Lvi(v) => ( - ViewportVariant::Large, - ViewportUnit::Vi, - v, - ), - ViewportPercentageLength::Dvi(v) => ( - ViewportVariant::Dynamic, - ViewportUnit::Vi, - v, - ), + ViewportPercentageLength::Vw(v) | + ViewportPercentageLength::Vh(v) | + ViewportPercentageLength::Vmin(v) | + ViewportPercentageLength::Vmax(v) => v < 0., } } + fn try_sum(&self, other: &Self) -> Result { use self::ViewportPercentageLength::*; @@ -529,39 +326,14 @@ impl ViewportPercentageLength { Ok(match (self, other) { (&Vw(one), &Vw(other)) => Vw(one + other), - (&Svw(one), &Svw(other)) => Svw(one + other), - (&Lvw(one), &Lvw(other)) => Lvw(one + other), - (&Dvw(one), &Dvw(other)) => Dvw(one + other), (&Vh(one), &Vh(other)) => Vh(one + other), - (&Svh(one), &Svh(other)) => Svh(one + other), - (&Lvh(one), &Lvh(other)) => Lvh(one + other), - (&Dvh(one), &Dvh(other)) => Dvh(one + other), (&Vmin(one), &Vmin(other)) => Vmin(one + other), - (&Svmin(one), &Svmin(other)) => Svmin(one + other), - (&Lvmin(one), &Lvmin(other)) => Lvmin(one + other), - (&Dvmin(one), &Dvmin(other)) => Dvmin(one + other), (&Vmax(one), &Vmax(other)) => Vmax(one + other), - (&Svmax(one), &Svmax(other)) => Svmax(one + other), - (&Lvmax(one), &Lvmax(other)) => Lvmax(one + other), - (&Dvmax(one), &Dvmax(other)) => Dvmax(one + other), - (&Vb(one), &Vb(other)) => Vb(one + other), - (&Svb(one), &Svb(other)) => Svb(one + other), - (&Lvb(one), &Lvb(other)) => Lvb(one + other), - (&Dvb(one), &Dvb(other)) => Dvb(one + other), - (&Vi(one), &Vi(other)) => Vi(one + other), - (&Svi(one), &Svi(other)) => Svi(one + other), - (&Lvi(one), &Lvi(other)) => Lvi(one + other), - (&Dvi(one), &Dvi(other)) => Dvi(one + other), // See https://github.com/rust-lang/rust/issues/68867. rustc isn't // able to figure it own on its own so we help. _ => unsafe { match *self { - Vw(..) | Svw(..) | Lvw(..) | Dvw(..) | - Vh(..) | Svh(..) | Lvh(..) | Dvh(..) | - Vmin(..) | Svmin(..) | Lvmin(..) | Dvmin(..) | - Vmax(..) | Svmax(..) | Lvmax(..) | Dvmax(..) | - Vb(..) | Svb(..) | Lvb(..) | Dvb(..) | - Vi(..) | Svi(..) | Lvi(..) | Dvi(..) => {}, + Vw(..) | Vh(..) | Vmin(..) | Vmax(..) => {}, } debug_unreachable!("Forgot to handle unit in try_sum()") }, @@ -569,24 +341,15 @@ impl ViewportPercentageLength { } /// Computes the given viewport-relative length for the given viewport size. - pub fn to_computed_value(&self, context: &Context) -> CSSPixelLength { - let (variant, unit, factor) = self.unpack(); - let size = context.viewport_size_for_viewport_unit_resolution(variant); - let length = match unit { - ViewportUnit::Vw => size.width, - ViewportUnit::Vh => size.height, - ViewportUnit::Vmin => cmp::min(size.width, size.height), - ViewportUnit::Vmax => cmp::max(size.width, size.height), - ViewportUnit::Vi | ViewportUnit::Vb => { - context - .rule_cache_conditions - .borrow_mut() - .set_writing_mode_dependency(context.builder.writing_mode); - if (unit == ViewportUnit::Vb) == context.style().writing_mode.is_vertical() { - size.width - } else { - size.height - } + pub fn to_computed_value(&self, viewport_size: Size2D) -> CSSPixelLength { + let (factor, length) = match *self { + ViewportPercentageLength::Vw(length) => (length, viewport_size.width), + ViewportPercentageLength::Vh(length) => (length, viewport_size.height), + ViewportPercentageLength::Vmin(length) => { + (length, cmp::min(viewport_size.width, viewport_size.height)) + }, + ViewportPercentageLength::Vmax(length) => { + (length, cmp::max(viewport_size.width, viewport_size.height)) }, }; @@ -815,75 +578,15 @@ impl NoCalcLength { "vw" if !context.in_page_rule() => { NoCalcLength::ViewportPercentage(ViewportPercentageLength::Vw(value)) }, - "svw" if !context.in_page_rule() => { - NoCalcLength::ViewportPercentage(ViewportPercentageLength::Svw(value)) - }, - "lvw" if !context.in_page_rule() => { - NoCalcLength::ViewportPercentage(ViewportPercentageLength::Lvw(value)) - }, - "dvw" if !context.in_page_rule() => { - NoCalcLength::ViewportPercentage(ViewportPercentageLength::Dvw(value)) - }, "vh" if !context.in_page_rule() => { NoCalcLength::ViewportPercentage(ViewportPercentageLength::Vh(value)) }, - "svh" if !context.in_page_rule() => { - NoCalcLength::ViewportPercentage(ViewportPercentageLength::Svh(value)) - }, - "lvh" if !context.in_page_rule() => { - NoCalcLength::ViewportPercentage(ViewportPercentageLength::Lvh(value)) - }, - "dvh" if !context.in_page_rule() => { - NoCalcLength::ViewportPercentage(ViewportPercentageLength::Dvh(value)) - }, "vmin" if !context.in_page_rule() => { NoCalcLength::ViewportPercentage(ViewportPercentageLength::Vmin(value)) }, - "svmin" if !context.in_page_rule() => { - NoCalcLength::ViewportPercentage(ViewportPercentageLength::Svmin(value)) - }, - "lvmin" if !context.in_page_rule() => { - NoCalcLength::ViewportPercentage(ViewportPercentageLength::Lvmin(value)) - }, - "dvmin" if !context.in_page_rule() => { - NoCalcLength::ViewportPercentage(ViewportPercentageLength::Dvmin(value)) - }, "vmax" if !context.in_page_rule() => { NoCalcLength::ViewportPercentage(ViewportPercentageLength::Vmax(value)) }, - "svmax" if !context.in_page_rule() => { - NoCalcLength::ViewportPercentage(ViewportPercentageLength::Svmax(value)) - }, - "lvmax" if !context.in_page_rule() => { - NoCalcLength::ViewportPercentage(ViewportPercentageLength::Lvmax(value)) - }, - "dvmax" if !context.in_page_rule() => { - NoCalcLength::ViewportPercentage(ViewportPercentageLength::Dvmax(value)) - }, - "vb" if !context.in_page_rule() => { - NoCalcLength::ViewportPercentage(ViewportPercentageLength::Vb(value)) - }, - "svb" if !context.in_page_rule() => { - NoCalcLength::ViewportPercentage(ViewportPercentageLength::Svb(value)) - }, - "lvb" if !context.in_page_rule() => { - NoCalcLength::ViewportPercentage(ViewportPercentageLength::Lvb(value)) - }, - "dvb" if !context.in_page_rule() => { - NoCalcLength::ViewportPercentage(ViewportPercentageLength::Dvb(value)) - }, - "vi" if !context.in_page_rule() => { - NoCalcLength::ViewportPercentage(ViewportPercentageLength::Vi(value)) - }, - "svi" if !context.in_page_rule() => { - NoCalcLength::ViewportPercentage(ViewportPercentageLength::Svi(value)) - }, - "lvi" if !context.in_page_rule() => { - NoCalcLength::ViewportPercentage(ViewportPercentageLength::Lvi(value)) - }, - "dvi" if !context.in_page_rule() => { - NoCalcLength::ViewportPercentage(ViewportPercentageLength::Dvi(value)) - }, _ => return Err(()), }) } @@ -1067,29 +770,9 @@ impl Mul for ViewportPercentageLength { fn mul(self, scalar: CSSFloat) -> ViewportPercentageLength { match self { ViewportPercentageLength::Vw(v) => ViewportPercentageLength::Vw(v * scalar), - ViewportPercentageLength::Svw(v) => ViewportPercentageLength::Svw(v * scalar), - ViewportPercentageLength::Lvw(v) => ViewportPercentageLength::Lvw(v * scalar), - ViewportPercentageLength::Dvw(v) => ViewportPercentageLength::Dvw(v * scalar), ViewportPercentageLength::Vh(v) => ViewportPercentageLength::Vh(v * scalar), - ViewportPercentageLength::Svh(v) => ViewportPercentageLength::Svh(v * scalar), - ViewportPercentageLength::Lvh(v) => ViewportPercentageLength::Lvh(v * scalar), - ViewportPercentageLength::Dvh(v) => ViewportPercentageLength::Dvh(v * scalar), ViewportPercentageLength::Vmin(v) => ViewportPercentageLength::Vmin(v * scalar), - ViewportPercentageLength::Svmin(v) => ViewportPercentageLength::Svmin(v * scalar), - ViewportPercentageLength::Lvmin(v) => ViewportPercentageLength::Lvmin(v * scalar), - ViewportPercentageLength::Dvmin(v) => ViewportPercentageLength::Dvmin(v * scalar), ViewportPercentageLength::Vmax(v) => ViewportPercentageLength::Vmax(v * scalar), - ViewportPercentageLength::Svmax(v) => ViewportPercentageLength::Svmax(v * scalar), - ViewportPercentageLength::Lvmax(v) => ViewportPercentageLength::Lvmax(v * scalar), - ViewportPercentageLength::Dvmax(v) => ViewportPercentageLength::Dvmax(v * scalar), - ViewportPercentageLength::Vb(v) => ViewportPercentageLength::Vb(v * scalar), - ViewportPercentageLength::Svb(v) => ViewportPercentageLength::Svb(v * scalar), - ViewportPercentageLength::Lvb(v) => ViewportPercentageLength::Lvb(v * scalar), - ViewportPercentageLength::Dvb(v) => ViewportPercentageLength::Dvb(v * scalar), - ViewportPercentageLength::Vi(v) => ViewportPercentageLength::Vi(v * scalar), - ViewportPercentageLength::Svi(v) => ViewportPercentageLength::Svi(v * scalar), - ViewportPercentageLength::Lvi(v) => ViewportPercentageLength::Lvi(v * scalar), - ViewportPercentageLength::Dvi(v) => ViewportPercentageLength::Dvi(v * scalar), } } } @@ -1104,39 +787,14 @@ impl PartialOrd for ViewportPercentageLength { match (self, other) { (&Vw(ref one), &Vw(ref other)) => one.partial_cmp(other), - (&Svw(ref one), &Svw(ref other)) => one.partial_cmp(other), - (&Lvw(ref one), &Lvw(ref other)) => one.partial_cmp(other), - (&Dvw(ref one), &Dvw(ref other)) => one.partial_cmp(other), (&Vh(ref one), &Vh(ref other)) => one.partial_cmp(other), - (&Svh(ref one), &Svh(ref other)) => one.partial_cmp(other), - (&Lvh(ref one), &Lvh(ref other)) => one.partial_cmp(other), - (&Dvh(ref one), &Dvh(ref other)) => one.partial_cmp(other), (&Vmin(ref one), &Vmin(ref other)) => one.partial_cmp(other), - (&Svmin(ref one), &Svmin(ref other)) => one.partial_cmp(other), - (&Lvmin(ref one), &Lvmin(ref other)) => one.partial_cmp(other), - (&Dvmin(ref one), &Dvmin(ref other)) => one.partial_cmp(other), (&Vmax(ref one), &Vmax(ref other)) => one.partial_cmp(other), - (&Svmax(ref one), &Svmax(ref other)) => one.partial_cmp(other), - (&Lvmax(ref one), &Lvmax(ref other)) => one.partial_cmp(other), - (&Dvmax(ref one), &Dvmax(ref other)) => one.partial_cmp(other), - (&Vb(ref one), &Vb(ref other)) => one.partial_cmp(other), - (&Svb(ref one), &Svb(ref other)) => one.partial_cmp(other), - (&Lvb(ref one), &Lvb(ref other)) => one.partial_cmp(other), - (&Dvb(ref one), &Dvb(ref other)) => one.partial_cmp(other), - (&Vi(ref one), &Vi(ref other)) => one.partial_cmp(other), - (&Svi(ref one), &Svi(ref other)) => one.partial_cmp(other), - (&Lvi(ref one), &Lvi(ref other)) => one.partial_cmp(other), - (&Dvi(ref one), &Dvi(ref other)) => one.partial_cmp(other), // See https://github.com/rust-lang/rust/issues/68867. rustc isn't // able to figure it own on its own so we help. _ => unsafe { match *self { - Vw(..) | Svw(..) | Lvw(..) | Dvw(..) | - Vh(..) | Svh(..) | Lvh(..) | Dvh(..) | - Vmin(..) | Svmin(..) | Lvmin(..) | Dvmin(..) | - Vmax(..) | Svmax(..) | Lvmax(..) | Dvmax(..) | - Vb(..) | Svb(..) | Lvb(..) | Dvb(..) | - Vi(..) | Svi(..) | Lvi(..) | Dvi(..) => {}, + Vw(..) | Vh(..) | Vmin(..) | Vmax(..) => {}, } debug_unreachable!("Forgot an arm in partial_cmp?") }, @@ -1328,9 +986,10 @@ impl From for LengthPercentage { impl From for LengthPercentage { #[inline] fn from(pc: Percentage) -> Self { - if let Some(clamping_mode) = pc.calc_clamping_mode() { + if pc.is_calc() { + // FIXME(emilio): Hard-coding the clamping mode is suspect. LengthPercentage::Calc(Box::new(CalcLengthPercentage { - clamping_mode, + clamping_mode: AllowedNumericType::All, node: CalcNode::Leaf(calc::Leaf::Percentage(pc.get())), })) } else { @@ -1363,12 +1022,6 @@ impl LengthPercentage { LengthPercentage::Percentage(computed::Percentage::zero()) } - #[inline] - /// Returns a `100%` value. - pub fn hundred_percent() -> LengthPercentage { - LengthPercentage::Percentage(computed::Percentage::hundred()) - } - fn parse_internal<'i, 't>( context: &ParserContext, input: &mut Parser<'i, 't>, diff --git a/components/style/values/specified/mod.rs b/components/style/values/specified/mod.rs index c06386ef97d..ac622961642 100644 --- a/components/style/values/specified/mod.rs +++ b/components/style/values/specified/mod.rs @@ -37,11 +37,10 @@ pub use self::border::{BorderCornerRadius, BorderImageSlice, BorderImageWidth}; pub use self::border::{BorderImageRepeat, BorderImageSideWidth}; pub use self::border::{BorderRadius, BorderSideWidth, BorderSpacing, BorderStyle}; pub use self::box_::{AnimationIterationCount, AnimationName, AnimationTimeline, Contain, Display}; -pub use self::box_::{Appearance, BreakBetween, BreakWithin, ContainerName, ContainerType}; +pub use self::box_::{Appearance, BreakBetween, BreakWithin}; pub use self::box_::{Clear, ContentVisibility, Float, Overflow, OverflowAnchor}; pub use self::box_::{OverflowClipBox, OverscrollBehavior, Perspective, Resize, ScrollbarGutter}; -pub use self::box_::{ScrollSnapAlign, ScrollSnapAxis, ScrollSnapStop}; -pub use self::box_::{ScrollSnapStrictness, ScrollSnapType}; +pub use self::box_::{ScrollSnapAlign, ScrollSnapAxis, ScrollSnapStrictness, ScrollSnapType}; pub use self::box_::{TouchAction, TransitionProperty, VerticalAlign, WillChange}; pub use self::color::{Color, ColorOrAuto, ColorPropertyValue, ColorScheme, PrintColorAdjust}; pub use self::column::ColumnCount; @@ -61,7 +60,7 @@ pub use self::length::{AbsoluteLength, CalcLengthPercentage, CharacterWidth}; pub use self::length::{FontRelativeLength, Length, LengthOrNumber, NonNegativeLengthOrNumber}; pub use self::length::{LengthOrAuto, LengthPercentage, LengthPercentageOrAuto}; pub use self::length::{MaxSize, Size}; -pub use self::length::{NoCalcLength, ViewportPercentageLength, ViewportVariant}; +pub use self::length::{NoCalcLength, ViewportPercentageLength}; pub use self::length::{ NonNegativeLength, NonNegativeLengthPercentage, NonNegativeLengthPercentageOrAuto, }; @@ -70,7 +69,7 @@ pub use self::list::ListStyleType; pub use self::list::Quotes; pub use self::motion::{OffsetPath, OffsetRotate}; pub use self::outline::OutlineStyle; -pub use self::page::{PageOrientation, PageName, PageSize, PaperSize}; +pub use self::page::{Orientation, PageName, PageSize, PaperSize}; pub use self::percentage::{NonNegativePercentage, Percentage}; pub use self::position::AspectRatio; pub use self::position::{ diff --git a/components/style/values/specified/page.rs b/components/style/values/specified/page.rs index e660b435327..883f529d867 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::PageOrientation; +pub use generics::page::Orientation; 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(PageOrientation::parse) - .unwrap_or(PageOrientation::Portrait); + .try_parse(Orientation::parse) + .unwrap_or(Orientation::Portrait); return Ok(PageSize::PaperSize(paper_size, orientation)); } // Try to parse as [ ] - if let Ok(orientation) = input.try_parse(PageOrientation::parse) { + if let Ok(orientation) = input.try_parse(Orientation::parse) { if let Ok(paper_size) = input.try_parse(PaperSize::parse) { return Ok(PageSize::PaperSize(paper_size, orientation)); } diff --git a/components/style/values/specified/percentage.rs b/components/style/values/specified/percentage.rs index 1f92c6c3e6a..a152f7d8dc0 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 the calc() clamping mode for this percentage. - pub fn calc_clamping_mode(&self) -> Option { - self.calc_clamping_mode + /// Returns whether this percentage is a `calc()` value. + pub fn is_calc(&self) -> bool { + self.calc_clamping_mode.is_some() } /// Reverses this percentage, preserving calc-ness. @@ -138,15 +138,6 @@ impl Percentage { Self::parse_with_clamping_mode(context, input, AllowedNumericType::NonNegative) } - /// Parses a percentage token, but rejects it if it's negative or more than - /// 100%. - pub fn parse_zero_to_a_hundred<'i, 't>( - context: &ParserContext, - input: &mut Parser<'i, 't>, - ) -> Result> { - Self::parse_with_clamping_mode(context, input, AllowedNumericType::ZeroToOne) - } - /// Clamp to 100% if the value is over 100%. #[inline] pub fn clamp_to_hundred(self) -> Self { @@ -183,24 +174,6 @@ impl ToComputedValue for Percentage { impl SpecifiedValueInfo for Percentage {} -/// Turns the percentage into a plain float. -pub trait ToPercentage { - /// Returns whether this percentage used to be a calc(). - fn is_calc(&self) -> bool { false } - /// Turns the percentage into a plain float. - fn to_percentage(&self) -> CSSFloat; -} - -impl ToPercentage for Percentage { - fn is_calc(&self) -> bool { - self.calc_clamping_mode.is_some() - } - - fn to_percentage(&self) -> CSSFloat { - self.get() - } -} - /// A wrapper of Percentage, whose value must be >= 0. pub type NonNegativePercentage = NonNegative; diff --git a/components/style/values/specified/position.rs b/components/style/values/specified/position.rs index b40460cdcd7..26359c155c6 100644 --- a/components/style/values/specified/position.rs +++ b/components/style/values/specified/position.rs @@ -556,7 +556,6 @@ 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 4023467d1a8..edb2c09f73e 100644 --- a/components/style/values/specified/source_size_list.rs +++ b/components/style/values/specified/source_size_list.rs @@ -6,9 +6,8 @@ #[cfg(feature = "gecko")] use crate::gecko_bindings::sugar::ownership::{HasBoxFFI, HasFFI, HasSimpleFFI}; -use crate::media_queries::Device; +use crate::media_queries::{Device, MediaCondition}; use crate::parser::{Parse, ParserContext}; -use crate::queries::{QueryCondition, FeatureType}; use crate::values::computed::{self, ToComputedValue}; use crate::values::specified::{Length, NoCalcLength, ViewportPercentageLength}; use app_units::Au; @@ -21,7 +20,7 @@ use style_traits::ParseError; /// https://html.spec.whatwg.org/multipage/#source-size #[derive(Debug)] pub struct SourceSize { - condition: QueryCondition, + condition: MediaCondition, value: Length, } @@ -30,8 +29,9 @@ impl Parse for SourceSize { context: &ParserContext, input: &mut Parser<'i, 't>, ) -> Result> { - let condition = QueryCondition::parse(context, input, FeatureType::Media)?; + let condition = MediaCondition::parse(context, input)?; let value = Length::parse_non_negative(context, input)?; + Ok(Self { condition, value }) } } @@ -56,12 +56,12 @@ impl SourceSizeList { /// Evaluate this to get the final viewport length. pub fn evaluate(&self, device: &Device, quirks_mode: QuirksMode) -> Au { - computed::Context::for_media_query_evaluation(device, quirks_mode, |context| { - let matching_source_size = self - .source_sizes - .iter() - .find(|source_size| source_size.condition.matches(context)); + let matching_source_size = self + .source_sizes + .iter() + .find(|source_size| source_size.condition.matches(device, quirks_mode)); + computed::Context::for_media_query_evaluation(device, quirks_mode, |context| { match matching_source_size { Some(source_size) => source_size.value.to_computed_value(context), None => match self.value { diff --git a/components/style/values/specified/text.rs b/components/style/values/specified/text.rs index 9c58b6a4572..2d561e04dbd 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, Parse, Serialize, SpecifiedValueInfo, ToCss, ToComputedValue, ToResolvedValue, ToShmem)] - #[css(bitflags(single = "none", mixed = "underline,overline,line-through,blink"))] + #[derive(MallocSizeOf, Serialize, SpecifiedValueInfo, ToComputedValue, ToResolvedValue, ToShmem)] + #[value_info(other_values = "none,underline,overline,line-through,blink")] #[repr(C)] /// Specified keyword values for the text-decoration-line property. pub struct TextDecorationLine: u8 { @@ -265,6 +265,94 @@ impl Default for TextDecorationLine { } } +impl Parse for TextDecorationLine { + /// none | [ underline || overline || line-through || blink ] + fn parse<'i, 't>( + _context: &ParserContext, + input: &mut Parser<'i, 't>, + ) -> Result> { + 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 @@ -311,7 +399,6 @@ impl TextTransform { } } -// TODO: This can be simplified by deriving it. impl Parse for TextTransform { fn parse<'i, 't>( _context: &ParserContext, @@ -528,20 +615,11 @@ pub enum TextAlign { /// unlike other keywords. #[cfg(feature = "gecko")] MatchParent, - /// This is how we implement the following HTML behavior from - /// https://html.spec.whatwg.org/#tables-2: - /// - /// User agents are expected to have a rule in their user agent style sheet - /// that matches th elements that have a parent node whose computed value - /// for the 'text-align' property is its initial value, whose declaration - /// block consists of just a single declaration that sets the 'text-align' - /// property to the value 'center'. - /// - /// Since selectors can't depend on the ancestor styles, we implement it with a - /// magic value that computes to the right thing. Since this is an - /// implementation detail, it shouldn't be exposed to web content. + /// `MozCenterOrInherit` value of text-align property. It cannot be parsed, + /// only set directly on the elements and it has a different handling + /// unlike other values. #[cfg(feature = "gecko")] - #[parse(condition = "ParserContext::in_ua_or_chrome_sheet")] + #[css(skip)] MozCenterOrInherit, } @@ -1116,7 +1194,6 @@ bitflags! { } } -// TODO: This can be derived with some care. impl Parse for TextUnderlinePosition { fn parse<'i, 't>( _context: &ParserContext, diff --git a/components/style_derive/parse.rs b/components/style_derive/parse.rs index 6c1b144066b..18ff31c0526 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::{CssBitflagAttrs, CssVariantAttrs}; +use crate::to_css::CssVariantAttrs; use darling::FromField; use darling::FromVariant; use derive_common::cg; -use proc_macro2::{TokenStream, Span}; -use quote::{quote, TokenStreamExt}; +use proc_macro2::TokenStream; +use quote::quote; use syn::parse_quote; -use syn::{self, Ident, DeriveInput, Path}; +use syn::{self, DeriveInput, Path}; use synstructure::{Structure, VariantInfo}; #[derive(Default, FromVariant)] @@ -25,70 +25,6 @@ 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, @@ -110,13 +46,6 @@ 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 deleted file mode 100644 index c7ad93bafe3..00000000000 --- a/components/style_derive/rustfmt.toml +++ /dev/null @@ -1 +0,0 @@ -disable_all_formatting = true diff --git a/components/style_derive/specified_value_info.rs b/components/style_derive/specified_value_info.rs index 5d14aaa05ac..d198990cca4 100644 --- a/components/style_derive/specified_value_info.rs +++ b/components/style_derive/specified_value_info.rs @@ -71,14 +71,7 @@ pub fn derive(mut input: DeriveInput) -> TokenStream { } }, Data::Struct(ref s) => { - 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) { + 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 ac0bf05b900..40e219c374b 100644 --- a/components/style_derive/to_css.rs +++ b/components/style_derive/to_css.rs @@ -5,84 +5,15 @@ use darling::util::Override; use darling::FromDeriveInput; use darling::FromField; -use darling::FromMeta; use darling::FromVariant; use derive_common::cg; -use proc_macro2::{Span, TokenStream}; +use proc_macro2::TokenStream; use quote::quote; use quote::{ToTokens, TokenStreamExt}; use syn::parse_quote; -use syn::{self, Data, Ident, Path, WhereClause}; +use syn::{self, Data, 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() { @@ -90,21 +21,12 @@ pub fn derive(mut input: syn::DeriveInput) -> TokenStream { } let input_attrs = cg::parse_input_attrs::(&input); - if matches!(input.data, Data::Enum(..)) || input_attrs.bitflags.is_some() { + if let Data::Enum(_) = input.data { assert!( input_attrs.function.is_none(), - "#[css(function)] is not allowed on enums or bitflags" + "#[css(function)] 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); + assert!(!input_attrs.comma, "#[css(comma)] is not allowed on enums"); } let match_body = { @@ -327,36 +249,6 @@ 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 { @@ -365,7 +257,6 @@ 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)] @@ -375,7 +266,6 @@ 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 8a135354cf8..779bb5487b5 100644 --- a/components/style_traits/Cargo.toml +++ b/components/style_traits/Cargo.toml @@ -27,7 +27,6 @@ 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 d7c7d8ad33d..28260272d66 100644 --- a/components/style_traits/lib.rs +++ b/components/style_traits/lib.rs @@ -10,9 +10,6 @@ #![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}; @@ -84,11 +81,9 @@ 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. @@ -153,7 +148,6 @@ 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 { @@ -175,7 +169,6 @@ 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 3bae5d2553b..124626bc973 100644 --- a/components/style_traits/values.rs +++ b/components/style_traits/values.rs @@ -44,36 +44,6 @@ 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 { @@ -587,8 +557,6 @@ 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 { @@ -609,7 +577,6 @@ pub mod specified { AllowedNumericType::All => true, AllowedNumericType::NonNegative => val >= 0.0, AllowedNumericType::AtLeastOne => val >= 1.0, - AllowedNumericType::ZeroToOne => val >= 0.0 && val <= 1.0, } } @@ -617,10 +584,9 @@ pub mod specified { #[inline] pub fn clamp(&self, val: f32) -> f32 { match *self { - AllowedNumericType::All => val, - AllowedNumericType::NonNegative => val.max(0.), - AllowedNumericType::AtLeastOne => val.max(1.), - AllowedNumericType::ZeroToOne => val.max(0.).min(1.), + AllowedNumericType::NonNegative if val < 0. => 0., + AllowedNumericType::AtLeastOne if val < 1. => 1., + _ => val, } } } diff --git a/servo-tidy.toml b/servo-tidy.toml index 0560ae5afd8..a2d4f10c724 100644 --- a/servo-tidy.toml +++ b/servo-tidy.toml @@ -86,9 +86,32 @@ packages = [ # Files that are ignored for all tidy and lint checks. files = [ "./components/net/tests/parsable_mime/text", - # Ignore style files to avoid diverging too much from upstream Gecko - "./components/style/", - "./components/style_derive/parse.rs", + # 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", "./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 38439c1f280..c9f17dcf973 100644 --- a/tests/unit/style/Cargo.toml +++ b/tests/unit/style/Cargo.toml @@ -21,6 +21,7 @@ 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 3ccb818dfcb..c87c8fbc370 100644 --- a/tests/unit/style/lib.rs +++ b/tests/unit/style/lib.rs @@ -16,6 +16,9 @@ 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; @@ -27,6 +30,8 @@ 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 new file mode 100644 index 00000000000..cb64495141b --- /dev/null +++ b/tests/unit/style/size_of.rs @@ -0,0 +1,51 @@ +/* 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 new file mode 100644 index 00000000000..d5c061ab502 --- /dev/null +++ b/tests/unit/style/specified_values.rs @@ -0,0 +1,50 @@ +/* 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 b680d8188a2..46d83fca317 100644 --- a/tests/unit/style/stylist.rs +++ b/tests/unit/style/stylist.rs @@ -16,9 +16,10 @@ 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::{ContainerConditionId, LayerId, Rule, Stylist}; +use style::stylist::{Rule, Stylist}; use style::thread_state::{self, ThreadState}; /// Helper method to get some Rules from selector strings. @@ -55,7 +56,6 @@ 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 new file mode 100644 index 00000000000..06a82c1aaba --- /dev/null +++ b/tests/wpt/meta-legacy-layout/css/css-values/viewport-units-compute.html.ini @@ -0,0 +1,90 @@ +[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 new file mode 100644 index 00000000000..804ae41be0b --- /dev/null +++ b/tests/wpt/meta-legacy-layout/css/css-values/viewport-units-keyframes.html.ini @@ -0,0 +1,60 @@ +[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 f35893c7154..3ab74d58d8e 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,4 +1,34 @@ [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 new file mode 100644 index 00000000000..cefd79faca5 --- /dev/null +++ b/tests/wpt/meta-legacy-layout/css/css-values/viewport-units-parsing.html.ini @@ -0,0 +1,60 @@ +[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 9fbce555a33..adcbd9435b3 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,4 +1,34 @@ [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 new file mode 100644 index 00000000000..cefd79faca5 --- /dev/null +++ b/tests/wpt/meta/css/css-values/viewport-units-parsing.html.ini @@ -0,0 +1,60 @@ +[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