From faf754dfa655f0b9a28f62bc47a78fbf78ebcaf4 Mon Sep 17 00:00:00 2001 From: Delan Azabani Date: Tue, 27 Feb 2024 23:39:06 +0800 Subject: [PATCH] Move Stylo to its own repo (#31350) * Remove packages that were moved to external repo * Add workspace dependencies pointing to 2023-06-14 branch * Fix servo-tidy.toml errors * Update commit to include #31346 * Update commit to include servo/stylo#2 * Move css-properties.json lookup to target/doc/stylo * Remove dependency on vendored mako in favour of pypi dependency This also removes etc/ci/generate_workflow.py, which has been unused since at least 9e71bd6a7010d6e5723831696ae0ebe26b47682f. * Add temporary code to debug Windows test failures * Fix failures on Windows due to custom target dir * Update commit to include servo/stylo#3 * Fix license in tests/unit/style/build.rs * Document how to build with local Stylo in Cargo.toml --- Cargo.lock | 23 +- Cargo.toml | 28 +- components/atoms/Cargo.toml | 17 - components/atoms/build.rs | 31 - components/atoms/lib.rs | 5 - components/atoms/static_atoms.txt | 174 - components/canvas/Cargo.toml | 4 +- components/config/Cargo.toml | 2 +- components/derive_common/Cargo.toml | 16 - components/derive_common/cg.rs | 396 -- components/derive_common/lib.rs | 13 - components/derive_common/rustfmt.toml | 1 - components/geometry/Cargo.toml | 2 +- components/gfx/Cargo.toml | 8 +- components/layout/Cargo.toml | 10 +- components/layout_2020/Cargo.toml | 4 +- components/layout_thread/Cargo.toml | 8 +- components/layout_thread_2020/Cargo.toml | 8 +- components/malloc_size_of/Cargo.toml | 52 - components/malloc_size_of/LICENSE-APACHE | 201 - components/malloc_size_of/LICENSE-MIT | 23 - components/malloc_size_of/lib.rs | 978 ---- components/malloc_size_of/rustfmt.toml | 1 - components/metrics/Cargo.toml | 2 +- components/net/Cargo.toml | 4 +- components/pixels/Cargo.toml | 2 +- components/range/Cargo.toml | 2 +- components/script/Cargo.toml | 10 +- components/selectors/Cargo.toml | 36 - components/selectors/README.md | 25 - components/selectors/attr.rs | 192 - components/selectors/bloom.rs | 422 -- components/selectors/build.rs | 77 - components/selectors/builder.rs | 398 -- components/selectors/context.rs | 363 -- components/selectors/lib.rs | 40 - components/selectors/matching.rs | 1133 ----- components/selectors/nth_index_cache.rs | 102 - components/selectors/parser.rs | 4140 ---------------- components/selectors/rustfmt.toml | 1 - components/selectors/sink.rs | 31 - components/selectors/tree.rs | 163 - components/selectors/visitor.rs | 111 - components/servo/Cargo.toml | 2 +- components/servo_arc/Cargo.toml | 20 - components/servo_arc/LICENSE-APACHE | 201 - components/servo_arc/LICENSE-MIT | 23 - components/servo_arc/lib.rs | 1370 ------ components/servo_arc/rustfmt.toml | 1 - components/shared/canvas/Cargo.toml | 4 +- components/shared/devtools/Cargo.toml | 2 +- components/shared/gfx/Cargo.toml | 2 +- components/shared/msg/Cargo.toml | 4 +- components/shared/net/Cargo.toml | 4 +- components/shared/script/Cargo.toml | 4 +- components/shared/script_layout/Cargo.toml | 10 +- components/size_of_test/Cargo.toml | 13 - components/size_of_test/lib.rs | 14 - components/style/Cargo.toml | 95 - components/style/README.md | 6 - components/style/animation.rs | 1415 ------ components/style/applicable_declarations.rs | 210 - components/style/attr.rs | 601 --- components/style/author_styles.rs | 70 - components/style/bezier.rs | 176 - components/style/bloom.rs | 401 -- components/style/build.rs | 87 - components/style/build_gecko.rs | 400 -- components/style/color/convert.rs | 888 ---- components/style/color/mix.rs | 475 -- components/style/color/mod.rs | 465 -- components/style/context.rs | 698 --- components/style/counter_style/mod.rs | 697 --- components/style/counter_style/predefined.rs | 61 - .../style/counter_style/update_predefined.py | 35 - components/style/custom_properties.rs | 1201 ----- components/style/data.rs | 545 --- components/style/dom.rs | 940 ---- components/style/dom_apis.rs | 767 --- components/style/driver.rs | 161 - components/style/encoding_support.rs | 105 - components/style/error_reporting.rs | 283 -- components/style/font_face.rs | 835 ---- components/style/font_metrics.rs | 58 - components/style/gecko/arc_types.rs | 171 - components/style/gecko/conversions.rs | 59 - components/style/gecko/data.rs | 198 - components/style/gecko/media_features.rs | 1028 ---- components/style/gecko/media_queries.rs | 560 --- components/style/gecko/mod.rs | 23 - .../style/gecko/non_ts_pseudo_class_list.rs | 100 - components/style/gecko/pseudo_element.rs | 237 - .../gecko/pseudo_element_definition.mako.rs | 276 -- components/style/gecko/regen_atoms.py | 218 - components/style/gecko/restyle_damage.rs | 121 - components/style/gecko/selector_parser.rs | 497 -- components/style/gecko/snapshot.rs | 174 - components/style/gecko/snapshot_helpers.rs | 309 -- components/style/gecko/traversal.rs | 53 - components/style/gecko/url.rs | 383 -- components/style/gecko/values.rs | 72 - components/style/gecko/wrapper.rs | 2125 -------- components/style/gecko_bindings/mod.rs | 28 - components/style/gecko_bindings/sugar/mod.rs | 13 - .../style/gecko_bindings/sugar/ns_com_ptr.rs | 15 - .../gecko_bindings/sugar/ns_compatibility.rs | 19 - .../sugar/ns_style_auto_array.rs | 111 - .../style/gecko_bindings/sugar/ns_t_array.rs | 144 - .../gecko_bindings/sugar/origin_flags.rs | 31 - .../style/gecko_bindings/sugar/ownership.rs | 61 - .../style/gecko_bindings/sugar/refptr.rs | 289 -- components/style/gecko_string_cache/mod.rs | 532 -- .../style/gecko_string_cache/namespace.rs | 105 - components/style/global_style_data.rs | 173 - .../invalidation/element/document_state.rs | 142 - .../invalidation/element/element_wrapper.rs | 391 -- .../invalidation/element/invalidation_map.rs | 547 --- .../style/invalidation/element/invalidator.rs | 1017 ---- components/style/invalidation/element/mod.rs | 12 - .../invalidation/element/restyle_hints.rs | 190 - .../element/state_and_attributes.rs | 552 --- .../style/invalidation/media_queries.rs | 130 - components/style/invalidation/mod.rs | 10 - components/style/invalidation/stylesheets.rs | 655 --- .../style/invalidation/viewport_units.rs | 71 - components/style/lib.rs | 329 -- components/style/logical_geometry.rs | 1522 ------ components/style/macros.rs | 98 - components/style/matching.rs | 1096 ----- components/style/media_queries/media_list.rs | 150 - components/style/media_queries/media_query.rs | 193 - components/style/media_queries/mod.rs | 18 - components/style/parallel.rs | 197 - components/style/parser.rs | 210 - components/style/piecewise_linear.rs | 293 -- .../Mako-1.1.2-py2.py3-none-any.whl | Bin 75521 -> 0 bytes components/style/properties/build.py | 172 - components/style/properties/cascade.rs | 1258 ----- .../style/properties/computed_value_flags.rs | 190 - .../properties/counted_unknown_properties.py | 110 - components/style/properties/data.py | 912 ---- .../style/properties/declaration_block.rs | 1613 ------- components/style/properties/gecko.mako.rs | 1971 -------- components/style/properties/helpers.mako.rs | 1023 ---- .../helpers/animated_properties.mako.rs | 716 --- .../properties/longhands/background.mako.rs | 116 - .../style/properties/longhands/border.mako.rs | 159 - .../style/properties/longhands/box.mako.rs | 591 --- .../style/properties/longhands/column.mako.rs | 82 - .../properties/longhands/counters.mako.rs | 50 - .../properties/longhands/effects.mako.rs | 86 - .../style/properties/longhands/font.mako.rs | 488 -- .../longhands/inherited_box.mako.rs | 96 - .../longhands/inherited_svg.mako.rs | 217 - .../longhands/inherited_table.mako.rs | 51 - .../longhands/inherited_text.mako.rs | 407 -- .../properties/longhands/inherited_ui.mako.rs | 118 - .../style/properties/longhands/list.mako.rs | 76 - .../style/properties/longhands/margin.mako.rs | 52 - .../properties/longhands/outline.mako.rs | 51 - .../properties/longhands/padding.mako.rs | 41 - .../style/properties/longhands/page.mako.rs | 42 - .../properties/longhands/position.mako.rs | 478 -- .../style/properties/longhands/svg.mako.rs | 258 - .../style/properties/longhands/table.mako.rs | 29 - .../style/properties/longhands/text.mako.rs | 83 - .../style/properties/longhands/ui.mako.rs | 397 -- .../style/properties/longhands/xul.mako.rs | 79 - components/style/properties/mod.rs | 27 - .../style/properties/properties.html.mako | 31 - .../style/properties/properties.mako.rs | 4277 ----------------- .../properties/shorthands/background.mako.rs | 289 -- .../properties/shorthands/border.mako.rs | 492 -- .../style/properties/shorthands/box.mako.rs | 311 -- .../properties/shorthands/column.mako.rs | 115 - .../style/properties/shorthands/font.mako.rs | 547 --- .../shorthands/inherited_svg.mako.rs | 38 - .../shorthands/inherited_text.mako.rs | 91 - .../style/properties/shorthands/list.mako.rs | 152 - .../properties/shorthands/margin.mako.rs | 60 - .../properties/shorthands/outline.mako.rs | 80 - .../properties/shorthands/padding.mako.rs | 58 - .../properties/shorthands/position.mako.rs | 893 ---- .../style/properties/shorthands/svg.mako.rs | 258 - .../style/properties/shorthands/text.mako.rs | 120 - .../style/properties/shorthands/ui.mako.rs | 427 -- components/style/properties_and_values/mod.rs | 10 - .../style/properties_and_values/rule.rs | 245 - .../properties_and_values/syntax/ascii.rs | 60 - .../properties_and_values/syntax/data_type.rs | 91 - .../style/properties_and_values/syntax/mod.rs | 328 -- components/style/queries/condition.rs | 366 -- components/style/queries/feature.rs | 195 - .../style/queries/feature_expression.rs | 754 --- components/style/queries/mod.rs | 19 - components/style/queries/values.rs | 36 - components/style/rule_cache.rs | 187 - components/style/rule_collector.rs | 505 -- components/style/rule_tree/core.rs | 772 --- components/style/rule_tree/level.rs | 249 - components/style/rule_tree/map.rs | 201 - components/style/rule_tree/mod.rs | 426 -- components/style/rule_tree/source.rs | 75 - components/style/rule_tree/unsafe_box.rs | 74 - components/style/rustfmt.toml | 1 - components/style/scoped_tls.rs | 75 - components/style/selector_map.rs | 868 ---- components/style/selector_parser.rs | 240 - components/style/servo/media_queries.rs | 305 -- components/style/servo/mod.rs | 12 - components/style/servo/restyle_damage.rs | 269 -- components/style/servo/selector_parser.rs | 837 ---- components/style/servo/url.rs | 246 - components/style/shared_lock.rs | 374 -- components/style/sharing/checks.rs | 182 - components/style/sharing/mod.rs | 910 ---- components/style/str.rs | 189 - components/style/style_adjuster.rs | 1013 ---- components/style/style_resolver.rs | 597 --- components/style/stylesheet_set.rs | 705 --- .../style/stylesheets/container_rule.rs | 632 --- .../style/stylesheets/counter_style_rule.rs | 7 - components/style/stylesheets/document_rule.rs | 305 -- .../stylesheets/font_feature_values_rule.rs | 490 -- .../stylesheets/font_palette_values_rule.rs | 268 -- components/style/stylesheets/import_rule.rs | 332 -- .../style/stylesheets/keyframes_rule.rs | 691 --- components/style/stylesheets/layer_rule.rs | 228 - components/style/stylesheets/loader.rs | 31 - components/style/stylesheets/media_rule.rs | 71 - components/style/stylesheets/mod.rs | 582 --- .../style/stylesheets/namespace_rule.rs | 43 - components/style/stylesheets/origin.rs | 247 - components/style/stylesheets/page_rule.rs | 145 - components/style/stylesheets/property_rule.rs | 5 - components/style/stylesheets/rule_list.rs | 198 - components/style/stylesheets/rule_parser.rs | 867 ---- .../style/stylesheets/rules_iterator.rs | 326 -- components/style/stylesheets/style_rule.rs | 104 - components/style/stylesheets/stylesheet.rs | 605 --- components/style/stylesheets/supports_rule.rs | 448 -- components/style/stylist.rs | 3290 ------------- components/style/thread_state.rs | 97 - components/style/traversal.rs | 841 ---- components/style/traversal_flags.rs | 67 - components/style/use_counters/mod.rs | 96 - components/style/values/animated/color.rs | 88 - components/style/values/animated/effects.rs | 27 - components/style/values/animated/font.rs | 37 - components/style/values/animated/grid.rs | 157 - components/style/values/animated/lists.rs | 141 - components/style/values/animated/mod.rs | 488 -- components/style/values/animated/svg.rs | 46 - components/style/values/animated/transform.rs | 1473 ------ components/style/values/computed/align.rs | 91 - components/style/values/computed/angle.rs | 101 - components/style/values/computed/animation.rs | 69 - .../style/values/computed/background.rs | 13 - .../style/values/computed/basic_shape.rs | 42 - components/style/values/computed/border.rs | 84 - components/style/values/computed/box.rs | 268 -- components/style/values/computed/color.rs | 104 - components/style/values/computed/column.rs | 11 - components/style/values/computed/counters.rs | 26 - components/style/values/computed/easing.rs | 109 - components/style/values/computed/effects.rs | 44 - components/style/values/computed/flex.rs | 19 - components/style/values/computed/font.rs | 1281 ----- components/style/values/computed/image.rs | 213 - components/style/values/computed/length.rs | 522 -- .../values/computed/length_percentage.rs | 899 ---- components/style/values/computed/list.rs | 17 - components/style/values/computed/mod.rs | 998 ---- components/style/values/computed/motion.rs | 65 - components/style/values/computed/outline.rs | 7 - components/style/values/computed/page.rs | 75 - .../style/values/computed/percentage.rs | 136 - components/style/values/computed/position.rs | 74 - components/style/values/computed/ratio.rs | 115 - components/style/values/computed/rect.rs | 11 - .../style/values/computed/resolution.rs | 56 - components/style/values/computed/svg.rs | 68 - components/style/values/computed/table.rs | 7 - components/style/values/computed/text.rs | 262 - components/style/values/computed/time.rs | 45 - components/style/values/computed/transform.rs | 558 --- components/style/values/computed/ui.rs | 22 - components/style/values/computed/url.rs | 15 - components/style/values/distance.rs | 138 - components/style/values/generics/animation.rs | 140 - .../style/values/generics/background.rs | 54 - .../style/values/generics/basic_shape.rs | 513 -- components/style/values/generics/border.rs | 257 - components/style/values/generics/box.rs | 208 - components/style/values/generics/calc.rs | 1343 ------ components/style/values/generics/color.rs | 196 - components/style/values/generics/column.rs | 45 - components/style/values/generics/counters.rs | 287 -- components/style/values/generics/easing.rs | 135 - components/style/values/generics/effects.rs | 121 - components/style/values/generics/flex.rs | 33 - components/style/values/generics/font.rs | 271 -- components/style/values/generics/grid.rs | 829 ---- components/style/values/generics/image.rs | 614 --- components/style/values/generics/length.rs | 314 -- components/style/values/generics/mod.rs | 386 -- components/style/values/generics/motion.rs | 205 - components/style/values/generics/page.rs | 162 - components/style/values/generics/position.rs | 237 - components/style/values/generics/ratio.rs | 50 - components/style/values/generics/rect.rs | 126 - components/style/values/generics/size.rs | 99 - components/style/values/generics/svg.rs | 221 - components/style/values/generics/text.rs | 156 - components/style/values/generics/transform.rs | 886 ---- components/style/values/generics/ui.rs | 129 - components/style/values/generics/url.rs | 47 - components/style/values/mod.rs | 793 --- components/style/values/resolved/color.rs | 48 - components/style/values/resolved/counters.rs | 51 - components/style/values/resolved/mod.rs | 277 -- components/style/values/specified/align.rs | 817 ---- components/style/values/specified/angle.rs | 276 -- .../style/values/specified/animation.rs | 420 -- .../style/values/specified/background.rs | 143 - .../style/values/specified/basic_shape.rs | 321 -- components/style/values/specified/border.rs | 398 -- components/style/values/specified/box.rs | 1879 -------- components/style/values/specified/calc.rs | 1047 ---- components/style/values/specified/color.rs | 1184 ----- components/style/values/specified/column.rs | 11 - components/style/values/specified/counters.rs | 281 -- components/style/values/specified/easing.rs | 252 - components/style/values/specified/effects.rs | 450 -- components/style/values/specified/flex.rs | 25 - components/style/values/specified/font.rs | 2125 -------- components/style/values/specified/gecko.rs | 82 - components/style/values/specified/grid.rs | 349 -- components/style/values/specified/image.rs | 1308 ----- components/style/values/specified/length.rs | 1849 ------- components/style/values/specified/list.rs | 202 - components/style/values/specified/mod.rs | 953 ---- components/style/values/specified/motion.rs | 238 - components/style/values/specified/outline.rs | 71 - components/style/values/specified/page.rs | 99 - .../style/values/specified/percentage.rs | 225 - components/style/values/specified/position.rs | 905 ---- components/style/values/specified/ratio.rs | 32 - components/style/values/specified/rect.rs | 11 - .../style/values/specified/resolution.rs | 141 - .../values/specified/source_size_list.rs | 136 - components/style/values/specified/svg.rs | 391 -- components/style/values/specified/svg_path.rs | 1030 ---- components/style/values/specified/table.rs | 36 - components/style/values/specified/text.rs | 1154 ----- components/style/values/specified/time.rs | 174 - .../style/values/specified/transform.rs | 487 -- components/style/values/specified/ui.rs | 232 - components/style/values/specified/url.rs | 15 - components/style_config/Cargo.toml | 14 - components/style_config/lib.rs | 97 - components/style_derive/Cargo.toml | 18 - components/style_derive/animate.rs | 135 - .../style_derive/compute_squared_distance.rs | 125 - components/style_derive/lib.rs | 82 - components/style_derive/parse.rs | 323 -- components/style_derive/rustfmt.toml | 1 - .../style_derive/specified_value_info.rs | 195 - components/style_derive/to_animated_value.rs | 35 - components/style_derive/to_animated_zero.rs | 65 - components/style_derive/to_computed_value.rs | 205 - components/style_derive/to_css.rs | 396 -- components/style_derive/to_resolved_value.rs | 52 - components/style_static_prefs/Cargo.toml | 7 - components/style_static_prefs/src/lib.rs | 162 - components/style_traits/Cargo.toml | 32 - components/style_traits/arc_slice.rs | 163 - components/style_traits/dom.rs | 216 - components/style_traits/lib.rs | 281 -- components/style_traits/owned_slice.rs | 198 - components/style_traits/owned_str.rs | 81 - components/style_traits/rustfmt.toml | 1 - .../style_traits/specified_value_info.rs | 138 - components/style_traits/values.rs | 567 --- components/to_shmem/Cargo.toml | 22 - components/to_shmem/lib.rs | 596 --- components/to_shmem/rustfmt.toml | 1 - components/to_shmem_derive/Cargo.toml | 18 - components/to_shmem_derive/lib.rs | 26 - components/to_shmem_derive/to_shmem.rs | 78 - components/url/Cargo.toml | 6 +- components/webgpu/Cargo.toml | 2 +- etc/ci/generate_workflow.py | 58 - python/requirements.txt | 3 + python/servo/package_commands.py | 4 - servo-tidy.toml | 7 - tests/unit/malloc_size_of/Cargo.toml | 4 +- tests/unit/style/Cargo.toml | 10 +- .../unit/style/build.rs | 7 +- tests/unit/style/properties/scaffolding.rs | 11 +- 400 files changed, 112 insertions(+), 123600 deletions(-) delete mode 100644 components/atoms/Cargo.toml delete mode 100644 components/atoms/build.rs delete mode 100644 components/atoms/lib.rs delete mode 100644 components/atoms/static_atoms.txt delete mode 100644 components/derive_common/Cargo.toml delete mode 100644 components/derive_common/cg.rs delete mode 100644 components/derive_common/lib.rs delete mode 100644 components/derive_common/rustfmt.toml delete mode 100644 components/malloc_size_of/Cargo.toml delete mode 100644 components/malloc_size_of/LICENSE-APACHE delete mode 100644 components/malloc_size_of/LICENSE-MIT delete mode 100644 components/malloc_size_of/lib.rs delete mode 100644 components/malloc_size_of/rustfmt.toml delete mode 100644 components/selectors/Cargo.toml delete mode 100644 components/selectors/README.md delete mode 100644 components/selectors/attr.rs delete mode 100644 components/selectors/bloom.rs delete mode 100644 components/selectors/build.rs delete mode 100644 components/selectors/builder.rs delete mode 100644 components/selectors/context.rs delete mode 100644 components/selectors/lib.rs delete mode 100644 components/selectors/matching.rs delete mode 100644 components/selectors/nth_index_cache.rs delete mode 100644 components/selectors/parser.rs delete mode 100644 components/selectors/rustfmt.toml delete mode 100644 components/selectors/sink.rs delete mode 100644 components/selectors/tree.rs delete mode 100644 components/selectors/visitor.rs delete mode 100644 components/servo_arc/Cargo.toml delete mode 100644 components/servo_arc/LICENSE-APACHE delete mode 100644 components/servo_arc/LICENSE-MIT delete mode 100644 components/servo_arc/lib.rs delete mode 100644 components/servo_arc/rustfmt.toml delete mode 100644 components/size_of_test/Cargo.toml delete mode 100644 components/size_of_test/lib.rs delete mode 100644 components/style/Cargo.toml delete mode 100644 components/style/README.md delete mode 100644 components/style/animation.rs delete mode 100644 components/style/applicable_declarations.rs delete mode 100644 components/style/attr.rs delete mode 100644 components/style/author_styles.rs delete mode 100644 components/style/bezier.rs delete mode 100644 components/style/bloom.rs delete mode 100644 components/style/build.rs delete mode 100644 components/style/build_gecko.rs delete mode 100644 components/style/color/convert.rs delete mode 100644 components/style/color/mix.rs delete mode 100644 components/style/color/mod.rs delete mode 100644 components/style/context.rs delete mode 100644 components/style/counter_style/mod.rs delete mode 100644 components/style/counter_style/predefined.rs delete mode 100755 components/style/counter_style/update_predefined.py delete mode 100644 components/style/custom_properties.rs delete mode 100644 components/style/data.rs delete mode 100644 components/style/dom.rs delete mode 100644 components/style/dom_apis.rs delete mode 100644 components/style/driver.rs delete mode 100644 components/style/encoding_support.rs delete mode 100644 components/style/error_reporting.rs delete mode 100644 components/style/font_face.rs delete mode 100644 components/style/font_metrics.rs delete mode 100644 components/style/gecko/arc_types.rs delete mode 100644 components/style/gecko/conversions.rs delete mode 100644 components/style/gecko/data.rs delete mode 100644 components/style/gecko/media_features.rs delete mode 100644 components/style/gecko/media_queries.rs delete mode 100644 components/style/gecko/mod.rs delete mode 100644 components/style/gecko/non_ts_pseudo_class_list.rs delete mode 100644 components/style/gecko/pseudo_element.rs delete mode 100644 components/style/gecko/pseudo_element_definition.mako.rs delete mode 100755 components/style/gecko/regen_atoms.py delete mode 100644 components/style/gecko/restyle_damage.rs delete mode 100644 components/style/gecko/selector_parser.rs delete mode 100644 components/style/gecko/snapshot.rs delete mode 100644 components/style/gecko/snapshot_helpers.rs delete mode 100644 components/style/gecko/traversal.rs delete mode 100644 components/style/gecko/url.rs delete mode 100644 components/style/gecko/values.rs delete mode 100644 components/style/gecko/wrapper.rs delete mode 100644 components/style/gecko_bindings/mod.rs delete mode 100644 components/style/gecko_bindings/sugar/mod.rs delete mode 100644 components/style/gecko_bindings/sugar/ns_com_ptr.rs delete mode 100644 components/style/gecko_bindings/sugar/ns_compatibility.rs delete mode 100644 components/style/gecko_bindings/sugar/ns_style_auto_array.rs delete mode 100644 components/style/gecko_bindings/sugar/ns_t_array.rs delete mode 100644 components/style/gecko_bindings/sugar/origin_flags.rs delete mode 100644 components/style/gecko_bindings/sugar/ownership.rs delete mode 100644 components/style/gecko_bindings/sugar/refptr.rs delete mode 100644 components/style/gecko_string_cache/mod.rs delete mode 100644 components/style/gecko_string_cache/namespace.rs delete mode 100644 components/style/global_style_data.rs delete mode 100644 components/style/invalidation/element/document_state.rs delete mode 100644 components/style/invalidation/element/element_wrapper.rs delete mode 100644 components/style/invalidation/element/invalidation_map.rs delete mode 100644 components/style/invalidation/element/invalidator.rs delete mode 100644 components/style/invalidation/element/mod.rs delete mode 100644 components/style/invalidation/element/restyle_hints.rs delete mode 100644 components/style/invalidation/element/state_and_attributes.rs delete mode 100644 components/style/invalidation/media_queries.rs delete mode 100644 components/style/invalidation/mod.rs delete mode 100644 components/style/invalidation/stylesheets.rs delete mode 100644 components/style/invalidation/viewport_units.rs delete mode 100644 components/style/lib.rs delete mode 100644 components/style/logical_geometry.rs delete mode 100644 components/style/macros.rs delete mode 100644 components/style/matching.rs delete mode 100644 components/style/media_queries/media_list.rs delete mode 100644 components/style/media_queries/media_query.rs delete mode 100644 components/style/media_queries/mod.rs delete mode 100644 components/style/parallel.rs delete mode 100644 components/style/parser.rs delete mode 100644 components/style/piecewise_linear.rs delete mode 100644 components/style/properties/Mako-1.1.2-py2.py3-none-any.whl delete mode 100644 components/style/properties/build.py delete mode 100644 components/style/properties/cascade.rs delete mode 100644 components/style/properties/computed_value_flags.rs delete mode 100644 components/style/properties/counted_unknown_properties.py delete mode 100644 components/style/properties/data.py delete mode 100644 components/style/properties/declaration_block.rs delete mode 100644 components/style/properties/gecko.mako.rs delete mode 100644 components/style/properties/helpers.mako.rs delete mode 100644 components/style/properties/helpers/animated_properties.mako.rs delete mode 100644 components/style/properties/longhands/background.mako.rs delete mode 100644 components/style/properties/longhands/border.mako.rs delete mode 100644 components/style/properties/longhands/box.mako.rs delete mode 100644 components/style/properties/longhands/column.mako.rs delete mode 100644 components/style/properties/longhands/counters.mako.rs delete mode 100644 components/style/properties/longhands/effects.mako.rs delete mode 100644 components/style/properties/longhands/font.mako.rs delete mode 100644 components/style/properties/longhands/inherited_box.mako.rs delete mode 100644 components/style/properties/longhands/inherited_svg.mako.rs delete mode 100644 components/style/properties/longhands/inherited_table.mako.rs delete mode 100644 components/style/properties/longhands/inherited_text.mako.rs delete mode 100644 components/style/properties/longhands/inherited_ui.mako.rs delete mode 100644 components/style/properties/longhands/list.mako.rs delete mode 100644 components/style/properties/longhands/margin.mako.rs delete mode 100644 components/style/properties/longhands/outline.mako.rs delete mode 100644 components/style/properties/longhands/padding.mako.rs delete mode 100644 components/style/properties/longhands/page.mako.rs delete mode 100644 components/style/properties/longhands/position.mako.rs delete mode 100644 components/style/properties/longhands/svg.mako.rs delete mode 100644 components/style/properties/longhands/table.mako.rs delete mode 100644 components/style/properties/longhands/text.mako.rs delete mode 100644 components/style/properties/longhands/ui.mako.rs delete mode 100644 components/style/properties/longhands/xul.mako.rs delete mode 100644 components/style/properties/mod.rs delete mode 100644 components/style/properties/properties.html.mako delete mode 100644 components/style/properties/properties.mako.rs delete mode 100644 components/style/properties/shorthands/background.mako.rs delete mode 100644 components/style/properties/shorthands/border.mako.rs delete mode 100644 components/style/properties/shorthands/box.mako.rs delete mode 100644 components/style/properties/shorthands/column.mako.rs delete mode 100644 components/style/properties/shorthands/font.mako.rs delete mode 100644 components/style/properties/shorthands/inherited_svg.mako.rs delete mode 100644 components/style/properties/shorthands/inherited_text.mako.rs delete mode 100644 components/style/properties/shorthands/list.mako.rs delete mode 100644 components/style/properties/shorthands/margin.mako.rs delete mode 100644 components/style/properties/shorthands/outline.mako.rs delete mode 100644 components/style/properties/shorthands/padding.mako.rs delete mode 100644 components/style/properties/shorthands/position.mako.rs delete mode 100644 components/style/properties/shorthands/svg.mako.rs delete mode 100644 components/style/properties/shorthands/text.mako.rs delete mode 100644 components/style/properties/shorthands/ui.mako.rs delete mode 100644 components/style/properties_and_values/mod.rs delete mode 100644 components/style/properties_and_values/rule.rs delete mode 100644 components/style/properties_and_values/syntax/ascii.rs delete mode 100644 components/style/properties_and_values/syntax/data_type.rs delete mode 100644 components/style/properties_and_values/syntax/mod.rs delete mode 100644 components/style/queries/condition.rs delete mode 100644 components/style/queries/feature.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/rule_cache.rs delete mode 100644 components/style/rule_collector.rs delete mode 100644 components/style/rule_tree/core.rs delete mode 100644 components/style/rule_tree/level.rs delete mode 100644 components/style/rule_tree/map.rs delete mode 100644 components/style/rule_tree/mod.rs delete mode 100644 components/style/rule_tree/source.rs delete mode 100644 components/style/rule_tree/unsafe_box.rs delete mode 100644 components/style/rustfmt.toml delete mode 100644 components/style/scoped_tls.rs delete mode 100644 components/style/selector_map.rs delete mode 100644 components/style/selector_parser.rs delete mode 100644 components/style/servo/media_queries.rs delete mode 100644 components/style/servo/mod.rs delete mode 100644 components/style/servo/restyle_damage.rs delete mode 100644 components/style/servo/selector_parser.rs delete mode 100644 components/style/servo/url.rs delete mode 100644 components/style/shared_lock.rs delete mode 100644 components/style/sharing/checks.rs delete mode 100644 components/style/sharing/mod.rs delete mode 100644 components/style/str.rs delete mode 100644 components/style/style_adjuster.rs delete mode 100644 components/style/style_resolver.rs delete mode 100644 components/style/stylesheet_set.rs delete mode 100644 components/style/stylesheets/container_rule.rs delete mode 100644 components/style/stylesheets/counter_style_rule.rs delete mode 100644 components/style/stylesheets/document_rule.rs delete mode 100644 components/style/stylesheets/font_feature_values_rule.rs delete mode 100644 components/style/stylesheets/font_palette_values_rule.rs delete mode 100644 components/style/stylesheets/import_rule.rs delete mode 100644 components/style/stylesheets/keyframes_rule.rs delete mode 100644 components/style/stylesheets/layer_rule.rs delete mode 100644 components/style/stylesheets/loader.rs delete mode 100644 components/style/stylesheets/media_rule.rs delete mode 100644 components/style/stylesheets/mod.rs delete mode 100644 components/style/stylesheets/namespace_rule.rs delete mode 100644 components/style/stylesheets/origin.rs delete mode 100644 components/style/stylesheets/page_rule.rs delete mode 100644 components/style/stylesheets/property_rule.rs delete mode 100644 components/style/stylesheets/rule_list.rs delete mode 100644 components/style/stylesheets/rule_parser.rs delete mode 100644 components/style/stylesheets/rules_iterator.rs delete mode 100644 components/style/stylesheets/style_rule.rs delete mode 100644 components/style/stylesheets/stylesheet.rs delete mode 100644 components/style/stylesheets/supports_rule.rs delete mode 100644 components/style/stylist.rs delete mode 100644 components/style/thread_state.rs delete mode 100644 components/style/traversal.rs delete mode 100644 components/style/traversal_flags.rs delete mode 100644 components/style/use_counters/mod.rs delete mode 100644 components/style/values/animated/color.rs delete mode 100644 components/style/values/animated/effects.rs delete mode 100644 components/style/values/animated/font.rs delete mode 100644 components/style/values/animated/grid.rs delete mode 100644 components/style/values/animated/lists.rs delete mode 100644 components/style/values/animated/mod.rs delete mode 100644 components/style/values/animated/svg.rs delete mode 100644 components/style/values/animated/transform.rs delete mode 100644 components/style/values/computed/align.rs delete mode 100644 components/style/values/computed/angle.rs delete mode 100644 components/style/values/computed/animation.rs delete mode 100644 components/style/values/computed/background.rs delete mode 100644 components/style/values/computed/basic_shape.rs delete mode 100644 components/style/values/computed/border.rs delete mode 100644 components/style/values/computed/box.rs delete mode 100644 components/style/values/computed/color.rs delete mode 100644 components/style/values/computed/column.rs delete mode 100644 components/style/values/computed/counters.rs delete mode 100644 components/style/values/computed/easing.rs delete mode 100644 components/style/values/computed/effects.rs delete mode 100644 components/style/values/computed/flex.rs delete mode 100644 components/style/values/computed/font.rs delete mode 100644 components/style/values/computed/image.rs delete mode 100644 components/style/values/computed/length.rs delete mode 100644 components/style/values/computed/length_percentage.rs delete mode 100644 components/style/values/computed/list.rs delete mode 100644 components/style/values/computed/mod.rs delete mode 100644 components/style/values/computed/motion.rs delete mode 100644 components/style/values/computed/outline.rs delete mode 100644 components/style/values/computed/page.rs delete mode 100644 components/style/values/computed/percentage.rs delete mode 100644 components/style/values/computed/position.rs delete mode 100644 components/style/values/computed/ratio.rs delete mode 100644 components/style/values/computed/rect.rs delete mode 100644 components/style/values/computed/resolution.rs delete mode 100644 components/style/values/computed/svg.rs delete mode 100644 components/style/values/computed/table.rs delete mode 100644 components/style/values/computed/text.rs delete mode 100644 components/style/values/computed/time.rs delete mode 100644 components/style/values/computed/transform.rs delete mode 100644 components/style/values/computed/ui.rs delete mode 100644 components/style/values/computed/url.rs delete mode 100644 components/style/values/distance.rs delete mode 100644 components/style/values/generics/animation.rs delete mode 100644 components/style/values/generics/background.rs delete mode 100644 components/style/values/generics/basic_shape.rs delete mode 100644 components/style/values/generics/border.rs delete mode 100644 components/style/values/generics/box.rs delete mode 100644 components/style/values/generics/calc.rs delete mode 100644 components/style/values/generics/color.rs delete mode 100644 components/style/values/generics/column.rs delete mode 100644 components/style/values/generics/counters.rs delete mode 100644 components/style/values/generics/easing.rs delete mode 100644 components/style/values/generics/effects.rs delete mode 100644 components/style/values/generics/flex.rs delete mode 100644 components/style/values/generics/font.rs delete mode 100644 components/style/values/generics/grid.rs delete mode 100644 components/style/values/generics/image.rs delete mode 100644 components/style/values/generics/length.rs delete mode 100644 components/style/values/generics/mod.rs delete mode 100644 components/style/values/generics/motion.rs delete mode 100644 components/style/values/generics/page.rs delete mode 100644 components/style/values/generics/position.rs delete mode 100644 components/style/values/generics/ratio.rs delete mode 100644 components/style/values/generics/rect.rs delete mode 100644 components/style/values/generics/size.rs delete mode 100644 components/style/values/generics/svg.rs delete mode 100644 components/style/values/generics/text.rs delete mode 100644 components/style/values/generics/transform.rs delete mode 100644 components/style/values/generics/ui.rs delete mode 100644 components/style/values/generics/url.rs delete mode 100644 components/style/values/mod.rs delete mode 100644 components/style/values/resolved/color.rs delete mode 100644 components/style/values/resolved/counters.rs delete mode 100644 components/style/values/resolved/mod.rs delete mode 100644 components/style/values/specified/align.rs delete mode 100644 components/style/values/specified/angle.rs delete mode 100644 components/style/values/specified/animation.rs delete mode 100644 components/style/values/specified/background.rs delete mode 100644 components/style/values/specified/basic_shape.rs delete mode 100644 components/style/values/specified/border.rs delete mode 100644 components/style/values/specified/box.rs delete mode 100644 components/style/values/specified/calc.rs delete mode 100644 components/style/values/specified/color.rs delete mode 100644 components/style/values/specified/column.rs delete mode 100644 components/style/values/specified/counters.rs delete mode 100644 components/style/values/specified/easing.rs delete mode 100644 components/style/values/specified/effects.rs delete mode 100644 components/style/values/specified/flex.rs delete mode 100644 components/style/values/specified/font.rs delete mode 100644 components/style/values/specified/gecko.rs delete mode 100644 components/style/values/specified/grid.rs delete mode 100644 components/style/values/specified/image.rs delete mode 100644 components/style/values/specified/length.rs delete mode 100644 components/style/values/specified/list.rs delete mode 100644 components/style/values/specified/mod.rs delete mode 100644 components/style/values/specified/motion.rs delete mode 100644 components/style/values/specified/outline.rs delete mode 100644 components/style/values/specified/page.rs delete mode 100644 components/style/values/specified/percentage.rs delete mode 100644 components/style/values/specified/position.rs delete mode 100644 components/style/values/specified/ratio.rs delete mode 100644 components/style/values/specified/rect.rs delete mode 100644 components/style/values/specified/resolution.rs delete mode 100644 components/style/values/specified/source_size_list.rs delete mode 100644 components/style/values/specified/svg.rs delete mode 100644 components/style/values/specified/svg_path.rs delete mode 100644 components/style/values/specified/table.rs delete mode 100644 components/style/values/specified/text.rs delete mode 100644 components/style/values/specified/time.rs delete mode 100644 components/style/values/specified/transform.rs delete mode 100644 components/style/values/specified/ui.rs delete mode 100644 components/style/values/specified/url.rs delete mode 100644 components/style_config/Cargo.toml delete mode 100644 components/style_config/lib.rs delete mode 100644 components/style_derive/Cargo.toml delete mode 100644 components/style_derive/animate.rs delete mode 100644 components/style_derive/compute_squared_distance.rs delete mode 100644 components/style_derive/lib.rs delete mode 100644 components/style_derive/parse.rs delete mode 100644 components/style_derive/rustfmt.toml delete mode 100644 components/style_derive/specified_value_info.rs delete mode 100644 components/style_derive/to_animated_value.rs delete mode 100644 components/style_derive/to_animated_zero.rs delete mode 100644 components/style_derive/to_computed_value.rs delete mode 100644 components/style_derive/to_css.rs delete mode 100644 components/style_derive/to_resolved_value.rs delete mode 100644 components/style_static_prefs/Cargo.toml delete mode 100644 components/style_static_prefs/src/lib.rs delete mode 100644 components/style_traits/Cargo.toml delete mode 100644 components/style_traits/arc_slice.rs delete mode 100644 components/style_traits/dom.rs delete mode 100644 components/style_traits/lib.rs delete mode 100644 components/style_traits/owned_slice.rs delete mode 100644 components/style_traits/owned_str.rs delete mode 100644 components/style_traits/rustfmt.toml delete mode 100644 components/style_traits/specified_value_info.rs delete mode 100644 components/style_traits/values.rs delete mode 100644 components/to_shmem/Cargo.toml delete mode 100644 components/to_shmem/lib.rs delete mode 100644 components/to_shmem/rustfmt.toml delete mode 100644 components/to_shmem_derive/Cargo.toml delete mode 100644 components/to_shmem_derive/lib.rs delete mode 100644 components/to_shmem_derive/to_shmem.rs delete mode 100644 etc/ci/generate_workflow.py rename components/style/stylesheets/font_face_rule.rs => tests/unit/style/build.rs (61%) diff --git a/Cargo.lock b/Cargo.lock index 71adf0ab738..f336e1928eb 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1208,6 +1208,7 @@ dependencies = [ [[package]] name = "derive_common" version = "0.0.1" +source = "git+https://github.com/servo/stylo.git?branch=2023-06-14#9d04df286684bbe1b05858ed1b337b006784bf36" dependencies = [ "darling", "proc-macro2", @@ -3459,6 +3460,7 @@ dependencies = [ [[package]] name = "malloc_size_of" version = "0.0.1" +source = "git+https://github.com/servo/stylo.git?branch=2023-06-14#9d04df286684bbe1b05858ed1b337b006784bf36" dependencies = [ "accountable-refcell", "app_units", @@ -3700,12 +3702,6 @@ dependencies = [ "walkdir", ] -[[package]] -name = "mozbuild" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "903970ae2f248d7275214cf8f387f8ba0c4ea7e3d87a320e85493db60ce28616" - [[package]] name = "mozjs" version = "0.14.1" @@ -5076,6 +5072,7 @@ dependencies = [ [[package]] name = "selectors" version = "0.24.0" +source = "git+https://github.com/servo/stylo.git?branch=2023-06-14#9d04df286684bbe1b05858ed1b337b006784bf36" dependencies = [ "bitflags 1.3.2", "cssparser", @@ -5363,6 +5360,7 @@ dependencies = [ [[package]] name = "servo_arc" version = "0.2.0" +source = "git+https://github.com/servo/stylo.git?branch=2023-06-14#9d04df286684bbe1b05858ed1b337b006784bf36" dependencies = [ "nodrop", "serde", @@ -5372,6 +5370,7 @@ dependencies = [ [[package]] name = "servo_atoms" version = "0.0.1" +source = "git+https://github.com/servo/stylo.git?branch=2023-06-14#9d04df286684bbe1b05858ed1b337b006784bf36" dependencies = [ "string_cache", "string_cache_codegen", @@ -5576,6 +5575,7 @@ checksum = "38b58827f4464d87d377d175e90bf58eb00fd8716ff0a62f80356b5e61555d0d" [[package]] name = "size_of_test" version = "0.0.1" +source = "git+https://github.com/servo/stylo.git?branch=2023-06-14#9d04df286684bbe1b05858ed1b337b006784bf36" dependencies = [ "static_assertions", ] @@ -5701,6 +5701,7 @@ checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" [[package]] name = "static_prefs" version = "0.1.0" +source = "git+https://github.com/servo/stylo.git?branch=2023-06-14#9d04df286684bbe1b05858ed1b337b006784bf36" [[package]] name = "str-buf" @@ -5743,11 +5744,11 @@ dependencies = [ [[package]] name = "style" version = "0.0.1" +source = "git+https://github.com/servo/stylo.git?branch=2023-06-14#9d04df286684bbe1b05858ed1b337b006784bf36" dependencies = [ "app_units", "arrayvec", "atomic_refcell", - "bindgen", "bitflags 1.3.2", "byteorder", "cssparser", @@ -5764,7 +5765,6 @@ dependencies = [ "malloc_size_of", "malloc_size_of_derive", "mime", - "mozbuild", "new_debug_unreachable", "num-derive", "num-integer", @@ -5774,7 +5774,6 @@ dependencies = [ "parking_lot", "precomputed-hash", "rayon", - "regex", "selectors", "serde", "servo_arc", @@ -5791,7 +5790,6 @@ dependencies = [ "time 0.1.45", "to_shmem", "to_shmem_derive", - "toml 0.5.11", "uluru", "unicode-bidi", "unicode-segmentation", @@ -5803,6 +5801,7 @@ dependencies = [ [[package]] name = "style_config" version = "0.0.1" +source = "git+https://github.com/servo/stylo.git?branch=2023-06-14#9d04df286684bbe1b05858ed1b337b006784bf36" dependencies = [ "lazy_static", ] @@ -5810,6 +5809,7 @@ dependencies = [ [[package]] name = "style_derive" version = "0.0.1" +source = "git+https://github.com/servo/stylo.git?branch=2023-06-14#9d04df286684bbe1b05858ed1b337b006784bf36" dependencies = [ "darling", "derive_common", @@ -5840,6 +5840,7 @@ dependencies = [ [[package]] name = "style_traits" version = "0.0.1" +source = "git+https://github.com/servo/stylo.git?branch=2023-06-14#9d04df286684bbe1b05858ed1b337b006784bf36" dependencies = [ "app_units", "bitflags 1.3.2", @@ -6182,6 +6183,7 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" [[package]] name = "to_shmem" version = "0.0.1" +source = "git+https://github.com/servo/stylo.git?branch=2023-06-14#9d04df286684bbe1b05858ed1b337b006784bf36" dependencies = [ "cssparser", "servo_arc", @@ -6194,6 +6196,7 @@ dependencies = [ [[package]] name = "to_shmem_derive" version = "0.0.1" +source = "git+https://github.com/servo/stylo.git?branch=2023-06-14#9d04df286684bbe1b05858ed1b337b006784bf36" dependencies = [ "darling", "derive_common", diff --git a/Cargo.toml b/Cargo.toml index 00b606a941d..95f2f9622a2 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -65,6 +65,7 @@ layout_traits = { path = "components/shared/layout" } lazy_static = "1.4" libc = "0.2" log = "0.4" +malloc_size_of = { git = "https://github.com/servo/stylo.git", branch = "2023-06-14", features = ["servo"] } malloc_size_of_derive = "0.1" mime = "0.3.13" mime_guess = "2.0.3" @@ -87,22 +88,28 @@ rustls = { version = "0.21.10", features = ["dangerous_configuration"] } rustls-pemfile = "1.0.4" script_layout_interface = { path = "components/shared/script_layout" } script_traits = { path = "components/shared/script" } +selectors = { git = "https://github.com/servo/stylo.git", branch = "2023-06-14" } serde = "1.0.197" serde_bytes = "0.11" serde_json = "1.0" +servo_arc = { git = "https://github.com/servo/stylo.git", branch = "2023-06-14" } +servo_atoms = { git = "https://github.com/servo/stylo.git", branch = "2023-06-14" } +size_of_test = { git = "https://github.com/servo/stylo.git", branch = "2023-06-14" } smallbitvec = "2.3.0" smallvec = "1.13" sparkle = "0.1.26" string_cache = "0.8" string_cache_codegen = "0.5" -style_config = { path = "components/style_config" } -style_traits = { path = "components/style_traits", features = ["servo"] } +style = { git = "https://github.com/servo/stylo.git", branch = "2023-06-14", features = ["servo"] } +style_config = { git = "https://github.com/servo/stylo.git", branch = "2023-06-14" } +style_traits = { git = "https://github.com/servo/stylo.git", branch = "2023-06-14", features = ["servo"] } # NOTE: the sm-angle feature only enables ANGLE on Windows, not other platforms! surfman = { version = "0.9", features = ["chains", "sm-angle", "sm-angle-default"] } syn = { version = "2", default-features = false, features = ["clone-impls", "derive", "parsing"] } synstructure = "0.13" thin-vec = "0.2.13" time = "0.1.41" +to_shmem = { git = "https://github.com/servo/stylo.git", branch = "2023-06-14" } tokio = "1" tokio-rustls = "0.24" tungstenite = "0.20" @@ -140,7 +147,22 @@ debug-assertions = false # # = { path = "/path/to/local/checkout" } # -# Or for a git dependency: +# Or for Stylo: +# +# [patch."https://github.com/servo/stylo.git"] +# derive_common = { path = "../stylo/derive_common" } +# malloc_size_of = { path = "../stylo/malloc_size_of" } +# selectors = { path = "../stylo/selectors" } +# servo_arc = { path = "../stylo/servo_arc" } +# servo_atoms = { path = "../stylo/atoms" } +# size_of_test = { path = "../stylo/size_of_test" } +# static_prefs = { path = "../stylo/style_static_prefs" } +# style_config = { path = "../stylo/style_config" } +# style_derive = { path = "../stylo/style_derive" } +# style = { path = "../stylo/style" } +# style_traits = { path = "../stylo/style_traits" } +# +# Or for another git dependency: # # [patch."https://github.com/servo/"] # = { path = "/path/to/local/checkout" } diff --git a/components/atoms/Cargo.toml b/components/atoms/Cargo.toml deleted file mode 100644 index d48dbf0a350..00000000000 --- a/components/atoms/Cargo.toml +++ /dev/null @@ -1,17 +0,0 @@ -[package] -name = "servo_atoms" -version = "0.0.1" -authors = ["The Servo Project Developers"] -license = "MPL-2.0" -edition = "2018" -publish = false -build = "build.rs" - -[lib] -path = "lib.rs" - -[dependencies] -string_cache = { workspace = true } - -[build-dependencies] -string_cache_codegen = { workspace = true } diff --git a/components/atoms/build.rs b/components/atoms/build.rs deleted file mode 100644 index 6bd2de37034..00000000000 --- a/components/atoms/build.rs +++ /dev/null @@ -1,31 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ - -use std::env; -use std::fs::File; -use std::io::{BufRead, BufReader}; -use std::path::Path; - -fn main() { - let static_atoms = - Path::new(&env::var_os("CARGO_MANIFEST_DIR").unwrap()).join("static_atoms.txt"); - let static_atoms = BufReader::new(File::open(&static_atoms).unwrap()); - let mut atom_type = string_cache_codegen::AtomType::new("Atom", "atom!"); - - macro_rules! predefined { - ($($name: expr,)+) => { - { - $( - atom_type.atom($name); - )+ - } - } - } - include!("../style/counter_style/predefined.rs"); - - atom_type - .atoms(static_atoms.lines().map(Result::unwrap)) - .write_to_file(&Path::new(&env::var_os("OUT_DIR").unwrap()).join("atom.rs")) - .unwrap(); -} diff --git a/components/atoms/lib.rs b/components/atoms/lib.rs deleted file mode 100644 index 03560a40c0b..00000000000 --- a/components/atoms/lib.rs +++ /dev/null @@ -1,5 +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/. */ - -include!(concat!(env!("OUT_DIR"), "/atom.rs")); diff --git a/components/atoms/static_atoms.txt b/components/atoms/static_atoms.txt deleted file mode 100644 index 5d8f7754270..00000000000 --- a/components/atoms/static_atoms.txt +++ /dev/null @@ -1,174 +0,0 @@ --moz-content-preferred-color-scheme --moz-device-pixel-ratio --moz-gtk-csd-close-button-position --moz-gtk-csd-maximize-button-position --moz-gtk-csd-menu-radius --moz-gtk-csd-minimize-button-position --moz-gtk-csd-titlebar-radius --moz-gtk-menu-radius -DOMContentLoaded -abort -activate -addtrack -animationcancel -animationend -animationiteration -animationstart -aspect-ratio -beforeunload -block-size -button -canplay -canplaythrough -center -change -characteristicvaluechanged -checkbox -click -close -closing -color -complete -compositionend -compositionstart -compositionupdate -controllerchange -cursive -dark -datachannel -date -datetime-local -dir -device-pixel-ratio -durationchange -email -emptied -end -ended -error -fantasy -fetch -file -fill -fill-opacity -formdata -fullscreenchange -fullscreenerror -gattserverdisconnected -hashchange -height -hidden -icecandidate -iceconnectionstatechange -icegatheringstatechange -image -inline-size -input -inputsourceschange -invalid -keydown -keypress -kind -left -light -ltr -load -loadeddata -loadedmetadata -loadend -loadstart -message -message -messageerror -monospace -month -mousedown -mousemove -mouseover -mouseup -negotiationneeded -none -normal -number -onchange -open -orientation -pagehide -pageshow -password -pause -play -playing -popstate -postershown -print -progress -radio -range -ratechange -readystatechange -referrer -reftest-wait -rejectionhandled -removetrack -reset -resize -resolution -resourcetimingbufferfull -right -rtl -sans-serif -safe-area-inset-top -safe-area-inset-bottom -safe-area-inset-left -safe-area-inset-right -scan -screen -scroll-position -scrollbar-inline-size -search -seeked -seeking -select -selectend -selectionchange -selectstart -serif -sessionavailable -signalingstatechange -squeeze -squeezeend -squeezestart -srclang -statechange -stroke -stroke-opacity -storage -submit -suspend -system-ui -tel -text -time -timeupdate -toggle -track -transitioncancel -transitionend -transitionrun -transitionstart -uncapturederror -unhandledrejection -unload -url -visibilitychange -volumechange -waiting -webglcontextcreationerror -webkitAnimationEnd -webkitAnimationIteration -webkitAnimationStart -webkitTransitionEnd -webkitTransitionRun -week -width diff --git a/components/canvas/Cargo.toml b/components/canvas/Cargo.toml index 692d2efdfce..ce420ae0c01 100644 --- a/components/canvas/Cargo.toml +++ b/components/canvas/Cargo.toml @@ -33,9 +33,9 @@ num-traits = { workspace = true } pathfinder_geometry = "0.5" pixels = { path = "../pixels" } raqote = "0.8.2" -servo_arc = { path = "../servo_arc" } +servo_arc = { workspace = true } sparkle = { workspace = true } -style = { path = "../style" } +style = { workspace = true } style_traits = { workspace = true } surfman = { workspace = true } time = { workspace = true, optional = true } diff --git a/components/config/Cargo.toml b/components/config/Cargo.toml index 8befa01cb54..ea92a1d99f8 100644 --- a/components/config/Cargo.toml +++ b/components/config/Cargo.toml @@ -22,7 +22,7 @@ serde_json = { workspace = true } servo_config_plugins = { path = "../config_plugins" } servo_geometry = { path = "../geometry" } servo_url = { path = "../url" } -style_config = { path = "../style_config" } +style_config = { workspace = true } url = { workspace = true } [target.'cfg(not(target_os = "android"))'.dependencies] diff --git a/components/derive_common/Cargo.toml b/components/derive_common/Cargo.toml deleted file mode 100644 index 5677069ad56..00000000000 --- a/components/derive_common/Cargo.toml +++ /dev/null @@ -1,16 +0,0 @@ -[package] -name = "derive_common" -version = "0.0.1" -authors = ["The Servo Project Developers"] -license = "MPL-2.0" -publish = false - -[lib] -path = "lib.rs" - -[dependencies] -darling = { workspace = true } -proc-macro2 = { workspace = true } -quote = { workspace = true } -syn = { workspace = true } -synstructure = { workspace = true } diff --git a/components/derive_common/cg.rs b/components/derive_common/cg.rs deleted file mode 100644 index 73301af2ff2..00000000000 --- a/components/derive_common/cg.rs +++ /dev/null @@ -1,396 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ - -use darling::{FromDeriveInput, FromField, FromVariant}; -use proc_macro2::{Span, TokenStream}; -use quote::TokenStreamExt; -use syn::{self, AngleBracketedGenericArguments, AssocType, DeriveInput, Field}; -use syn::{GenericArgument, GenericParam, Ident, Path}; -use syn::{PathArguments, PathSegment, QSelf, Type, TypeArray, TypeGroup}; -use syn::{TypeParam, TypeParen, TypePath, TypeSlice, TypeTuple}; -use syn::{Variant, WherePredicate}; -use synstructure::{self, BindStyle, BindingInfo, VariantAst, VariantInfo}; - -/// Given an input type which has some where clauses already, like: -/// -/// struct InputType -/// where -/// T: Zero, -/// { -/// ... -/// } -/// -/// Add the necessary `where` clauses so that the output type of a trait -/// fulfils them. -/// -/// For example: -/// -/// ```ignore -/// ::ComputedValue: Zero, -/// ``` -/// -/// This needs to run before adding other bounds to the type parameters. -pub fn propagate_clauses_to_output_type( - where_clause: &mut Option, - generics: &syn::Generics, - trait_path: &Path, - trait_output: &Ident, -) { - let where_clause = match *where_clause { - Some(ref mut clause) => clause, - None => return, - }; - let mut extra_bounds = vec![]; - for pred in &where_clause.predicates { - let ty = match *pred { - syn::WherePredicate::Type(ref ty) => ty, - ref predicate => panic!("Unhanded complex where predicate: {:?}", predicate), - }; - - let path = match ty.bounded_ty { - syn::Type::Path(ref p) => &p.path, - ref ty => panic!("Unhanded complex where type: {:?}", ty), - }; - - assert!( - ty.lifetimes.is_none(), - "Unhanded complex lifetime bound: {:?}", - ty, - ); - - let ident = match path_to_ident(path) { - Some(i) => i, - None => panic!("Unhanded complex where type path: {:?}", path), - }; - - if generics.type_params().any(|param| param.ident == *ident) { - extra_bounds.push(ty.clone()); - } - } - - for bound in extra_bounds { - let ty = bound.bounded_ty; - let bounds = bound.bounds; - where_clause - .predicates - .push(parse_quote!(<#ty as #trait_path>::#trait_output: #bounds)) - } -} - -pub fn add_predicate(where_clause: &mut Option, pred: WherePredicate) { - where_clause - .get_or_insert(parse_quote!(where)) - .predicates - .push(pred); -} - -pub fn fmap_match(input: &DeriveInput, bind_style: BindStyle, f: F) -> TokenStream -where - F: FnMut(&BindingInfo) -> TokenStream, -{ - fmap2_match(input, bind_style, f, |_| None) -} - -pub fn fmap2_match( - input: &DeriveInput, - bind_style: BindStyle, - mut f: F, - mut g: G, -) -> TokenStream -where - F: FnMut(&BindingInfo) -> TokenStream, - G: FnMut(&BindingInfo) -> Option, -{ - let mut s = synstructure::Structure::new(input); - s.variants_mut().iter_mut().for_each(|v| { - v.bind_with(|_| bind_style); - }); - s.each_variant(|variant| { - let (mapped, mapped_fields) = value(variant, "mapped"); - let fields_pairs = variant.bindings().iter().zip(mapped_fields.iter()); - let mut computations = quote!(); - computations.append_all(fields_pairs.map(|(field, mapped_field)| { - let expr = f(field); - quote! { let #mapped_field = #expr; } - })); - computations.append_all( - mapped_fields - .iter() - .map(|mapped_field| match g(mapped_field) { - Some(expr) => quote! { let #mapped_field = #expr; }, - None => quote!(), - }), - ); - computations.append_all(mapped); - Some(computations) - }) -} - -pub fn fmap_trait_output(input: &DeriveInput, trait_path: &Path, trait_output: &Ident) -> Path { - let segment = PathSegment { - ident: input.ident.clone(), - arguments: PathArguments::AngleBracketed(AngleBracketedGenericArguments { - args: input - .generics - .params - .iter() - .map(|arg| match arg { - &GenericParam::Lifetime(ref data) => { - GenericArgument::Lifetime(data.lifetime.clone()) - }, - &GenericParam::Type(ref data) => { - let ident = &data.ident; - GenericArgument::Type(parse_quote!(<#ident as #trait_path>::#trait_output)) - }, - &GenericParam::Const(ref inner) => { - let ident = &inner.ident; - GenericArgument::Const(parse_quote!(#ident)) - }, - }) - .collect(), - colon2_token: Default::default(), - gt_token: Default::default(), - lt_token: Default::default(), - }), - }; - segment.into() -} - -pub fn map_type_params(ty: &Type, params: &[&TypeParam], self_type: &Path, f: &mut F) -> Type -where - F: FnMut(&Ident) -> Type, -{ - match *ty { - Type::Slice(ref inner) => Type::from(TypeSlice { - elem: Box::new(map_type_params(&inner.elem, params, self_type, f)), - ..inner.clone() - }), - Type::Array(ref inner) => { - //ref ty, ref expr) => { - Type::from(TypeArray { - elem: Box::new(map_type_params(&inner.elem, params, self_type, f)), - ..inner.clone() - }) - }, - ref ty @ Type::Never(_) => ty.clone(), - Type::Tuple(ref inner) => Type::from(TypeTuple { - elems: inner - .elems - .iter() - .map(|ty| map_type_params(&ty, params, self_type, f)) - .collect(), - ..inner.clone() - }), - Type::Path(TypePath { - qself: None, - ref path, - }) => { - if let Some(ident) = path_to_ident(path) { - if params.iter().any(|ref param| ¶m.ident == ident) { - return f(ident); - } - if ident == "Self" { - return Type::from(TypePath { - qself: None, - path: self_type.clone(), - }); - } - } - Type::from(TypePath { - qself: None, - path: map_type_params_in_path(path, params, self_type, f), - }) - }, - Type::Path(TypePath { - ref qself, - ref path, - }) => Type::from(TypePath { - qself: qself.as_ref().map(|qself| QSelf { - ty: Box::new(map_type_params(&qself.ty, params, self_type, f)), - position: qself.position, - ..qself.clone() - }), - path: map_type_params_in_path(path, params, self_type, f), - }), - Type::Paren(ref inner) => Type::from(TypeParen { - elem: Box::new(map_type_params(&inner.elem, params, self_type, f)), - ..inner.clone() - }), - Type::Group(ref inner) => Type::from(TypeGroup { - elem: Box::new(map_type_params(&inner.elem, params, self_type, f)), - ..inner.clone() - }), - ref ty => panic!("type {:?} cannot be mapped yet", ty), - } -} - -fn map_type_params_in_path( - path: &Path, - params: &[&TypeParam], - self_type: &Path, - f: &mut F, -) -> Path -where - F: FnMut(&Ident) -> Type, -{ - Path { - leading_colon: path.leading_colon, - segments: path - .segments - .iter() - .map(|segment| PathSegment { - ident: segment.ident.clone(), - arguments: match segment.arguments { - PathArguments::AngleBracketed(ref data) => { - PathArguments::AngleBracketed(AngleBracketedGenericArguments { - args: data - .args - .iter() - .map(|arg| match arg { - ty @ &GenericArgument::Lifetime(_) => ty.clone(), - &GenericArgument::Type(ref data) => GenericArgument::Type( - map_type_params(data, params, self_type, f), - ), - &GenericArgument::AssocType(ref data) => { - GenericArgument::AssocType(AssocType { - ty: map_type_params(&data.ty, params, self_type, f), - ..data.clone() - }) - }, - ref arg => panic!("arguments {:?} cannot be mapped yet", arg), - }) - .collect(), - ..data.clone() - }) - }, - ref arg @ PathArguments::None => arg.clone(), - ref parameters => panic!("parameters {:?} cannot be mapped yet", parameters), - }, - }) - .collect(), - } -} - -fn path_to_ident(path: &Path) -> Option<&Ident> { - match *path { - Path { - leading_colon: None, - ref segments, - } if segments.len() == 1 => { - if segments[0].arguments.is_empty() { - Some(&segments[0].ident) - } else { - None - } - }, - _ => None, - } -} - -pub fn parse_field_attrs(field: &Field) -> A -where - A: FromField, -{ - match A::from_field(field) { - Ok(attrs) => attrs, - Err(e) => panic!("failed to parse field attributes: {}", e), - } -} - -pub fn parse_input_attrs(input: &DeriveInput) -> A -where - A: FromDeriveInput, -{ - match A::from_derive_input(input) { - Ok(attrs) => attrs, - Err(e) => panic!("failed to parse input attributes: {}", e), - } -} - -pub fn parse_variant_attrs_from_ast(variant: &VariantAst) -> A -where - A: FromVariant, -{ - let v = Variant { - ident: variant.ident.clone(), - attrs: variant.attrs.to_vec(), - fields: variant.fields.clone(), - discriminant: variant.discriminant.clone(), - }; - parse_variant_attrs(&v) -} - -pub fn parse_variant_attrs(variant: &Variant) -> A -where - A: FromVariant, -{ - match A::from_variant(variant) { - Ok(attrs) => attrs, - Err(e) => panic!("failed to parse variant attributes: {}", e), - } -} - -pub fn ref_pattern<'a>( - variant: &'a VariantInfo, - prefix: &str, -) -> (TokenStream, Vec>) { - let mut v = variant.clone(); - v.bind_with(|_| BindStyle::Ref); - v.bindings_mut().iter_mut().for_each(|b| { - b.binding = Ident::new(&format!("{}_{}", b.binding, prefix), Span::call_site()) - }); - (v.pat(), v.bindings().to_vec()) -} - -pub fn value<'a>(variant: &'a VariantInfo, prefix: &str) -> (TokenStream, Vec>) { - let mut v = variant.clone(); - v.bindings_mut().iter_mut().for_each(|b| { - b.binding = Ident::new(&format!("{}_{}", b.binding, prefix), Span::call_site()) - }); - v.bind_with(|_| BindStyle::Move); - (v.pat(), v.bindings().to_vec()) -} - -/// Transforms "FooBar" to "foo-bar". -/// -/// If the first Camel segment is "Moz", "Webkit", or "Servo", the result string -/// is prepended with "-". -pub fn to_css_identifier(mut camel_case: &str) -> String { - camel_case = camel_case.trim_end_matches('_'); - let mut first = true; - let mut result = String::with_capacity(camel_case.len()); - while let Some(segment) = split_camel_segment(&mut camel_case) { - if first { - match segment { - "Moz" | "Webkit" | "Servo" => first = false, - _ => {}, - } - } - if !first { - result.push('-'); - } - first = false; - result.push_str(&segment.to_lowercase()); - } - 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() { - None => return None, - Some(ch) => ch.len_utf8(), - }; - let end_position = camel_case[index..] - .find(char::is_uppercase) - .map_or(camel_case.len(), |pos| index + pos); - let result = &camel_case[..end_position]; - *camel_case = &camel_case[end_position..]; - Some(result) -} diff --git a/components/derive_common/lib.rs b/components/derive_common/lib.rs deleted file mode 100644 index 14415351449..00000000000 --- a/components/derive_common/lib.rs +++ /dev/null @@ -1,13 +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/. */ - -extern crate darling; -extern crate proc_macro2; -#[macro_use] -extern crate quote; -#[macro_use] -extern crate syn; -extern crate synstructure; - -pub mod cg; diff --git a/components/derive_common/rustfmt.toml b/components/derive_common/rustfmt.toml deleted file mode 100644 index c7ad93bafe3..00000000000 --- a/components/derive_common/rustfmt.toml +++ /dev/null @@ -1 +0,0 @@ -disable_all_formatting = true diff --git a/components/geometry/Cargo.toml b/components/geometry/Cargo.toml index 87a870c0aa5..a816b6f5278 100644 --- a/components/geometry/Cargo.toml +++ b/components/geometry/Cargo.toml @@ -13,6 +13,6 @@ path = "lib.rs" [dependencies] app_units = { workspace = true } euclid = { workspace = true } -malloc_size_of = { path = "../malloc_size_of" } +malloc_size_of = { workspace = true } malloc_size_of_derive = { workspace = true } webrender_api = { workspace = true } diff --git a/components/gfx/Cargo.toml b/components/gfx/Cargo.toml index 9538ce11960..114e727d041 100644 --- a/components/gfx/Cargo.toml +++ b/components/gfx/Cargo.toml @@ -25,16 +25,16 @@ ipc-channel = { workspace = true } lazy_static = { workspace = true } libc = { workspace = true } log = { workspace = true } -malloc_size_of = { path = "../malloc_size_of" } +malloc_size_of = { workspace = true } net_traits = { workspace = true } range = { path = "../range" } serde = { workspace = true } -servo_arc = { path = "../servo_arc" } -servo_atoms = { path = "../atoms" } +servo_arc = { workspace = true } +servo_atoms = { workspace = true } servo_url = { path = "../url" } smallvec = { workspace = true, features = ["union"] } surfman = { workspace = true } -style = { path = "../style", features = ["servo"] } +style = { workspace = true } ucd = "0.1.1" unicode-bidi = { workspace = true, features = ["with_serde"] } unicode-script = { workspace = true } diff --git a/components/layout/Cargo.toml b/components/layout/Cargo.toml index 2f4040c9a48..6df0d11c36f 100644 --- a/components/layout/Cargo.toml +++ b/components/layout/Cargo.toml @@ -26,7 +26,7 @@ html5ever = { workspace = true } ipc-channel = { workspace = true } lazy_static = { workspace = true } log = { workspace = true } -malloc_size_of = { path = "../malloc_size_of" } +malloc_size_of = { workspace = true } msg = { workspace = true } net_traits = { workspace = true } parking_lot = { workspace = true } @@ -37,14 +37,14 @@ script_layout_interface = { workspace = true } script_traits = { workspace = true } serde = { workspace = true } serde_json = { workspace = true } -servo_arc = { path = "../servo_arc" } -servo_atoms = { path = "../atoms" } +servo_arc = { workspace = true } +servo_atoms = { workspace = true } servo_config = { path = "../config" } servo_geometry = { path = "../geometry" } servo_url = { path = "../url" } -size_of_test = { path = "../size_of_test" } +size_of_test = { workspace = true } smallvec = { workspace = true, features = ["union"] } -style = { path = "../style", features = ["servo"] } +style = { workspace = true } style_traits = { workspace = true } unicode-bidi = { workspace = true, features = ["with_serde"] } unicode-script = { workspace = true } diff --git a/components/layout_2020/Cargo.toml b/components/layout_2020/Cargo.toml index e5b647955b8..78ea0bf49a4 100644 --- a/components/layout_2020/Cargo.toml +++ b/components/layout_2020/Cargo.toml @@ -36,10 +36,10 @@ script_layout_interface = { workspace = true } script_traits = { workspace = true } serde = { workspace = true } serde_json = { workspace = true } -servo_arc = { path = "../servo_arc" } +servo_arc = { workspace = true } servo_config = { path = "../config" } servo_url = { path = "../url" } -style = { path = "../style", features = ["servo"] } +style = { workspace = true } style_traits = { workspace = true } unicode-script = { workspace = true } unicode-segmentation = { workspace = true } diff --git a/components/layout_thread/Cargo.toml b/components/layout_thread/Cargo.toml index 4da4f2d785a..97fc689dd2d 100644 --- a/components/layout_thread/Cargo.toml +++ b/components/layout_thread/Cargo.toml @@ -24,7 +24,7 @@ ipc-channel = { workspace = true } layout = { path = "../layout", package = "layout_2013" } lazy_static = { workspace = true } log = { workspace = true } -malloc_size_of = { path = "../malloc_size_of" } +malloc_size_of = { workspace = true } metrics = { path = "../metrics" } msg = { workspace = true } net_traits = { workspace = true } @@ -36,11 +36,11 @@ script_layout_interface = { workspace = true } script_traits = { workspace = true } serde_json = { workspace = true } servo_allocator = { path = "../allocator" } -servo_arc = { path = "../servo_arc" } -servo_atoms = { path = "../atoms" } +servo_arc = { workspace = true } +servo_atoms = { workspace = true } servo_config = { path = "../config" } servo_url = { path = "../url" } -style = { path = "../style" } +style = { workspace = true } style_traits = { workspace = true } time = { workspace = true } url = { workspace = true } diff --git a/components/layout_thread_2020/Cargo.toml b/components/layout_thread_2020/Cargo.toml index 2a1e510630c..c5010281ae8 100644 --- a/components/layout_thread_2020/Cargo.toml +++ b/components/layout_thread_2020/Cargo.toml @@ -23,7 +23,7 @@ ipc-channel = { workspace = true } layout = { path = "../layout_2020", package = "layout_2020" } lazy_static = { workspace = true } log = { workspace = true } -malloc_size_of = { path = "../malloc_size_of" } +malloc_size_of = { workspace = true } metrics = { path = "../metrics" } msg = { workspace = true } net_traits = { workspace = true } @@ -33,11 +33,11 @@ script = { path = "../script" } script_layout_interface = { workspace = true } script_traits = { workspace = true } servo_allocator = { path = "../allocator" } -servo_arc = { path = "../servo_arc" } -servo_atoms = { path = "../atoms" } +servo_arc = { workspace = true } +servo_atoms = { workspace = true } servo_config = { path = "../config" } servo_url = { path = "../url" } -style = { path = "../style" } +style = { workspace = true } style_traits = { workspace = true } url = { workspace = true } webrender_api = { workspace = true } diff --git a/components/malloc_size_of/Cargo.toml b/components/malloc_size_of/Cargo.toml deleted file mode 100644 index 01204a98e41..00000000000 --- a/components/malloc_size_of/Cargo.toml +++ /dev/null @@ -1,52 +0,0 @@ -[package] -name = "malloc_size_of" -version = "0.0.1" -authors = ["The Servo Project Developers"] -license = "MIT OR Apache-2.0" -publish = false - -[lib] -path = "lib.rs" - -[features] -servo = [ - "accountable-refcell", - "content-security-policy", - "crossbeam-channel", - "http", - "keyboard-types", - "serde", - "serde_bytes", - "string_cache", - "time", - "url", - "uuid", - "webrender_api", - "xml5ever", -] - -[dependencies] -accountable-refcell = { workspace = true, optional = true } -app_units = { workspace = true } -content-security-policy = { workspace = true, optional = true } -crossbeam-channel = { workspace = true, optional = true } -cssparser = { workspace = true } -euclid = { workspace = true } -http = { workspace = true, optional = true } -indexmap = { workspace = true } -keyboard-types = { workspace = true, optional = true } -selectors = { path = "../selectors" } -serde = { workspace = true, optional = true } -serde_bytes = { workspace = true, optional = true } -servo_arc = { path = "../servo_arc" } -smallbitvec = { workspace = true } -smallvec = { workspace = true } -string_cache = { workspace = true, optional = true } -thin-vec = { workspace = true } -time = { workspace = true, optional = true } -tokio = { workspace = true, features = ["sync"] } -url = { workspace = true, optional = true } -uuid = { workspace = true, optional = true } -void = "1.0.2" -webrender_api = { workspace = true, optional = true } -xml5ever = { workspace = true, optional = true } diff --git a/components/malloc_size_of/LICENSE-APACHE b/components/malloc_size_of/LICENSE-APACHE deleted file mode 100644 index 16fe87b06e8..00000000000 --- a/components/malloc_size_of/LICENSE-APACHE +++ /dev/null @@ -1,201 +0,0 @@ - Apache License - Version 2.0, January 2004 - http://www.apache.org/licenses/ - -TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION - -1. Definitions. - - "License" shall mean the terms and conditions for use, reproduction, - and distribution as defined by Sections 1 through 9 of this document. - - "Licensor" shall mean the copyright owner or entity authorized by - the copyright owner that is granting the License. - - "Legal Entity" shall mean the union of the acting entity and all - other entities that control, are controlled by, or are under common - control with that entity. For the purposes of this definition, - "control" means (i) the power, direct or indirect, to cause the - direction or management of such entity, whether by contract or - otherwise, or (ii) ownership of fifty percent (50%) or more of the - outstanding shares, or (iii) beneficial ownership of such entity. - - "You" (or "Your") shall mean an individual or Legal Entity - exercising permissions granted by this License. - - "Source" form shall mean the preferred form for making modifications, - including but not limited to software source code, documentation - source, and configuration files. - - "Object" form shall mean any form resulting from mechanical - transformation or translation of a Source form, including but - not limited to compiled object code, generated documentation, - and conversions to other media types. - - "Work" shall mean the work of authorship, whether in Source or - Object form, made available under the License, as indicated by a - copyright notice that is included in or attached to the work - (an example is provided in the Appendix below). - - "Derivative Works" shall mean any work, whether in Source or Object - form, that is based on (or derived from) the Work and for which the - editorial revisions, annotations, elaborations, or other modifications - represent, as a whole, an original work of authorship. For the purposes - of this License, Derivative Works shall not include works that remain - separable from, or merely link (or bind by name) to the interfaces of, - the Work and Derivative Works thereof. - - "Contribution" shall mean any work of authorship, including - the original version of the Work and any modifications or additions - to that Work or Derivative Works thereof, that is intentionally - submitted to Licensor for inclusion in the Work by the copyright owner - or by an individual or Legal Entity authorized to submit on behalf of - the copyright owner. For the purposes of this definition, "submitted" - means any form of electronic, verbal, or written communication sent - to the Licensor or its representatives, including but not limited to - communication on electronic mailing lists, source code control systems, - and issue tracking systems that are managed by, or on behalf of, the - Licensor for the purpose of discussing and improving the Work, but - excluding communication that is conspicuously marked or otherwise - designated in writing by the copyright owner as "Not a Contribution." - - "Contributor" shall mean Licensor and any individual or Legal Entity - on behalf of whom a Contribution has been received by Licensor and - subsequently incorporated within the Work. - -2. Grant of Copyright License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - copyright license to reproduce, prepare Derivative Works of, - publicly display, publicly perform, sublicense, and distribute the - Work and such Derivative Works in Source or Object form. - -3. Grant of Patent License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - (except as stated in this section) patent license to make, have made, - use, offer to sell, sell, import, and otherwise transfer the Work, - where such license applies only to those patent claims licensable - by such Contributor that are necessarily infringed by their - Contribution(s) alone or by combination of their Contribution(s) - with the Work to which such Contribution(s) was submitted. If You - institute patent litigation against any entity (including a - cross-claim or counterclaim in a lawsuit) alleging that the Work - or a Contribution incorporated within the Work constitutes direct - or contributory patent infringement, then any patent licenses - granted to You under this License for that Work shall terminate - as of the date such litigation is filed. - -4. Redistribution. You may reproduce and distribute copies of the - Work or Derivative Works thereof in any medium, with or without - modifications, and in Source or Object form, provided that You - meet the following conditions: - - (a) You must give any other recipients of the Work or - Derivative Works a copy of this License; and - - (b) You must cause any modified files to carry prominent notices - stating that You changed the files; and - - (c) You must retain, in the Source form of any Derivative Works - that You distribute, all copyright, patent, trademark, and - attribution notices from the Source form of the Work, - excluding those notices that do not pertain to any part of - the Derivative Works; and - - (d) If the Work includes a "NOTICE" text file as part of its - distribution, then any Derivative Works that You distribute must - include a readable copy of the attribution notices contained - within such NOTICE file, excluding those notices that do not - pertain to any part of the Derivative Works, in at least one - of the following places: within a NOTICE text file distributed - as part of the Derivative Works; within the Source form or - documentation, if provided along with the Derivative Works; or, - within a display generated by the Derivative Works, if and - wherever such third-party notices normally appear. The contents - of the NOTICE file are for informational purposes only and - do not modify the License. You may add Your own attribution - notices within Derivative Works that You distribute, alongside - or as an addendum to the NOTICE text from the Work, provided - that such additional attribution notices cannot be construed - as modifying the License. - - You may add Your own copyright statement to Your modifications and - may provide additional or different license terms and conditions - for use, reproduction, or distribution of Your modifications, or - for any such Derivative Works as a whole, provided Your use, - reproduction, and distribution of the Work otherwise complies with - the conditions stated in this License. - -5. Submission of Contributions. Unless You explicitly state otherwise, - any Contribution intentionally submitted for inclusion in the Work - by You to the Licensor shall be under the terms and conditions of - this License, without any additional terms or conditions. - Notwithstanding the above, nothing herein shall supersede or modify - the terms of any separate license agreement you may have executed - with Licensor regarding such Contributions. - -6. Trademarks. This License does not grant permission to use the trade - names, trademarks, service marks, or product names of the Licensor, - except as required for reasonable and customary use in describing the - origin of the Work and reproducing the content of the NOTICE file. - -7. Disclaimer of Warranty. Unless required by applicable law or - agreed to in writing, Licensor provides the Work (and each - Contributor provides its Contributions) on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or - implied, including, without limitation, any warranties or conditions - of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A - PARTICULAR PURPOSE. You are solely responsible for determining the - appropriateness of using or redistributing the Work and assume any - risks associated with Your exercise of permissions under this License. - -8. Limitation of Liability. In no event and under no legal theory, - whether in tort (including negligence), contract, or otherwise, - unless required by applicable law (such as deliberate and grossly - negligent acts) or agreed to in writing, shall any Contributor be - liable to You for damages, including any direct, indirect, special, - incidental, or consequential damages of any character arising as a - result of this License or out of the use or inability to use the - Work (including but not limited to damages for loss of goodwill, - work stoppage, computer failure or malfunction, or any and all - other commercial damages or losses), even if such Contributor - has been advised of the possibility of such damages. - -9. Accepting Warranty or Additional Liability. While redistributing - the Work or Derivative Works thereof, You may choose to offer, - and charge a fee for, acceptance of support, warranty, indemnity, - or other liability obligations and/or rights consistent with this - License. However, in accepting such obligations, You may act only - on Your own behalf and on Your sole responsibility, not on behalf - of any other Contributor, and only if You agree to indemnify, - defend, and hold each Contributor harmless for any liability - incurred by, or claims asserted against, such Contributor by reason - of your accepting any such warranty or additional liability. - -END OF TERMS AND CONDITIONS - -APPENDIX: How to apply the Apache License to your work. - - To apply the Apache License to your work, attach the following - boilerplate notice, with the fields enclosed by brackets "[]" - replaced with your own identifying information. (Don't include - the brackets!) The text should be enclosed in the appropriate - comment syntax for the file format. We also recommend that a - file or class name and description of purpose be included on the - same "printed page" as the copyright notice for easier - identification within third-party archives. - -Copyright [yyyy] [name of copyright owner] - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. diff --git a/components/malloc_size_of/LICENSE-MIT b/components/malloc_size_of/LICENSE-MIT deleted file mode 100644 index 31aa79387f2..00000000000 --- a/components/malloc_size_of/LICENSE-MIT +++ /dev/null @@ -1,23 +0,0 @@ -Permission is hereby granted, free of charge, to any -person obtaining a copy of this software and associated -documentation files (the "Software"), to deal in the -Software without restriction, including without -limitation the rights to use, copy, modify, merge, -publish, distribute, sublicense, and/or sell copies of -the Software, and to permit persons to whom the Software -is furnished to do so, subject to the following -conditions: - -The above copyright notice and this permission notice -shall be included in all copies or substantial portions -of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF -ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED -TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A -PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT -SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY -CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION -OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR -IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER -DEALINGS IN THE SOFTWARE. diff --git a/components/malloc_size_of/lib.rs b/components/malloc_size_of/lib.rs deleted file mode 100644 index dd0ca38e17c..00000000000 --- a/components/malloc_size_of/lib.rs +++ /dev/null @@ -1,978 +0,0 @@ -// Copyright 2016-2017 The Servo Project Developers. See the COPYRIGHT -// file at the top-level directory of this distribution and at -// http://rust-lang.org/COPYRIGHT. -// -// Licensed under the Apache License, Version 2.0 or the MIT license -// , at your -// option. This file may not be copied, modified, or distributed -// except according to those terms. - -//! A crate for measuring the heap usage of data structures in a way that -//! integrates with Firefox's memory reporting, particularly the use of -//! mozjemalloc and DMD. In particular, it has the following features. -//! - It isn't bound to a particular heap allocator. -//! - It provides traits for both "shallow" and "deep" measurement, which gives -//! flexibility in the cases where the traits can't be used. -//! - It allows for measuring blocks even when only an interior pointer can be -//! obtained for heap allocations, e.g. `HashSet` and `HashMap`. (This relies -//! on the heap allocator having suitable support, which mozjemalloc has.) -//! - It allows handling of types like `Rc` and `Arc` by providing traits that -//! are different to the ones for non-graph structures. -//! -//! Suggested uses are as follows. -//! - When possible, use the `MallocSizeOf` trait. (Deriving support is -//! provided by the `malloc_size_of_derive` crate.) -//! - If you need an additional synchronization argument, provide a function -//! that is like the standard trait method, but with the extra argument. -//! - If you need multiple measurements for a type, provide a function named -//! `add_size_of` that takes a mutable reference to a struct that contains -//! the multiple measurement fields. -//! - When deep measurement (via `MallocSizeOf`) cannot be implemented for a -//! type, shallow measurement (via `MallocShallowSizeOf`) in combination with -//! iteration can be a useful substitute. -//! - `Rc` and `Arc` are always tricky, which is why `MallocSizeOf` is not (and -//! should not be) implemented for them. -//! - If an `Rc` or `Arc` is known to be a "primary" reference and can always -//! be measured, it should be measured via the `MallocUnconditionalSizeOf` -//! trait. -//! - If an `Rc` or `Arc` should be measured only if it hasn't been seen -//! before, it should be measured via the `MallocConditionalSizeOf` trait. -//! - Using universal function call syntax is a good idea when measuring boxed -//! fields in structs, because it makes it clear that the Box is being -//! measured as well as the thing it points to. E.g. -//! ` as MallocSizeOf>::size_of(field, ops)`. -//! -//! Note: WebRender has a reduced fork of this crate, so that we can avoid -//! publishing this crate on crates.io. - -#[cfg(feature = "servo")] -extern crate accountable_refcell; -extern crate app_units; -#[cfg(feature = "servo")] -extern crate content_security_policy; -#[cfg(feature = "servo")] -extern crate crossbeam_channel; -extern crate cssparser; -extern crate euclid; -#[cfg(feature = "servo")] -extern crate http; -#[cfg(feature = "servo")] -extern crate keyboard_types; -extern crate selectors; -#[cfg(feature = "servo")] -extern crate serde; -#[cfg(feature = "servo")] -extern crate serde_bytes; -extern crate servo_arc; -extern crate smallbitvec; -extern crate smallvec; -#[cfg(feature = "servo")] -extern crate string_cache; -#[cfg(feature = "servo")] -extern crate time; -#[cfg(feature = "url")] -extern crate url; -#[cfg(feature = "servo")] -extern crate uuid; -extern crate void; -#[cfg(feature = "webrender_api")] -extern crate webrender_api; -#[cfg(feature = "servo")] -extern crate xml5ever; - -#[cfg(feature = "servo")] -use content_security_policy as csp; -#[cfg(feature = "servo")] -use serde_bytes::ByteBuf; -use std::hash::{BuildHasher, Hash}; -use std::mem::size_of; -use std::ops::Range; -use std::ops::{Deref, DerefMut}; -use std::os::raw::c_void; -#[cfg(feature = "servo")] -use uuid::Uuid; -use void::Void; - -/// A C function that takes a pointer to a heap allocation and returns its size. -type VoidPtrToSizeFn = unsafe extern "C" fn(ptr: *const c_void) -> usize; - -/// A closure implementing a stateful predicate on pointers. -type VoidPtrToBoolFnMut = dyn FnMut(*const c_void) -> bool; - -/// Operations used when measuring heap usage of data structures. -pub struct MallocSizeOfOps { - /// A function that returns the size of a heap allocation. - size_of_op: VoidPtrToSizeFn, - - /// Like `size_of_op`, but can take an interior pointer. Optional because - /// not all allocators support this operation. If it's not provided, some - /// memory measurements will actually be computed estimates rather than - /// real and accurate measurements. - enclosing_size_of_op: Option, - - /// Check if a pointer has been seen before, and remember it for next time. - /// Useful when measuring `Rc`s and `Arc`s. Optional, because many places - /// don't need it. - have_seen_ptr_op: Option>, -} - -impl MallocSizeOfOps { - pub fn new( - size_of: VoidPtrToSizeFn, - malloc_enclosing_size_of: Option, - have_seen_ptr: Option>, - ) -> Self { - MallocSizeOfOps { - size_of_op: size_of, - enclosing_size_of_op: malloc_enclosing_size_of, - have_seen_ptr_op: have_seen_ptr, - } - } - - /// Check if an allocation is empty. This relies on knowledge of how Rust - /// handles empty allocations, which may change in the future. - fn is_empty(ptr: *const T) -> bool { - // The correct condition is this: - // `ptr as usize <= ::std::mem::align_of::()` - // But we can't call align_of() on a ?Sized T. So we approximate it - // with the following. 256 is large enough that it should always be - // larger than the required alignment, but small enough that it is - // always in the first page of memory and therefore not a legitimate - // address. - return ptr as *const usize as usize <= 256; - } - - /// Call `size_of_op` on `ptr`, first checking that the allocation isn't - /// empty, because some types (such as `Vec`) utilize empty allocations. - pub unsafe fn malloc_size_of(&self, ptr: *const T) -> usize { - if MallocSizeOfOps::is_empty(ptr) { - 0 - } else { - (self.size_of_op)(ptr as *const c_void) - } - } - - /// Is an `enclosing_size_of_op` available? - pub fn has_malloc_enclosing_size_of(&self) -> bool { - self.enclosing_size_of_op.is_some() - } - - /// Call `enclosing_size_of_op`, which must be available, on `ptr`, which - /// must not be empty. - pub unsafe fn malloc_enclosing_size_of(&self, ptr: *const T) -> usize { - assert!(!MallocSizeOfOps::is_empty(ptr)); - (self.enclosing_size_of_op.unwrap())(ptr as *const c_void) - } - - /// Call `have_seen_ptr_op` on `ptr`. - pub fn have_seen_ptr(&mut self, ptr: *const T) -> bool { - let have_seen_ptr_op = self - .have_seen_ptr_op - .as_mut() - .expect("missing have_seen_ptr_op"); - have_seen_ptr_op(ptr as *const c_void) - } -} - -/// Trait for measuring the "deep" heap usage of a data structure. This is the -/// most commonly-used of the traits. -pub trait MallocSizeOf { - /// Measure the heap usage of all descendant heap-allocated structures, but - /// not the space taken up by the value itself. - fn size_of(&self, ops: &mut MallocSizeOfOps) -> usize; -} - -/// Trait for measuring the "shallow" heap usage of a container. -pub trait MallocShallowSizeOf { - /// Measure the heap usage of immediate heap-allocated descendant - /// structures, but not the space taken up by the value itself. Anything - /// beyond the immediate descendants must be measured separately, using - /// iteration. - fn shallow_size_of(&self, ops: &mut MallocSizeOfOps) -> usize; -} - -/// Like `MallocSizeOf`, but with a different name so it cannot be used -/// accidentally with derive(MallocSizeOf). For use with types like `Rc` and -/// `Arc` when appropriate (e.g. when measuring a "primary" reference). -pub trait MallocUnconditionalSizeOf { - /// Measure the heap usage of all heap-allocated descendant structures, but - /// not the space taken up by the value itself. - fn unconditional_size_of(&self, ops: &mut MallocSizeOfOps) -> usize; -} - -/// `MallocUnconditionalSizeOf` combined with `MallocShallowSizeOf`. -pub trait MallocUnconditionalShallowSizeOf { - /// `unconditional_size_of` combined with `shallow_size_of`. - fn unconditional_shallow_size_of(&self, ops: &mut MallocSizeOfOps) -> usize; -} - -/// Like `MallocSizeOf`, but only measures if the value hasn't already been -/// measured. For use with types like `Rc` and `Arc` when appropriate (e.g. -/// when there is no "primary" reference). -pub trait MallocConditionalSizeOf { - /// Measure the heap usage of all heap-allocated descendant structures, but - /// not the space taken up by the value itself, and only if that heap usage - /// hasn't already been measured. - fn conditional_size_of(&self, ops: &mut MallocSizeOfOps) -> usize; -} - -/// `MallocConditionalSizeOf` combined with `MallocShallowSizeOf`. -pub trait MallocConditionalShallowSizeOf { - /// `conditional_size_of` combined with `shallow_size_of`. - fn conditional_shallow_size_of(&self, ops: &mut MallocSizeOfOps) -> usize; -} - -impl MallocSizeOf for String { - fn size_of(&self, ops: &mut MallocSizeOfOps) -> usize { - unsafe { ops.malloc_size_of(self.as_ptr()) } - } -} - -impl<'a, T: ?Sized> MallocSizeOf for &'a T { - fn size_of(&self, _ops: &mut MallocSizeOfOps) -> usize { - // Zero makes sense for a non-owning reference. - 0 - } -} - -impl MallocShallowSizeOf for Box { - fn shallow_size_of(&self, ops: &mut MallocSizeOfOps) -> usize { - unsafe { ops.malloc_size_of(&**self) } - } -} - -impl MallocSizeOf for Box { - fn size_of(&self, ops: &mut MallocSizeOfOps) -> usize { - self.shallow_size_of(ops) + (**self).size_of(ops) - } -} - -impl MallocSizeOf for () { - fn size_of(&self, _ops: &mut MallocSizeOfOps) -> usize { - 0 - } -} - -impl MallocSizeOf for (T1, T2) -where - T1: MallocSizeOf, - T2: MallocSizeOf, -{ - fn size_of(&self, ops: &mut MallocSizeOfOps) -> usize { - self.0.size_of(ops) + self.1.size_of(ops) - } -} - -impl MallocSizeOf for (T1, T2, T3) -where - T1: MallocSizeOf, - T2: MallocSizeOf, - T3: MallocSizeOf, -{ - fn size_of(&self, ops: &mut MallocSizeOfOps) -> usize { - self.0.size_of(ops) + self.1.size_of(ops) + self.2.size_of(ops) - } -} - -impl MallocSizeOf for (T1, T2, T3, T4) -where - T1: MallocSizeOf, - T2: MallocSizeOf, - T3: MallocSizeOf, - T4: MallocSizeOf, -{ - fn size_of(&self, ops: &mut MallocSizeOfOps) -> usize { - self.0.size_of(ops) + self.1.size_of(ops) + self.2.size_of(ops) + self.3.size_of(ops) - } -} - -impl MallocSizeOf for Option { - fn size_of(&self, ops: &mut MallocSizeOfOps) -> usize { - if let Some(val) = self.as_ref() { - val.size_of(ops) - } else { - 0 - } - } -} - -impl MallocSizeOf for Result { - fn size_of(&self, ops: &mut MallocSizeOfOps) -> usize { - match *self { - Ok(ref x) => x.size_of(ops), - Err(ref e) => e.size_of(ops), - } - } -} - -impl MallocSizeOf for std::cell::Cell { - fn size_of(&self, ops: &mut MallocSizeOfOps) -> usize { - self.get().size_of(ops) - } -} - -impl MallocSizeOf for std::cell::RefCell { - fn size_of(&self, ops: &mut MallocSizeOfOps) -> usize { - self.borrow().size_of(ops) - } -} - -impl<'a, B: ?Sized + ToOwned> MallocSizeOf for std::borrow::Cow<'a, B> -where - B::Owned: MallocSizeOf, -{ - fn size_of(&self, ops: &mut MallocSizeOfOps) -> usize { - match *self { - std::borrow::Cow::Borrowed(_) => 0, - std::borrow::Cow::Owned(ref b) => b.size_of(ops), - } - } -} - -impl MallocSizeOf for [T] { - fn size_of(&self, ops: &mut MallocSizeOfOps) -> usize { - let mut n = 0; - for elem in self.iter() { - n += elem.size_of(ops); - } - n - } -} - -#[cfg(feature = "servo")] -impl MallocShallowSizeOf for ByteBuf { - fn shallow_size_of(&self, ops: &mut MallocSizeOfOps) -> usize { - unsafe { ops.malloc_size_of(self.as_ptr()) } - } -} - -#[cfg(feature = "servo")] -impl MallocSizeOf for ByteBuf { - fn size_of(&self, ops: &mut MallocSizeOfOps) -> usize { - let mut n = self.shallow_size_of(ops); - for elem in self.iter() { - n += elem.size_of(ops); - } - n - } -} - -impl MallocShallowSizeOf for Vec { - fn shallow_size_of(&self, ops: &mut MallocSizeOfOps) -> usize { - unsafe { ops.malloc_size_of(self.as_ptr()) } - } -} - -impl MallocSizeOf for Vec { - fn size_of(&self, ops: &mut MallocSizeOfOps) -> usize { - let mut n = self.shallow_size_of(ops); - for elem in self.iter() { - n += elem.size_of(ops); - } - n - } -} - -impl MallocShallowSizeOf for std::collections::VecDeque { - fn shallow_size_of(&self, ops: &mut MallocSizeOfOps) -> usize { - if ops.has_malloc_enclosing_size_of() { - if let Some(front) = self.front() { - // The front element is an interior pointer. - unsafe { ops.malloc_enclosing_size_of(&*front) } - } else { - // This assumes that no memory is allocated when the VecDeque is empty. - 0 - } - } else { - // An estimate. - self.capacity() * size_of::() - } - } -} - -impl MallocSizeOf for std::collections::VecDeque { - fn size_of(&self, ops: &mut MallocSizeOfOps) -> usize { - let mut n = self.shallow_size_of(ops); - for elem in self.iter() { - n += elem.size_of(ops); - } - n - } -} - -impl MallocShallowSizeOf for smallvec::SmallVec { - fn shallow_size_of(&self, ops: &mut MallocSizeOfOps) -> usize { - if self.spilled() { - unsafe { ops.malloc_size_of(self.as_ptr()) } - } else { - 0 - } - } -} - -impl MallocSizeOf for smallvec::SmallVec -where - A: smallvec::Array, - A::Item: MallocSizeOf, -{ - fn size_of(&self, ops: &mut MallocSizeOfOps) -> usize { - let mut n = self.shallow_size_of(ops); - for elem in self.iter() { - n += elem.size_of(ops); - } - n - } -} - -impl MallocShallowSizeOf for thin_vec::ThinVec { - fn shallow_size_of(&self, ops: &mut MallocSizeOfOps) -> usize { - if self.capacity() == 0 { - // If it's the singleton we might not be a heap pointer. - return 0; - } - - assert_eq!( - std::mem::size_of::(), - std::mem::size_of::<*const ()>() - ); - unsafe { ops.malloc_size_of(*(self as *const Self as *const *const ())) } - } -} - -impl MallocSizeOf for thin_vec::ThinVec { - fn size_of(&self, ops: &mut MallocSizeOfOps) -> usize { - let mut n = self.shallow_size_of(ops); - for elem in self.iter() { - n += elem.size_of(ops); - } - n - } -} - -macro_rules! malloc_size_of_hash_set { - ($ty:ty) => { - impl MallocShallowSizeOf for $ty - where - T: Eq + Hash, - S: BuildHasher, - { - fn shallow_size_of(&self, ops: &mut MallocSizeOfOps) -> usize { - if ops.has_malloc_enclosing_size_of() { - // The first value from the iterator gives us an interior pointer. - // `ops.malloc_enclosing_size_of()` then gives us the storage size. - // This assumes that the `HashSet`'s contents (values and hashes) - // are all stored in a single contiguous heap allocation. - self.iter() - .next() - .map_or(0, |t| unsafe { ops.malloc_enclosing_size_of(t) }) - } else { - // An estimate. - self.capacity() * (size_of::() + size_of::()) - } - } - } - - impl MallocSizeOf for $ty - where - T: Eq + Hash + MallocSizeOf, - S: BuildHasher, - { - fn size_of(&self, ops: &mut MallocSizeOfOps) -> usize { - let mut n = self.shallow_size_of(ops); - for t in self.iter() { - n += t.size_of(ops); - } - n - } - } - }; -} - -malloc_size_of_hash_set!(std::collections::HashSet); -malloc_size_of_hash_set!(indexmap::IndexSet); - -macro_rules! malloc_size_of_hash_map { - ($ty:ty) => { - impl MallocShallowSizeOf for $ty - where - K: Eq + Hash, - S: BuildHasher, - { - fn shallow_size_of(&self, ops: &mut MallocSizeOfOps) -> usize { - // See the implementation for std::collections::HashSet for details. - if ops.has_malloc_enclosing_size_of() { - self.values() - .next() - .map_or(0, |v| unsafe { ops.malloc_enclosing_size_of(v) }) - } else { - self.capacity() * (size_of::() + size_of::() + size_of::()) - } - } - } - - impl MallocSizeOf for $ty - where - K: Eq + Hash + MallocSizeOf, - V: MallocSizeOf, - S: BuildHasher, - { - fn size_of(&self, ops: &mut MallocSizeOfOps) -> usize { - let mut n = self.shallow_size_of(ops); - for (k, v) in self.iter() { - n += k.size_of(ops); - n += v.size_of(ops); - } - n - } - } - }; -} - -malloc_size_of_hash_map!(std::collections::HashMap); -malloc_size_of_hash_map!(indexmap::IndexMap); - -impl MallocShallowSizeOf for std::collections::BTreeMap -where - K: Eq + Hash, -{ - fn shallow_size_of(&self, ops: &mut MallocSizeOfOps) -> usize { - if ops.has_malloc_enclosing_size_of() { - self.values() - .next() - .map_or(0, |v| unsafe { ops.malloc_enclosing_size_of(v) }) - } else { - self.len() * (size_of::() + size_of::() + size_of::()) - } - } -} - -impl MallocSizeOf for std::collections::BTreeMap -where - K: Eq + Hash + MallocSizeOf, - V: MallocSizeOf, -{ - fn size_of(&self, ops: &mut MallocSizeOfOps) -> usize { - let mut n = self.shallow_size_of(ops); - for (k, v) in self.iter() { - n += k.size_of(ops); - n += v.size_of(ops); - } - n - } -} - -// PhantomData is always 0. -impl MallocSizeOf for std::marker::PhantomData { - fn size_of(&self, _ops: &mut MallocSizeOfOps) -> usize { - 0 - } -} - -// XXX: we don't want MallocSizeOf to be defined for Rc and Arc. If negative -// trait bounds are ever allowed, this code should be uncommented. -// (We do have a compile-fail test for this: -// rc_arc_must_not_derive_malloc_size_of.rs) -//impl !MallocSizeOf for Arc { } -//impl !MallocShallowSizeOf for Arc { } - -impl MallocUnconditionalShallowSizeOf for servo_arc::Arc { - fn unconditional_shallow_size_of(&self, ops: &mut MallocSizeOfOps) -> usize { - unsafe { ops.malloc_size_of(self.heap_ptr()) } - } -} - -impl MallocUnconditionalSizeOf for servo_arc::Arc { - fn unconditional_size_of(&self, ops: &mut MallocSizeOfOps) -> usize { - self.unconditional_shallow_size_of(ops) + (**self).size_of(ops) - } -} - -impl MallocConditionalShallowSizeOf for servo_arc::Arc { - fn conditional_shallow_size_of(&self, ops: &mut MallocSizeOfOps) -> usize { - if ops.have_seen_ptr(self.heap_ptr()) { - 0 - } else { - self.unconditional_shallow_size_of(ops) - } - } -} - -impl MallocConditionalSizeOf for servo_arc::Arc { - fn conditional_size_of(&self, ops: &mut MallocSizeOfOps) -> usize { - if ops.have_seen_ptr(self.heap_ptr()) { - 0 - } else { - self.unconditional_size_of(ops) - } - } -} - -/// If a mutex is stored directly as a member of a data type that is being measured, -/// it is the unique owner of its contents and deserves to be measured. -/// -/// If a mutex is stored inside of an Arc value as a member of a data type that is being measured, -/// the Arc will not be automatically measured so there is no risk of overcounting the mutex's -/// contents. -impl MallocSizeOf for std::sync::Mutex { - fn size_of(&self, ops: &mut MallocSizeOfOps) -> usize { - (*self.lock().unwrap()).size_of(ops) - } -} - -impl MallocSizeOf for smallbitvec::SmallBitVec { - fn size_of(&self, ops: &mut MallocSizeOfOps) -> usize { - if let Some(ptr) = self.heap_ptr() { - unsafe { ops.malloc_size_of(ptr) } - } else { - 0 - } - } -} - -impl MallocSizeOf for euclid::Length { - fn size_of(&self, ops: &mut MallocSizeOfOps) -> usize { - self.0.size_of(ops) - } -} - -impl MallocSizeOf for euclid::Scale { - fn size_of(&self, ops: &mut MallocSizeOfOps) -> usize { - self.0.size_of(ops) - } -} - -impl MallocSizeOf for euclid::Point2D { - fn size_of(&self, ops: &mut MallocSizeOfOps) -> usize { - self.x.size_of(ops) + self.y.size_of(ops) - } -} - -impl MallocSizeOf for euclid::Rect { - fn size_of(&self, ops: &mut MallocSizeOfOps) -> usize { - self.origin.size_of(ops) + self.size.size_of(ops) - } -} - -impl MallocSizeOf for euclid::SideOffsets2D { - fn size_of(&self, ops: &mut MallocSizeOfOps) -> usize { - self.top.size_of(ops) + - self.right.size_of(ops) + - self.bottom.size_of(ops) + - self.left.size_of(ops) - } -} - -impl MallocSizeOf for euclid::Size2D { - fn size_of(&self, ops: &mut MallocSizeOfOps) -> usize { - self.width.size_of(ops) + self.height.size_of(ops) - } -} - -impl MallocSizeOf for euclid::Transform2D { - fn size_of(&self, ops: &mut MallocSizeOfOps) -> usize { - self.m11.size_of(ops) + - self.m12.size_of(ops) + - self.m21.size_of(ops) + - self.m22.size_of(ops) + - self.m31.size_of(ops) + - self.m32.size_of(ops) - } -} - -impl MallocSizeOf for euclid::Transform3D { - fn size_of(&self, ops: &mut MallocSizeOfOps) -> usize { - self.m11.size_of(ops) + - self.m12.size_of(ops) + - self.m13.size_of(ops) + - self.m14.size_of(ops) + - self.m21.size_of(ops) + - self.m22.size_of(ops) + - self.m23.size_of(ops) + - self.m24.size_of(ops) + - self.m31.size_of(ops) + - self.m32.size_of(ops) + - self.m33.size_of(ops) + - self.m34.size_of(ops) + - self.m41.size_of(ops) + - self.m42.size_of(ops) + - self.m43.size_of(ops) + - self.m44.size_of(ops) - } -} - -impl MallocSizeOf for euclid::Vector2D { - fn size_of(&self, ops: &mut MallocSizeOfOps) -> usize { - self.x.size_of(ops) + self.y.size_of(ops) - } -} - -impl MallocSizeOf for selectors::parser::AncestorHashes { - fn size_of(&self, ops: &mut MallocSizeOfOps) -> usize { - let selectors::parser::AncestorHashes { ref packed_hashes } = *self; - packed_hashes.size_of(ops) - } -} - -impl MallocSizeOf for selectors::parser::Selector -where - Impl::NonTSPseudoClass: MallocSizeOf, - Impl::PseudoElement: MallocSizeOf, -{ - fn size_of(&self, ops: &mut MallocSizeOfOps) -> usize { - let mut n = 0; - - // It's OK to measure this ThinArc directly because it's the - // "primary" reference. (The secondary references are on the - // Stylist.) - n += unsafe { ops.malloc_size_of(self.thin_arc_heap_ptr()) }; - for component in self.iter_raw_match_order() { - n += component.size_of(ops); - } - - n - } -} - -impl MallocSizeOf for selectors::parser::Component -where - Impl::NonTSPseudoClass: MallocSizeOf, - Impl::PseudoElement: MallocSizeOf, -{ - fn size_of(&self, ops: &mut MallocSizeOfOps) -> usize { - use selectors::parser::Component; - - match self { - Component::AttributeOther(ref attr_selector) => attr_selector.size_of(ops), - Component::Negation(ref components) => components.size_of(ops), - Component::NonTSPseudoClass(ref pseudo) => (*pseudo).size_of(ops), - Component::Slotted(ref selector) | Component::Host(Some(ref selector)) => { - selector.size_of(ops) - }, - Component::Is(ref list) | Component::Where(ref list) => list.size_of(ops), - Component::Has(ref relative_selectors) => relative_selectors.size_of(ops), - Component::NthOf(ref nth_of_data) => nth_of_data.size_of(ops), - Component::PseudoElement(ref pseudo) => (*pseudo).size_of(ops), - Component::Combinator(..) | - Component::ExplicitAnyNamespace | - Component::ExplicitNoNamespace | - Component::DefaultNamespace(..) | - Component::Namespace(..) | - Component::ExplicitUniversalType | - Component::LocalName(..) | - Component::ID(..) | - Component::Part(..) | - Component::Class(..) | - Component::AttributeInNoNamespaceExists { .. } | - Component::AttributeInNoNamespace { .. } | - Component::Root | - Component::Empty | - Component::Scope | - Component::ParentSelector | - Component::Nth(..) | - Component::Host(None) | - Component::RelativeSelectorAnchor => 0, - } - } -} - -impl MallocSizeOf - for selectors::attr::AttrSelectorWithOptionalNamespace -{ - fn size_of(&self, _ops: &mut MallocSizeOfOps) -> usize { - 0 - } -} - -impl MallocSizeOf for Void { - #[inline] - fn size_of(&self, _ops: &mut MallocSizeOfOps) -> usize { - void::unreachable(*self) - } -} - -#[cfg(feature = "servo")] -impl MallocSizeOf for string_cache::Atom { - fn size_of(&self, _ops: &mut MallocSizeOfOps) -> usize { - 0 - } -} - -/// For use on types where size_of() returns 0. -#[macro_export] -macro_rules! malloc_size_of_is_0( - ($($ty:ty),+) => ( - $( - impl $crate::MallocSizeOf for $ty { - #[inline(always)] - fn size_of(&self, _: &mut $crate::MallocSizeOfOps) -> usize { - 0 - } - } - )+ - ); - ($($ty:ident<$($gen:ident),+>),+) => ( - $( - impl<$($gen: $crate::MallocSizeOf),+> $crate::MallocSizeOf for $ty<$($gen),+> { - #[inline(always)] - fn size_of(&self, _: &mut $crate::MallocSizeOfOps) -> usize { - 0 - } - } - )+ - ); -); - -malloc_size_of_is_0!(bool, char, str); -malloc_size_of_is_0!(u8, u16, u32, u64, u128, usize); -malloc_size_of_is_0!(i8, i16, i32, i64, i128, isize); -malloc_size_of_is_0!(f32, f64); -malloc_size_of_is_0!(std::num::NonZeroU64); - -malloc_size_of_is_0!(std::sync::atomic::AtomicBool); -malloc_size_of_is_0!(std::sync::atomic::AtomicIsize); -malloc_size_of_is_0!(std::sync::atomic::AtomicUsize); - -malloc_size_of_is_0!(Range, Range, Range, Range, Range); -malloc_size_of_is_0!(Range, Range, Range, Range, Range); -malloc_size_of_is_0!(Range, Range); - -malloc_size_of_is_0!(app_units::Au); - -malloc_size_of_is_0!(cssparser::RGBA, cssparser::TokenSerializationType); - -#[cfg(feature = "servo")] -malloc_size_of_is_0!(csp::Destination); - -#[cfg(feature = "servo")] -malloc_size_of_is_0!(Uuid); - -#[cfg(feature = "url")] -impl MallocSizeOf for url::Host { - fn size_of(&self, ops: &mut MallocSizeOfOps) -> usize { - match *self { - url::Host::Domain(ref s) => s.size_of(ops), - _ => 0, - } - } -} -#[cfg(feature = "webrender_api")] -malloc_size_of_is_0!(webrender_api::BorderRadius); -#[cfg(feature = "webrender_api")] -malloc_size_of_is_0!(webrender_api::BorderStyle); -#[cfg(feature = "webrender_api")] -malloc_size_of_is_0!(webrender_api::BoxShadowClipMode); -#[cfg(feature = "webrender_api")] -malloc_size_of_is_0!(webrender_api::ColorF); -#[cfg(feature = "webrender_api")] -malloc_size_of_is_0!(webrender_api::ComplexClipRegion); -#[cfg(feature = "webrender_api")] -malloc_size_of_is_0!(webrender_api::ExtendMode); -#[cfg(feature = "webrender_api")] -malloc_size_of_is_0!(webrender_api::FilterOp); -#[cfg(feature = "webrender_api")] -malloc_size_of_is_0!(webrender_api::ExternalScrollId); -#[cfg(feature = "webrender_api")] -malloc_size_of_is_0!(webrender_api::FontInstanceKey); -#[cfg(feature = "webrender_api")] -malloc_size_of_is_0!(webrender_api::GradientStop); -#[cfg(feature = "webrender_api")] -malloc_size_of_is_0!(webrender_api::GlyphInstance); -#[cfg(feature = "webrender_api")] -malloc_size_of_is_0!(webrender_api::NinePatchBorder); -#[cfg(feature = "webrender_api")] -malloc_size_of_is_0!(webrender_api::ImageKey); -#[cfg(feature = "webrender_api")] -malloc_size_of_is_0!(webrender_api::ImageRendering); -#[cfg(feature = "webrender_api")] -malloc_size_of_is_0!(webrender_api::LineStyle); -#[cfg(feature = "webrender_api")] -malloc_size_of_is_0!(webrender_api::MixBlendMode); -#[cfg(feature = "webrender_api")] -malloc_size_of_is_0!(webrender_api::NormalBorder); -#[cfg(feature = "webrender_api")] -malloc_size_of_is_0!(webrender_api::RepeatMode); -#[cfg(feature = "webrender_api")] -malloc_size_of_is_0!(webrender_api::StickyOffsetBounds); -#[cfg(feature = "webrender_api")] -malloc_size_of_is_0!(webrender_api::TransformStyle); - -#[cfg(feature = "servo")] -impl MallocSizeOf for keyboard_types::Key { - fn size_of(&self, ops: &mut MallocSizeOfOps) -> usize { - match self { - keyboard_types::Key::Character(ref s) => s.size_of(ops), - _ => 0, - } - } -} - -#[cfg(feature = "servo")] -malloc_size_of_is_0!(keyboard_types::Modifiers); - -#[cfg(feature = "servo")] -impl MallocSizeOf for xml5ever::QualName { - fn size_of(&self, ops: &mut MallocSizeOfOps) -> usize { - self.prefix.size_of(ops) + self.ns.size_of(ops) + self.local.size_of(ops) - } -} - -#[cfg(feature = "servo")] -malloc_size_of_is_0!(time::Duration); -#[cfg(feature = "servo")] -malloc_size_of_is_0!(time::Tm); -#[cfg(feature = "servo")] -malloc_size_of_is_0!(std::time::Duration); -#[cfg(feature = "servo")] -malloc_size_of_is_0!(std::time::SystemTime); -#[cfg(feature = "servo")] -malloc_size_of_is_0!(std::time::Instant); - -// Placeholder for unique case where internals of Sender cannot be measured. -// malloc size of is 0 macro complains about type supplied! -#[cfg(feature = "servo")] -impl MallocSizeOf for crossbeam_channel::Sender { - fn size_of(&self, _ops: &mut MallocSizeOfOps) -> usize { - 0 - } -} - -#[cfg(feature = "servo")] -impl MallocSizeOf for tokio::sync::mpsc::UnboundedSender { - fn size_of(&self, _ops: &mut MallocSizeOfOps) -> usize { - 0 - } -} - -#[cfg(feature = "servo")] -impl MallocSizeOf for http::StatusCode { - fn size_of(&self, _ops: &mut MallocSizeOfOps) -> usize { - 0 - } -} - -/// Measurable that defers to inner value and used to verify MallocSizeOf implementation in a -/// struct. -#[derive(Clone)] -pub struct Measurable(pub T); - -impl Deref for Measurable { - type Target = T; - - fn deref(&self) -> &T { - &self.0 - } -} - -impl DerefMut for Measurable { - fn deref_mut(&mut self) -> &mut T { - &mut self.0 - } -} - -#[cfg(feature = "servo")] -impl MallocSizeOf for accountable_refcell::RefCell { - fn size_of(&self, ops: &mut MallocSizeOfOps) -> usize { - self.borrow().size_of(ops) - } -} diff --git a/components/malloc_size_of/rustfmt.toml b/components/malloc_size_of/rustfmt.toml deleted file mode 100644 index c7ad93bafe3..00000000000 --- a/components/malloc_size_of/rustfmt.toml +++ /dev/null @@ -1 +0,0 @@ -disable_all_formatting = true diff --git a/components/metrics/Cargo.toml b/components/metrics/Cargo.toml index 29579d9aacb..08701edf372 100644 --- a/components/metrics/Cargo.toml +++ b/components/metrics/Cargo.toml @@ -14,7 +14,7 @@ path = "lib.rs" gfx_traits = { workspace = true } ipc-channel = { workspace = true } log = { workspace = true } -malloc_size_of = { path = "../malloc_size_of" } +malloc_size_of = { workspace = true } malloc_size_of_derive = { workspace = true } msg = { workspace = true } profile_traits = { workspace = true } diff --git a/components/net/Cargo.toml b/components/net/Cargo.toml index 0d188ba4d13..01da269cd0b 100644 --- a/components/net/Cargo.toml +++ b/components/net/Cargo.toml @@ -38,7 +38,7 @@ ipc-channel = { workspace = true } lazy_static = { workspace = true } libflate = "0.1" log = { workspace = true } -malloc_size_of = { path = "../malloc_size_of" } +malloc_size_of = { workspace = true } malloc_size_of_derive = { workspace = true } mime = { workspace = true } mime_guess = { workspace = true } @@ -53,7 +53,7 @@ rustls-pemfile = { workspace = true } serde = { workspace = true } serde_json = { workspace = true } servo_allocator = { path = "../allocator" } -servo_arc = { path = "../servo_arc" } +servo_arc = { workspace = true } servo_config = { path = "../config" } servo_url = { path = "../url" } sha2 = "0.10" diff --git a/components/pixels/Cargo.toml b/components/pixels/Cargo.toml index 708b16163ba..f201ff7c0e4 100644 --- a/components/pixels/Cargo.toml +++ b/components/pixels/Cargo.toml @@ -12,6 +12,6 @@ path = "lib.rs" [dependencies] euclid = { workspace = true } -malloc_size_of = { path = "../malloc_size_of" } +malloc_size_of = { workspace = true } malloc_size_of_derive = { workspace = true } serde = { workspace = true, features = ["derive"] } diff --git a/components/range/Cargo.toml b/components/range/Cargo.toml index bcaf874786f..c674d5a799c 100644 --- a/components/range/Cargo.toml +++ b/components/range/Cargo.toml @@ -11,7 +11,7 @@ name = "range" path = "lib.rs" [dependencies] -malloc_size_of = { path = "../malloc_size_of" } +malloc_size_of = { workspace = true } malloc_size_of_derive = { workspace = true } num-traits = { workspace = true } serde = { workspace = true } diff --git a/components/script/Cargo.toml b/components/script/Cargo.toml index cc29865bd69..b9e9ff1249c 100644 --- a/components/script/Cargo.toml +++ b/components/script/Cargo.toml @@ -68,7 +68,7 @@ keyboard-types = { workspace = true } lazy_static = { workspace = true } libc = { workspace = true } log = { workspace = true } -malloc_size_of = { path = "../malloc_size_of" } +malloc_size_of = { workspace = true } malloc_size_of_derive = { workspace = true } media = { path = "../media" } metrics = { path = "../metrics" } @@ -88,20 +88,20 @@ ref_filter_map = "1.0.1" regex = { workspace = true } script_layout_interface = { workspace = true } script_traits = { workspace = true } -selectors = { path = "../selectors" } +selectors = { workspace = true } serde = { workspace = true, features = ["derive"] } serde_bytes = { workspace = true } servo-media = { git = "https://github.com/servo/media" } servo_allocator = { path = "../allocator" } -servo_arc = { path = "../servo_arc" } -servo_atoms = { path = "../atoms" } +servo_arc = { workspace = true } +servo_atoms = { workspace = true } servo_config = { path = "../config" } servo_geometry = { path = "../geometry" } servo_rand = { path = "../rand" } servo_url = { path = "../url" } smallvec = { workspace = true, features = ["union"] } sparkle = { workspace = true } -style = { path = "../style", features = ["servo"] } +style = { workspace = true } style_traits = { workspace = true } swapper = "0.1" tempfile = "3" diff --git a/components/selectors/Cargo.toml b/components/selectors/Cargo.toml deleted file mode 100644 index da1d0e509d8..00000000000 --- a/components/selectors/Cargo.toml +++ /dev/null @@ -1,36 +0,0 @@ -[package] -name = "selectors" -version = "0.24.0" -authors = ["The Servo Project Developers"] -documentation = "https://docs.rs/selectors/" -description = "CSS Selectors matching for Rust" -repository = "https://github.com/servo/servo" -readme = "README.md" -keywords = ["css", "selectors"] -license = "MPL-2.0" -build = "build.rs" - -[lib] -name = "selectors" -path = "lib.rs" - -[features] -bench = [] - -[dependencies] -bitflags = "1.0" -cssparser = { workspace = true } -derive_more = { version = "0.99", default-features = false, features = ["add", "add_assign"] } -fxhash = "0.2" -log = "0.4" -new_debug_unreachable = "1" -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.1", path = "../to_shmem" } -to_shmem_derive = { version = "0.0.1", path = "../to_shmem_derive" } - -[build-dependencies] -phf_codegen = "0.10" diff --git a/components/selectors/README.md b/components/selectors/README.md deleted file mode 100644 index dac4a7ff91a..00000000000 --- a/components/selectors/README.md +++ /dev/null @@ -1,25 +0,0 @@ -rust-selectors -============== - -* [![Build Status](https://travis-ci.com/servo/rust-selectors.svg?branch=master)]( - https://travis-ci.com/servo/rust-selectors) -* [Documentation](https://docs.rs/selectors/) -* [crates.io](https://crates.io/crates/selectors) - -CSS Selectors library for Rust. -Includes parsing and serilization of selectors, -as well as matching against a generic tree of elements. -Pseudo-elements and most pseudo-classes are generic as well. - -**Warning:** breaking changes are made to this library fairly frequently -(13 times in 2016, for example). -However you can use this crate without updating it that often, -old versions stay available on crates.io and Cargo will only automatically update -to versions that are numbered as compatible. - -To see how to use this library with your own tree representation, -see [Kuchiki’s `src/select.rs`](https://github.com/kuchiki-rs/kuchiki/blob/master/src/select.rs). -(Note however that Kuchiki is not always up to date with the latest rust-selectors version, -so that code may need to be tweaked.) -If you don’t already have a tree data structure, -consider using [Kuchiki](https://github.com/kuchiki-rs/kuchiki) itself. diff --git a/components/selectors/attr.rs b/components/selectors/attr.rs deleted file mode 100644 index e3122ea701e..00000000000 --- a/components/selectors/attr.rs +++ /dev/null @@ -1,192 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ - -use crate::parser::SelectorImpl; -use cssparser::ToCss; -use std::fmt; - -#[derive(Clone, Eq, PartialEq, ToShmem)] -#[shmem(no_bounds)] -pub struct AttrSelectorWithOptionalNamespace { - #[shmem(field_bound)] - pub namespace: Option>, - #[shmem(field_bound)] - pub local_name: Impl::LocalName, - pub local_name_lower: Impl::LocalName, - #[shmem(field_bound)] - pub operation: ParsedAttrSelectorOperation, -} - -impl AttrSelectorWithOptionalNamespace { - pub fn namespace(&self) -> Option> { - self.namespace.as_ref().map(|ns| match ns { - NamespaceConstraint::Any => NamespaceConstraint::Any, - NamespaceConstraint::Specific((_, ref url)) => NamespaceConstraint::Specific(url), - }) - } -} - -#[derive(Clone, Eq, PartialEq, ToShmem)] -pub enum NamespaceConstraint { - Any, - - /// Empty string for no namespace - Specific(NamespaceUrl), -} - -#[derive(Clone, Eq, PartialEq, ToShmem)] -pub enum ParsedAttrSelectorOperation { - Exists, - WithValue { - operator: AttrSelectorOperator, - case_sensitivity: ParsedCaseSensitivity, - value: AttrValue, - }, -} - -#[derive(Clone, Eq, PartialEq)] -pub enum AttrSelectorOperation { - Exists, - WithValue { - operator: AttrSelectorOperator, - case_sensitivity: CaseSensitivity, - value: AttrValue, - }, -} - -impl AttrSelectorOperation { - pub fn eval_str(&self, element_attr_value: &str) -> bool - where - AttrValue: AsRef, - { - match *self { - AttrSelectorOperation::Exists => true, - AttrSelectorOperation::WithValue { - operator, - case_sensitivity, - ref value, - } => operator.eval_str( - element_attr_value, - value.as_ref(), - case_sensitivity, - ), - } - } -} - -#[derive(Clone, Copy, Eq, PartialEq, ToShmem)] -pub enum AttrSelectorOperator { - Equal, - Includes, - DashMatch, - Prefix, - Substring, - Suffix, -} - -impl ToCss for AttrSelectorOperator { - fn to_css(&self, dest: &mut W) -> fmt::Result - where - W: fmt::Write, - { - // https://drafts.csswg.org/cssom/#serializing-selectors - // See "attribute selector". - dest.write_str(match *self { - AttrSelectorOperator::Equal => "=", - AttrSelectorOperator::Includes => "~=", - AttrSelectorOperator::DashMatch => "|=", - AttrSelectorOperator::Prefix => "^=", - AttrSelectorOperator::Substring => "*=", - AttrSelectorOperator::Suffix => "$=", - }) - } -} - -impl AttrSelectorOperator { - pub fn eval_str( - self, - element_attr_value: &str, - attr_selector_value: &str, - case_sensitivity: CaseSensitivity, - ) -> bool { - let e = element_attr_value.as_bytes(); - let s = attr_selector_value.as_bytes(); - let case = case_sensitivity; - match self { - AttrSelectorOperator::Equal => case.eq(e, s), - AttrSelectorOperator::Prefix => { - !s.is_empty() && e.len() >= s.len() && case.eq(&e[..s.len()], s) - }, - AttrSelectorOperator::Suffix => { - !s.is_empty() && e.len() >= s.len() && case.eq(&e[(e.len() - s.len())..], s) - }, - AttrSelectorOperator::Substring => { - !s.is_empty() && case.contains(element_attr_value, attr_selector_value) - }, - AttrSelectorOperator::Includes => { - !s.is_empty() && - element_attr_value - .split(SELECTOR_WHITESPACE) - .any(|part| case.eq(part.as_bytes(), s)) - }, - AttrSelectorOperator::DashMatch => { - case.eq(e, s) || (e.get(s.len()) == Some(&b'-') && case.eq(&e[..s.len()], s)) - }, - } - } -} - -/// The definition of whitespace per CSS Selectors Level 3 § 4. -pub static SELECTOR_WHITESPACE: &[char] = &[' ', '\t', '\n', '\r', '\x0C']; - -#[derive(Clone, Copy, Debug, Eq, PartialEq, ToShmem)] -pub enum ParsedCaseSensitivity { - /// 's' was specified. - ExplicitCaseSensitive, - /// 'i' was specified. - AsciiCaseInsensitive, - /// No flags were specified and HTML says this is a case-sensitive attribute. - CaseSensitive, - /// No flags were specified and HTML says this is a case-insensitive attribute. - AsciiCaseInsensitiveIfInHtmlElementInHtmlDocument, -} - -#[derive(Clone, Copy, Debug, Eq, PartialEq)] -pub enum CaseSensitivity { - CaseSensitive, - AsciiCaseInsensitive, -} - -impl CaseSensitivity { - pub fn eq(self, a: &[u8], b: &[u8]) -> bool { - match self { - CaseSensitivity::CaseSensitive => a == b, - CaseSensitivity::AsciiCaseInsensitive => a.eq_ignore_ascii_case(b), - } - } - - pub fn contains(self, haystack: &str, needle: &str) -> bool { - match self { - CaseSensitivity::CaseSensitive => haystack.contains(needle), - CaseSensitivity::AsciiCaseInsensitive => { - if let Some((&n_first_byte, n_rest)) = needle.as_bytes().split_first() { - haystack.bytes().enumerate().any(|(i, byte)| { - if !byte.eq_ignore_ascii_case(&n_first_byte) { - return false; - } - let after_this_byte = &haystack.as_bytes()[i + 1..]; - match after_this_byte.get(..n_rest.len()) { - None => false, - Some(haystack_slice) => haystack_slice.eq_ignore_ascii_case(n_rest), - } - }) - } else { - // any_str.contains("") == true, - // though these cases should be handled with *NeverMatches and never go here. - true - } - }, - } - } -} diff --git a/components/selectors/bloom.rs b/components/selectors/bloom.rs deleted file mode 100644 index 98461d1ba24..00000000000 --- a/components/selectors/bloom.rs +++ /dev/null @@ -1,422 +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/. */ - -//! Counting and non-counting Bloom filters tuned for use as ancestor filters -//! for selector matching. - -use std::fmt::{self, Debug}; - -// The top 8 bits of the 32-bit hash value are not used by the bloom filter. -// Consumers may rely on this to pack hashes more efficiently. -pub const BLOOM_HASH_MASK: u32 = 0x00ffffff; -const KEY_SIZE: usize = 12; - -const ARRAY_SIZE: usize = 1 << KEY_SIZE; -const KEY_MASK: u32 = (1 << KEY_SIZE) - 1; - -/// A counting Bloom filter with 8-bit counters. -pub type BloomFilter = CountingBloomFilter; - -/// A counting Bloom filter with parameterized storage to handle -/// counters of different sizes. For now we assume that having two hash -/// functions is enough, but we may revisit that decision later. -/// -/// The filter uses an array with 2**KeySize entries. -/// -/// Assuming a well-distributed hash function, a Bloom filter with -/// array size M containing N elements and -/// using k hash function has expected false positive rate exactly -/// -/// $ (1 - (1 - 1/M)^{kN})^k $ -/// -/// because each array slot has a -/// -/// $ (1 - 1/M)^{kN} $ -/// -/// chance of being 0, and the expected false positive rate is the -/// probability that all of the k hash functions will hit a nonzero -/// slot. -/// -/// For reasonable assumptions (M large, kN large, which should both -/// hold if we're worried about false positives) about M and kN this -/// becomes approximately -/// -/// $$ (1 - \exp(-kN/M))^k $$ -/// -/// For our special case of k == 2, that's $(1 - \exp(-2N/M))^2$, -/// or in other words -/// -/// $$ N/M = -0.5 * \ln(1 - \sqrt(r)) $$ -/// -/// where r is the false positive rate. This can be used to compute -/// the desired KeySize for a given load N and false positive rate r. -/// -/// If N/M is assumed small, then the false positive rate can -/// further be approximated as 4*N^2/M^2. So increasing KeySize by -/// 1, which doubles M, reduces the false positive rate by about a -/// factor of 4, and a false positive rate of 1% corresponds to -/// about M/N == 20. -/// -/// What this means in practice is that for a few hundred keys using a -/// KeySize of 12 gives false positive rates on the order of 0.25-4%. -/// -/// Similarly, using a KeySize of 10 would lead to a 4% false -/// positive rate for N == 100 and to quite bad false positive -/// rates for larger N. -#[derive(Clone, Default)] -pub struct CountingBloomFilter -where - S: BloomStorage, -{ - storage: S, -} - -impl CountingBloomFilter -where - S: BloomStorage, -{ - /// Creates a new bloom filter. - #[inline] - pub fn new() -> Self { - Default::default() - } - - #[inline] - pub fn clear(&mut self) { - self.storage = Default::default(); - } - - // Slow linear accessor to make sure the bloom filter is zeroed. This should - // never be used in release builds. - #[cfg(debug_assertions)] - pub fn is_zeroed(&self) -> bool { - self.storage.is_zeroed() - } - - #[cfg(not(debug_assertions))] - pub fn is_zeroed(&self) -> bool { - unreachable!() - } - - /// Inserts an item with a particular hash into the bloom filter. - #[inline] - pub fn insert_hash(&mut self, hash: u32) { - self.storage.adjust_first_slot(hash, true); - self.storage.adjust_second_slot(hash, true); - } - - /// Removes an item with a particular hash from the bloom filter. - #[inline] - pub fn remove_hash(&mut self, hash: u32) { - self.storage.adjust_first_slot(hash, false); - self.storage.adjust_second_slot(hash, false); - } - - /// Check whether the filter might contain an item with the given hash. - /// This can sometimes return true even if the item is not in the filter, - /// but will never return false for items that are actually in the filter. - #[inline] - pub fn might_contain_hash(&self, hash: u32) -> bool { - !self.storage.first_slot_is_empty(hash) && !self.storage.second_slot_is_empty(hash) - } -} - -impl Debug for CountingBloomFilter -where - S: BloomStorage, -{ - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - let mut slots_used = 0; - for i in 0..ARRAY_SIZE { - if !self.storage.slot_is_empty(i) { - slots_used += 1; - } - } - write!(f, "BloomFilter({}/{})", slots_used, ARRAY_SIZE) - } -} - -pub trait BloomStorage: Clone + Default { - fn slot_is_empty(&self, index: usize) -> bool; - fn adjust_slot(&mut self, index: usize, increment: bool); - fn is_zeroed(&self) -> bool; - - #[inline] - fn first_slot_is_empty(&self, hash: u32) -> bool { - self.slot_is_empty(Self::first_slot_index(hash)) - } - - #[inline] - fn second_slot_is_empty(&self, hash: u32) -> bool { - self.slot_is_empty(Self::second_slot_index(hash)) - } - - #[inline] - fn adjust_first_slot(&mut self, hash: u32, increment: bool) { - self.adjust_slot(Self::first_slot_index(hash), increment) - } - - #[inline] - fn adjust_second_slot(&mut self, hash: u32, increment: bool) { - self.adjust_slot(Self::second_slot_index(hash), increment) - } - - #[inline] - fn first_slot_index(hash: u32) -> usize { - hash1(hash) as usize - } - - #[inline] - fn second_slot_index(hash: u32) -> usize { - hash2(hash) as usize - } -} - -/// Storage class for a CountingBloomFilter that has 8-bit counters. -pub struct BloomStorageU8 { - counters: [u8; ARRAY_SIZE], -} - -impl BloomStorage for BloomStorageU8 { - #[inline] - fn adjust_slot(&mut self, index: usize, increment: bool) { - let slot = &mut self.counters[index]; - if *slot != 0xff { - // full - if increment { - *slot += 1; - } else { - *slot -= 1; - } - } - } - - #[inline] - fn slot_is_empty(&self, index: usize) -> bool { - self.counters[index] == 0 - } - - #[inline] - fn is_zeroed(&self) -> bool { - self.counters.iter().all(|x| *x == 0) - } -} - -impl Default for BloomStorageU8 { - fn default() -> Self { - BloomStorageU8 { - counters: [0; ARRAY_SIZE], - } - } -} - -impl Clone for BloomStorageU8 { - fn clone(&self) -> Self { - BloomStorageU8 { - counters: self.counters, - } - } -} - -/// Storage class for a CountingBloomFilter that has 1-bit counters. -pub struct BloomStorageBool { - counters: [u8; ARRAY_SIZE / 8], -} - -impl BloomStorage for BloomStorageBool { - #[inline] - fn adjust_slot(&mut self, index: usize, increment: bool) { - let bit = 1 << (index % 8); - let byte = &mut self.counters[index / 8]; - - // Since we have only one bit for storage, decrementing it - // should never do anything. Assert against an accidental - // decrementing of a bit that was never set. - assert!( - increment || (*byte & bit) != 0, - "should not decrement if slot is already false" - ); - - if increment { - *byte |= bit; - } - } - - #[inline] - fn slot_is_empty(&self, index: usize) -> bool { - let bit = 1 << (index % 8); - (self.counters[index / 8] & bit) == 0 - } - - #[inline] - fn is_zeroed(&self) -> bool { - self.counters.iter().all(|x| *x == 0) - } -} - -impl Default for BloomStorageBool { - fn default() -> Self { - BloomStorageBool { - counters: [0; ARRAY_SIZE / 8], - } - } -} - -impl Clone for BloomStorageBool { - fn clone(&self) -> Self { - BloomStorageBool { - counters: self.counters, - } - } -} - -#[inline] -fn hash1(hash: u32) -> u32 { - hash & KEY_MASK -} - -#[inline] -fn hash2(hash: u32) -> u32 { - (hash >> KEY_SIZE) & KEY_MASK -} - -#[test] -fn create_and_insert_some_stuff() { - use fxhash::FxHasher; - use std::hash::{Hash, Hasher}; - use std::mem::transmute; - - fn hash_as_str(i: usize) -> u32 { - let mut hasher = FxHasher::default(); - let s = i.to_string(); - s.hash(&mut hasher); - let hash: u64 = hasher.finish(); - (hash >> 32) as u32 ^ (hash as u32) - } - - let mut bf = BloomFilter::new(); - - // Statically assert that ARRAY_SIZE is a multiple of 8, which - // BloomStorageBool relies on. - unsafe { - transmute::<[u8; ARRAY_SIZE % 8], [u8; 0]>([]); - } - - for i in 0_usize..1000 { - bf.insert_hash(hash_as_str(i)); - } - - for i in 0_usize..1000 { - assert!(bf.might_contain_hash(hash_as_str(i))); - } - - let false_positives = (1001_usize..2000) - .filter(|i| bf.might_contain_hash(hash_as_str(*i))) - .count(); - - assert!(false_positives < 190, "{} is not < 190", false_positives); // 19%. - - for i in 0_usize..100 { - bf.remove_hash(hash_as_str(i)); - } - - for i in 100_usize..1000 { - assert!(bf.might_contain_hash(hash_as_str(i))); - } - - let false_positives = (0_usize..100) - .filter(|i| bf.might_contain_hash(hash_as_str(*i))) - .count(); - - assert!(false_positives < 20, "{} is not < 20", false_positives); // 20%. - - bf.clear(); - - for i in 0_usize..2000 { - assert!(!bf.might_contain_hash(hash_as_str(i))); - } -} - -#[cfg(feature = "bench")] -#[cfg(test)] -mod bench { - extern crate test; - use super::BloomFilter; - - #[derive(Default)] - struct HashGenerator(u32); - - impl HashGenerator { - fn next(&mut self) -> u32 { - // Each hash is split into two twelve-bit segments, which are used - // as an index into an array. We increment each by 64 so that we - // hit the next cache line, and then another 1 so that our wrapping - // behavior leads us to different entries each time. - // - // Trying to simulate cold caches is rather difficult with the cargo - // benchmarking setup, so it may all be moot depending on the number - // of iterations that end up being run. But we might as well. - self.0 += (65) + (65 << super::KEY_SIZE); - self.0 - } - } - - #[bench] - fn create_insert_1000_remove_100_lookup_100(b: &mut test::Bencher) { - b.iter(|| { - let mut gen1 = HashGenerator::default(); - let mut gen2 = HashGenerator::default(); - let mut bf = BloomFilter::new(); - for _ in 0_usize..1000 { - bf.insert_hash(gen1.next()); - } - for _ in 0_usize..100 { - bf.remove_hash(gen2.next()); - } - for _ in 100_usize..200 { - test::black_box(bf.might_contain_hash(gen2.next())); - } - }); - } - - #[bench] - fn might_contain_10(b: &mut test::Bencher) { - let bf = BloomFilter::new(); - let mut gen = HashGenerator::default(); - b.iter(|| { - for _ in 0..10 { - test::black_box(bf.might_contain_hash(gen.next())); - } - }); - } - - #[bench] - fn clear(b: &mut test::Bencher) { - let mut bf = Box::new(BloomFilter::new()); - b.iter(|| test::black_box(&mut bf).clear()); - } - - #[bench] - fn insert_10(b: &mut test::Bencher) { - let mut bf = BloomFilter::new(); - let mut gen = HashGenerator::default(); - b.iter(|| { - for _ in 0..10 { - test::black_box(bf.insert_hash(gen.next())); - } - }); - } - - #[bench] - fn remove_10(b: &mut test::Bencher) { - let mut bf = BloomFilter::new(); - let mut gen = HashGenerator::default(); - // Note: this will underflow, and that's ok. - b.iter(|| { - for _ in 0..10 { - bf.remove_hash(gen.next()) - } - }); - } -} diff --git a/components/selectors/build.rs b/components/selectors/build.rs deleted file mode 100644 index c5c3803991e..00000000000 --- a/components/selectors/build.rs +++ /dev/null @@ -1,77 +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/. */ - -extern crate phf_codegen; - -use std::env; -use std::fs::File; -use std::io::{BufWriter, Write}; -use std::path::Path; - -fn main() { - let path = Path::new(&env::var_os("OUT_DIR").unwrap()) - .join("ascii_case_insensitive_html_attributes.rs"); - let mut file = BufWriter::new(File::create(&path).unwrap()); - - let mut set = phf_codegen::Set::new(); - for name in ASCII_CASE_INSENSITIVE_HTML_ATTRIBUTES.split_whitespace() { - set.entry(name); - } - write!( - &mut file, - "{{ static SET: ::phf::Set<&'static str> = {}; &SET }}", - set.build(), - ) - .unwrap(); -} - -/// -static ASCII_CASE_INSENSITIVE_HTML_ATTRIBUTES: &str = r#" - accept - accept-charset - align - alink - axis - bgcolor - charset - checked - clear - codetype - color - compact - declare - defer - dir - direction - disabled - enctype - face - frame - hreflang - http-equiv - lang - language - link - media - method - multiple - nohref - noresize - noshade - nowrap - readonly - rel - rev - rules - scope - scrolling - selected - shape - target - text - type - valign - valuetype - vlink -"#; diff --git a/components/selectors/builder.rs b/components/selectors/builder.rs deleted file mode 100644 index 63a6323c532..00000000000 --- a/components/selectors/builder.rs +++ /dev/null @@ -1,398 +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/. */ - -//! Helper module to build up a selector safely and efficiently. -//! -//! Our selector representation is designed to optimize matching, and has -//! several requirements: -//! * All simple selectors and combinators are stored inline in the same buffer -//! as Component instances. -//! * We store the top-level compound selectors from right to left, i.e. in -//! matching order. -//! * We store the simple selectors for each combinator from left to right, so -//! that we match the cheaper simple selectors first. -//! -//! Meeting all these constraints without extra memmove traffic during parsing -//! is non-trivial. This module encapsulates those details and presents an -//! easy-to-use API for the parser. - -use crate::parser::{Combinator, Component, RelativeSelector, Selector, SelectorImpl}; -use crate::sink::Push; -use servo_arc::{Arc, HeaderWithLength, ThinArc}; -use smallvec::{self, SmallVec}; -use std::cmp; -use std::iter; -use std::ptr; -use std::slice; - -/// Top-level SelectorBuilder struct. This should be stack-allocated by the -/// consumer and never moved (because it contains a lot of inline data that -/// would be slow to memmov). -/// -/// After instantation, callers may call the push_simple_selector() and -/// push_combinator() methods to append selector data as it is encountered -/// (from left to right). Once the process is complete, callers should invoke -/// build(), which transforms the contents of the SelectorBuilder into a heap- -/// allocated Selector and leaves the builder in a drained state. -#[derive(Debug)] -pub struct SelectorBuilder { - /// The entire sequence of simple selectors, from left to right, without combinators. - /// - /// We make this large because the result of parsing a selector is fed into a new - /// Arc-ed allocation, so any spilled vec would be a wasted allocation. Also, - /// Components are large enough that we don't have much cache locality benefit - /// from reserving stack space for fewer of them. - simple_selectors: SmallVec<[Component; 32]>, - /// The combinators, and the length of the compound selector to their left. - combinators: SmallVec<[(Combinator, usize); 16]>, - /// The length of the current compount selector. - current_len: usize, -} - -impl Default for SelectorBuilder { - #[inline(always)] - fn default() -> Self { - SelectorBuilder { - simple_selectors: SmallVec::new(), - combinators: SmallVec::new(), - current_len: 0, - } - } -} - -impl Push> for SelectorBuilder { - fn push(&mut self, value: Component) { - self.push_simple_selector(value); - } -} - -impl SelectorBuilder { - /// Pushes a simple selector onto the current compound selector. - #[inline(always)] - pub fn push_simple_selector(&mut self, ss: Component) { - assert!(!ss.is_combinator()); - self.simple_selectors.push(ss); - self.current_len += 1; - } - - /// Completes the current compound selector and starts a new one, delimited - /// by the given combinator. - #[inline(always)] - pub fn push_combinator(&mut self, c: Combinator) { - self.combinators.push((c, self.current_len)); - self.current_len = 0; - } - - /// Returns true if combinators have ever been pushed to this builder. - #[inline(always)] - pub fn has_combinators(&self) -> bool { - !self.combinators.is_empty() - } - - /// Consumes the builder, producing a Selector. - #[inline(always)] - pub fn build(&mut self) -> ThinArc> { - // Compute the specificity and flags. - let sf = specificity_and_flags(self.simple_selectors.iter()); - self.build_with_specificity_and_flags(sf) - } - - /// Builds with an explicit SpecificityAndFlags. This is separated from build() so - /// that unit tests can pass an explicit specificity. - #[inline(always)] - pub(crate) fn build_with_specificity_and_flags( - &mut self, - spec: SpecificityAndFlags, - ) -> ThinArc> { - // First, compute the total number of Components we'll need to allocate - // space for. - let full_len = self.simple_selectors.len() + self.combinators.len(); - - // Create the header. - let header = HeaderWithLength::new(spec, full_len); - - // Create the Arc using an iterator that drains our buffers. - - // Use a raw pointer to be able to call set_len despite "borrowing" the slice. - // This is similar to SmallVec::drain, but we use a slice here because - // we’re gonna traverse it non-linearly. - let raw_simple_selectors: *const [Component] = &*self.simple_selectors; - unsafe { - // Panic-safety: if SelectorBuilderIter is not iterated to the end, - // some simple selectors will safely leak. - self.simple_selectors.set_len(0) - } - let (rest, current) = split_from_end(unsafe { &*raw_simple_selectors }, self.current_len); - let iter = SelectorBuilderIter { - current_simple_selectors: current.iter(), - rest_of_simple_selectors: rest, - combinators: self.combinators.drain(..).rev(), - }; - - Arc::into_thin(Arc::from_header_and_iter(header, iter)) - } -} - -struct SelectorBuilderIter<'a, Impl: SelectorImpl> { - current_simple_selectors: slice::Iter<'a, Component>, - rest_of_simple_selectors: &'a [Component], - combinators: iter::Rev>, -} - -impl<'a, Impl: SelectorImpl> ExactSizeIterator for SelectorBuilderIter<'a, Impl> { - fn len(&self) -> usize { - self.current_simple_selectors.len() + - self.rest_of_simple_selectors.len() + - self.combinators.len() - } -} - -impl<'a, Impl: SelectorImpl> Iterator for SelectorBuilderIter<'a, Impl> { - type Item = Component; - #[inline(always)] - fn next(&mut self) -> Option { - if let Some(simple_selector_ref) = self.current_simple_selectors.next() { - // Move a simple selector out of this slice iterator. - // This is safe because we’ve called SmallVec::set_len(0) above, - // so SmallVec::drop won’t drop this simple selector. - unsafe { Some(ptr::read(simple_selector_ref)) } - } else { - self.combinators.next().map(|(combinator, len)| { - let (rest, current) = split_from_end(self.rest_of_simple_selectors, len); - self.rest_of_simple_selectors = rest; - self.current_simple_selectors = current.iter(); - Component::Combinator(combinator) - }) - } - } - - fn size_hint(&self) -> (usize, Option) { - (self.len(), Some(self.len())) - } -} - -fn split_from_end(s: &[T], at: usize) -> (&[T], &[T]) { - s.split_at(s.len() - at) -} - -bitflags! { - /// Flags that indicate at which point of parsing a selector are we. - #[derive(Default, ToShmem)] - pub (crate) struct SelectorFlags : u8 { - const HAS_PSEUDO = 1 << 0; - const HAS_SLOTTED = 1 << 1; - const HAS_PART = 1 << 2; - const HAS_PARENT = 1 << 3; - } -} - -#[derive(Clone, Copy, Debug, Eq, PartialEq, ToShmem)] -pub struct SpecificityAndFlags { - /// There are two free bits here, since we use ten bits for each specificity - /// kind (id, class, element). - pub(crate) specificity: u32, - /// There's padding after this field due to the size of the flags. - pub(crate) flags: SelectorFlags, -} - -impl SpecificityAndFlags { - #[inline] - pub fn specificity(&self) -> u32 { - self.specificity - } - - #[inline] - pub fn has_pseudo_element(&self) -> bool { - self.flags.intersects(SelectorFlags::HAS_PSEUDO) - } - - #[inline] - pub fn has_parent_selector(&self) -> bool { - self.flags.intersects(SelectorFlags::HAS_PARENT) - } - - #[inline] - pub fn is_slotted(&self) -> bool { - self.flags.intersects(SelectorFlags::HAS_SLOTTED) - } - - #[inline] - pub fn is_part(&self) -> bool { - self.flags.intersects(SelectorFlags::HAS_PART) - } -} - -const MAX_10BIT: u32 = (1u32 << 10) - 1; - -#[derive(Add, AddAssign, Clone, Copy, Default, Eq, Ord, PartialEq, PartialOrd)] -pub(crate) struct Specificity { - id_selectors: u32, - class_like_selectors: u32, - element_selectors: u32, -} - -impl From for Specificity { - #[inline] - fn from(value: u32) -> Specificity { - assert!(value <= MAX_10BIT << 20 | MAX_10BIT << 10 | MAX_10BIT); - Specificity { - id_selectors: value >> 20, - class_like_selectors: (value >> 10) & MAX_10BIT, - element_selectors: value & MAX_10BIT, - } - } -} - -impl From for u32 { - #[inline] - fn from(specificity: Specificity) -> u32 { - cmp::min(specificity.id_selectors, MAX_10BIT) << 20 | - cmp::min(specificity.class_like_selectors, MAX_10BIT) << 10 | - cmp::min(specificity.element_selectors, MAX_10BIT) - } -} - -pub(crate) fn specificity_and_flags(iter: slice::Iter>) -> SpecificityAndFlags -where - Impl: SelectorImpl, -{ - complex_selector_specificity_and_flags(iter).into() -} - -fn complex_selector_specificity_and_flags( - iter: slice::Iter>, -) -> SpecificityAndFlags -where - Impl: SelectorImpl, -{ - fn component_specificity( - simple_selector: &Component, - specificity: &mut Specificity, - flags: &mut SelectorFlags, - ) where - Impl: SelectorImpl, - { - match *simple_selector { - Component::Combinator(..) => {}, - Component::ParentSelector => flags.insert(SelectorFlags::HAS_PARENT), - Component::Part(..) => { - flags.insert(SelectorFlags::HAS_PART); - specificity.element_selectors += 1 - }, - Component::PseudoElement(..) => { - flags.insert(SelectorFlags::HAS_PSEUDO); - specificity.element_selectors += 1 - }, - Component::LocalName(..) => specificity.element_selectors += 1, - Component::Slotted(ref selector) => { - flags.insert(SelectorFlags::HAS_SLOTTED); - specificity.element_selectors += 1; - // Note that due to the way ::slotted works we only compete with - // other ::slotted rules, so the above rule doesn't really - // matter, but we do it still for consistency with other - // pseudo-elements. - // - // See: https://github.com/w3c/csswg-drafts/issues/1915 - *specificity += Specificity::from(selector.specificity()); - if selector.has_parent_selector() { - flags.insert(SelectorFlags::HAS_PARENT); - } - }, - Component::Host(ref selector) => { - specificity.class_like_selectors += 1; - if let Some(ref selector) = *selector { - // See: https://github.com/w3c/csswg-drafts/issues/1915 - *specificity += Specificity::from(selector.specificity()); - if selector.has_parent_selector() { - flags.insert(SelectorFlags::HAS_PARENT); - } - } - }, - Component::ID(..) => { - specificity.id_selectors += 1; - }, - Component::Class(..) | - Component::AttributeInNoNamespace { .. } | - Component::AttributeInNoNamespaceExists { .. } | - Component::AttributeOther(..) | - Component::Root | - Component::Empty | - Component::Scope | - Component::Nth(..) | - Component::NonTSPseudoClass(..) => { - specificity.class_like_selectors += 1; - }, - Component::NthOf(ref nth_of_data) => { - // https://drafts.csswg.org/selectors/#specificity-rules: - // - // The specificity of the :nth-last-child() pseudo-class, - // like the :nth-child() pseudo-class, combines the - // specificity of a regular pseudo-class with that of its - // selector argument S. - specificity.class_like_selectors += 1; - let sf = selector_list_specificity_and_flags(nth_of_data.selectors().iter()); - *specificity += Specificity::from(sf.specificity); - flags.insert(sf.flags); - }, - // https://drafts.csswg.org/selectors/#specificity-rules: - // - // The specificity of an :is(), :not(), or :has() pseudo-class - // is replaced by the specificity of the most specific complex - // selector in its selector list argument. - Component::Where(ref list) | - Component::Negation(ref list) | - Component::Is(ref list) => { - let sf = selector_list_specificity_and_flags(list.iter()); - if !matches!(*simple_selector, Component::Where(..)) { - *specificity += Specificity::from(sf.specificity); - } - flags.insert(sf.flags); - }, - Component::Has(ref relative_selectors) => { - let sf = relative_selector_list_specificity_and_flags(relative_selectors); - *specificity += Specificity::from(sf.specificity); - flags.insert(sf.flags); - }, - Component::ExplicitUniversalType | - Component::ExplicitAnyNamespace | - Component::ExplicitNoNamespace | - Component::DefaultNamespace(..) | - Component::Namespace(..) | - Component::RelativeSelectorAnchor => { - // Does not affect specificity - }, - } - } - - let mut specificity = Default::default(); - let mut flags = Default::default(); - for simple_selector in iter { - component_specificity(&simple_selector, &mut specificity, &mut flags); - } - SpecificityAndFlags { - specificity: specificity.into(), - flags, - } -} - -/// Finds the maximum specificity of elements in the list and returns it. -pub(crate) fn selector_list_specificity_and_flags<'a, Impl: SelectorImpl>( - itr: impl Iterator>, -) -> SpecificityAndFlags { - let mut specificity = 0; - let mut flags = SelectorFlags::empty(); - for selector in itr { - specificity = std::cmp::max(specificity, selector.specificity()); - if selector.has_parent_selector() { - flags.insert(SelectorFlags::HAS_PARENT); - } - } - SpecificityAndFlags { specificity, flags } -} - -pub(crate) fn relative_selector_list_specificity_and_flags( - list: &[RelativeSelector], -) -> SpecificityAndFlags { - selector_list_specificity_and_flags(list.iter().map(|rel| &rel.selector)) -} diff --git a/components/selectors/context.rs b/components/selectors/context.rs deleted file mode 100644 index d8c461660d3..00000000000 --- a/components/selectors/context.rs +++ /dev/null @@ -1,363 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ - -use crate::attr::CaseSensitivity; -use crate::bloom::BloomFilter; -use crate::nth_index_cache::{NthIndexCache, NthIndexCacheInner}; -use crate::parser::{Selector, SelectorImpl}; -use crate::tree::{Element, OpaqueElement}; - -/// What kind of selector matching mode we should use. -/// -/// There are two modes of selector matching. The difference is only noticeable -/// in presence of pseudo-elements. -#[derive(Clone, Copy, Debug, PartialEq)] -pub enum MatchingMode { - /// Don't ignore any pseudo-element selectors. - Normal, - - /// Ignores any stateless pseudo-element selectors in the rightmost sequence - /// of simple selectors. - /// - /// This is useful, for example, to match against ::before when you aren't a - /// pseudo-element yourself. - /// - /// For example, in presence of `::before:hover`, it would never match, but - /// `::before` would be ignored as in "matching". - /// - /// It's required for all the selectors you match using this mode to have a - /// pseudo-element. - ForStatelessPseudoElement, -} - -/// The mode to use when matching unvisited and visited links. -#[derive(Clone, Copy, Debug, Eq, PartialEq)] -pub enum VisitedHandlingMode { - /// All links are matched as if they are unvisted. - AllLinksUnvisited, - /// All links are matched as if they are visited and unvisited (both :link - /// and :visited match). - /// - /// This is intended to be used from invalidation code, to be conservative - /// about whether we need to restyle a link. - AllLinksVisitedAndUnvisited, - /// A element's "relevant link" is the element being matched if it is a link - /// or the nearest ancestor link. The relevant link is matched as though it - /// is visited, and all other links are matched as if they are unvisited. - RelevantLinkVisited, -} - -impl VisitedHandlingMode { - #[inline] - pub fn matches_visited(&self) -> bool { - matches!( - *self, - VisitedHandlingMode::RelevantLinkVisited | - VisitedHandlingMode::AllLinksVisitedAndUnvisited - ) - } - - #[inline] - pub fn matches_unvisited(&self) -> bool { - matches!( - *self, - VisitedHandlingMode::AllLinksUnvisited | - VisitedHandlingMode::AllLinksVisitedAndUnvisited - ) - } -} - -/// 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/ -#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)] -pub enum QuirksMode { - /// Quirks mode. - Quirks, - /// Limited quirks mode. - LimitedQuirks, - /// No quirks mode. - NoQuirks, -} - -impl QuirksMode { - #[inline] - pub fn classes_and_ids_case_sensitivity(self) -> CaseSensitivity { - match self { - QuirksMode::NoQuirks | QuirksMode::LimitedQuirks => CaseSensitivity::CaseSensitive, - QuirksMode::Quirks => CaseSensitivity::AsciiCaseInsensitive, - } - } -} - -/// Whether or not this matching considered relative selector. -#[derive(Clone, Copy, Debug, PartialEq)] -pub enum RelativeSelectorMatchingState { - /// Was not considered for any relative selector. - None, - /// Relative selector was considered for a match, but the element to be - /// under matching would not anchor the relative selector. i.e. The - /// relative selector was not part of the first compound selector (in match - /// order). - Considered, - /// Same as above, but the relative selector was part of the first compound - /// selector (in match order). - ConsideredAnchor, -} - -/// Data associated with the matching process for a element. This context is -/// used across many selectors for an element, so it's not appropriate for -/// transient data that applies to only a single selector. -pub struct MatchingContext<'a, Impl> -where - Impl: SelectorImpl, -{ - /// Input with the matching mode we should use when matching selectors. - matching_mode: MatchingMode, - /// Input with the bloom filter used to fast-reject selectors. - pub bloom_filter: Option<&'a BloomFilter>, - /// A cache to speed up nth-index-like selectors. - pub nth_index_cache: &'a mut NthIndexCache, - /// The element which is going to match :scope pseudo-class. It can be - /// either one :scope element, or the scoping element. - /// - /// Note that, although in theory there can be multiple :scope elements, - /// in current specs, at most one is specified, and when there is one, - /// scoping element is not relevant anymore, so we use a single field for - /// them. - /// - /// When this is None, :scope will match the root element. - /// - /// See https://drafts.csswg.org/selectors-4/#scope-pseudo - pub scope_element: Option, - - /// The current shadow host we're collecting :host rules for. - pub current_host: Option, - - /// Controls how matching for links is handled. - visited_handling: VisitedHandlingMode, - - /// The current nesting level of selectors that we're matching. - nesting_level: usize, - - /// Whether we're inside a negation or not. - in_negation: bool, - - /// An optional hook function for checking whether a pseudo-element - /// should match when matching_mode is ForStatelessPseudoElement. - pub pseudo_element_matching_fn: Option<&'a dyn Fn(&Impl::PseudoElement) -> bool>, - - /// Extra implementation-dependent matching data. - pub extra_data: Impl::ExtraMatchingData<'a>, - - /// The current element we're anchoring on for evaluating the relative selector. - current_relative_selector_anchor: Option, - pub considered_relative_selector: RelativeSelectorMatchingState, - - quirks_mode: QuirksMode, - needs_selector_flags: NeedsSelectorFlags, - classes_and_ids_case_sensitivity: CaseSensitivity, - _impl: ::std::marker::PhantomData, -} - -impl<'a, Impl> MatchingContext<'a, Impl> -where - Impl: SelectorImpl, -{ - /// Constructs a new `MatchingContext`. - pub fn new( - matching_mode: MatchingMode, - bloom_filter: Option<&'a BloomFilter>, - nth_index_cache: &'a mut NthIndexCache, - quirks_mode: QuirksMode, - needs_selector_flags: NeedsSelectorFlags, - ) -> Self { - Self::new_for_visited( - matching_mode, - bloom_filter, - nth_index_cache, - VisitedHandlingMode::AllLinksUnvisited, - quirks_mode, - needs_selector_flags, - ) - } - - /// Constructs a new `MatchingContext` for use in visited matching. - pub fn new_for_visited( - matching_mode: MatchingMode, - bloom_filter: Option<&'a BloomFilter>, - nth_index_cache: &'a mut NthIndexCache, - visited_handling: VisitedHandlingMode, - quirks_mode: QuirksMode, - needs_selector_flags: NeedsSelectorFlags, - ) -> Self { - Self { - matching_mode, - bloom_filter, - visited_handling, - 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, - in_negation: false, - pseudo_element_matching_fn: None, - extra_data: Default::default(), - current_relative_selector_anchor: None, - considered_relative_selector: RelativeSelectorMatchingState::None, - _impl: ::std::marker::PhantomData, - } - } - - // Grab a reference to the appropriate cache. - #[inline] - pub fn nth_index_cache( - &mut self, - is_of_type: bool, - is_from_end: bool, - selectors: &[Selector], - ) -> &mut NthIndexCacheInner { - self.nth_index_cache.get(is_of_type, is_from_end, selectors) - } - - /// Whether we're matching a nested selector. - #[inline] - pub fn is_nested(&self) -> bool { - self.nesting_level != 0 - } - - /// Whether we're matching inside a :not(..) selector. - #[inline] - pub fn in_negation(&self) -> bool { - self.in_negation - } - - /// The quirks mode of the document. - #[inline] - pub fn quirks_mode(&self) -> QuirksMode { - self.quirks_mode - } - - /// The matching-mode for this selector-matching operation. - #[inline] - pub fn matching_mode(&self) -> MatchingMode { - 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 { - self.classes_and_ids_case_sensitivity - } - - /// Runs F with a deeper nesting level. - #[inline] - pub fn nest(&mut self, f: F) -> R - where - F: FnOnce(&mut Self) -> R, - { - self.nesting_level += 1; - let result = f(self); - self.nesting_level -= 1; - result - } - - /// Runs F with a deeper nesting level, and marking ourselves in a negation, - /// for a :not(..) selector, for example. - #[inline] - pub fn nest_for_negation(&mut self, f: F) -> R - where - F: FnOnce(&mut Self) -> R, - { - let old_in_negation = self.in_negation; - self.in_negation = true; - let result = self.nest(f); - self.in_negation = old_in_negation; - result - } - - #[inline] - pub fn visited_handling(&self) -> VisitedHandlingMode { - self.visited_handling - } - - /// Runs F with a different VisitedHandlingMode. - #[inline] - pub fn with_visited_handling_mode( - &mut self, - handling_mode: VisitedHandlingMode, - f: F, - ) -> R - where - F: FnOnce(&mut Self) -> R, - { - let original_handling_mode = self.visited_handling; - self.visited_handling = handling_mode; - let result = f(self); - self.visited_handling = original_handling_mode; - result - } - - /// Runs F with a given shadow host which is the root of the tree whose - /// rules we're matching. - #[inline] - pub fn with_shadow_host(&mut self, host: Option, f: F) -> R - where - E: Element, - F: FnOnce(&mut Self) -> R, - { - let original_host = self.current_host.take(); - self.current_host = host.map(|h| h.opaque()); - let result = f(self); - self.current_host = original_host; - result - } - - /// Returns the current shadow host whose shadow root we're matching rules - /// against. - #[inline] - pub fn shadow_host(&self) -> Option { - self.current_host - } - - /// Runs F with a deeper nesting level, with the given element as the anchor, - /// for a :has(...) selector, for example. - #[inline] - pub fn nest_for_relative_selector(&mut self, anchor: OpaqueElement, f: F) -> R - where - F: FnOnce(&mut Self) -> R, - { - debug_assert!( - self.current_relative_selector_anchor.is_none(), - "Nesting should've been rejected at parse time" - ); - self.current_relative_selector_anchor = Some(anchor); - self.considered_relative_selector = RelativeSelectorMatchingState::Considered; - let result = self.nest(f); - self.current_relative_selector_anchor = None; - result - } - - /// Returns the current anchor element to evaluate the relative selector against. - #[inline] - pub fn relative_selector_anchor(&self) -> Option { - self.current_relative_selector_anchor - } -} diff --git a/components/selectors/lib.rs b/components/selectors/lib.rs deleted file mode 100644 index d71d3c84876..00000000000 --- a/components/selectors/lib.rs +++ /dev/null @@ -1,40 +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/. */ - -// Make |cargo bench| work. -#![cfg_attr(feature = "bench", feature(test))] - -#[macro_use] -extern crate bitflags; -#[macro_use] -extern crate cssparser; -#[macro_use] -extern crate debug_unreachable; -#[macro_use] -extern crate derive_more; -extern crate fxhash; -#[macro_use] -extern crate log; -extern crate phf; -extern crate precomputed_hash; -extern crate servo_arc; -extern crate smallvec; -extern crate to_shmem; -#[macro_use] -extern crate to_shmem_derive; - -pub mod attr; -pub mod bloom; -mod builder; -pub mod context; -pub mod matching; -mod nth_index_cache; -pub mod parser; -pub mod sink; -mod tree; -pub mod visitor; - -pub use crate::nth_index_cache::NthIndexCache; -pub use crate::parser::{Parser, SelectorImpl, SelectorList}; -pub use crate::tree::{Element, OpaqueElement}; diff --git a/components/selectors/matching.rs b/components/selectors/matching.rs deleted file mode 100644 index 83bc878797a..00000000000 --- a/components/selectors/matching.rs +++ /dev/null @@ -1,1133 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ - -use crate::attr::{ - AttrSelectorOperation, CaseSensitivity, NamespaceConstraint, ParsedAttrSelectorOperation, - ParsedCaseSensitivity, -}; -use crate::bloom::{BloomFilter, BLOOM_HASH_MASK}; -use crate::parser::{ - AncestorHashes, Combinator, Component, LocalName, NthSelectorData, RelativeSelectorMatchHint, -}; -use crate::parser::{ - NonTSPseudoClass, RelativeSelector, Selector, SelectorImpl, SelectorIter, SelectorList, -}; -use crate::tree::Element; -use bitflags::bitflags; -use smallvec::SmallVec; -use std::borrow::Borrow; -use std::iter; - -pub use crate::context::*; - -// The bloom filter for descendant CSS selectors will have a <1% false -// positive rate until it has this many selectors in it, then it will -// rapidly increase. -pub static RECOMMENDED_SELECTOR_BLOOM_FILTER_SIZE: usize = 4096; - -bitflags! { - /// Set of flags that are set on either the element or its parent (depending - /// on the flag) if the element could potentially match a selector. - pub struct ElementSelectorFlags: usize { - /// When a child is added or removed from the parent, all the children - /// must be restyled, because they may match :nth-last-child, - /// :last-of-type, :nth-last-of-type, or :only-of-type. - const HAS_SLOW_SELECTOR = 1 << 0; - - /// When a child is added or removed from the parent, any later - /// children must be restyled, because they may match :nth-child, - /// :first-of-type, or :nth-of-type. - const HAS_SLOW_SELECTOR_LATER_SIBLINGS = 1 << 1; - - /// When a DOM mutation occurs on a child that might be matched by - /// :nth-last-child(.. of ), earlier children must be - /// restyled, and HAS_SLOW_SELECTOR will be set (which normally - /// indicates that all children will be restyled). - /// - /// Similarly, when a DOM mutation occurs on a child that might be - /// matched by :nth-child(.. of ), later children must be - /// restyled, and HAS_SLOW_SELECTOR_LATER_SIBLINGS will be set. - const HAS_SLOW_SELECTOR_NTH_OF = 1 << 2; - - /// When a child is added or removed from the parent, the first and - /// last children must be restyled, because they may match :first-child, - /// :last-child, or :only-child. - const HAS_EDGE_CHILD_SELECTOR = 1 << 3; - - /// The element has an empty selector, so when a child is appended we - /// might need to restyle the parent completely. - const HAS_EMPTY_SELECTOR = 1 << 4; - } -} - -impl ElementSelectorFlags { - /// Returns the subset of flags that apply to the element. - pub fn for_self(self) -> ElementSelectorFlags { - self & ElementSelectorFlags::HAS_EMPTY_SELECTOR - } - - /// Returns the subset of flags that apply to the parent. - pub fn for_parent(self) -> ElementSelectorFlags { - self & (ElementSelectorFlags::HAS_SLOW_SELECTOR | - ElementSelectorFlags::HAS_SLOW_SELECTOR_LATER_SIBLINGS | - ElementSelectorFlags::HAS_SLOW_SELECTOR_NTH_OF | - ElementSelectorFlags::HAS_EDGE_CHILD_SELECTOR) - } -} - -/// Holds per-compound-selector data. -struct LocalMatchingContext<'a, 'b: 'a, Impl: SelectorImpl> { - shared: &'a mut MatchingContext<'b, Impl>, - quirks_data: Option<(Rightmost, SelectorIter<'a, Impl>)>, -} - -#[inline(always)] -pub fn matches_selector_list( - selector_list: &SelectorList, - element: &E, - context: &mut MatchingContext, -) -> bool -where - E: Element, -{ - // 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); - if matches { - return true; - } - } - - false -} - -#[inline(always)] -fn may_match(hashes: &AncestorHashes, bf: &BloomFilter) -> bool { - // Check the first three hashes. Note that we can check for zero before - // masking off the high bits, since if any of the first three hashes is - // zero the fourth will be as well. We also take care to avoid the - // special-case complexity of the fourth hash until we actually reach it, - // because we usually don't. - // - // To be clear: this is all extremely hot. - for i in 0..3 { - let packed = hashes.packed_hashes[i]; - if packed == 0 { - // No more hashes left - unable to fast-reject. - return true; - } - - if !bf.might_contain_hash(packed & BLOOM_HASH_MASK) { - // Hooray! We fast-rejected on this hash. - return false; - } - } - - // Now do the slighty-more-complex work of synthesizing the fourth hash, - // and check it against the filter if it exists. - let fourth = hashes.fourth_hash(); - fourth == 0 || bf.might_contain_hash(fourth) -} - -/// A result of selector matching, includes 3 failure types, -/// -/// NotMatchedAndRestartFromClosestLaterSibling -/// NotMatchedAndRestartFromClosestDescendant -/// NotMatchedGlobally -/// -/// When NotMatchedGlobally appears, stop selector matching completely since -/// the succeeding selectors never matches. -/// It is raised when -/// Child combinator cannot find the candidate element. -/// Descendant combinator cannot find the candidate element. -/// -/// When NotMatchedAndRestartFromClosestDescendant appears, the selector -/// matching does backtracking and restarts from the closest Descendant -/// combinator. -/// It is raised when -/// NextSibling combinator cannot find the candidate element. -/// LaterSibling combinator cannot find the candidate element. -/// Child combinator doesn't match on the found element. -/// -/// When NotMatchedAndRestartFromClosestLaterSibling appears, the selector -/// matching does backtracking and restarts from the closest LaterSibling -/// combinator. -/// It is raised when -/// NextSibling combinator doesn't match on the found element. -/// -/// For example, when the selector "d1 d2 a" is provided and we cannot *find* -/// an appropriate ancestor element for "d1", this selector matching raises -/// NotMatchedGlobally since even if "d2" is moved to more upper element, the -/// candidates for "d1" becomes less than before and d1 . -/// -/// The next example is siblings. When the selector "b1 + b2 ~ d1 a" is -/// provided and we cannot *find* an appropriate brother element for b1, -/// the selector matching raises NotMatchedAndRestartFromClosestDescendant. -/// The selectors ("b1 + b2 ~") doesn't match and matching restart from "d1". -/// -/// The additional example is child and sibling. When the selector -/// "b1 + c1 > b2 ~ d1 a" is provided and the selector "b1" doesn't match on -/// the element, this "b1" raises NotMatchedAndRestartFromClosestLaterSibling. -/// However since the selector "c1" raises -/// NotMatchedAndRestartFromClosestDescendant. So the selector -/// "b1 + c1 > b2 ~ " doesn't match and restart matching from "d1". -#[derive(Clone, Copy, Eq, PartialEq)] -enum SelectorMatchingResult { - Matched, - NotMatchedAndRestartFromClosestLaterSibling, - NotMatchedAndRestartFromClosestDescendant, - NotMatchedGlobally, -} - -/// Matches a selector, fast-rejecting against a bloom filter. -/// -/// We accept an offset to allow consumers to represent and match against -/// partial selectors (indexed from the right). We use this API design, rather -/// than having the callers pass a SelectorIter, because creating a SelectorIter -/// requires dereferencing the selector to get the length, which adds an -/// 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( - selector: &Selector, - offset: usize, - hashes: Option<&AncestorHashes>, - element: &E, - context: &mut MatchingContext, -) -> bool -where - E: Element, -{ - // Use the bloom filter to fast-reject. - if let Some(hashes) = hashes { - if let Some(filter) = context.bloom_filter { - if !may_match(hashes, filter) { - return false; - } - } - } - - matches_complex_selector(selector.iter_from(offset), element, context) -} - -/// Whether a compound selector matched, and whether it was the rightmost -/// selector inside the complex selector. -pub enum CompoundSelectorMatchingResult { - /// The selector was fully matched. - FullyMatched, - /// The compound selector matched, and the next combinator offset is - /// `next_combinator_offset`. - Matched { next_combinator_offset: usize }, - /// The selector didn't match. - NotMatched, -} - -/// Matches a compound selector belonging to `selector`, starting at offset -/// `from_offset`, matching left to right. -/// -/// Requires that `from_offset` points to a `Combinator`. -/// -/// NOTE(emilio): This doesn't allow to match in the leftmost sequence of the -/// complex selector, but it happens to be the case we don't need it. -pub fn matches_compound_selector_from( - selector: &Selector, - mut from_offset: usize, - context: &mut MatchingContext, - element: &E, -) -> CompoundSelectorMatchingResult -where - E: Element, -{ - if cfg!(debug_assertions) && from_offset != 0 { - selector.combinator_at_parse_order(from_offset - 1); // This asserts. - } - - let mut local_context = LocalMatchingContext { - shared: context, - quirks_data: None, - }; - - // Find the end of the selector or the next combinator, then match - // backwards, so that we match in the same order as - // matches_complex_selector, which is usually faster. - let start_offset = from_offset; - for component in selector.iter_raw_parse_order_from(from_offset) { - if matches!(*component, Component::Combinator(..)) { - debug_assert_ne!(from_offset, 0, "Selector started with a combinator?"); - break; - } - - from_offset += 1; - } - - debug_assert!(from_offset >= 1); - debug_assert!(from_offset <= selector.len()); - - let iter = selector.iter_from(selector.len() - from_offset); - debug_assert!( - iter.clone().next().is_some() || - (from_offset != selector.len() && - matches!( - selector.combinator_at_parse_order(from_offset), - Combinator::SlotAssignment | Combinator::PseudoElement - )), - "Got the math wrong: {:?} | {:?} | {} {}", - selector, - selector.iter_raw_match_order().as_slice(), - from_offset, - start_offset - ); - - for component in iter { - if !matches_simple_selector(component, element, &mut local_context) { - return CompoundSelectorMatchingResult::NotMatched; - } - } - - if from_offset != selector.len() { - return CompoundSelectorMatchingResult::Matched { - next_combinator_offset: from_offset, - }; - } - - CompoundSelectorMatchingResult::FullyMatched -} - -/// Matches a complex selector. -#[inline(always)] -pub fn matches_complex_selector( - mut iter: SelectorIter, - element: &E, - context: &mut MatchingContext, -) -> bool -where - E: Element, -{ - // If this is the special pseudo-element mode, consume the ::pseudo-element - // before proceeding, since the caller has already handled that part. - if context.matching_mode() == MatchingMode::ForStatelessPseudoElement && !context.is_nested() { - // Consume the pseudo. - match *iter.next().unwrap() { - Component::PseudoElement(ref pseudo) => { - if let Some(ref f) = context.pseudo_element_matching_fn { - if !f(pseudo) { - return false; - } - } - }, - ref other => { - debug_assert!( - false, - "Used MatchingMode::ForStatelessPseudoElement \ - in a non-pseudo selector {:?}", - other - ); - return false; - }, - } - - if !iter.matches_for_stateless_pseudo_element() { - return false; - } - - // Advance to the non-pseudo-element part of the selector. - let next_sequence = iter.next_sequence().unwrap(); - debug_assert_eq!(next_sequence, Combinator::PseudoElement); - } - - let result = matches_complex_selector_internal(iter, element, context, Rightmost::Yes); - - matches!(result, SelectorMatchingResult::Matched) -} - -/// Matches each selector of a list as a complex selector -#[inline(always)] -pub fn list_matches_complex_selector( - list: &[Selector], - element: &E, - context: &mut MatchingContext, -) -> bool { - for selector in list { - if matches_complex_selector(selector.iter(), element, context) { - return true; - } - } - false -} - -/// Matches a relative selector in a list of relative selectors. -fn matches_relative_selectors( - selectors: &[RelativeSelector], - element: &E, - context: &mut MatchingContext, -) -> bool { - // If we've considered anchoring `:has()` selector while trying to match this element, - // mark it as such, as it has implications on style sharing (See style sharing - // code for further information). - context.considered_relative_selector = RelativeSelectorMatchingState::ConsideredAnchor; - for RelativeSelector { - match_hint, - selector, - } in selectors.iter() - { - let (traverse_subtree, traverse_siblings, mut next_element) = match match_hint { - RelativeSelectorMatchHint::InChild => (false, true, element.first_element_child()), - RelativeSelectorMatchHint::InSubtree => (true, true, element.first_element_child()), - RelativeSelectorMatchHint::InSibling => (false, true, element.next_sibling_element()), - RelativeSelectorMatchHint::InSiblingSubtree => { - (true, true, element.next_sibling_element()) - }, - RelativeSelectorMatchHint::InNextSibling => { - (false, false, element.next_sibling_element()) - }, - RelativeSelectorMatchHint::InNextSiblingSubtree => { - (true, false, element.next_sibling_element()) - }, - }; - while let Some(el) = next_element { - // TODO(dshin): `:has()` matching can get expensive when determining style changes. - // We'll need caching/filtering here, which is tracked in bug 1822177. - if matches_complex_selector(selector.iter(), &el, context) { - return true; - } - if traverse_subtree && matches_relative_selector_subtree(selector, &el, context) { - return true; - } - if !traverse_siblings { - break; - } - next_element = el.next_sibling_element(); - } - } - - false -} - -fn matches_relative_selector_subtree( - selector: &Selector, - element: &E, - context: &mut MatchingContext, -) -> bool { - let mut current = element.first_element_child(); - - while let Some(el) = current { - if matches_complex_selector(selector.iter(), &el, context) { - return true; - } - - if matches_relative_selector_subtree(selector, &el, context) { - return true; - } - - current = el.next_sibling_element(); - } - - false -} - -/// Whether the :hover and :active quirk applies. -/// -/// https://quirks.spec.whatwg.org/#the-active-and-hover-quirk -fn hover_and_active_quirk_applies( - selector_iter: &SelectorIter, - context: &MatchingContext, - rightmost: Rightmost, -) -> bool { - if context.quirks_mode() != QuirksMode::Quirks { - return false; - } - - if context.is_nested() { - return false; - } - - // This compound selector had a pseudo-element to the right that we - // intentionally skipped. - if rightmost == Rightmost::Yes && - context.matching_mode() == MatchingMode::ForStatelessPseudoElement - { - return false; - } - - selector_iter.clone().all(|simple| match *simple { - Component::LocalName(_) | - Component::AttributeInNoNamespaceExists { .. } | - Component::AttributeInNoNamespace { .. } | - Component::AttributeOther(_) | - Component::ID(_) | - Component::Class(_) | - Component::PseudoElement(_) | - Component::Negation(_) | - Component::Empty | - Component::Nth(_) | - Component::NthOf(_) => false, - Component::NonTSPseudoClass(ref pseudo_class) => pseudo_class.is_active_or_hover(), - _ => true, - }) -} - -#[derive(Clone, Copy, PartialEq)] -enum Rightmost { - Yes, - No, -} - -#[inline(always)] -fn next_element_for_combinator( - element: &E, - combinator: Combinator, - selector: &SelectorIter, - context: &MatchingContext, -) -> Option -where - E: Element, -{ - match combinator { - Combinator::NextSibling | Combinator::LaterSibling => element.prev_sibling_element(), - Combinator::Child | Combinator::Descendant => { - match element.parent_element() { - Some(e) => return Some(e), - None => {}, - } - - if !element.parent_node_is_shadow_root() { - return None; - } - - // https://drafts.csswg.org/css-scoping/#host-element-in-tree: - // - // For the purpose of Selectors, a shadow host also appears in - // its shadow tree, with the contents of the shadow tree treated - // as its children. (In other words, the shadow host is treated as - // replacing the shadow root node.) - // - // and also: - // - // When considered within its own shadow trees, the shadow host is - // featureless. Only the :host, :host(), and :host-context() - // pseudo-classes are allowed to match it. - // - // Since we know that the parent is a shadow root, we necessarily - // are in a shadow tree of the host, and the next selector will only - // match if the selector is a featureless :host selector. - if !selector.clone().is_featureless_host_selector() { - return None; - } - - element.containing_shadow_host() - }, - Combinator::Part => element.containing_shadow_host(), - Combinator::SlotAssignment => { - debug_assert!(element - .assigned_slot() - .map_or(true, |s| s.is_html_slot_element())); - let scope = context.current_host?; - let mut current_slot = element.assigned_slot()?; - while current_slot.containing_shadow_host().unwrap().opaque() != scope { - current_slot = current_slot.assigned_slot()?; - } - Some(current_slot) - }, - Combinator::PseudoElement => element.pseudo_element_originating_element(), - } -} - -fn matches_complex_selector_internal( - mut selector_iter: SelectorIter, - element: &E, - context: &mut MatchingContext, - rightmost: Rightmost, -) -> SelectorMatchingResult -where - E: Element, -{ - debug!( - "Matching complex selector {:?} for {:?}", - selector_iter, element - ); - - let matches_compound_selector = - matches_compound_selector(&mut selector_iter, element, context, 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); - } - } - - if !matches_compound_selector { - return SelectorMatchingResult::NotMatchedAndRestartFromClosestLaterSibling; - } - - let combinator = match combinator { - None => return SelectorMatchingResult::Matched, - Some(c) => c, - }; - - let candidate_not_found = match combinator { - Combinator::NextSibling | Combinator::LaterSibling => { - SelectorMatchingResult::NotMatchedAndRestartFromClosestDescendant - }, - Combinator::Child | - Combinator::Descendant | - Combinator::SlotAssignment | - Combinator::Part | - Combinator::PseudoElement => SelectorMatchingResult::NotMatchedGlobally, - }; - - // Stop matching :visited as soon as we find a link, or a combinator for - // something that isn't an ancestor. - let mut visited_handling = if combinator.is_sibling() { - VisitedHandlingMode::AllLinksUnvisited - } else { - context.visited_handling() - }; - - let mut element = element.clone(); - loop { - if element.is_link() { - visited_handling = VisitedHandlingMode::AllLinksUnvisited; - } - - element = match next_element_for_combinator(&element, combinator, &selector_iter, &context) - { - None => return candidate_not_found, - Some(next_element) => next_element, - }; - - let result = context.with_visited_handling_mode(visited_handling, |context| { - matches_complex_selector_internal( - selector_iter.clone(), - &element, - context, - Rightmost::No, - ) - }); - - match (result, combinator) { - // Return the status immediately. - (SelectorMatchingResult::Matched, _) | - (SelectorMatchingResult::NotMatchedGlobally, _) | - (_, Combinator::NextSibling) => { - return result; - }, - - // Upgrade the failure status to - // NotMatchedAndRestartFromClosestDescendant. - (_, Combinator::PseudoElement) | (_, Combinator::Child) => { - return SelectorMatchingResult::NotMatchedAndRestartFromClosestDescendant; - }, - - // If the failure status is - // NotMatchedAndRestartFromClosestDescendant and combinator is - // Combinator::LaterSibling, give up this Combinator::LaterSibling - // matching and restart from the closest descendant combinator. - ( - SelectorMatchingResult::NotMatchedAndRestartFromClosestDescendant, - Combinator::LaterSibling, - ) => { - return result; - }, - - // The Combinator::Descendant combinator and the status is - // NotMatchedAndRestartFromClosestLaterSibling or - // NotMatchedAndRestartFromClosestDescendant, or the - // Combinator::LaterSibling combinator and the status is - // NotMatchedAndRestartFromClosestDescendant, we can continue to - // matching on the next candidate element. - _ => {}, - } - } -} - -#[inline] -fn matches_local_name(element: &E, local_name: &LocalName) -> bool -where - E: Element, -{ - let name = select_name(element, &local_name.name, &local_name.lower_name).borrow(); - element.has_local_name(name) -} - -/// Determines whether the given element matches the given compound selector. -#[inline] -fn matches_compound_selector( - selector_iter: &mut SelectorIter, - element: &E, - context: &mut MatchingContext, - rightmost: Rightmost, -) -> bool -where - E: Element, -{ - let quirks_data = if context.quirks_mode() == QuirksMode::Quirks { - Some((rightmost, selector_iter.clone())) - } else { - None - }; - - // Handle some common cases first. - // We may want to get rid of this at some point if we can make the - // generic case fast enough. - let mut selector = selector_iter.next(); - if let Some(&Component::LocalName(ref local_name)) = selector { - if !matches_local_name(element, local_name) { - return false; - } - selector = selector_iter.next(); - } - let class_and_id_case_sensitivity = context.classes_and_ids_case_sensitivity(); - if let Some(&Component::ID(ref id)) = selector { - if !element.has_id(id, class_and_id_case_sensitivity) { - return false; - } - selector = selector_iter.next(); - } - while let Some(&Component::Class(ref class)) = selector { - if !element.has_class(class, class_and_id_case_sensitivity) { - return false; - } - selector = selector_iter.next(); - } - let selector = match selector { - Some(s) => s, - None => return true, - }; - - let mut local_context = LocalMatchingContext { - shared: context, - quirks_data, - }; - iter::once(selector) - .chain(selector_iter) - .all(|simple| matches_simple_selector(simple, element, &mut local_context)) -} - -/// Determines whether the given element matches the given single selector. -fn matches_simple_selector( - selector: &Component, - element: &E, - context: &mut LocalMatchingContext, -) -> bool -where - E: Element, -{ - debug_assert!(context.shared.is_nested() || !context.shared.in_negation()); - - match *selector { - Component::ID(ref id) => { - element.has_id(id, context.shared.classes_and_ids_case_sensitivity()) - }, - Component::Class(ref class) => { - element.has_class(class, context.shared.classes_and_ids_case_sensitivity()) - }, - Component::LocalName(ref local_name) => matches_local_name(element, local_name), - Component::AttributeInNoNamespaceExists { - ref local_name, - ref local_name_lower, - } => element.has_attr_in_no_namespace(select_name(element, local_name, local_name_lower)), - Component::AttributeInNoNamespace { - ref local_name, - ref value, - operator, - case_sensitivity, - } => { - element.attr_matches( - &NamespaceConstraint::Specific(&crate::parser::namespace_empty_string::()), - local_name, - &AttrSelectorOperation::WithValue { - operator, - case_sensitivity: to_unconditional_case_sensitivity(case_sensitivity, element), - value, - }, - ) - }, - Component::AttributeOther(ref attr_sel) => { - let empty_string; - let namespace = match attr_sel.namespace() { - Some(ns) => ns, - None => { - empty_string = crate::parser::namespace_empty_string::(); - NamespaceConstraint::Specific(&empty_string) - }, - }; - element.attr_matches( - &namespace, - select_name(element, &attr_sel.local_name, &attr_sel.local_name_lower), - &match attr_sel.operation { - ParsedAttrSelectorOperation::Exists => AttrSelectorOperation::Exists, - ParsedAttrSelectorOperation::WithValue { - operator, - case_sensitivity, - ref value, - } => AttrSelectorOperation::WithValue { - operator, - case_sensitivity: to_unconditional_case_sensitivity( - case_sensitivity, - element, - ), - value, - }, - }, - ) - }, - Component::Part(ref parts) => { - let mut hosts = SmallVec::<[E; 4]>::new(); - - let mut host = match element.containing_shadow_host() { - Some(h) => h, - None => return false, - }; - - let current_host = context.shared.current_host; - if current_host != Some(host.opaque()) { - loop { - let outer_host = host.containing_shadow_host(); - if outer_host.as_ref().map(|h| h.opaque()) == current_host { - break; - } - let outer_host = match outer_host { - Some(h) => h, - None => return false, - }; - // TODO(emilio): if worth it, we could early return if - // host doesn't have the exportparts attribute. - hosts.push(host); - host = outer_host; - } - } - - // Translate the part into the right scope. - parts.iter().all(|part| { - let mut part = part.clone(); - for host in hosts.iter().rev() { - part = match host.imported_part(&part) { - Some(p) => p, - None => return false, - }; - } - element.is_part(&part) - }) - }, - Component::Slotted(ref selector) => { - // are never flattened tree slottables. - !element.is_html_slot_element() && - context - .shared - .nest(|context| matches_complex_selector(selector.iter(), element, context)) - }, - Component::PseudoElement(ref pseudo) => { - element.match_pseudo_element(pseudo, context.shared) - }, - Component::ExplicitUniversalType | Component::ExplicitAnyNamespace => true, - Component::Namespace(_, ref url) | Component::DefaultNamespace(ref url) => { - element.has_namespace(&url.borrow()) - }, - Component::ExplicitNoNamespace => { - let ns = crate::parser::namespace_empty_string::(); - element.has_namespace(&ns.borrow()) - }, - Component::NonTSPseudoClass(ref pc) => { - if let Some((ref rightmost, ref iter)) = context.quirks_data { - if pc.is_active_or_hover() && - !element.is_link() && - hover_and_active_quirk_applies(iter, context.shared, *rightmost) - { - return false; - } - } - element.match_non_ts_pseudo_class(pc, &mut context.shared) - }, - Component::Root => element.is_root(), - Component::Empty => { - if context.shared.needs_selector_flags() { - element.apply_selector_flags(ElementSelectorFlags::HAS_EMPTY_SELECTOR); - } - element.is_empty() - }, - Component::Host(ref selector) => { - context - .shared - .shadow_host() - .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)) - }) - }, - // These should only work at parse time, should be replaced with :is() at CascadeData build - // time. - Component::ParentSelector => false, - Component::Scope => match context.shared.scope_element { - Some(ref scope_element) => element.opaque() == *scope_element, - None => element.is_root(), - }, - Component::Nth(ref nth_data) => { - matches_generic_nth_child(element, context.shared, nth_data, &[]) - }, - Component::NthOf(ref nth_of_data) => context.shared.nest(|context| { - matches_generic_nth_child( - element, - context, - nth_of_data.nth_data(), - nth_of_data.selectors(), - ) - }), - Component::Is(ref list) | Component::Where(ref list) => context - .shared - .nest(|context| list_matches_complex_selector(list, element, context)), - Component::Negation(ref list) => context - .shared - .nest_for_negation(|context| !list_matches_complex_selector(list, element, context)), - Component::Has(ref relative_selectors) => context - .shared - .nest_for_relative_selector(element.opaque(), |context| { - matches_relative_selectors(relative_selectors, element, context) - }), - Component::Combinator(_) => unsafe { - debug_unreachable!("Shouldn't try to selector-match combinators") - }, - Component::RelativeSelectorAnchor => { - let anchor = context.shared.relative_selector_anchor(); - debug_assert!( - anchor.is_some(), - "Relative selector outside of relative selector matching?" - ); - anchor.map_or(false, |a| a == element.opaque()) - }, - } -} - -#[inline(always)] -fn select_name<'a, E: Element, T: PartialEq>( - element: &E, - local_name: &'a T, - local_name_lower: &'a T, -) -> &'a T { - if local_name == local_name_lower || element.is_html_element_in_html_document() { - local_name_lower - } else { - local_name - } -} - -#[inline(always)] -fn to_unconditional_case_sensitivity<'a, E: Element>( - parsed: ParsedCaseSensitivity, - element: &E, -) -> CaseSensitivity { - match parsed { - ParsedCaseSensitivity::CaseSensitive | ParsedCaseSensitivity::ExplicitCaseSensitive => { - CaseSensitivity::CaseSensitive - }, - ParsedCaseSensitivity::AsciiCaseInsensitive => CaseSensitivity::AsciiCaseInsensitive, - ParsedCaseSensitivity::AsciiCaseInsensitiveIfInHtmlElementInHtmlDocument => { - if element.is_html_element_in_html_document() { - CaseSensitivity::AsciiCaseInsensitive - } else { - CaseSensitivity::CaseSensitive - } - }, - } -} - -fn matches_generic_nth_child( - element: &E, - context: &mut MatchingContext, - nth_data: &NthSelectorData, - selectors: &[Selector], -) -> bool -where - E: Element, -{ - if element.ignores_nth_child_selectors() { - return false; - } - - let NthSelectorData { ty, a, b, .. } = *nth_data; - let is_of_type = ty.is_of_type(); - if ty.is_only() { - debug_assert!( - selectors.is_empty(), - ":only-child and :only-of-type cannot have a selector list!" - ); - return matches_generic_nth_child( - element, - context, - &NthSelectorData::first(is_of_type), - selectors, - ) && matches_generic_nth_child( - element, - context, - &NthSelectorData::last(is_of_type), - selectors, - ); - } - - let is_from_end = ty.is_from_end(); - - // It's useful to know whether this can only select the first/last element - // child for optimization purposes, see the `HAS_EDGE_CHILD_SELECTOR` flag. - let is_edge_child_selector = a == 0 && b == 1 && !is_of_type && selectors.is_empty(); - - if context.needs_selector_flags() { - let mut flags = if is_edge_child_selector { - ElementSelectorFlags::HAS_EDGE_CHILD_SELECTOR - } else if is_from_end { - ElementSelectorFlags::HAS_SLOW_SELECTOR - } else { - ElementSelectorFlags::HAS_SLOW_SELECTOR_LATER_SIBLINGS - }; - if !selectors.is_empty() { - flags |= ElementSelectorFlags::HAS_SLOW_SELECTOR_NTH_OF; - } - element.apply_selector_flags(flags); - } - - if !selectors.is_empty() && !list_matches_complex_selector(selectors, element, context) { - return false; - } - - // :first/last-child are rather trivial to match, don't bother with the - // cache. - if is_edge_child_selector { - return if is_from_end { - element.next_sibling_element() - } else { - element.prev_sibling_element() - } - .is_none(); - } - - // Lookup or compute the index. - let index = if let Some(i) = context - .nth_index_cache(is_of_type, is_from_end, selectors) - .lookup(element.opaque()) - { - i - } else { - let i = nth_child_index( - element, - context, - selectors, - is_of_type, - is_from_end, - /* check_cache = */ true, - ); - context - .nth_index_cache(is_of_type, is_from_end, selectors) - .insert(element.opaque(), i); - i - }; - debug_assert_eq!( - index, - nth_child_index( - element, - context, - selectors, - is_of_type, - is_from_end, - /* check_cache = */ false - ), - "invalid cache" - ); - - // Is there a non-negative integer n such that An+B=index? - match index.checked_sub(b) { - None => false, - Some(an) => match an.checked_div(a) { - Some(n) => n >= 0 && a * n == an, - None /* a == 0 */ => an == 0, - }, - } -} - -#[inline] -fn nth_child_index( - element: &E, - context: &mut MatchingContext, - selectors: &[Selector], - is_of_type: bool, - is_from_end: bool, - check_cache: bool, -) -> i32 -where - E: Element, -{ - // The traversal mostly processes siblings left to right. So when we walk - // siblings to the right when computing NthLast/NthLastOfType we're unlikely - // to get cache hits along the way. As such, we take the hit of walking the - // siblings to the left checking the cache in the is_from_end case (this - // matches what Gecko does). The indices-from-the-left is handled during the - // regular look further below. - if check_cache && - is_from_end && - !context - .nth_index_cache(is_of_type, is_from_end, selectors) - .is_empty() - { - let mut index: i32 = 1; - let mut curr = element.clone(); - while let Some(e) = curr.prev_sibling_element() { - curr = e; - let matches = if is_of_type { - element.is_same_type(&curr) - } else if !selectors.is_empty() { - list_matches_complex_selector(selectors, &curr, context) - } else { - true - }; - if !matches { - continue; - } - if let Some(i) = context - .nth_index_cache(is_of_type, is_from_end, selectors) - .lookup(curr.opaque()) - { - return i - index; - } - index += 1; - } - } - - let mut index: i32 = 1; - let mut curr = element.clone(); - let next = |e: E| { - if is_from_end { - e.next_sibling_element() - } else { - e.prev_sibling_element() - } - }; - while let Some(e) = next(curr) { - curr = e; - let matches = if is_of_type { - element.is_same_type(&curr) - } else if !selectors.is_empty() { - list_matches_complex_selector(selectors, &curr, context) - } else { - true - }; - if !matches { - continue; - } - // If we're computing indices from the left, check each element in the - // cache. We handle the indices-from-the-right case at the top of this - // function. - if !is_from_end && check_cache { - if let Some(i) = context - .nth_index_cache(is_of_type, is_from_end, selectors) - .lookup(curr.opaque()) - { - return i + index; - } - } - index += 1; - } - - index -} diff --git a/components/selectors/nth_index_cache.rs b/components/selectors/nth_index_cache.rs deleted file mode 100644 index b4b41578d02..00000000000 --- a/components/selectors/nth_index_cache.rs +++ /dev/null @@ -1,102 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ - -use std::hash::Hash; - -use crate::{parser::Selector, tree::OpaqueElement, SelectorImpl}; -use fxhash::FxHashMap; - -/// A cache to speed up matching of nth-index-like selectors. -/// -/// See [1] for some discussion around the design tradeoffs. -/// -/// [1] https://bugzilla.mozilla.org/show_bug.cgi?id=1401855#c3 -#[derive(Default)] -pub struct NthIndexCache { - nth: NthIndexCacheInner, - nth_of_selectors: NthIndexOfSelectorsCaches, - nth_last: NthIndexCacheInner, - nth_last_of_selectors: NthIndexOfSelectorsCaches, - nth_of_type: NthIndexCacheInner, - nth_last_of_type: NthIndexCacheInner, -} - -impl NthIndexCache { - /// Gets the appropriate cache for the given parameters. - pub fn get( - &mut self, - is_of_type: bool, - is_from_end: bool, - selectors: &[Selector], - ) -> &mut NthIndexCacheInner { - if is_of_type { - return if is_from_end { - &mut self.nth_last_of_type - } else { - &mut self.nth_of_type - }; - } - if !selectors.is_empty() { - return if is_from_end { - self.nth_last_of_selectors.lookup(selectors) - } else { - self.nth_of_selectors.lookup(selectors) - }; - } - if is_from_end { - &mut self.nth_last - } else { - &mut self.nth - } - } -} - -#[derive(Hash, Eq, PartialEq)] -struct SelectorListCacheKey(usize); - -/// Use the selector list's pointer as the cache key -impl SelectorListCacheKey { - // :nth-child of selectors are reference-counted with `ThinArc`, so we know their pointers are stable. - fn new(selectors: &[Selector]) -> Self { - Self(selectors.as_ptr() as usize) - } -} - -/// Use a different map of cached indices per :nth-child's or :nth-last-child's selector list -#[derive(Default)] -pub struct NthIndexOfSelectorsCaches(FxHashMap); - -/// Get or insert a map of cached incides for the selector list of this -/// particular :nth-child or :nth-last-child pseudoclass -impl NthIndexOfSelectorsCaches { - pub fn lookup( - &mut self, - selectors: &[Selector], - ) -> &mut NthIndexCacheInner { - self.0 - .entry(SelectorListCacheKey::new(selectors)) - .or_default() - } -} - -/// The concrete per-pseudo-class cache. -#[derive(Default)] -pub struct NthIndexCacheInner(FxHashMap); - -impl NthIndexCacheInner { - /// Does a lookup for a given element in the cache. - pub fn lookup(&mut self, el: OpaqueElement) -> Option { - self.0.get(&el).copied() - } - - /// Inserts an entry into the cache. - pub fn insert(&mut self, element: OpaqueElement, index: i32) { - self.0.insert(element, index); - } - - /// Returns whether the cache is empty. - pub fn is_empty(&self) -> bool { - self.0.is_empty() - } -} diff --git a/components/selectors/parser.rs b/components/selectors/parser.rs deleted file mode 100644 index 2c44da8018e..00000000000 --- a/components/selectors/parser.rs +++ /dev/null @@ -1,4140 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ - -use crate::attr::{AttrSelectorOperator, AttrSelectorWithOptionalNamespace}; -use crate::attr::{NamespaceConstraint, ParsedAttrSelectorOperation, ParsedCaseSensitivity}; -use crate::bloom::BLOOM_HASH_MASK; -use crate::builder::{ - relative_selector_list_specificity_and_flags, selector_list_specificity_and_flags, - SelectorBuilder, SelectorFlags, Specificity, SpecificityAndFlags, -}; -use crate::context::QuirksMode; -use crate::sink::Push; -use crate::visitor::SelectorListKind; -pub use crate::visitor::SelectorVisitor; -use bitflags::bitflags; -use cssparser::parse_nth; -use cssparser::{BasicParseError, BasicParseErrorKind, ParseError, ParseErrorKind}; -use cssparser::{CowRcStr, Delimiter, SourceLocation}; -use cssparser::{Parser as CssParser, ToCss, Token}; -use precomputed_hash::PrecomputedHash; -use servo_arc::{HeaderWithLength, ThinArc, UniqueArc}; -use smallvec::SmallVec; -use std::borrow::{Borrow, Cow}; -use std::fmt::{self, Debug}; -use std::iter::Rev; -use std::slice; - -/// A trait that represents a pseudo-element. -pub trait PseudoElement: Sized + ToCss { - /// The `SelectorImpl` this pseudo-element is used for. - type Impl: SelectorImpl; - - /// Whether the pseudo-element supports a given state selector to the right - /// of it. - fn accepts_state_pseudo_classes(&self) -> bool { - false - } - - /// Whether this pseudo-element is valid after a ::slotted(..) pseudo. - fn valid_after_slotted(&self) -> bool { - false - } -} - -/// A trait that represents a pseudo-class. -pub trait NonTSPseudoClass: Sized + ToCss { - /// The `SelectorImpl` this pseudo-element is used for. - type Impl: SelectorImpl; - - /// Whether this pseudo-class is :active or :hover. - fn is_active_or_hover(&self) -> bool; - - /// Whether this pseudo-class belongs to: - /// - /// https://drafts.csswg.org/selectors-4/#useraction-pseudos - fn is_user_action_state(&self) -> bool; - - fn visit(&self, _visitor: &mut V) -> bool - where - V: SelectorVisitor, - { - true - } -} - -/// Returns a Cow::Borrowed if `s` is already ASCII lowercase, and a -/// Cow::Owned if `s` had to be converted into ASCII lowercase. -fn to_ascii_lowercase(s: &str) -> Cow { - if let Some(first_uppercase) = s.bytes().position(|byte| byte >= b'A' && byte <= b'Z') { - let mut string = s.to_owned(); - string[first_uppercase..].make_ascii_lowercase(); - string.into() - } else { - s.into() - } -} - -bitflags! { - /// Flags that indicate at which point of parsing a selector are we. - struct SelectorParsingState: u8 { - /// Whether we should avoid adding default namespaces to selectors that - /// aren't type or universal selectors. - const SKIP_DEFAULT_NAMESPACE = 1 << 0; - - /// Whether we've parsed a ::slotted() pseudo-element already. - /// - /// If so, then we can only parse a subset of pseudo-elements, and - /// whatever comes after them if so. - const AFTER_SLOTTED = 1 << 1; - /// Whether we've parsed a ::part() pseudo-element already. - /// - /// If so, then we can only parse a subset of pseudo-elements, and - /// whatever comes after them if so. - const AFTER_PART = 1 << 2; - /// Whether we've parsed a pseudo-element (as in, an - /// `Impl::PseudoElement` thus not accounting for `::slotted` or - /// `::part`) already. - /// - /// If so, then other pseudo-elements and most other selectors are - /// disallowed. - const AFTER_PSEUDO_ELEMENT = 1 << 3; - /// Whether we've parsed a non-stateful pseudo-element (again, as-in - /// `Impl::PseudoElement`) already. If so, then other pseudo-classes are - /// disallowed. If this flag is set, `AFTER_PSEUDO_ELEMENT` must be set - /// as well. - const AFTER_NON_STATEFUL_PSEUDO_ELEMENT = 1 << 4; - - /// Whether we are after any of the pseudo-like things. - const AFTER_PSEUDO = Self::AFTER_PART.bits | Self::AFTER_SLOTTED.bits | Self::AFTER_PSEUDO_ELEMENT.bits; - - /// Whether we explicitly disallow combinators. - const DISALLOW_COMBINATORS = 1 << 5; - - /// Whether we explicitly disallow pseudo-element-like things. - const DISALLOW_PSEUDOS = 1 << 6; - - /// Whether we explicitly disallow relative selectors (i.e. `:has()`). - const DISALLOW_RELATIVE_SELECTOR = 1 << 7; - } -} - -impl SelectorParsingState { - #[inline] - fn allows_pseudos(self) -> bool { - // NOTE(emilio): We allow pseudos after ::part and such. - !self.intersects(Self::AFTER_PSEUDO_ELEMENT | Self::DISALLOW_PSEUDOS) - } - - #[inline] - fn allows_slotted(self) -> bool { - !self.intersects(Self::AFTER_PSEUDO | Self::DISALLOW_PSEUDOS) - } - - #[inline] - fn allows_part(self) -> bool { - !self.intersects(Self::AFTER_PSEUDO | Self::DISALLOW_PSEUDOS) - } - - // TODO(emilio): Maybe some of these should be allowed, but this gets us on - // the safe side for now, matching previous behavior. Gotta be careful with - // the ones like :-moz-any, which allow nested selectors but don't carry the - // state, and so on. - #[inline] - fn allows_custom_functional_pseudo_classes(self) -> bool { - !self.intersects(Self::AFTER_PSEUDO) - } - - #[inline] - fn allows_non_functional_pseudo_classes(self) -> bool { - !self.intersects(Self::AFTER_SLOTTED | Self::AFTER_NON_STATEFUL_PSEUDO_ELEMENT) - } - - #[inline] - fn allows_tree_structural_pseudo_classes(self) -> bool { - !self.intersects(Self::AFTER_PSEUDO) - } - - #[inline] - fn allows_combinators(self) -> bool { - !self.intersects(Self::DISALLOW_COMBINATORS) - } -} - -pub type SelectorParseError<'i> = ParseError<'i, SelectorParseErrorKind<'i>>; - -#[derive(Clone, Debug, PartialEq)] -pub enum SelectorParseErrorKind<'i> { - NoQualifiedNameInAttributeSelector(Token<'i>), - EmptySelector, - DanglingCombinator, - NonCompoundSelector, - NonPseudoElementAfterSlotted, - InvalidPseudoElementAfterSlotted, - InvalidPseudoElementInsideWhere, - InvalidState, - UnexpectedTokenInAttributeSelector(Token<'i>), - PseudoElementExpectedColon(Token<'i>), - PseudoElementExpectedIdent(Token<'i>), - NoIdentForPseudo(Token<'i>), - UnsupportedPseudoClassOrElement(CowRcStr<'i>), - UnexpectedIdent(CowRcStr<'i>), - ExpectedNamespace(CowRcStr<'i>), - ExpectedBarInAttr(Token<'i>), - BadValueInAttr(Token<'i>), - InvalidQualNameInAttr(Token<'i>), - ExplicitNamespaceUnexpectedToken(Token<'i>), - ClassNeedsIdent(Token<'i>), -} - -macro_rules! with_all_bounds { - ( - [ $( $InSelector: tt )* ] - [ $( $CommonBounds: tt )* ] - [ $( $FromStr: tt )* ] - ) => { - /// This trait allows to define the parser implementation in regards - /// of pseudo-classes/elements - /// - /// NB: We need Clone so that we can derive(Clone) on struct with that - /// are parameterized on SelectorImpl. See - /// - pub trait SelectorImpl: Clone + Debug + Sized + 'static { - type ExtraMatchingData<'a>: Sized + Default; - type AttrValue: $($InSelector)*; - type Identifier: $($InSelector)*; - type LocalName: $($InSelector)* + Borrow; - type NamespaceUrl: $($CommonBounds)* + Default + Borrow; - type NamespacePrefix: $($InSelector)* + Default; - type BorrowedNamespaceUrl: ?Sized + Eq; - type BorrowedLocalName: ?Sized + Eq; - - /// non tree-structural pseudo-classes - /// (see: https://drafts.csswg.org/selectors/#structural-pseudos) - type NonTSPseudoClass: $($CommonBounds)* + NonTSPseudoClass; - - /// pseudo-elements - type PseudoElement: $($CommonBounds)* + PseudoElement; - - /// Whether attribute hashes should be collected for filtering - /// purposes. - fn should_collect_attr_hash(_name: &Self::LocalName) -> bool { - false - } - } - } -} - -macro_rules! with_bounds { - ( [ $( $CommonBounds: tt )* ] [ $( $FromStr: tt )* ]) => { - with_all_bounds! { - [$($CommonBounds)* + $($FromStr)* + ToCss] - [$($CommonBounds)*] - [$($FromStr)*] - } - } -} - -with_bounds! { - [Clone + Eq] - [for<'a> From<&'a str>] -} - -pub trait Parser<'i> { - type Impl: SelectorImpl; - type Error: 'i + From>; - - /// Whether to parse the `::slotted()` pseudo-element. - fn parse_slotted(&self) -> bool { - false - } - - /// Whether to parse the `::part()` pseudo-element. - fn parse_part(&self) -> bool { - false - } - - /// Whether to parse the selector list of nth-child() or nth-last-child(). - fn parse_nth_child_of(&self) -> bool { - false - } - - /// Whether to parse the `:where` pseudo-class. - fn parse_is_and_where(&self) -> bool { - false - } - - /// Whether to parse the :has pseudo-class. - fn parse_has(&self) -> bool { - false - } - - /// Whether to parse the '&' delimiter as a parent selector. - fn parse_parent_selector(&self) -> bool { - false - } - - /// Whether the given function name is an alias for the `:is()` function. - fn is_is_alias(&self, _name: &str) -> bool { - false - } - - /// Whether to parse the `:host` pseudo-class. - fn parse_host(&self) -> bool { - false - } - - /// Whether to allow forgiving selector-list parsing. - fn allow_forgiving_selectors(&self) -> bool { - true - } - - /// This function can return an "Err" pseudo-element in order to support CSS2.1 - /// pseudo-elements. - fn parse_non_ts_pseudo_class( - &self, - location: SourceLocation, - name: CowRcStr<'i>, - ) -> Result<::NonTSPseudoClass, ParseError<'i, Self::Error>> { - Err( - location.new_custom_error(SelectorParseErrorKind::UnsupportedPseudoClassOrElement( - name, - )), - ) - } - - fn parse_non_ts_functional_pseudo_class<'t>( - &self, - name: CowRcStr<'i>, - arguments: &mut CssParser<'i, 't>, - ) -> Result<::NonTSPseudoClass, ParseError<'i, Self::Error>> { - Err( - arguments.new_custom_error(SelectorParseErrorKind::UnsupportedPseudoClassOrElement( - name, - )), - ) - } - - fn parse_pseudo_element( - &self, - location: SourceLocation, - name: CowRcStr<'i>, - ) -> Result<::PseudoElement, ParseError<'i, Self::Error>> { - Err( - location.new_custom_error(SelectorParseErrorKind::UnsupportedPseudoClassOrElement( - name, - )), - ) - } - - fn parse_functional_pseudo_element<'t>( - &self, - name: CowRcStr<'i>, - arguments: &mut CssParser<'i, 't>, - ) -> Result<::PseudoElement, ParseError<'i, Self::Error>> { - Err( - arguments.new_custom_error(SelectorParseErrorKind::UnsupportedPseudoClassOrElement( - name, - )), - ) - } - - fn default_namespace(&self) -> Option<::NamespaceUrl> { - None - } - - fn namespace_for_prefix( - &self, - _prefix: &::NamespacePrefix, - ) -> Option<::NamespaceUrl> { - None - } -} - -#[derive(Clone, Debug, Eq, PartialEq, ToShmem)] -#[shmem(no_bounds)] -pub struct SelectorList( - #[shmem(field_bound)] pub SmallVec<[Selector; 1]>, -); - -/// Whether or not we're using forgiving parsing mode -enum ForgivingParsing { - /// Discard the entire selector list upon encountering any invalid selector. - /// This is the default behavior for almost all of CSS. - No, - /// Ignore invalid selectors, potentially creating an empty selector list. - /// - /// This is the error recovery mode of :is() and :where() - Yes, -} - -/// Flag indicating if we're parsing relative selectors. -#[derive(Copy, Clone, PartialEq)] -enum ParseRelative { - /// Expect selectors to start with a combinator, assuming descendant combinator if not present. - Yes, - /// Treat as parse error if any selector begins with a combinator. - No, -} - -impl SelectorList { - /// Returns a selector list with a single `&` - pub fn ampersand() -> Self { - Self(smallvec::smallvec![Selector::ampersand()]) - } - - /// Parse a comma-separated list of Selectors. - /// - /// - /// Return the Selectors or Err if there is an invalid selector. - pub fn parse<'i, 't, P>( - parser: &P, - input: &mut CssParser<'i, 't>, - ) -> Result> - where - P: Parser<'i, Impl = Impl>, - { - Self::parse_with_state( - parser, - input, - SelectorParsingState::empty(), - ForgivingParsing::No, - ParseRelative::No, - ) - } - - #[inline] - fn parse_with_state<'i, 't, P>( - parser: &P, - input: &mut CssParser<'i, 't>, - state: SelectorParsingState, - recovery: ForgivingParsing, - parse_relative: ParseRelative, - ) -> Result> - where - P: Parser<'i, Impl = Impl>, - { - let mut values = SmallVec::new(); - loop { - let selector = input.parse_until_before(Delimiter::Comma, |i| { - parse_selector(parser, i, state, parse_relative) - }); - - let was_ok = selector.is_ok(); - match selector { - Ok(selector) => values.push(selector), - Err(err) => match recovery { - ForgivingParsing::No => return Err(err), - ForgivingParsing::Yes => { - if !parser.allow_forgiving_selectors() { - return Err(err); - } - }, - }, - } - - loop { - match input.next() { - Err(_) => return Ok(SelectorList(values)), - Ok(&Token::Comma) => break, - Ok(_) => { - debug_assert!(!was_ok, "Shouldn't have got a selector if getting here"); - }, - } - } - } - } - - /// Creates a SelectorList from a Vec of selectors. Used in tests. - #[allow(dead_code)] - pub(crate) fn from_vec(v: Vec>) -> Self { - SelectorList(SmallVec::from_vec(v)) - } -} - -/// Parses one compound selector suitable for nested stuff like :-moz-any, etc. -fn parse_inner_compound_selector<'i, 't, P, Impl>( - parser: &P, - input: &mut CssParser<'i, 't>, - state: SelectorParsingState, -) -> Result, ParseError<'i, P::Error>> -where - P: Parser<'i, Impl = Impl>, - Impl: SelectorImpl, -{ - parse_selector( - parser, - input, - state | SelectorParsingState::DISALLOW_PSEUDOS | SelectorParsingState::DISALLOW_COMBINATORS, - ParseRelative::No, - ) -} - -/// Ancestor hashes for the bloom filter. We precompute these and store them -/// inline with selectors to optimize cache performance during matching. -/// This matters a lot. -/// -/// We use 4 hashes, which is copied from Gecko, who copied it from WebKit. -/// Note that increasing the number of hashes here will adversely affect the -/// cache hit when fast-rejecting long lists of Rules with inline hashes. -/// -/// Because the bloom filter only uses the bottom 24 bits of the hash, we pack -/// the fourth hash into the upper bits of the first three hashes in order to -/// shrink Rule (whose size matters a lot). This scheme minimizes the runtime -/// overhead of the packing for the first three hashes (we just need to mask -/// off the upper bits) at the expense of making the fourth somewhat more -/// complicated to assemble, because we often bail out before checking all the -/// hashes. -#[derive(Clone, Debug, Eq, PartialEq)] -pub struct AncestorHashes { - pub packed_hashes: [u32; 3], -} - -fn collect_ancestor_hashes( - iter: SelectorIter, - quirks_mode: QuirksMode, - hashes: &mut [u32; 4], - len: &mut usize, -) -> bool -where - Impl::Identifier: PrecomputedHash, - Impl::LocalName: PrecomputedHash, - Impl::NamespaceUrl: PrecomputedHash, -{ - for component in AncestorIter::new(iter) { - let hash = match *component { - Component::LocalName(LocalName { - ref name, - ref lower_name, - }) => { - // Only insert the local-name into the filter if it's all - // lowercase. Otherwise we would need to test both hashes, and - // our data structures aren't really set up for that. - if name != lower_name { - continue; - } - name.precomputed_hash() - }, - Component::DefaultNamespace(ref url) | Component::Namespace(_, ref url) => { - url.precomputed_hash() - }, - // In quirks mode, class and id selectors should match - // case-insensitively, so just avoid inserting them into the filter. - Component::ID(ref id) if quirks_mode != QuirksMode::Quirks => id.precomputed_hash(), - Component::Class(ref class) if quirks_mode != QuirksMode::Quirks => { - class.precomputed_hash() - }, - Component::AttributeInNoNamespace { ref local_name, .. } - if Impl::should_collect_attr_hash(local_name) => - { - // AttributeInNoNamespace is only used when local_name == - // local_name_lower. - local_name.precomputed_hash() - }, - Component::AttributeInNoNamespaceExists { - ref local_name, - ref local_name_lower, - .. - } => { - // Only insert the local-name into the filter if it's all - // lowercase. Otherwise we would need to test both hashes, and - // our data structures aren't really set up for that. - if local_name != local_name_lower || !Impl::should_collect_attr_hash(local_name) { - continue; - } - local_name.precomputed_hash() - }, - Component::AttributeOther(ref selector) => { - if selector.local_name != selector.local_name_lower || - !Impl::should_collect_attr_hash(&selector.local_name) - { - continue; - } - selector.local_name.precomputed_hash() - }, - Component::Is(ref list) | Component::Where(ref list) => { - // :where and :is OR their selectors, so we can't put any hash - // in the filter if there's more than one selector, as that'd - // exclude elements that may match one of the other selectors. - if list.len() == 1 && - !collect_ancestor_hashes(list[0].iter(), quirks_mode, hashes, len) - { - return false; - } - continue; - }, - _ => continue, - }; - - hashes[*len] = hash & BLOOM_HASH_MASK; - *len += 1; - if *len == hashes.len() { - return false; - } - } - true -} - -impl AncestorHashes { - pub fn new(selector: &Selector, quirks_mode: QuirksMode) -> Self - where - Impl::Identifier: PrecomputedHash, - Impl::LocalName: PrecomputedHash, - Impl::NamespaceUrl: PrecomputedHash, - { - // Compute ancestor hashes for the bloom filter. - let mut hashes = [0u32; 4]; - let mut len = 0; - collect_ancestor_hashes(selector.iter(), quirks_mode, &mut hashes, &mut len); - debug_assert!(len <= 4); - - // Now, pack the fourth hash (if it exists) into the upper byte of each of - // the other three hashes. - if len == 4 { - let fourth = hashes[3]; - hashes[0] |= (fourth & 0x000000ff) << 24; - hashes[1] |= (fourth & 0x0000ff00) << 16; - hashes[2] |= (fourth & 0x00ff0000) << 8; - } - - AncestorHashes { - packed_hashes: [hashes[0], hashes[1], hashes[2]], - } - } - - /// Returns the fourth hash, reassembled from parts. - pub fn fourth_hash(&self) -> u32 { - ((self.packed_hashes[0] & 0xff000000) >> 24) | - ((self.packed_hashes[1] & 0xff000000) >> 16) | - ((self.packed_hashes[2] & 0xff000000) >> 8) - } -} - -#[inline] -pub fn namespace_empty_string() -> Impl::NamespaceUrl { - // Rust type’s default, not default namespace - Impl::NamespaceUrl::default() -} - -/// A Selector stores a sequence of simple selectors and combinators. The -/// iterator classes allow callers to iterate at either the raw sequence level or -/// at the level of sequences of simple selectors separated by combinators. Most -/// callers want the higher-level iterator. -/// -/// We store compound selectors internally right-to-left (in matching order). -/// Additionally, we invert the order of top-level compound selectors so that -/// each one matches left-to-right. This is because matching namespace, local name, -/// id, and class are all relatively cheap, whereas matching pseudo-classes might -/// be expensive (depending on the pseudo-class). Since authors tend to put the -/// pseudo-classes on the right, it's faster to start matching on the left. -/// -/// This reordering doesn't change the semantics of selector matching, and we -/// handle it in to_css to make it invisible to serialization. -#[derive(Clone, Eq, PartialEq, ToShmem)] -#[shmem(no_bounds)] -pub struct Selector( - #[shmem(field_bound)] ThinArc>, -); - -impl Selector { - /// See Arc::mark_as_intentionally_leaked - pub fn mark_as_intentionally_leaked(&self) { - self.0.with_arc(|a| a.mark_as_intentionally_leaked()) - } - - fn ampersand() -> Self { - Self(ThinArc::from_header_and_iter( - SpecificityAndFlags { - specificity: 0, - flags: SelectorFlags::HAS_PARENT, - }, - std::iter::once(Component::ParentSelector), - )) - } - - #[inline] - pub fn specificity(&self) -> u32 { - self.0.header.header.specificity() - } - - #[inline] - fn flags(&self) -> SelectorFlags { - self.0.header.header.flags - } - - #[inline] - pub fn has_pseudo_element(&self) -> bool { - self.0.header.header.has_pseudo_element() - } - - #[inline] - pub fn has_parent_selector(&self) -> bool { - self.0.header.header.has_parent_selector() - } - - #[inline] - pub fn is_slotted(&self) -> bool { - self.0.header.header.is_slotted() - } - - #[inline] - pub fn is_part(&self) -> bool { - self.0.header.header.is_part() - } - - #[inline] - pub fn parts(&self) -> Option<&[Impl::Identifier]> { - if !self.is_part() { - return None; - } - - let mut iter = self.iter(); - if self.has_pseudo_element() { - // Skip the pseudo-element. - for _ in &mut iter {} - - let combinator = iter.next_sequence()?; - debug_assert_eq!(combinator, Combinator::PseudoElement); - } - - for component in iter { - if let Component::Part(ref part) = *component { - return Some(part); - } - } - - debug_assert!(false, "is_part() lied somehow?"); - None - } - - #[inline] - pub fn pseudo_element(&self) -> Option<&Impl::PseudoElement> { - if !self.has_pseudo_element() { - return None; - } - - for component in self.iter() { - if let Component::PseudoElement(ref pseudo) = *component { - return Some(pseudo); - } - } - - debug_assert!(false, "has_pseudo_element lied!"); - None - } - - /// Whether this selector (pseudo-element part excluded) matches every element. - /// - /// Used for "pre-computed" pseudo-elements in components/style/stylist.rs - #[inline] - pub fn is_universal(&self) -> bool { - self.iter_raw_match_order().all(|c| { - matches!( - *c, - Component::ExplicitUniversalType | - Component::ExplicitAnyNamespace | - Component::Combinator(Combinator::PseudoElement) | - Component::PseudoElement(..) - ) - }) - } - - /// Returns an iterator over this selector in matching order (right-to-left). - /// When a combinator is reached, the iterator will return None, and - /// next_sequence() may be called to continue to the next sequence. - #[inline] - pub fn iter(&self) -> SelectorIter { - SelectorIter { - iter: self.iter_raw_match_order(), - next_combinator: None, - } - } - - /// Same as `iter()`, but skips `RelativeSelectorAnchor` and its associated combinator. - #[inline] - pub fn iter_skip_relative_selector_anchor(&self) -> SelectorIter { - if cfg!(debug_assertions) { - let mut selector_iter = self.iter_raw_parse_order_from(0); - assert!( - matches!( - selector_iter.next().unwrap(), - Component::RelativeSelectorAnchor - ), - "Relative selector does not start with RelativeSelectorAnchor" - ); - assert!( - selector_iter.next().unwrap().is_combinator(), - "Relative combinator does not exist" - ); - } - - SelectorIter { - iter: self.0.slice[..self.len() - 2].iter(), - next_combinator: None, - } - } - - /// Whether this selector is a featureless :host selector, with no - /// combinators to the left, and optionally has a pseudo-element to the - /// right. - #[inline] - pub fn is_featureless_host_selector_or_pseudo_element(&self) -> bool { - let mut iter = self.iter(); - if !self.has_pseudo_element() { - return iter.is_featureless_host_selector(); - } - - // Skip the pseudo-element. - for _ in &mut iter {} - - match iter.next_sequence() { - None => return false, - Some(combinator) => { - debug_assert_eq!(combinator, Combinator::PseudoElement); - }, - } - - iter.is_featureless_host_selector() - } - - /// Returns an iterator over this selector in matching order (right-to-left), - /// skipping the rightmost |offset| Components. - #[inline] - pub fn iter_from(&self, offset: usize) -> SelectorIter { - let iter = self.0.slice[offset..].iter(); - SelectorIter { - iter, - next_combinator: None, - } - } - - /// Returns the combinator at index `index` (zero-indexed from the right), - /// or panics if the component is not a combinator. - #[inline] - pub fn combinator_at_match_order(&self, index: usize) -> Combinator { - match self.0.slice[index] { - Component::Combinator(c) => c, - ref other => panic!( - "Not a combinator: {:?}, {:?}, index: {}", - other, self, index - ), - } - } - - /// Returns an iterator over the entire sequence of simple selectors and - /// combinators, in matching order (from right to left). - #[inline] - pub fn iter_raw_match_order(&self) -> slice::Iter> { - self.0.slice.iter() - } - - /// Returns the combinator at index `index` (zero-indexed from the left), - /// or panics if the component is not a combinator. - #[inline] - pub fn combinator_at_parse_order(&self, index: usize) -> Combinator { - match self.0.slice[self.len() - index - 1] { - Component::Combinator(c) => c, - ref other => panic!( - "Not a combinator: {:?}, {:?}, index: {}", - other, self, index - ), - } - } - - /// Returns an iterator over the sequence of simple selectors and - /// combinators, in parse order (from left to right), starting from - /// `offset`. - #[inline] - pub fn iter_raw_parse_order_from(&self, offset: usize) -> Rev>> { - self.0.slice[..self.len() - offset].iter().rev() - } - - /// Creates a Selector from a vec of Components, specified in parse order. Used in tests. - #[allow(dead_code)] - pub(crate) fn from_vec( - vec: Vec>, - specificity: u32, - flags: SelectorFlags, - ) -> Self { - let mut builder = SelectorBuilder::default(); - for component in vec.into_iter() { - if let Some(combinator) = component.as_combinator() { - builder.push_combinator(combinator); - } else { - builder.push_simple_selector(component); - } - } - let spec = SpecificityAndFlags { specificity, flags }; - Selector(builder.build_with_specificity_and_flags(spec)) - } - - pub fn replace_parent_selector(&self, parent: &[Selector]) -> Self { - // FIXME(emilio): Shouldn't allow replacing if parent has a pseudo-element selector - // or what not. - let flags = self.flags() - SelectorFlags::HAS_PARENT; - let mut specificity = Specificity::from(self.specificity()); - let parent_specificity = - Specificity::from(selector_list_specificity_and_flags(parent.iter()).specificity()); - - // The specificity at this point will be wrong, we replace it by the correct one after the - // fact. - let specificity_and_flags = SpecificityAndFlags { - specificity: self.specificity(), - flags, - }; - - fn replace_parent_on_selector_list( - orig: &[Selector], - parent: &[Selector], - specificity: &mut Specificity, - with_specificity: bool, - ) -> Vec> { - let mut any = false; - - let result = orig - .iter() - .map(|s| { - if !s.has_parent_selector() { - return s.clone(); - } - any = true; - s.replace_parent_selector(parent) - }) - .collect(); - - if !any || !with_specificity { - return result; - } - - *specificity += Specificity::from( - selector_list_specificity_and_flags(result.iter()).specificity - - selector_list_specificity_and_flags(orig.iter()).specificity, - ); - result - } - - fn replace_parent_on_relative_selector_list( - orig: &[RelativeSelector], - parent: &[Selector], - specificity: &mut Specificity, - ) -> Vec> { - let mut any = false; - - let result = orig - .iter() - .map(|s| { - if !s.selector.has_parent_selector() { - return s.clone(); - } - any = true; - RelativeSelector { - match_hint: s.match_hint, - selector: s.selector.replace_parent_selector(parent), - } - }) - .collect(); - - if !any { - return result; - } - - *specificity += Specificity::from( - relative_selector_list_specificity_and_flags(&result).specificity - - relative_selector_list_specificity_and_flags(orig).specificity, - ); - result - } - - fn replace_parent_on_selector( - orig: &Selector, - parent: &[Selector], - specificity: &mut Specificity, - ) -> Selector { - if !orig.has_parent_selector() { - return orig.clone(); - } - let new_selector = orig.replace_parent_selector(parent); - *specificity += Specificity::from(new_selector.specificity() - orig.specificity()); - new_selector - } - - let mut items = if !self.has_parent_selector() { - // Implicit `&` plus descendant combinator. - let iter = self.iter_raw_match_order(); - let len = iter.len() + 2; - specificity += parent_specificity; - let iter = iter - .cloned() - .chain(std::iter::once(Component::Combinator( - Combinator::Descendant, - ))) - .chain(std::iter::once(Component::Is( - parent.to_vec().into_boxed_slice(), - ))); - let header = HeaderWithLength::new(specificity_and_flags, len); - UniqueArc::from_header_and_iter_with_size(header, iter, len) - } else { - let iter = self.iter_raw_match_order().map(|component| { - use self::Component::*; - match *component { - LocalName(..) | - ID(..) | - Class(..) | - AttributeInNoNamespaceExists { .. } | - AttributeInNoNamespace { .. } | - AttributeOther(..) | - ExplicitUniversalType | - ExplicitAnyNamespace | - ExplicitNoNamespace | - DefaultNamespace(..) | - Namespace(..) | - Root | - Empty | - Scope | - Nth(..) | - NonTSPseudoClass(..) | - PseudoElement(..) | - Combinator(..) | - Host(None) | - Part(..) | - RelativeSelectorAnchor => component.clone(), - ParentSelector => { - specificity += parent_specificity; - Is(parent.to_vec().into_boxed_slice()) - }, - Negation(ref selectors) => { - Negation( - replace_parent_on_selector_list( - selectors, - parent, - &mut specificity, - /* with_specificity = */ true, - ) - .into_boxed_slice(), - ) - }, - Is(ref selectors) => { - Is(replace_parent_on_selector_list( - selectors, - parent, - &mut specificity, - /* with_specificity = */ true, - ) - .into_boxed_slice()) - }, - Where(ref selectors) => { - Where( - replace_parent_on_selector_list( - selectors, - parent, - &mut specificity, - /* with_specificity = */ false, - ) - .into_boxed_slice(), - ) - }, - Has(ref selectors) => Has(replace_parent_on_relative_selector_list( - selectors, - parent, - &mut specificity, - ) - .into_boxed_slice()), - - Host(Some(ref selector)) => Host(Some(replace_parent_on_selector( - selector, - parent, - &mut specificity, - ))), - NthOf(ref data) => { - let selectors = replace_parent_on_selector_list( - data.selectors(), - parent, - &mut specificity, - /* with_specificity = */ true, - ); - NthOf(NthOfSelectorData::new( - data.nth_data(), - selectors.into_iter(), - )) - }, - Slotted(ref selector) => Slotted(replace_parent_on_selector( - selector, - parent, - &mut specificity, - )), - } - }); - let header = HeaderWithLength::new(specificity_and_flags, iter.len()); - UniqueArc::from_header_and_iter(header, iter) - }; - items.header_mut().specificity = specificity.into(); - Selector(items.shareable_thin()) - } - - /// Returns count of simple selectors and combinators in the Selector. - #[inline] - pub fn len(&self) -> usize { - self.0.slice.len() - } - - /// Returns the address on the heap of the ThinArc for memory reporting. - pub fn thin_arc_heap_ptr(&self) -> *const ::std::os::raw::c_void { - self.0.heap_ptr() - } - - /// Traverse selector components inside `self`. - /// - /// Implementations of this method should call `SelectorVisitor` methods - /// or other impls of `Visit` as appropriate based on the fields of `Self`. - /// - /// A return value of `false` indicates terminating the traversal. - /// It should be propagated with an early return. - /// On the contrary, `true` indicates that all fields of `self` have been traversed: - /// - /// ```rust,ignore - /// if !visitor.visit_simple_selector(&self.some_simple_selector) { - /// return false; - /// } - /// if !self.some_component.visit(visitor) { - /// return false; - /// } - /// true - /// ``` - pub fn visit(&self, visitor: &mut V) -> bool - where - V: SelectorVisitor, - { - let mut current = self.iter(); - let mut combinator = None; - loop { - if !visitor.visit_complex_selector(combinator) { - return false; - } - - for selector in &mut current { - if !selector.visit(visitor) { - return false; - } - } - - combinator = current.next_sequence(); - if combinator.is_none() { - break; - } - } - - true - } -} - -#[derive(Clone)] -pub struct SelectorIter<'a, Impl: 'a + SelectorImpl> { - iter: slice::Iter<'a, Component>, - next_combinator: Option, -} - -impl<'a, Impl: 'a + SelectorImpl> SelectorIter<'a, Impl> { - /// Prepares this iterator to point to the next sequence to the left, - /// returning the combinator if the sequence was found. - #[inline] - pub fn next_sequence(&mut self) -> Option { - self.next_combinator.take() - } - - /// Whether this selector is a featureless host selector, with no - /// combinators to the left. - #[inline] - pub(crate) fn is_featureless_host_selector(&mut self) -> bool { - self.selector_length() > 0 && - self.all(|component| component.is_host()) && - self.next_sequence().is_none() - } - - #[inline] - pub(crate) fn matches_for_stateless_pseudo_element(&mut self) -> bool { - let first = match self.next() { - Some(c) => c, - // Note that this is the common path that we keep inline: the - // pseudo-element not having anything to its right. - None => return true, - }; - self.matches_for_stateless_pseudo_element_internal(first) - } - - #[inline(never)] - fn matches_for_stateless_pseudo_element_internal(&mut self, first: &Component) -> bool { - if !first.matches_for_stateless_pseudo_element() { - return false; - } - for component in self { - // The only other parser-allowed Components in this sequence are - // state pseudo-classes, or one of the other things that can contain - // them. - if !component.matches_for_stateless_pseudo_element() { - return false; - } - } - true - } - - /// Returns remaining count of the simple selectors and combinators in the Selector. - #[inline] - pub fn selector_length(&self) -> usize { - self.iter.len() - } -} - -impl<'a, Impl: SelectorImpl> Iterator for SelectorIter<'a, Impl> { - type Item = &'a Component; - - #[inline] - fn next(&mut self) -> Option { - debug_assert!( - self.next_combinator.is_none(), - "You should call next_sequence!" - ); - match *self.iter.next()? { - Component::Combinator(c) => { - self.next_combinator = Some(c); - None - }, - ref x => Some(x), - } - } -} - -impl<'a, Impl: SelectorImpl> fmt::Debug for SelectorIter<'a, Impl> { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - let iter = self.iter.clone().rev(); - for component in iter { - component.to_css(f)? - } - Ok(()) - } -} - -/// An iterator over all combinators in a selector. Does not traverse selectors within psuedoclasses. -struct CombinatorIter<'a, Impl: 'a + SelectorImpl>(SelectorIter<'a, Impl>); -impl<'a, Impl: 'a + SelectorImpl> CombinatorIter<'a, Impl> { - fn new(inner: SelectorIter<'a, Impl>) -> Self { - let mut result = CombinatorIter(inner); - result.consume_non_combinators(); - result - } - - fn consume_non_combinators(&mut self) { - while self.0.next().is_some() {} - } -} - -impl<'a, Impl: SelectorImpl> Iterator for CombinatorIter<'a, Impl> { - type Item = Combinator; - fn next(&mut self) -> Option { - let result = self.0.next_sequence(); - self.consume_non_combinators(); - result - } -} - -/// An iterator over all simple selectors belonging to ancestors. -struct AncestorIter<'a, Impl: 'a + SelectorImpl>(SelectorIter<'a, Impl>); -impl<'a, Impl: 'a + SelectorImpl> AncestorIter<'a, Impl> { - /// Creates an AncestorIter. The passed-in iterator is assumed to point to - /// the beginning of the child sequence, which will be skipped. - fn new(inner: SelectorIter<'a, Impl>) -> Self { - let mut result = AncestorIter(inner); - result.skip_until_ancestor(); - result - } - - /// Skips a sequence of simple selectors and all subsequent sequences until - /// a non-pseudo-element ancestor combinator is reached. - fn skip_until_ancestor(&mut self) { - loop { - while self.0.next().is_some() {} - // If this is ever changed to stop at the "pseudo-element" - // combinator, we will need to fix the way we compute hashes for - // revalidation selectors. - if self.0.next_sequence().map_or(true, |x| { - matches!(x, Combinator::Child | Combinator::Descendant) - }) { - break; - } - } - } -} - -impl<'a, Impl: SelectorImpl> Iterator for AncestorIter<'a, Impl> { - type Item = &'a Component; - fn next(&mut self) -> Option { - // Grab the next simple selector in the sequence if available. - let next = self.0.next(); - if next.is_some() { - return next; - } - - // See if there are more sequences. If so, skip any non-ancestor sequences. - if let Some(combinator) = self.0.next_sequence() { - if !matches!(combinator, Combinator::Child | Combinator::Descendant) { - self.skip_until_ancestor(); - } - } - - self.0.next() - } -} - -#[derive(Clone, Copy, Debug, Eq, PartialEq, ToShmem)] -pub enum Combinator { - Child, // > - Descendant, // space - NextSibling, // + - LaterSibling, // ~ - /// A dummy combinator we use to the left of pseudo-elements. - /// - /// It serializes as the empty string, and acts effectively as a child - /// combinator in most cases. If we ever actually start using a child - /// combinator for this, we will need to fix up the way hashes are computed - /// for revalidation selectors. - PseudoElement, - /// Another combinator used for ::slotted(), which represent the jump from - /// a node to its assigned slot. - SlotAssignment, - /// Another combinator used for `::part()`, which represents the jump from - /// the part to the containing shadow host. - Part, -} - -impl Combinator { - /// Returns true if this combinator is a child or descendant combinator. - #[inline] - pub fn is_ancestor(&self) -> bool { - matches!( - *self, - Combinator::Child | - Combinator::Descendant | - Combinator::PseudoElement | - Combinator::SlotAssignment - ) - } - - /// Returns true if this combinator is a pseudo-element combinator. - #[inline] - pub fn is_pseudo_element(&self) -> bool { - matches!(*self, Combinator::PseudoElement) - } - - /// Returns true if this combinator is a next- or later-sibling combinator. - #[inline] - pub fn is_sibling(&self) -> bool { - matches!(*self, Combinator::NextSibling | Combinator::LaterSibling) - } -} - -/// An enum for the different types of :nth- pseudoclasses -#[derive(Copy, Clone, Eq, PartialEq, ToShmem)] -#[shmem(no_bounds)] -pub enum NthType { - Child, - LastChild, - OnlyChild, - OfType, - LastOfType, - OnlyOfType, -} - -impl NthType { - pub fn is_only(self) -> bool { - self == Self::OnlyChild || self == Self::OnlyOfType - } - - pub fn is_of_type(self) -> bool { - self == Self::OfType || self == Self::LastOfType || self == Self::OnlyOfType - } - - pub fn is_from_end(self) -> bool { - self == Self::LastChild || self == Self::LastOfType - } -} - -/// The properties that comprise an :nth- pseudoclass as of Selectors 3 (e.g., -/// nth-child(An+B)). -/// https://www.w3.org/TR/selectors-3/#nth-child-pseudo -#[derive(Copy, Clone, Eq, PartialEq, ToShmem)] -#[shmem(no_bounds)] -pub struct NthSelectorData { - pub ty: NthType, - pub is_function: bool, - pub a: i32, - pub b: i32, -} - -impl NthSelectorData { - /// Returns selector data for :only-{child,of-type} - #[inline] - pub const fn only(of_type: bool) -> Self { - Self { - ty: if of_type { - NthType::OnlyOfType - } else { - NthType::OnlyChild - }, - is_function: false, - a: 0, - b: 1, - } - } - - /// Returns selector data for :first-{child,of-type} - #[inline] - pub const fn first(of_type: bool) -> Self { - Self { - ty: if of_type { - NthType::OfType - } else { - NthType::Child - }, - is_function: false, - a: 0, - b: 1, - } - } - - /// Returns selector data for :last-{child,of-type} - #[inline] - pub const fn last(of_type: bool) -> Self { - Self { - ty: if of_type { - NthType::LastOfType - } else { - NthType::LastChild - }, - is_function: false, - a: 0, - b: 1, - } - } - - /// Writes the beginning of the selector. - #[inline] - fn write_start(&self, dest: &mut W) -> fmt::Result { - dest.write_str(match self.ty { - NthType::Child if self.is_function => ":nth-child(", - NthType::Child => ":first-child", - NthType::LastChild if self.is_function => ":nth-last-child(", - NthType::LastChild => ":last-child", - NthType::OfType if self.is_function => ":nth-of-type(", - NthType::OfType => ":first-of-type", - NthType::LastOfType if self.is_function => ":nth-last-of-type(", - NthType::LastOfType => ":last-of-type", - NthType::OnlyChild => ":only-child", - NthType::OnlyOfType => ":only-of-type", - }) - } - - /// Serialize (part of the CSS Syntax spec, but currently only used here). - /// - #[inline] - fn write_affine(&self, dest: &mut W) -> fmt::Result { - match (self.a, self.b) { - (0, 0) => dest.write_char('0'), - - (1, 0) => dest.write_char('n'), - (-1, 0) => dest.write_str("-n"), - (_, 0) => write!(dest, "{}n", self.a), - - (0, _) => write!(dest, "{}", self.b), - (1, _) => write!(dest, "n{:+}", self.b), - (-1, _) => write!(dest, "-n{:+}", self.b), - (_, _) => write!(dest, "{}n{:+}", self.a, self.b), - } - } -} - -/// The properties that comprise an :nth- pseudoclass as of Selectors 4 (e.g., -/// nth-child(An+B [of S]?)). -/// https://www.w3.org/TR/selectors-4/#nth-child-pseudo -#[derive(Clone, Eq, PartialEq, ToShmem)] -#[shmem(no_bounds)] -pub struct NthOfSelectorData( - #[shmem(field_bound)] ThinArc>, -); - -impl NthOfSelectorData { - /// Returns selector data for :nth-{,last-}{child,of-type}(An+B [of S]) - #[inline] - pub fn new(nth_data: &NthSelectorData, selectors: I) -> Self - where - I: Iterator> + ExactSizeIterator, - { - Self(ThinArc::from_header_and_iter(*nth_data, selectors)) - } - - /// Returns the An+B part of the selector - #[inline] - pub fn nth_data(&self) -> &NthSelectorData { - &self.0.header.header - } - - /// Returns the selector list part of the selector - #[inline] - pub fn selectors(&self) -> &[Selector] { - &self.0.slice - } -} - -/// Flag indicating where a given relative selector's match would be contained. -#[derive(Clone, Copy, Eq, PartialEq, ToShmem)] -pub enum RelativeSelectorMatchHint { - /// Within this element's subtree. - InSubtree, - /// Within this element's direct children. - InChild, - /// This element's next sibling. - InNextSibling, - /// Within this element's next sibling's subtree. - InNextSiblingSubtree, - /// Within this element's subsequent siblings. - InSibling, - /// Across this element's subsequent siblings and their subtrees. - InSiblingSubtree, -} - -/// Storage for a relative selector. -#[derive(Clone, Eq, PartialEq, ToShmem)] -#[shmem(no_bounds)] -pub struct RelativeSelector { - /// Match space constraining hint. - pub match_hint: RelativeSelectorMatchHint, - /// The selector. Guaranteed to contain `RelativeSelectorAnchor` and the relative combinator in parse order. - #[shmem(field_bound)] - pub selector: Selector, -} - -bitflags! { - /// Composition of combinators in a given selector, not traversing selectors of pseudoclasses. - struct CombinatorComposition: u8 { - const DESCENDANTS = 1 << 0; - const SIBLINGS = 1 << 1; - } -} - -impl CombinatorComposition { - fn for_relative_selector(inner_selector: &Selector) -> Self { - let mut result = CombinatorComposition::empty(); - for combinator in CombinatorIter::new(inner_selector.iter_skip_relative_selector_anchor()) { - match combinator { - Combinator::Descendant | Combinator::Child => { - result.insert(Self::DESCENDANTS); - }, - Combinator::NextSibling | Combinator::LaterSibling => { - result.insert(Self::SIBLINGS); - }, - Combinator::Part | Combinator::PseudoElement | Combinator::SlotAssignment => { - continue - }, - }; - if result.is_all() { - break; - } - } - return result; - } -} - -impl RelativeSelector { - fn from_selector_list(selector_list: SelectorList) -> Box<[Self]> { - let vec: Vec = selector_list - .0 - .into_iter() - .map(|selector| { - // It's more efficient to keep track of all this during the parse time, but that seems like a lot of special - // case handling for what it's worth. - if cfg!(debug_assertions) { - let relative_selector_anchor = selector.iter_raw_parse_order_from(0).next(); - debug_assert!( - relative_selector_anchor.is_some(), - "Relative selector is empty" - ); - debug_assert!( - matches!( - relative_selector_anchor.unwrap(), - Component::RelativeSelectorAnchor - ), - "Relative selector anchor is missing" - ); - } - // Leave a hint for narrowing down the search space when we're matching. - let match_hint = match selector.combinator_at_parse_order(1) { - Combinator::Descendant => RelativeSelectorMatchHint::InSubtree, - Combinator::Child => { - let composition = CombinatorComposition::for_relative_selector(&selector); - if composition.is_empty() || composition == CombinatorComposition::SIBLINGS - { - RelativeSelectorMatchHint::InChild - } else { - // Technically, for any composition that consists of child combinators only, - // the search space is depth-constrained, but it's probably not worth optimizing for. - RelativeSelectorMatchHint::InSubtree - } - }, - Combinator::NextSibling => { - let composition = CombinatorComposition::for_relative_selector(&selector); - if composition.is_empty() { - RelativeSelectorMatchHint::InNextSibling - } else if composition == CombinatorComposition::SIBLINGS { - RelativeSelectorMatchHint::InSibling - } else if composition == CombinatorComposition::DESCENDANTS { - // Match won't cross multiple siblings. - RelativeSelectorMatchHint::InNextSiblingSubtree - } else { - RelativeSelectorMatchHint::InSiblingSubtree - } - }, - Combinator::LaterSibling => { - let composition = CombinatorComposition::for_relative_selector(&selector); - if composition.is_empty() || composition == CombinatorComposition::SIBLINGS - { - RelativeSelectorMatchHint::InSibling - } else { - // Even if the match may not cross multiple siblings, we have to look until - // we find a match anyway. - RelativeSelectorMatchHint::InSiblingSubtree - } - }, - Combinator::Part | Combinator::PseudoElement | Combinator::SlotAssignment => { - debug_assert!(false, "Unexpected relative combinator"); - RelativeSelectorMatchHint::InSubtree - }, - }; - RelativeSelector { - match_hint, - selector, - } - }) - .collect(); - vec.into_boxed_slice() - } -} - -/// A CSS simple selector or combinator. We store both in the same enum for -/// optimal packing and cache performance, see [1]. -/// -/// [1] https://bugzilla.mozilla.org/show_bug.cgi?id=1357973 -#[derive(Clone, Eq, PartialEq, ToShmem)] -#[shmem(no_bounds)] -pub enum Component { - LocalName(LocalName), - - ID(#[shmem(field_bound)] Impl::Identifier), - Class(#[shmem(field_bound)] Impl::Identifier), - - AttributeInNoNamespaceExists { - #[shmem(field_bound)] - local_name: Impl::LocalName, - local_name_lower: Impl::LocalName, - }, - // Used only when local_name is already lowercase. - AttributeInNoNamespace { - local_name: Impl::LocalName, - operator: AttrSelectorOperator, - #[shmem(field_bound)] - value: Impl::AttrValue, - case_sensitivity: ParsedCaseSensitivity, - }, - // Use a Box in the less common cases with more data to keep size_of::() small. - AttributeOther(Box>), - - ExplicitUniversalType, - ExplicitAnyNamespace, - - ExplicitNoNamespace, - DefaultNamespace(#[shmem(field_bound)] Impl::NamespaceUrl), - Namespace( - #[shmem(field_bound)] Impl::NamespacePrefix, - #[shmem(field_bound)] Impl::NamespaceUrl, - ), - - /// Pseudo-classes - Negation(Box<[Selector]>), - Root, - Empty, - Scope, - ParentSelector, - Nth(NthSelectorData), - NthOf(NthOfSelectorData), - NonTSPseudoClass(#[shmem(field_bound)] Impl::NonTSPseudoClass), - /// The ::slotted() pseudo-element: - /// - /// https://drafts.csswg.org/css-scoping/#slotted-pseudo - /// - /// The selector here is a compound selector, that is, no combinators. - /// - /// NOTE(emilio): This should support a list of selectors, but as of this - /// writing no other browser does, and that allows them to put ::slotted() - /// in the rule hash, so we do that too. - /// - /// See https://github.com/w3c/csswg-drafts/issues/2158 - Slotted(Selector), - /// The `::part` pseudo-element. - /// https://drafts.csswg.org/css-shadow-parts/#part - Part(#[shmem(field_bound)] Box<[Impl::Identifier]>), - /// The `:host` pseudo-class: - /// - /// https://drafts.csswg.org/css-scoping/#host-selector - /// - /// NOTE(emilio): This should support a list of selectors, but as of this - /// writing no other browser does, and that allows them to put :host() - /// in the rule hash, so we do that too. - /// - /// See https://github.com/w3c/csswg-drafts/issues/2158 - Host(Option>), - /// The `:where` pseudo-class. - /// - /// https://drafts.csswg.org/selectors/#zero-matches - /// - /// The inner argument is conceptually a SelectorList, but we move the - /// selectors to the heap to keep Component small. - Where(Box<[Selector]>), - /// The `:is` pseudo-class. - /// - /// https://drafts.csswg.org/selectors/#matches-pseudo - /// - /// Same comment as above re. the argument. - Is(Box<[Selector]>), - /// The `:has` pseudo-class. - /// - /// https://drafts.csswg.org/selectors/#has-pseudo - /// - /// Same comment as above re. the argument. - Has(Box<[RelativeSelector]>), - /// An implementation-dependent pseudo-element selector. - PseudoElement(#[shmem(field_bound)] Impl::PseudoElement), - - Combinator(Combinator), - - /// Used only for relative selectors, which starts with a combinator - /// (With an implied descendant combinator if not specified). - /// - /// https://drafts.csswg.org/selectors-4/#typedef-relative-selector - RelativeSelectorAnchor, -} - -impl Component { - /// Returns true if this is a combinator. - #[inline] - pub fn is_combinator(&self) -> bool { - matches!(*self, Component::Combinator(_)) - } - - /// Returns true if this is a :host() selector. - #[inline] - pub fn is_host(&self) -> bool { - matches!(*self, Component::Host(..)) - } - - /// Returns the value as a combinator if applicable, None otherwise. - pub fn as_combinator(&self) -> Option { - match *self { - Component::Combinator(c) => Some(c), - _ => None, - } - } - - /// Whether this component is valid after a pseudo-element. Only intended - /// for sanity-checking. - pub fn maybe_allowed_after_pseudo_element(&self) -> bool { - match *self { - Component::NonTSPseudoClass(..) => true, - Component::Negation(ref selectors) | - Component::Is(ref selectors) | - Component::Where(ref selectors) => selectors.iter().all(|selector| { - selector - .iter_raw_match_order() - .all(|c| c.maybe_allowed_after_pseudo_element()) - }), - _ => false, - } - } - - /// Whether a given selector should match for stateless pseudo-elements. - /// - /// This is a bit subtle: Only selectors that return true in - /// `maybe_allowed_after_pseudo_element` should end up here, and - /// `NonTSPseudoClass` never matches (as it is a stateless pseudo after - /// all). - fn matches_for_stateless_pseudo_element(&self) -> bool { - debug_assert!( - self.maybe_allowed_after_pseudo_element(), - "Someone messed up pseudo-element parsing: {:?}", - *self - ); - match *self { - Component::Negation(ref selectors) => !selectors.iter().all(|selector| { - selector - .iter_raw_match_order() - .all(|c| c.matches_for_stateless_pseudo_element()) - }), - Component::Is(ref selectors) | Component::Where(ref selectors) => { - selectors.iter().any(|selector| { - selector - .iter_raw_match_order() - .all(|c| c.matches_for_stateless_pseudo_element()) - }) - }, - _ => false, - } - } - - pub fn visit(&self, visitor: &mut V) -> bool - where - V: SelectorVisitor, - { - use self::Component::*; - if !visitor.visit_simple_selector(self) { - return false; - } - - match *self { - Slotted(ref selector) => { - if !selector.visit(visitor) { - return false; - } - }, - Host(Some(ref selector)) => { - if !selector.visit(visitor) { - return false; - } - }, - AttributeInNoNamespaceExists { - ref local_name, - ref local_name_lower, - } => { - if !visitor.visit_attribute_selector( - &NamespaceConstraint::Specific(&namespace_empty_string::()), - local_name, - local_name_lower, - ) { - return false; - } - }, - AttributeInNoNamespace { ref local_name, .. } => { - if !visitor.visit_attribute_selector( - &NamespaceConstraint::Specific(&namespace_empty_string::()), - local_name, - local_name, - ) { - return false; - } - }, - AttributeOther(ref attr_selector) => { - let empty_string; - let namespace = match attr_selector.namespace() { - Some(ns) => ns, - None => { - empty_string = crate::parser::namespace_empty_string::(); - NamespaceConstraint::Specific(&empty_string) - }, - }; - if !visitor.visit_attribute_selector( - &namespace, - &attr_selector.local_name, - &attr_selector.local_name_lower, - ) { - return false; - } - }, - - NonTSPseudoClass(ref pseudo_class) => { - if !pseudo_class.visit(visitor) { - return false; - } - }, - Negation(ref list) | Is(ref list) | Where(ref list) => { - let list_kind = SelectorListKind::from_component(self); - debug_assert!(!list_kind.is_empty()); - if !visitor.visit_selector_list(list_kind, &list) { - return false; - } - }, - NthOf(ref nth_of_data) => { - if !visitor.visit_selector_list(SelectorListKind::NTH_OF, nth_of_data.selectors()) { - return false; - } - }, - _ => {}, - } - - true - } -} - -#[derive(Clone, Eq, PartialEq, ToShmem)] -#[shmem(no_bounds)] -pub struct LocalName { - #[shmem(field_bound)] - pub name: Impl::LocalName, - pub lower_name: Impl::LocalName, -} - -impl Debug for Selector { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - f.write_str("Selector(")?; - self.to_css(f)?; - write!( - f, - ", specificity = {:#x}, flags = {:?})", - self.specificity(), - self.flags() - ) - } -} - -impl Debug for Component { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - self.to_css(f) - } -} -impl Debug for AttrSelectorWithOptionalNamespace { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - self.to_css(f) - } -} -impl Debug for LocalName { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - self.to_css(f) - } -} - -fn serialize_selector_list<'a, Impl, I, W>(iter: I, dest: &mut W) -> fmt::Result -where - Impl: SelectorImpl, - I: Iterator>, - W: fmt::Write, -{ - let mut first = true; - for selector in iter { - if !first { - dest.write_str(", ")?; - } - first = false; - selector.to_css(dest)?; - } - Ok(()) -} - -impl ToCss for SelectorList { - fn to_css(&self, dest: &mut W) -> fmt::Result - where - W: fmt::Write, - { - serialize_selector_list(self.0.iter(), dest) - } -} - -impl ToCss for Selector { - fn to_css(&self, dest: &mut W) -> fmt::Result - where - W: fmt::Write, - { - // Compound selectors invert the order of their contents, so we need to - // undo that during serialization. - // - // This two-iterator strategy involves walking over the selector twice. - // We could do something more clever, but selector serialization probably - // isn't hot enough to justify it, and the stringification likely - // dominates anyway. - // - // NB: A parse-order iterator is a Rev<>, which doesn't expose as_slice(), - // which we need for |split|. So we split by combinators on a match-order - // sequence and then reverse. - - let mut combinators = self - .iter_raw_match_order() - .rev() - .filter_map(|x| x.as_combinator()); - let compound_selectors = self - .iter_raw_match_order() - .as_slice() - .split(|x| x.is_combinator()) - .rev(); - - let mut combinators_exhausted = false; - for compound in compound_selectors { - debug_assert!(!combinators_exhausted); - - // https://drafts.csswg.org/cssom/#serializing-selectors - if compound.is_empty() { - continue; - } - if let Component::RelativeSelectorAnchor = compound.first().unwrap() { - debug_assert!( - compound.len() == 1, - "RelativeLeft should only be a simple selector" - ); - combinators.next().unwrap().to_css_relative(dest)?; - continue; - } - - // 1. If there is only one simple selector in the compound selectors - // which is a universal selector, append the result of - // serializing the universal selector to s. - // - // Check if `!compound.empty()` first--this can happen if we have - // something like `... > ::before`, because we store `>` and `::` - // both as combinators internally. - // - // If we are in this case, after we have serialized the universal - // selector, we skip Step 2 and continue with the algorithm. - let (can_elide_namespace, first_non_namespace) = match compound[0] { - Component::ExplicitAnyNamespace | - Component::ExplicitNoNamespace | - Component::Namespace(..) => (false, 1), - Component::DefaultNamespace(..) => (true, 1), - _ => (true, 0), - }; - let mut perform_step_2 = true; - let next_combinator = combinators.next(); - if first_non_namespace == compound.len() - 1 { - match (next_combinator, &compound[first_non_namespace]) { - // We have to be careful here, because if there is a - // pseudo element "combinator" there isn't really just - // the one simple selector. Technically this compound - // selector contains the pseudo element selector as well - // -- Combinator::PseudoElement, just like - // Combinator::SlotAssignment, don't exist in the - // spec. - (Some(Combinator::PseudoElement), _) | - (Some(Combinator::SlotAssignment), _) => (), - (_, &Component::ExplicitUniversalType) => { - // Iterate over everything so we serialize the namespace - // too. - for simple in compound.iter() { - simple.to_css(dest)?; - } - // Skip step 2, which is an "otherwise". - perform_step_2 = false; - }, - _ => (), - } - } - - // 2. Otherwise, for each simple selector in the compound selectors - // that is not a universal selector of which the namespace prefix - // maps to a namespace that is not the default namespace - // serialize the simple selector and append the result to s. - // - // See https://github.com/w3c/csswg-drafts/issues/1606, which is - // proposing to change this to match up with the behavior asserted - // in cssom/serialize-namespaced-type-selectors.html, which the - // following code tries to match. - if perform_step_2 { - for simple in compound.iter() { - if let Component::ExplicitUniversalType = *simple { - // Can't have a namespace followed by a pseudo-element - // selector followed by a universal selector in the same - // compound selector, so we don't have to worry about the - // real namespace being in a different `compound`. - if can_elide_namespace { - continue; - } - } - simple.to_css(dest)?; - } - } - - // 3. If this is not the last part of the chain of the selector - // append a single SPACE (U+0020), followed by the combinator - // ">", "+", "~", ">>", "||", as appropriate, followed by another - // single SPACE (U+0020) if the combinator was not whitespace, to - // s. - match next_combinator { - Some(c) => c.to_css(dest)?, - None => combinators_exhausted = true, - }; - - // 4. If this is the last part of the chain of the selector and - // there is a pseudo-element, append "::" followed by the name of - // the pseudo-element, to s. - // - // (we handle this above) - } - - Ok(()) - } -} - -impl Combinator { - fn to_css_internal(&self, dest: &mut W, prefix_space: bool) -> fmt::Result - where - W: fmt::Write, - { - if matches!( - *self, - Combinator::PseudoElement | Combinator::Part | Combinator::SlotAssignment - ) { - return Ok(()); - } - if prefix_space { - dest.write_char(' ')?; - } - match *self { - Combinator::Child => dest.write_str("> "), - Combinator::Descendant => Ok(()), - Combinator::NextSibling => dest.write_str("+ "), - Combinator::LaterSibling => dest.write_str("~ "), - Combinator::PseudoElement | Combinator::Part | Combinator::SlotAssignment => unsafe { - debug_unreachable!("Already handled") - }, - } - } - - fn to_css_relative(&self, dest: &mut W) -> fmt::Result - where - W: fmt::Write, - { - self.to_css_internal(dest, false) - } -} - -impl ToCss for Combinator { - fn to_css(&self, dest: &mut W) -> fmt::Result - where - W: fmt::Write, - { - self.to_css_internal(dest, true) - } -} - -impl ToCss for Component { - fn to_css(&self, dest: &mut W) -> fmt::Result - where - W: fmt::Write, - { - use self::Component::*; - - match *self { - Combinator(ref c) => c.to_css(dest), - Slotted(ref selector) => { - dest.write_str("::slotted(")?; - selector.to_css(dest)?; - dest.write_char(')') - }, - Part(ref part_names) => { - dest.write_str("::part(")?; - for (i, name) in part_names.iter().enumerate() { - if i != 0 { - dest.write_char(' ')?; - } - name.to_css(dest)?; - } - dest.write_char(')') - }, - PseudoElement(ref p) => p.to_css(dest), - ID(ref s) => { - dest.write_char('#')?; - s.to_css(dest) - }, - Class(ref s) => { - dest.write_char('.')?; - s.to_css(dest) - }, - LocalName(ref s) => s.to_css(dest), - ExplicitUniversalType => dest.write_char('*'), - - DefaultNamespace(_) => Ok(()), - ExplicitNoNamespace => dest.write_char('|'), - ExplicitAnyNamespace => dest.write_str("*|"), - Namespace(ref prefix, _) => { - prefix.to_css(dest)?; - dest.write_char('|') - }, - - AttributeInNoNamespaceExists { ref local_name, .. } => { - dest.write_char('[')?; - local_name.to_css(dest)?; - dest.write_char(']') - }, - AttributeInNoNamespace { - ref local_name, - operator, - ref value, - case_sensitivity, - .. - } => { - dest.write_char('[')?; - local_name.to_css(dest)?; - operator.to_css(dest)?; - dest.write_char('"')?; - value.to_css(dest)?; - dest.write_char('"')?; - match case_sensitivity { - ParsedCaseSensitivity::CaseSensitive | - ParsedCaseSensitivity::AsciiCaseInsensitiveIfInHtmlElementInHtmlDocument => {}, - ParsedCaseSensitivity::AsciiCaseInsensitive => dest.write_str(" i")?, - ParsedCaseSensitivity::ExplicitCaseSensitive => dest.write_str(" s")?, - } - dest.write_char(']') - }, - AttributeOther(ref attr_selector) => attr_selector.to_css(dest), - - // Pseudo-classes - Root => dest.write_str(":root"), - Empty => dest.write_str(":empty"), - Scope => dest.write_str(":scope"), - ParentSelector => dest.write_char('&'), - Host(ref selector) => { - dest.write_str(":host")?; - if let Some(ref selector) = *selector { - dest.write_char('(')?; - selector.to_css(dest)?; - dest.write_char(')')?; - } - Ok(()) - }, - Nth(ref nth_data) => { - nth_data.write_start(dest)?; - if nth_data.is_function { - nth_data.write_affine(dest)?; - dest.write_char(')')?; - } - Ok(()) - }, - NthOf(ref nth_of_data) => { - let nth_data = nth_of_data.nth_data(); - nth_data.write_start(dest)?; - debug_assert!( - nth_data.is_function, - "A selector must be a function to hold An+B notation" - ); - nth_data.write_affine(dest)?; - debug_assert!( - matches!(nth_data.ty, NthType::Child | NthType::LastChild), - "Only :nth-child or :nth-last-child can be of a selector list" - ); - debug_assert!( - !nth_of_data.selectors().is_empty(), - "The selector list should not be empty" - ); - dest.write_str(" of ")?; - serialize_selector_list(nth_of_data.selectors().iter(), dest)?; - dest.write_char(')') - }, - Is(ref list) | Where(ref list) | Negation(ref list) => { - match *self { - Where(..) => dest.write_str(":where(")?, - Is(..) => dest.write_str(":is(")?, - Negation(..) => dest.write_str(":not(")?, - _ => unreachable!(), - } - serialize_selector_list(list.iter(), dest)?; - dest.write_str(")") - }, - Has(ref list) => { - dest.write_str(":has(")?; - let mut first = true; - for RelativeSelector { ref selector, .. } in list.iter() { - if !first { - dest.write_str(", ")?; - } - first = false; - selector.to_css(dest)?; - } - dest.write_str(")") - }, - NonTSPseudoClass(ref pseudo) => pseudo.to_css(dest), - RelativeSelectorAnchor => Ok(()), - } - } -} - -impl ToCss for AttrSelectorWithOptionalNamespace { - fn to_css(&self, dest: &mut W) -> fmt::Result - where - W: fmt::Write, - { - dest.write_char('[')?; - match self.namespace { - Some(NamespaceConstraint::Specific((ref prefix, _))) => { - prefix.to_css(dest)?; - dest.write_char('|')? - }, - Some(NamespaceConstraint::Any) => dest.write_str("*|")?, - None => {}, - } - self.local_name.to_css(dest)?; - match self.operation { - ParsedAttrSelectorOperation::Exists => {}, - ParsedAttrSelectorOperation::WithValue { - operator, - case_sensitivity, - ref value, - } => { - operator.to_css(dest)?; - dest.write_char('"')?; - value.to_css(dest)?; - dest.write_char('"')?; - match case_sensitivity { - ParsedCaseSensitivity::CaseSensitive | - ParsedCaseSensitivity::AsciiCaseInsensitiveIfInHtmlElementInHtmlDocument => {}, - ParsedCaseSensitivity::AsciiCaseInsensitive => dest.write_str(" i")?, - ParsedCaseSensitivity::ExplicitCaseSensitive => dest.write_str(" s")?, - } - }, - } - dest.write_char(']') - } -} - -impl ToCss for LocalName { - fn to_css(&self, dest: &mut W) -> fmt::Result - where - W: fmt::Write, - { - self.name.to_css(dest) - } -} - -/// Build up a Selector. -/// selector : simple_selector_sequence [ combinator simple_selector_sequence ]* ; -/// -/// `Err` means invalid selector. -fn parse_selector<'i, 't, P, Impl>( - parser: &P, - input: &mut CssParser<'i, 't>, - mut state: SelectorParsingState, - parse_relative: ParseRelative, -) -> Result, ParseError<'i, P::Error>> -where - P: Parser<'i, Impl = Impl>, - Impl: SelectorImpl, -{ - let mut builder = SelectorBuilder::default(); - if parse_relative == ParseRelative::Yes { - builder.push_simple_selector(Component::RelativeSelectorAnchor); - // Do we see a combinator? If so, push that. Otherwise, push a descendant combinator. - builder - .push_combinator(parse_combinator::(input).unwrap_or(Combinator::Descendant)); - } - 'outer_loop: loop { - // Parse a sequence of simple selectors. - let empty = parse_compound_selector(parser, &mut state, input, &mut builder)?; - if empty { - return Err(input.new_custom_error(if builder.has_combinators() { - SelectorParseErrorKind::DanglingCombinator - } else { - SelectorParseErrorKind::EmptySelector - })); - } - - if state.intersects(SelectorParsingState::AFTER_PSEUDO) { - debug_assert!(state.intersects( - SelectorParsingState::AFTER_PSEUDO_ELEMENT | - SelectorParsingState::AFTER_SLOTTED | - SelectorParsingState::AFTER_PART - )); - break; - } - - let combinator = if let Ok(c) = parse_combinator::(input) { - c - } else { - break 'outer_loop; - }; - - if !state.allows_combinators() { - return Err(input.new_custom_error(SelectorParseErrorKind::InvalidState)); - } - - builder.push_combinator(combinator); - } - return Ok(Selector(builder.build())); -} - -fn parse_combinator<'i, 't, P, Impl>(input: &mut CssParser<'i, 't>) -> Result { - let mut any_whitespace = false; - loop { - let before_this_token = input.state(); - match input.next_including_whitespace() { - Err(_e) => return Err(()), - Ok(&Token::WhiteSpace(_)) => any_whitespace = true, - Ok(&Token::Delim('>')) => { - return Ok(Combinator::Child); - }, - Ok(&Token::Delim('+')) => { - return Ok(Combinator::NextSibling); - }, - Ok(&Token::Delim('~')) => { - return Ok(Combinator::LaterSibling); - }, - Ok(_) => { - input.reset(&before_this_token); - if any_whitespace { - return Ok(Combinator::Descendant); - } else { - return Err(()); - } - }, - } - } -} - -impl Selector { - /// Parse a selector, without any pseudo-element. - #[inline] - pub fn parse<'i, 't, P>( - parser: &P, - input: &mut CssParser<'i, 't>, - ) -> Result> - where - P: Parser<'i, Impl = Impl>, - { - parse_selector( - parser, - input, - SelectorParsingState::empty(), - ParseRelative::No, - ) - } -} - -/// * `Err(())`: Invalid selector, abort -/// * `Ok(false)`: Not a type selector, could be something else. `input` was not consumed. -/// * `Ok(true)`: Length 0 (`*|*`), 1 (`*|E` or `ns|*`) or 2 (`|E` or `ns|E`) -fn parse_type_selector<'i, 't, P, Impl, S>( - parser: &P, - input: &mut CssParser<'i, 't>, - state: SelectorParsingState, - sink: &mut S, -) -> Result> -where - P: Parser<'i, Impl = Impl>, - Impl: SelectorImpl, - S: Push>, -{ - match parse_qualified_name(parser, input, /* in_attr_selector = */ false) { - Err(ParseError { - kind: ParseErrorKind::Basic(BasicParseErrorKind::EndOfInput), - .. - }) | - Ok(OptionalQName::None(_)) => Ok(false), - Ok(OptionalQName::Some(namespace, local_name)) => { - if state.intersects(SelectorParsingState::AFTER_PSEUDO) { - return Err(input.new_custom_error(SelectorParseErrorKind::InvalidState)); - } - match namespace { - QNamePrefix::ImplicitAnyNamespace => {}, - QNamePrefix::ImplicitDefaultNamespace(url) => { - sink.push(Component::DefaultNamespace(url)) - }, - QNamePrefix::ExplicitNamespace(prefix, url) => { - sink.push(match parser.default_namespace() { - Some(ref default_url) if url == *default_url => { - Component::DefaultNamespace(url) - }, - _ => Component::Namespace(prefix, url), - }) - }, - QNamePrefix::ExplicitNoNamespace => sink.push(Component::ExplicitNoNamespace), - QNamePrefix::ExplicitAnyNamespace => { - match parser.default_namespace() { - // Element type selectors that have no namespace - // component (no namespace separator) represent elements - // without regard to the element's namespace (equivalent - // to "*|") unless a default namespace has been declared - // for namespaced selectors (e.g. in CSS, in the style - // sheet). If a default namespace has been declared, - // such selectors will represent only elements in the - // default namespace. - // -- Selectors § 6.1.1 - // So we'll have this act the same as the - // QNamePrefix::ImplicitAnyNamespace case. - None => {}, - Some(_) => sink.push(Component::ExplicitAnyNamespace), - } - }, - QNamePrefix::ImplicitNoNamespace => { - unreachable!() // Not returned with in_attr_selector = false - }, - } - match local_name { - Some(name) => sink.push(Component::LocalName(LocalName { - lower_name: to_ascii_lowercase(&name).as_ref().into(), - name: name.as_ref().into(), - })), - None => sink.push(Component::ExplicitUniversalType), - } - Ok(true) - }, - Err(e) => Err(e), - } -} - -#[derive(Debug)] -enum SimpleSelectorParseResult { - SimpleSelector(Component), - PseudoElement(Impl::PseudoElement), - SlottedPseudo(Selector), - PartPseudo(Box<[Impl::Identifier]>), -} - -#[derive(Debug)] -enum QNamePrefix { - ImplicitNoNamespace, // `foo` in attr selectors - ImplicitAnyNamespace, // `foo` in type selectors, without a default ns - ImplicitDefaultNamespace(Impl::NamespaceUrl), // `foo` in type selectors, with a default ns - ExplicitNoNamespace, // `|foo` - ExplicitAnyNamespace, // `*|foo` - ExplicitNamespace(Impl::NamespacePrefix, Impl::NamespaceUrl), // `prefix|foo` -} - -enum OptionalQName<'i, Impl: SelectorImpl> { - Some(QNamePrefix, Option>), - None(Token<'i>), -} - -/// * `Err(())`: Invalid selector, abort -/// * `Ok(None(token))`: Not a simple selector, could be something else. `input` was not consumed, -/// but the token is still returned. -/// * `Ok(Some(namespace, local_name))`: `None` for the local name means a `*` universal selector -fn parse_qualified_name<'i, 't, P, Impl>( - parser: &P, - input: &mut CssParser<'i, 't>, - in_attr_selector: bool, -) -> Result, ParseError<'i, P::Error>> -where - P: Parser<'i, Impl = Impl>, - Impl: SelectorImpl, -{ - let default_namespace = |local_name| { - let namespace = match parser.default_namespace() { - Some(url) => QNamePrefix::ImplicitDefaultNamespace(url), - None => QNamePrefix::ImplicitAnyNamespace, - }; - Ok(OptionalQName::Some(namespace, local_name)) - }; - - let explicit_namespace = |input: &mut CssParser<'i, 't>, namespace| { - let location = input.current_source_location(); - match input.next_including_whitespace() { - Ok(&Token::Delim('*')) if !in_attr_selector => Ok(OptionalQName::Some(namespace, None)), - Ok(&Token::Ident(ref local_name)) => { - Ok(OptionalQName::Some(namespace, Some(local_name.clone()))) - }, - Ok(t) if in_attr_selector => { - let e = SelectorParseErrorKind::InvalidQualNameInAttr(t.clone()); - Err(location.new_custom_error(e)) - }, - Ok(t) => Err(location.new_custom_error( - SelectorParseErrorKind::ExplicitNamespaceUnexpectedToken(t.clone()), - )), - Err(e) => Err(e.into()), - } - }; - - let start = input.state(); - match input.next_including_whitespace() { - Ok(Token::Ident(value)) => { - let value = value.clone(); - let after_ident = input.state(); - match input.next_including_whitespace() { - Ok(&Token::Delim('|')) => { - let prefix = value.as_ref().into(); - let result = parser.namespace_for_prefix(&prefix); - let url = result.ok_or( - after_ident - .source_location() - .new_custom_error(SelectorParseErrorKind::ExpectedNamespace(value)), - )?; - explicit_namespace(input, QNamePrefix::ExplicitNamespace(prefix, url)) - }, - _ => { - input.reset(&after_ident); - if in_attr_selector { - Ok(OptionalQName::Some( - QNamePrefix::ImplicitNoNamespace, - Some(value), - )) - } else { - default_namespace(Some(value)) - } - }, - } - }, - Ok(Token::Delim('*')) => { - let after_star = input.state(); - match input.next_including_whitespace() { - Ok(&Token::Delim('|')) => { - explicit_namespace(input, QNamePrefix::ExplicitAnyNamespace) - }, - _ if !in_attr_selector => { - input.reset(&after_star); - default_namespace(None) - }, - result => { - let t = result?; - Err(after_star - .source_location() - .new_custom_error(SelectorParseErrorKind::ExpectedBarInAttr(t.clone()))) - }, - } - }, - Ok(Token::Delim('|')) => explicit_namespace(input, QNamePrefix::ExplicitNoNamespace), - Ok(t) => { - let t = t.clone(); - input.reset(&start); - Ok(OptionalQName::None(t)) - }, - Err(e) => { - input.reset(&start); - Err(e.into()) - }, - } -} - -fn parse_attribute_selector<'i, 't, P, Impl>( - parser: &P, - input: &mut CssParser<'i, 't>, -) -> Result, ParseError<'i, P::Error>> -where - P: Parser<'i, Impl = Impl>, - Impl: SelectorImpl, -{ - let namespace; - let local_name; - - input.skip_whitespace(); - - match parse_qualified_name(parser, input, /* in_attr_selector = */ true)? { - OptionalQName::None(t) => { - return Err(input.new_custom_error( - SelectorParseErrorKind::NoQualifiedNameInAttributeSelector(t), - )); - }, - OptionalQName::Some(_, None) => unreachable!(), - OptionalQName::Some(ns, Some(ln)) => { - local_name = ln; - namespace = match ns { - QNamePrefix::ImplicitNoNamespace | QNamePrefix::ExplicitNoNamespace => None, - QNamePrefix::ExplicitNamespace(prefix, url) => { - Some(NamespaceConstraint::Specific((prefix, url))) - }, - QNamePrefix::ExplicitAnyNamespace => Some(NamespaceConstraint::Any), - QNamePrefix::ImplicitAnyNamespace | QNamePrefix::ImplicitDefaultNamespace(_) => { - unreachable!() // Not returned with in_attr_selector = true - }, - } - }, - } - - let location = input.current_source_location(); - let operator = match input.next() { - // [foo] - Err(_) => { - let local_name_lower = to_ascii_lowercase(&local_name).as_ref().into(); - let local_name = local_name.as_ref().into(); - if let Some(namespace) = namespace { - return Ok(Component::AttributeOther(Box::new( - AttrSelectorWithOptionalNamespace { - namespace: Some(namespace), - local_name, - local_name_lower, - operation: ParsedAttrSelectorOperation::Exists, - }, - ))); - } else { - return Ok(Component::AttributeInNoNamespaceExists { - local_name, - local_name_lower, - }); - } - }, - - // [foo=bar] - Ok(&Token::Delim('=')) => AttrSelectorOperator::Equal, - // [foo~=bar] - Ok(&Token::IncludeMatch) => AttrSelectorOperator::Includes, - // [foo|=bar] - Ok(&Token::DashMatch) => AttrSelectorOperator::DashMatch, - // [foo^=bar] - Ok(&Token::PrefixMatch) => AttrSelectorOperator::Prefix, - // [foo*=bar] - Ok(&Token::SubstringMatch) => AttrSelectorOperator::Substring, - // [foo$=bar] - Ok(&Token::SuffixMatch) => AttrSelectorOperator::Suffix, - Ok(t) => { - return Err(location.new_custom_error( - SelectorParseErrorKind::UnexpectedTokenInAttributeSelector(t.clone()), - )); - }, - }; - - let value = match input.expect_ident_or_string() { - Ok(t) => t.clone(), - Err(BasicParseError { - kind: BasicParseErrorKind::UnexpectedToken(t), - location, - }) => return Err(location.new_custom_error(SelectorParseErrorKind::BadValueInAttr(t))), - Err(e) => return Err(e.into()), - }; - - let attribute_flags = parse_attribute_flags(input)?; - let value = value.as_ref().into(); - let local_name_lower; - let local_name_is_ascii_lowercase; - let case_sensitivity; - { - let local_name_lower_cow = to_ascii_lowercase(&local_name); - case_sensitivity = - attribute_flags.to_case_sensitivity(local_name_lower_cow.as_ref(), namespace.is_some()); - local_name_lower = local_name_lower_cow.as_ref().into(); - local_name_is_ascii_lowercase = matches!(local_name_lower_cow, Cow::Borrowed(..)); - } - let local_name = local_name.as_ref().into(); - if namespace.is_some() || !local_name_is_ascii_lowercase { - Ok(Component::AttributeOther(Box::new( - AttrSelectorWithOptionalNamespace { - namespace, - local_name, - local_name_lower, - operation: ParsedAttrSelectorOperation::WithValue { - operator, - case_sensitivity, - value, - }, - }, - ))) - } else { - Ok(Component::AttributeInNoNamespace { - local_name, - operator, - value, - case_sensitivity, - }) - } -} - -/// An attribute selector can have 's' or 'i' as flags, or no flags at all. -enum AttributeFlags { - // Matching should be case-sensitive ('s' flag). - CaseSensitive, - // Matching should be case-insensitive ('i' flag). - AsciiCaseInsensitive, - // No flags. Matching behavior depends on the name of the attribute. - CaseSensitivityDependsOnName, -} - -impl AttributeFlags { - fn to_case_sensitivity(self, local_name: &str, have_namespace: bool) -> ParsedCaseSensitivity { - match self { - AttributeFlags::CaseSensitive => ParsedCaseSensitivity::ExplicitCaseSensitive, - AttributeFlags::AsciiCaseInsensitive => ParsedCaseSensitivity::AsciiCaseInsensitive, - AttributeFlags::CaseSensitivityDependsOnName => { - if !have_namespace && - include!(concat!( - env!("OUT_DIR"), - "/ascii_case_insensitive_html_attributes.rs" - )) - .contains(local_name) - { - ParsedCaseSensitivity::AsciiCaseInsensitiveIfInHtmlElementInHtmlDocument - } else { - ParsedCaseSensitivity::CaseSensitive - } - }, - } - } -} - -fn parse_attribute_flags<'i, 't>( - input: &mut CssParser<'i, 't>, -) -> Result> { - let location = input.current_source_location(); - let token = match input.next() { - Ok(t) => t, - Err(..) => { - // Selectors spec says language-defined; HTML says it depends on the - // exact attribute name. - return Ok(AttributeFlags::CaseSensitivityDependsOnName); - }, - }; - - let ident = match *token { - Token::Ident(ref i) => i, - ref other => return Err(location.new_basic_unexpected_token_error(other.clone())), - }; - - Ok(match_ignore_ascii_case! { - ident, - "i" => AttributeFlags::AsciiCaseInsensitive, - "s" => AttributeFlags::CaseSensitive, - _ => return Err(location.new_basic_unexpected_token_error(token.clone())), - }) -} - -/// Level 3: Parse **one** simple_selector. (Though we might insert a second -/// implied "|*" type selector.) -fn parse_negation<'i, 't, P, Impl>( - parser: &P, - input: &mut CssParser<'i, 't>, - state: SelectorParsingState, -) -> Result, ParseError<'i, P::Error>> -where - P: Parser<'i, Impl = Impl>, - Impl: SelectorImpl, -{ - let list = SelectorList::parse_with_state( - parser, - input, - state | - SelectorParsingState::SKIP_DEFAULT_NAMESPACE | - SelectorParsingState::DISALLOW_PSEUDOS, - ForgivingParsing::No, - ParseRelative::No, - )?; - - Ok(Component::Negation(list.0.into_vec().into_boxed_slice())) -} - -/// simple_selector_sequence -/// : [ type_selector | universal ] [ HASH | class | attrib | pseudo | negation ]* -/// | [ HASH | class | attrib | pseudo | negation ]+ -/// -/// `Err(())` means invalid selector. -/// `Ok(true)` is an empty selector -fn parse_compound_selector<'i, 't, P, Impl>( - parser: &P, - state: &mut SelectorParsingState, - input: &mut CssParser<'i, 't>, - builder: &mut SelectorBuilder, -) -> Result> -where - P: Parser<'i, Impl = Impl>, - Impl: SelectorImpl, -{ - input.skip_whitespace(); - - let mut empty = true; - if parse_type_selector(parser, input, *state, builder)? { - empty = false; - } - - loop { - let result = match parse_one_simple_selector(parser, input, *state)? { - None => break, - Some(result) => result, - }; - - if empty { - if let Some(url) = parser.default_namespace() { - // If there was no explicit type selector, but there is a - // default namespace, there is an implicit "|*" type - // selector. Except for :host() or :not() / :is() / :where(), - // where we ignore it. - // - // https://drafts.csswg.org/css-scoping/#host-element-in-tree: - // - // When considered within its own shadow trees, the shadow - // host is featureless. Only the :host, :host(), and - // :host-context() pseudo-classes are allowed to match it. - // - // https://drafts.csswg.org/selectors-4/#featureless: - // - // A featureless element does not match any selector at all, - // except those it is explicitly defined to match. If a - // given selector is allowed to match a featureless element, - // it must do so while ignoring the default namespace. - // - // https://drafts.csswg.org/selectors-4/#matches - // - // Default namespace declarations do not affect the compound - // selector representing the subject of any selector within - // a :is() pseudo-class, unless that compound selector - // contains an explicit universal selector or type selector. - // - // (Similar quotes for :where() / :not()) - // - let ignore_default_ns = state - .intersects(SelectorParsingState::SKIP_DEFAULT_NAMESPACE) || - matches!( - result, - SimpleSelectorParseResult::SimpleSelector(Component::Host(..)) - ); - if !ignore_default_ns { - builder.push_simple_selector(Component::DefaultNamespace(url)); - } - } - } - - empty = false; - - match result { - SimpleSelectorParseResult::SimpleSelector(s) => { - builder.push_simple_selector(s); - }, - SimpleSelectorParseResult::PartPseudo(part_names) => { - state.insert(SelectorParsingState::AFTER_PART); - builder.push_combinator(Combinator::Part); - builder.push_simple_selector(Component::Part(part_names)); - }, - SimpleSelectorParseResult::SlottedPseudo(selector) => { - state.insert(SelectorParsingState::AFTER_SLOTTED); - builder.push_combinator(Combinator::SlotAssignment); - builder.push_simple_selector(Component::Slotted(selector)); - }, - SimpleSelectorParseResult::PseudoElement(p) => { - state.insert(SelectorParsingState::AFTER_PSEUDO_ELEMENT); - if !p.accepts_state_pseudo_classes() { - state.insert(SelectorParsingState::AFTER_NON_STATEFUL_PSEUDO_ELEMENT); - } - builder.push_combinator(Combinator::PseudoElement); - builder.push_simple_selector(Component::PseudoElement(p)); - }, - } - } - Ok(empty) -} - -fn parse_is_where<'i, 't, P, Impl>( - parser: &P, - input: &mut CssParser<'i, 't>, - state: SelectorParsingState, - component: impl FnOnce(Box<[Selector]>) -> Component, -) -> Result, ParseError<'i, P::Error>> -where - P: Parser<'i, Impl = Impl>, - Impl: SelectorImpl, -{ - debug_assert!(parser.parse_is_and_where()); - // https://drafts.csswg.org/selectors/#matches-pseudo: - // - // Pseudo-elements cannot be represented by the matches-any - // pseudo-class; they are not valid within :is(). - // - let inner = SelectorList::parse_with_state( - parser, - input, - state | - SelectorParsingState::SKIP_DEFAULT_NAMESPACE | - SelectorParsingState::DISALLOW_PSEUDOS, - ForgivingParsing::Yes, - ParseRelative::No, - )?; - Ok(component(inner.0.into_vec().into_boxed_slice())) -} - -fn parse_has<'i, 't, P, Impl>( - parser: &P, - input: &mut CssParser<'i, 't>, - state: SelectorParsingState, -) -> Result, ParseError<'i, P::Error>> -where - P: Parser<'i, Impl = Impl>, - Impl: SelectorImpl, -{ - debug_assert!(parser.parse_has()); - if state.intersects(SelectorParsingState::DISALLOW_RELATIVE_SELECTOR) { - return Err(input.new_custom_error(SelectorParseErrorKind::InvalidState)); - } - // Nested `:has()` is disallowed, mark it as such. - // Note: The spec defines ":has-allowed pseudo-element," but there's no - // pseudo-element defined as such at the moment. - // https://w3c.github.io/csswg-drafts/selectors-4/#has-allowed-pseudo-element - let inner = SelectorList::parse_with_state( - parser, - input, - state | - SelectorParsingState::SKIP_DEFAULT_NAMESPACE | - SelectorParsingState::DISALLOW_PSEUDOS | - SelectorParsingState::DISALLOW_RELATIVE_SELECTOR, - ForgivingParsing::No, - ParseRelative::Yes, - )?; - Ok(Component::Has(RelativeSelector::from_selector_list(inner))) -} - -fn parse_functional_pseudo_class<'i, 't, P, Impl>( - parser: &P, - input: &mut CssParser<'i, 't>, - name: CowRcStr<'i>, - state: SelectorParsingState, -) -> Result, ParseError<'i, P::Error>> -where - P: Parser<'i, Impl = Impl>, - Impl: SelectorImpl, -{ - match_ignore_ascii_case! { &name, - "nth-child" => return parse_nth_pseudo_class(parser, input, state, NthType::Child), - "nth-of-type" => return parse_nth_pseudo_class(parser, input, state, NthType::OfType), - "nth-last-child" => return parse_nth_pseudo_class(parser, input, state, NthType::LastChild), - "nth-last-of-type" => return parse_nth_pseudo_class(parser, input, state, NthType::LastOfType), - "is" if parser.parse_is_and_where() => return parse_is_where(parser, input, state, Component::Is), - "where" if parser.parse_is_and_where() => return parse_is_where(parser, input, state, Component::Where), - "has" if parser.parse_has() => return parse_has(parser, input, state), - "host" => { - if !state.allows_tree_structural_pseudo_classes() { - return Err(input.new_custom_error(SelectorParseErrorKind::InvalidState)); - } - return Ok(Component::Host(Some(parse_inner_compound_selector(parser, input, state)?))); - }, - "not" => { - return parse_negation(parser, input, state) - }, - _ => {} - } - - if parser.parse_is_and_where() && parser.is_is_alias(&name) { - return parse_is_where(parser, input, state, Component::Is); - } - - if !state.allows_custom_functional_pseudo_classes() { - return Err(input.new_custom_error(SelectorParseErrorKind::InvalidState)); - } - - P::parse_non_ts_functional_pseudo_class(parser, name, input).map(Component::NonTSPseudoClass) -} - -fn parse_nth_pseudo_class<'i, 't, P, Impl>( - parser: &P, - input: &mut CssParser<'i, 't>, - state: SelectorParsingState, - ty: NthType, -) -> Result, ParseError<'i, P::Error>> -where - P: Parser<'i, Impl = Impl>, - Impl: SelectorImpl, -{ - if !state.allows_tree_structural_pseudo_classes() { - return Err(input.new_custom_error(SelectorParseErrorKind::InvalidState)); - } - let (a, b) = parse_nth(input)?; - let nth_data = NthSelectorData { - ty, - is_function: true, - a, - b, - }; - if !parser.parse_nth_child_of() || ty.is_of_type() { - return Ok(Component::Nth(nth_data)); - } - - // Try to parse "of ". - if input.try_parse(|i| i.expect_ident_matching("of")).is_err() { - return Ok(Component::Nth(nth_data)); - } - // Whitespace between "of" and the selector list is optional - // https://github.com/w3c/csswg-drafts/issues/8285 - let mut selectors = SelectorList::parse_with_state( - parser, - input, - state | - SelectorParsingState::SKIP_DEFAULT_NAMESPACE | - SelectorParsingState::DISALLOW_PSEUDOS, - ForgivingParsing::No, - ParseRelative::No, - )?; - Ok(Component::NthOf(NthOfSelectorData::new( - &nth_data, - selectors.0.drain(..), - ))) -} - -/// Returns whether the name corresponds to a CSS2 pseudo-element that -/// can be specified with the single colon syntax (in addition to the -/// double-colon syntax, which can be used for all pseudo-elements). -fn is_css2_pseudo_element(name: &str) -> bool { - // ** Do not add to this list! ** - match_ignore_ascii_case! { name, - "before" | "after" | "first-line" | "first-letter" => true, - _ => false, - } -} - -/// Parse a simple selector other than a type selector. -/// -/// * `Err(())`: Invalid selector, abort -/// * `Ok(None)`: Not a simple selector, could be something else. `input` was not consumed. -/// * `Ok(Some(_))`: Parsed a simple selector or pseudo-element -fn parse_one_simple_selector<'i, 't, P, Impl>( - parser: &P, - input: &mut CssParser<'i, 't>, - state: SelectorParsingState, -) -> Result>, ParseError<'i, P::Error>> -where - P: Parser<'i, Impl = Impl>, - Impl: SelectorImpl, -{ - let start = input.state(); - let token = match input.next_including_whitespace().map(|t| t.clone()) { - Ok(t) => t, - Err(..) => { - input.reset(&start); - return Ok(None); - }, - }; - - Ok(Some(match token { - Token::IDHash(id) => { - if state.intersects(SelectorParsingState::AFTER_PSEUDO) { - return Err(input.new_custom_error(SelectorParseErrorKind::InvalidState)); - } - let id = Component::ID(id.as_ref().into()); - SimpleSelectorParseResult::SimpleSelector(id) - }, - Token::Delim(delim) if delim == '.' || (delim == '&' && parser.parse_parent_selector()) => { - if state.intersects(SelectorParsingState::AFTER_PSEUDO) { - return Err(input.new_custom_error(SelectorParseErrorKind::InvalidState)); - } - let location = input.current_source_location(); - SimpleSelectorParseResult::SimpleSelector(if delim == '&' { - Component::ParentSelector - } else { - let class = match *input.next_including_whitespace()? { - Token::Ident(ref class) => class, - ref t => { - let e = SelectorParseErrorKind::ClassNeedsIdent(t.clone()); - return Err(location.new_custom_error(e)); - }, - }; - Component::Class(class.as_ref().into()) - }) - }, - Token::SquareBracketBlock => { - if state.intersects(SelectorParsingState::AFTER_PSEUDO) { - return Err(input.new_custom_error(SelectorParseErrorKind::InvalidState)); - } - let attr = input.parse_nested_block(|input| parse_attribute_selector(parser, input))?; - SimpleSelectorParseResult::SimpleSelector(attr) - }, - Token::Colon => { - let location = input.current_source_location(); - let (is_single_colon, next_token) = match input.next_including_whitespace()?.clone() { - Token::Colon => (false, input.next_including_whitespace()?.clone()), - t => (true, t), - }; - let (name, is_functional) = match next_token { - Token::Ident(name) => (name, false), - Token::Function(name) => (name, true), - t => { - let e = SelectorParseErrorKind::PseudoElementExpectedIdent(t); - return Err(input.new_custom_error(e)); - }, - }; - let is_pseudo_element = !is_single_colon || is_css2_pseudo_element(&name); - if is_pseudo_element { - if !state.allows_pseudos() { - return Err(input.new_custom_error(SelectorParseErrorKind::InvalidState)); - } - let pseudo_element = if is_functional { - if P::parse_part(parser) && name.eq_ignore_ascii_case("part") { - if !state.allows_part() { - return Err( - input.new_custom_error(SelectorParseErrorKind::InvalidState) - ); - } - let names = input.parse_nested_block(|input| { - let mut result = Vec::with_capacity(1); - result.push(input.expect_ident()?.as_ref().into()); - while !input.is_exhausted() { - result.push(input.expect_ident()?.as_ref().into()); - } - Ok(result.into_boxed_slice()) - })?; - return Ok(Some(SimpleSelectorParseResult::PartPseudo(names))); - } - if P::parse_slotted(parser) && name.eq_ignore_ascii_case("slotted") { - if !state.allows_slotted() { - return Err( - input.new_custom_error(SelectorParseErrorKind::InvalidState) - ); - } - let selector = input.parse_nested_block(|input| { - parse_inner_compound_selector(parser, input, state) - })?; - return Ok(Some(SimpleSelectorParseResult::SlottedPseudo(selector))); - } - input.parse_nested_block(|input| { - P::parse_functional_pseudo_element(parser, name, input) - })? - } else { - P::parse_pseudo_element(parser, location, name)? - }; - - if state.intersects(SelectorParsingState::AFTER_SLOTTED) && - !pseudo_element.valid_after_slotted() - { - return Err(input.new_custom_error(SelectorParseErrorKind::InvalidState)); - } - SimpleSelectorParseResult::PseudoElement(pseudo_element) - } else { - let pseudo_class = if is_functional { - input.parse_nested_block(|input| { - parse_functional_pseudo_class(parser, input, name, state) - })? - } else { - parse_simple_pseudo_class(parser, location, name, state)? - }; - SimpleSelectorParseResult::SimpleSelector(pseudo_class) - } - }, - _ => { - input.reset(&start); - return Ok(None); - }, - })) -} - -fn parse_simple_pseudo_class<'i, P, Impl>( - parser: &P, - location: SourceLocation, - name: CowRcStr<'i>, - state: SelectorParsingState, -) -> Result, ParseError<'i, P::Error>> -where - P: Parser<'i, Impl = Impl>, - Impl: SelectorImpl, -{ - if !state.allows_non_functional_pseudo_classes() { - return Err(location.new_custom_error(SelectorParseErrorKind::InvalidState)); - } - - if state.allows_tree_structural_pseudo_classes() { - match_ignore_ascii_case! { &name, - "first-child" => return Ok(Component::Nth(NthSelectorData::first(/* of_type = */ false))), - "last-child" => return Ok(Component::Nth(NthSelectorData::last(/* of_type = */ false))), - "only-child" => return Ok(Component::Nth(NthSelectorData::only(/* of_type = */ false))), - "root" => return Ok(Component::Root), - "empty" => return Ok(Component::Empty), - "scope" => return Ok(Component::Scope), - "host" if P::parse_host(parser) => return Ok(Component::Host(None)), - "first-of-type" => return Ok(Component::Nth(NthSelectorData::first(/* of_type = */ true))), - "last-of-type" => return Ok(Component::Nth(NthSelectorData::last(/* of_type = */ true))), - "only-of-type" => return Ok(Component::Nth(NthSelectorData::only(/* of_type = */ true))), - _ => {}, - } - } - - let pseudo_class = P::parse_non_ts_pseudo_class(parser, location, name)?; - if state.intersects(SelectorParsingState::AFTER_PSEUDO_ELEMENT) && - !pseudo_class.is_user_action_state() - { - return Err(location.new_custom_error(SelectorParseErrorKind::InvalidState)); - } - Ok(Component::NonTSPseudoClass(pseudo_class)) -} - -// NB: pub module in order to access the DummyParser -#[cfg(test)] -pub mod tests { - use super::*; - use crate::builder::SelectorFlags; - use crate::parser; - use cssparser::{serialize_identifier, Parser as CssParser, ParserInput, ToCss}; - use std::collections::HashMap; - use std::fmt; - - #[derive(Clone, Debug, Eq, PartialEq)] - pub enum PseudoClass { - Hover, - Active, - Lang(String), - } - - #[derive(Clone, Debug, Eq, PartialEq)] - pub enum PseudoElement { - Before, - After, - Highlight(String), - } - - impl parser::PseudoElement for PseudoElement { - type Impl = DummySelectorImpl; - - fn accepts_state_pseudo_classes(&self) -> bool { - true - } - - fn valid_after_slotted(&self) -> bool { - true - } - } - - impl parser::NonTSPseudoClass for PseudoClass { - type Impl = DummySelectorImpl; - - #[inline] - fn is_active_or_hover(&self) -> bool { - matches!(*self, PseudoClass::Active | PseudoClass::Hover) - } - - #[inline] - fn is_user_action_state(&self) -> bool { - self.is_active_or_hover() - } - } - - impl ToCss for PseudoClass { - fn to_css(&self, dest: &mut W) -> fmt::Result - where - W: fmt::Write, - { - match *self { - PseudoClass::Hover => dest.write_str(":hover"), - PseudoClass::Active => dest.write_str(":active"), - PseudoClass::Lang(ref lang) => { - dest.write_str(":lang(")?; - serialize_identifier(lang, dest)?; - dest.write_char(')') - }, - } - } - } - - impl ToCss for PseudoElement { - fn to_css(&self, dest: &mut W) -> fmt::Result - where - W: fmt::Write, - { - match *self { - PseudoElement::Before => dest.write_str("::before"), - PseudoElement::After => dest.write_str("::after"), - PseudoElement::Highlight(ref name) => { - dest.write_str("::highlight(")?; - serialize_identifier(&name, dest)?; - dest.write_char(')') - }, - } - } - } - - #[derive(Clone, Debug, PartialEq)] - pub struct DummySelectorImpl; - - #[derive(Default)] - pub struct DummyParser { - default_ns: Option, - ns_prefixes: HashMap, - } - - impl DummyParser { - fn default_with_namespace(default_ns: DummyAtom) -> DummyParser { - DummyParser { - default_ns: Some(default_ns), - ns_prefixes: Default::default(), - } - } - } - - impl SelectorImpl for DummySelectorImpl { - type ExtraMatchingData<'a> = std::marker::PhantomData<&'a ()>; - type AttrValue = DummyAttrValue; - type Identifier = DummyAtom; - type LocalName = DummyAtom; - type NamespaceUrl = DummyAtom; - type NamespacePrefix = DummyAtom; - type BorrowedLocalName = DummyAtom; - type BorrowedNamespaceUrl = DummyAtom; - type NonTSPseudoClass = PseudoClass; - type PseudoElement = PseudoElement; - } - - #[derive(Clone, Debug, Default, Eq, Hash, PartialEq)] - pub struct DummyAttrValue(String); - - impl ToCss for DummyAttrValue { - fn to_css(&self, dest: &mut W) -> fmt::Result - where - W: fmt::Write, - { - use std::fmt::Write; - - write!(cssparser::CssStringWriter::new(dest), "{}", &self.0) - } - } - - impl<'a> From<&'a str> for DummyAttrValue { - fn from(string: &'a str) -> Self { - Self(string.into()) - } - } - - #[derive(Clone, Debug, Default, Eq, Hash, PartialEq)] - pub struct DummyAtom(String); - - impl ToCss for DummyAtom { - fn to_css(&self, dest: &mut W) -> fmt::Result - where - W: fmt::Write, - { - serialize_identifier(&self.0, dest) - } - } - - impl From for DummyAtom { - fn from(string: String) -> Self { - DummyAtom(string) - } - } - - impl<'a> From<&'a str> for DummyAtom { - fn from(string: &'a str) -> Self { - DummyAtom(string.into()) - } - } - - impl<'i> Parser<'i> for DummyParser { - type Impl = DummySelectorImpl; - type Error = SelectorParseErrorKind<'i>; - - fn parse_slotted(&self) -> bool { - true - } - - fn parse_nth_child_of(&self) -> bool { - true - } - - fn parse_is_and_where(&self) -> bool { - true - } - - fn parse_has(&self) -> bool { - true - } - - fn parse_parent_selector(&self) -> bool { - true - } - - fn parse_part(&self) -> bool { - true - } - - fn parse_non_ts_pseudo_class( - &self, - location: SourceLocation, - name: CowRcStr<'i>, - ) -> Result> { - match_ignore_ascii_case! { &name, - "hover" => return Ok(PseudoClass::Hover), - "active" => return Ok(PseudoClass::Active), - _ => {} - } - Err( - location.new_custom_error(SelectorParseErrorKind::UnsupportedPseudoClassOrElement( - name, - )), - ) - } - - fn parse_non_ts_functional_pseudo_class<'t>( - &self, - name: CowRcStr<'i>, - parser: &mut CssParser<'i, 't>, - ) -> Result> { - match_ignore_ascii_case! { &name, - "lang" => { - let lang = parser.expect_ident_or_string()?.as_ref().to_owned(); - return Ok(PseudoClass::Lang(lang)); - }, - _ => {} - } - Err( - parser.new_custom_error(SelectorParseErrorKind::UnsupportedPseudoClassOrElement( - name, - )), - ) - } - - fn parse_pseudo_element( - &self, - location: SourceLocation, - name: CowRcStr<'i>, - ) -> Result> { - match_ignore_ascii_case! { &name, - "before" => return Ok(PseudoElement::Before), - "after" => return Ok(PseudoElement::After), - _ => {} - } - Err( - location.new_custom_error(SelectorParseErrorKind::UnsupportedPseudoClassOrElement( - name, - )), - ) - } - - fn parse_functional_pseudo_element<'t>( - &self, - name: CowRcStr<'i>, - parser: &mut CssParser<'i, 't>, - ) -> Result> { - match_ignore_ascii_case! {&name, - "highlight" => return Ok(PseudoElement::Highlight(parser.expect_ident()?.as_ref().to_owned())), - _ => {} - } - Err( - parser.new_custom_error(SelectorParseErrorKind::UnsupportedPseudoClassOrElement( - name, - )), - ) - } - - fn default_namespace(&self) -> Option { - self.default_ns.clone() - } - - fn namespace_for_prefix(&self, prefix: &DummyAtom) -> Option { - self.ns_prefixes.get(prefix).cloned() - } - } - - fn parse<'i>( - input: &'i str, - ) -> Result, SelectorParseError<'i>> { - parse_ns(input, &DummyParser::default()) - } - - fn parse_expected<'i, 'a>( - input: &'i str, - expected: Option<&'a str>, - ) -> Result, SelectorParseError<'i>> { - parse_ns_expected(input, &DummyParser::default(), expected) - } - - fn parse_ns<'i>( - input: &'i str, - parser: &DummyParser, - ) -> Result, SelectorParseError<'i>> { - parse_ns_expected(input, parser, None) - } - - fn parse_ns_expected<'i, 'a>( - input: &'i str, - parser: &DummyParser, - expected: Option<&'a str>, - ) -> Result, SelectorParseError<'i>> { - let mut parser_input = ParserInput::new(input); - let result = SelectorList::parse(parser, &mut CssParser::new(&mut parser_input)); - if let Ok(ref selectors) = result { - // We can't assume that the serialized parsed selector will equal - // the input; for example, if there is no default namespace, '*|foo' - // should serialize to 'foo'. - assert_eq!( - selectors.to_css_string(), - match expected { - Some(x) => x, - None => input, - } - ); - } - result - } - - fn specificity(a: u32, b: u32, c: u32) -> u32 { - a << 20 | b << 10 | c - } - - #[test] - fn test_empty() { - let mut input = ParserInput::new(":empty"); - let list = SelectorList::parse(&DummyParser::default(), &mut CssParser::new(&mut input)); - assert!(list.is_ok()); - } - - const MATHML: &str = "http://www.w3.org/1998/Math/MathML"; - const SVG: &str = "http://www.w3.org/2000/svg"; - - #[test] - fn test_parsing() { - assert!(parse("").is_err()); - assert!(parse(":lang(4)").is_err()); - assert!(parse(":lang(en US)").is_err()); - assert_eq!( - parse("EeÉ"), - Ok(SelectorList::from_vec(vec![Selector::from_vec( - vec![Component::LocalName(LocalName { - name: DummyAtom::from("EeÉ"), - lower_name: DummyAtom::from("eeÉ"), - })], - specificity(0, 0, 1), - Default::default(), - )])) - ); - assert_eq!( - parse("|e"), - Ok(SelectorList::from_vec(vec![Selector::from_vec( - vec![ - Component::ExplicitNoNamespace, - Component::LocalName(LocalName { - name: DummyAtom::from("e"), - lower_name: DummyAtom::from("e"), - }), - ], - specificity(0, 0, 1), - Default::default(), - )])) - ); - // When the default namespace is not set, *| should be elided. - // https://github.com/servo/servo/pull/17537 - assert_eq!( - parse_expected("*|e", Some("e")), - Ok(SelectorList::from_vec(vec![Selector::from_vec( - vec![Component::LocalName(LocalName { - name: DummyAtom::from("e"), - lower_name: DummyAtom::from("e"), - })], - specificity(0, 0, 1), - Default::default(), - )])) - ); - // When the default namespace is set, *| should _not_ be elided (as foo - // is no longer equivalent to *|foo--the former is only for foo in the - // default namespace). - // https://github.com/servo/servo/issues/16020 - assert_eq!( - parse_ns( - "*|e", - &DummyParser::default_with_namespace(DummyAtom::from("https://mozilla.org")) - ), - Ok(SelectorList::from_vec(vec![Selector::from_vec( - vec![ - Component::ExplicitAnyNamespace, - Component::LocalName(LocalName { - name: DummyAtom::from("e"), - lower_name: DummyAtom::from("e"), - }), - ], - specificity(0, 0, 1), - Default::default(), - )])) - ); - assert_eq!( - parse("*"), - Ok(SelectorList::from_vec(vec![Selector::from_vec( - vec![Component::ExplicitUniversalType], - specificity(0, 0, 0), - Default::default(), - )])) - ); - assert_eq!( - parse("|*"), - Ok(SelectorList::from_vec(vec![Selector::from_vec( - vec![ - Component::ExplicitNoNamespace, - Component::ExplicitUniversalType, - ], - specificity(0, 0, 0), - Default::default(), - )])) - ); - assert_eq!( - parse_expected("*|*", Some("*")), - Ok(SelectorList::from_vec(vec![Selector::from_vec( - vec![Component::ExplicitUniversalType], - specificity(0, 0, 0), - Default::default(), - )])) - ); - assert_eq!( - parse_ns( - "*|*", - &DummyParser::default_with_namespace(DummyAtom::from("https://mozilla.org")) - ), - Ok(SelectorList::from_vec(vec![Selector::from_vec( - vec![ - Component::ExplicitAnyNamespace, - Component::ExplicitUniversalType, - ], - specificity(0, 0, 0), - Default::default(), - )])) - ); - assert_eq!( - parse(".foo:lang(en-US)"), - Ok(SelectorList::from_vec(vec![Selector::from_vec( - vec![ - Component::Class(DummyAtom::from("foo")), - Component::NonTSPseudoClass(PseudoClass::Lang("en-US".to_owned())), - ], - specificity(0, 2, 0), - Default::default(), - )])) - ); - assert_eq!( - parse("#bar"), - Ok(SelectorList::from_vec(vec![Selector::from_vec( - vec![Component::ID(DummyAtom::from("bar"))], - specificity(1, 0, 0), - Default::default(), - )])) - ); - assert_eq!( - parse("e.foo#bar"), - Ok(SelectorList::from_vec(vec![Selector::from_vec( - vec![ - Component::LocalName(LocalName { - name: DummyAtom::from("e"), - lower_name: DummyAtom::from("e"), - }), - Component::Class(DummyAtom::from("foo")), - Component::ID(DummyAtom::from("bar")), - ], - specificity(1, 1, 1), - Default::default(), - )])) - ); - assert_eq!( - parse("e.foo #bar"), - Ok(SelectorList::from_vec(vec![Selector::from_vec( - vec![ - Component::LocalName(LocalName { - name: DummyAtom::from("e"), - lower_name: DummyAtom::from("e"), - }), - Component::Class(DummyAtom::from("foo")), - Component::Combinator(Combinator::Descendant), - Component::ID(DummyAtom::from("bar")), - ], - specificity(1, 1, 1), - Default::default(), - )])) - ); - // Default namespace does not apply to attribute selectors - // https://github.com/mozilla/servo/pull/1652 - let mut parser = DummyParser::default(); - assert_eq!( - parse_ns("[Foo]", &parser), - Ok(SelectorList::from_vec(vec![Selector::from_vec( - vec![Component::AttributeInNoNamespaceExists { - local_name: DummyAtom::from("Foo"), - local_name_lower: DummyAtom::from("foo"), - }], - specificity(0, 1, 0), - Default::default(), - )])) - ); - assert!(parse_ns("svg|circle", &parser).is_err()); - parser - .ns_prefixes - .insert(DummyAtom("svg".into()), DummyAtom(SVG.into())); - assert_eq!( - parse_ns("svg|circle", &parser), - Ok(SelectorList::from_vec(vec![Selector::from_vec( - vec![ - Component::Namespace(DummyAtom("svg".into()), SVG.into()), - Component::LocalName(LocalName { - name: DummyAtom::from("circle"), - lower_name: DummyAtom::from("circle"), - }), - ], - specificity(0, 0, 1), - Default::default(), - )])) - ); - assert_eq!( - parse_ns("svg|*", &parser), - Ok(SelectorList::from_vec(vec![Selector::from_vec( - vec![ - Component::Namespace(DummyAtom("svg".into()), SVG.into()), - Component::ExplicitUniversalType, - ], - specificity(0, 0, 0), - Default::default(), - )])) - ); - // Default namespace does not apply to attribute selectors - // https://github.com/mozilla/servo/pull/1652 - // but it does apply to implicit type selectors - // https://github.com/servo/rust-selectors/pull/82 - parser.default_ns = Some(MATHML.into()); - assert_eq!( - parse_ns("[Foo]", &parser), - Ok(SelectorList::from_vec(vec![Selector::from_vec( - vec![ - Component::DefaultNamespace(MATHML.into()), - Component::AttributeInNoNamespaceExists { - local_name: DummyAtom::from("Foo"), - local_name_lower: DummyAtom::from("foo"), - }, - ], - specificity(0, 1, 0), - Default::default(), - )])) - ); - // Default namespace does apply to type selectors - assert_eq!( - parse_ns("e", &parser), - Ok(SelectorList::from_vec(vec![Selector::from_vec( - vec![ - Component::DefaultNamespace(MATHML.into()), - Component::LocalName(LocalName { - name: DummyAtom::from("e"), - lower_name: DummyAtom::from("e"), - }), - ], - specificity(0, 0, 1), - Default::default(), - )])) - ); - assert_eq!( - parse_ns("*", &parser), - Ok(SelectorList::from_vec(vec![Selector::from_vec( - vec![ - Component::DefaultNamespace(MATHML.into()), - Component::ExplicitUniversalType, - ], - specificity(0, 0, 0), - Default::default(), - )])) - ); - assert_eq!( - parse_ns("*|*", &parser), - Ok(SelectorList::from_vec(vec![Selector::from_vec( - vec![ - Component::ExplicitAnyNamespace, - Component::ExplicitUniversalType, - ], - specificity(0, 0, 0), - Default::default(), - )])) - ); - // Default namespace applies to universal and type selectors inside :not and :matches, - // but not otherwise. - assert_eq!( - parse_ns(":not(.cl)", &parser), - Ok(SelectorList::from_vec(vec![Selector::from_vec( - vec![ - Component::DefaultNamespace(MATHML.into()), - Component::Negation( - vec![Selector::from_vec( - vec![Component::Class(DummyAtom::from("cl"))], - specificity(0, 1, 0), - Default::default(), - )] - .into_boxed_slice() - ), - ], - specificity(0, 1, 0), - Default::default(), - )])) - ); - assert_eq!( - parse_ns(":not(*)", &parser), - Ok(SelectorList::from_vec(vec![Selector::from_vec( - vec![ - Component::DefaultNamespace(MATHML.into()), - Component::Negation( - vec![Selector::from_vec( - vec![ - Component::DefaultNamespace(MATHML.into()), - Component::ExplicitUniversalType, - ], - specificity(0, 0, 0), - Default::default(), - )] - .into_boxed_slice(), - ), - ], - specificity(0, 0, 0), - Default::default(), - )])) - ); - assert_eq!( - parse_ns(":not(e)", &parser), - Ok(SelectorList::from_vec(vec![Selector::from_vec( - vec![ - Component::DefaultNamespace(MATHML.into()), - Component::Negation( - vec![Selector::from_vec( - vec![ - Component::DefaultNamespace(MATHML.into()), - Component::LocalName(LocalName { - name: DummyAtom::from("e"), - lower_name: DummyAtom::from("e"), - }), - ], - specificity(0, 0, 1), - Default::default(), - ),] - .into_boxed_slice() - ), - ], - specificity(0, 0, 1), - Default::default(), - )])) - ); - assert_eq!( - parse("[attr|=\"foo\"]"), - Ok(SelectorList::from_vec(vec![Selector::from_vec( - vec![Component::AttributeInNoNamespace { - local_name: DummyAtom::from("attr"), - operator: AttrSelectorOperator::DashMatch, - value: DummyAttrValue::from("foo"), - case_sensitivity: ParsedCaseSensitivity::CaseSensitive, - }], - specificity(0, 1, 0), - Default::default(), - )])) - ); - // https://github.com/mozilla/servo/issues/1723 - assert_eq!( - parse("::before"), - Ok(SelectorList::from_vec(vec![Selector::from_vec( - vec![ - Component::Combinator(Combinator::PseudoElement), - Component::PseudoElement(PseudoElement::Before), - ], - specificity(0, 0, 1), - SelectorFlags::HAS_PSEUDO, - )])) - ); - assert_eq!( - parse("::before:hover"), - Ok(SelectorList::from_vec(vec![Selector::from_vec( - vec![ - Component::Combinator(Combinator::PseudoElement), - Component::PseudoElement(PseudoElement::Before), - Component::NonTSPseudoClass(PseudoClass::Hover), - ], - specificity(0, 1, 1), - SelectorFlags::HAS_PSEUDO, - )])) - ); - assert_eq!( - parse("::before:hover:hover"), - Ok(SelectorList::from_vec(vec![Selector::from_vec( - vec![ - Component::Combinator(Combinator::PseudoElement), - Component::PseudoElement(PseudoElement::Before), - Component::NonTSPseudoClass(PseudoClass::Hover), - Component::NonTSPseudoClass(PseudoClass::Hover), - ], - specificity(0, 2, 1), - SelectorFlags::HAS_PSEUDO, - )])) - ); - assert!(parse("::before:hover:lang(foo)").is_err()); - assert!(parse("::before:hover .foo").is_err()); - assert!(parse("::before .foo").is_err()); - assert!(parse("::before ~ bar").is_err()); - assert!(parse("::before:active").is_ok()); - - // https://github.com/servo/servo/issues/15335 - assert!(parse(":: before").is_err()); - assert_eq!( - parse("div ::after"), - Ok(SelectorList::from_vec(vec![Selector::from_vec( - vec![ - Component::LocalName(LocalName { - name: DummyAtom::from("div"), - lower_name: DummyAtom::from("div"), - }), - Component::Combinator(Combinator::Descendant), - Component::Combinator(Combinator::PseudoElement), - Component::PseudoElement(PseudoElement::After), - ], - specificity(0, 0, 2), - SelectorFlags::HAS_PSEUDO, - )])) - ); - assert_eq!( - parse("#d1 > .ok"), - Ok(SelectorList::from_vec(vec![Selector::from_vec( - vec![ - Component::ID(DummyAtom::from("d1")), - Component::Combinator(Combinator::Child), - Component::Class(DummyAtom::from("ok")), - ], - (1 << 20) + (1 << 10) + (0 << 0), - Default::default(), - )])) - ); - parser.default_ns = None; - assert!(parse(":not(#provel.old)").is_ok()); - assert!(parse(":not(#provel > old)").is_ok()); - assert!(parse("table[rules]:not([rules=\"none\"]):not([rules=\"\"])").is_ok()); - // https://github.com/servo/servo/issues/16017 - assert_eq!( - parse_ns(":not(*)", &parser), - Ok(SelectorList::from_vec(vec![Selector::from_vec( - vec![Component::Negation( - vec![Selector::from_vec( - vec![Component::ExplicitUniversalType], - specificity(0, 0, 0), - Default::default(), - )] - .into_boxed_slice() - )], - specificity(0, 0, 0), - Default::default(), - )])) - ); - assert_eq!( - parse_ns(":not(|*)", &parser), - Ok(SelectorList::from_vec(vec![Selector::from_vec( - vec![Component::Negation( - vec![Selector::from_vec( - vec![ - Component::ExplicitNoNamespace, - Component::ExplicitUniversalType, - ], - specificity(0, 0, 0), - Default::default(), - )] - .into_boxed_slice(), - )], - specificity(0, 0, 0), - Default::default(), - )])) - ); - // *| should be elided if there is no default namespace. - // https://github.com/servo/servo/pull/17537 - assert_eq!( - parse_ns_expected(":not(*|*)", &parser, Some(":not(*)")), - Ok(SelectorList::from_vec(vec![Selector::from_vec( - vec![Component::Negation( - vec![Selector::from_vec( - vec![Component::ExplicitUniversalType], - specificity(0, 0, 0), - Default::default() - )] - .into_boxed_slice() - )], - specificity(0, 0, 0), - Default::default(), - )])) - ); - - assert!(parse("::highlight(foo)").is_ok()); - - assert!(parse("::slotted()").is_err()); - assert!(parse("::slotted(div)").is_ok()); - assert!(parse("::slotted(div).foo").is_err()); - assert!(parse("::slotted(div + bar)").is_err()); - assert!(parse("::slotted(div) + foo").is_err()); - - assert!(parse("::part()").is_err()); - assert!(parse("::part(42)").is_err()); - assert!(parse("::part(foo bar)").is_ok()); - assert!(parse("::part(foo):hover").is_ok()); - assert!(parse("::part(foo) + bar").is_err()); - - assert!(parse("div ::slotted(div)").is_ok()); - assert!(parse("div + slot::slotted(div)").is_ok()); - assert!(parse("div + slot::slotted(div.foo)").is_ok()); - assert!(parse("slot::slotted(div,foo)::first-line").is_err()); - assert!(parse("::slotted(div)::before").is_ok()); - assert!(parse("slot::slotted(div,foo)").is_err()); - - assert!(parse("foo:where()").is_ok()); - assert!(parse("foo:where(div, foo, .bar baz)").is_ok()); - assert!(parse_expected("foo:where(::before)", Some("foo:where()")).is_ok()); - } - - #[test] - fn parent_selector() { - assert!(parse("foo &").is_ok()); - assert_eq!( - parse("#foo &.bar"), - Ok(SelectorList::from_vec(vec![Selector::from_vec( - vec![ - Component::ID(DummyAtom::from("foo")), - Component::Combinator(Combinator::Descendant), - Component::ParentSelector, - Component::Class(DummyAtom::from("bar")), - ], - (1 << 20) + (1 << 10) + (0 << 0), - SelectorFlags::HAS_PARENT - )])) - ); - - let parent = parse(".bar, div .baz").unwrap(); - let child = parse("#foo &.bar").unwrap(); - assert_eq!( - SelectorList::from_vec(vec![child.0[0].replace_parent_selector(&parent.0)]), - parse("#foo :is(.bar, div .baz).bar").unwrap() - ); - - let has_child = parse("#foo:has(&.bar)").unwrap(); - assert_eq!( - SelectorList::from_vec(vec![has_child.0[0].replace_parent_selector(&parent.0)]), - parse("#foo:has(:is(.bar, div .baz).bar)").unwrap() - ); - - let child = parse("#foo").unwrap(); - assert_eq!( - SelectorList::from_vec(vec![child.0[0].replace_parent_selector(&parent.0)]), - parse(":is(.bar, div .baz) #foo").unwrap() - ); - } - - #[test] - fn test_pseudo_iter() { - let selector = &parse("q::before").unwrap().0[0]; - assert!(!selector.is_universal()); - let mut iter = selector.iter(); - assert_eq!( - iter.next(), - Some(&Component::PseudoElement(PseudoElement::Before)) - ); - assert_eq!(iter.next(), None); - let combinator = iter.next_sequence(); - assert_eq!(combinator, Some(Combinator::PseudoElement)); - assert!(matches!(iter.next(), Some(&Component::LocalName(..)))); - assert_eq!(iter.next(), None); - assert_eq!(iter.next_sequence(), None); - } - - #[test] - fn test_universal() { - let selector = &parse_ns( - "*|*::before", - &DummyParser::default_with_namespace(DummyAtom::from("https://mozilla.org")), - ) - .unwrap() - .0[0]; - assert!(selector.is_universal()); - } - - #[test] - fn test_empty_pseudo_iter() { - let selector = &parse("::before").unwrap().0[0]; - assert!(selector.is_universal()); - let mut iter = selector.iter(); - assert_eq!( - iter.next(), - Some(&Component::PseudoElement(PseudoElement::Before)) - ); - assert_eq!(iter.next(), None); - assert_eq!(iter.next_sequence(), Some(Combinator::PseudoElement)); - assert_eq!(iter.next(), None); - assert_eq!(iter.next_sequence(), None); - } - - struct TestVisitor { - seen: Vec, - } - - impl SelectorVisitor for TestVisitor { - type Impl = DummySelectorImpl; - - fn visit_simple_selector(&mut self, s: &Component) -> bool { - let mut dest = String::new(); - s.to_css(&mut dest).unwrap(); - self.seen.push(dest); - true - } - } - - #[test] - fn visitor() { - let mut test_visitor = TestVisitor { seen: vec![] }; - parse(":not(:hover) ~ label").unwrap().0[0].visit(&mut test_visitor); - assert!(test_visitor.seen.contains(&":hover".into())); - - let mut test_visitor = TestVisitor { seen: vec![] }; - parse("::before:hover").unwrap().0[0].visit(&mut test_visitor); - assert!(test_visitor.seen.contains(&":hover".into())); - } -} 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/sink.rs b/components/selectors/sink.rs deleted file mode 100644 index dcdd7ff259d..00000000000 --- a/components/selectors/sink.rs +++ /dev/null @@ -1,31 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ - -//! Small helpers to abstract over different containers. -#![deny(missing_docs)] - -use smallvec::{Array, SmallVec}; - -/// A trait to abstract over a `push` method that may be implemented for -/// different kind of types. -/// -/// Used to abstract over `Array`, `SmallVec` and `Vec`, and also to implement a -/// type which `push` method does only tweak a byte when we only need to check -/// for the presence of something. -pub trait Push { - /// Push a value into self. - fn push(&mut self, value: T); -} - -impl Push for Vec { - fn push(&mut self, value: T) { - Vec::push(self, value); - } -} - -impl Push for SmallVec { - fn push(&mut self, value: A::Item) { - SmallVec::push(self, value); - } -} diff --git a/components/selectors/tree.rs b/components/selectors/tree.rs deleted file mode 100644 index 560b1e61d81..00000000000 --- a/components/selectors/tree.rs +++ /dev/null @@ -1,163 +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/. */ - -//! Traits that nodes must implement. Breaks the otherwise-cyclic dependency -//! between layout and style. - -use crate::attr::{AttrSelectorOperation, CaseSensitivity, NamespaceConstraint}; -use crate::matching::{ElementSelectorFlags, MatchingContext}; -use crate::parser::SelectorImpl; -use std::fmt::Debug; -use std::ptr::NonNull; - -/// Opaque representation of an Element, for identity comparisons. -#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)] -pub struct OpaqueElement(NonNull<()>); - -unsafe impl Send for OpaqueElement {} - -impl OpaqueElement { - /// Creates a new OpaqueElement from an arbitrarily-typed pointer. - pub fn new(ptr: &T) -> Self { - unsafe { - OpaqueElement(NonNull::new_unchecked( - ptr as *const T as *const () as *mut (), - )) - } - } -} - -pub trait Element: Sized + Clone + Debug { - type Impl: SelectorImpl; - - /// Converts self into an opaque representation. - fn opaque(&self) -> OpaqueElement; - - fn parent_element(&self) -> Option; - - /// Whether the parent node of this element is a shadow root. - fn parent_node_is_shadow_root(&self) -> bool; - - /// The host of the containing shadow root, if any. - fn containing_shadow_host(&self) -> Option; - - /// The parent of a given pseudo-element, after matching a pseudo-element - /// selector. - /// - /// This is guaranteed to be called in a pseudo-element. - fn pseudo_element_originating_element(&self) -> Option { - debug_assert!(self.is_pseudo_element()); - self.parent_element() - } - - /// Whether we're matching on a pseudo-element. - fn is_pseudo_element(&self) -> bool; - - /// Skips non-element nodes - fn prev_sibling_element(&self) -> Option; - - /// Skips non-element nodes - fn next_sibling_element(&self) -> Option; - - /// Skips non-element nodes - fn first_element_child(&self) -> Option; - - fn is_html_element_in_html_document(&self) -> bool; - - fn has_local_name(&self, local_name: &::BorrowedLocalName) -> bool; - - /// Empty string for no namespace - fn has_namespace(&self, ns: &::BorrowedNamespaceUrl) -> bool; - - /// Whether this element and the `other` element have the same local name and namespace. - fn is_same_type(&self, other: &Self) -> bool; - - fn attr_matches( - &self, - ns: &NamespaceConstraint<&::NamespaceUrl>, - local_name: &::LocalName, - operation: &AttrSelectorOperation<&::AttrValue>, - ) -> bool; - - fn has_attr_in_no_namespace( - &self, - local_name: &::LocalName, - ) -> bool { - self.attr_matches( - &NamespaceConstraint::Specific(&crate::parser::namespace_empty_string::()), - local_name, - &AttrSelectorOperation::Exists, - ) - } - - fn match_non_ts_pseudo_class( - &self, - pc: &::NonTSPseudoClass, - context: &mut MatchingContext, - ) -> bool; - - fn match_pseudo_element( - &self, - pe: &::PseudoElement, - context: &mut MatchingContext, - ) -> bool; - - /// Sets selector flags on the elemnt itself or the parent, depending on the - /// flags, which indicate what kind of work may need to be performed when - /// DOM state changes. - fn apply_selector_flags(&self, flags: ElementSelectorFlags); - - /// Whether this element is a `link`. - fn is_link(&self) -> bool; - - /// Returns whether the element is an HTML element. - fn is_html_slot_element(&self) -> bool; - - /// Returns the assigned element this element is assigned to. - /// - /// Necessary for the `::slotted` pseudo-class. - fn assigned_slot(&self) -> Option { - None - } - - fn has_id( - &self, - id: &::Identifier, - case_sensitivity: CaseSensitivity, - ) -> bool; - - fn has_class( - &self, - name: &::Identifier, - case_sensitivity: CaseSensitivity, - ) -> bool; - - /// Returns the mapping from the `exportparts` attribute in the reverse - /// direction, that is, in an outer-tree -> inner-tree direction. - fn imported_part( - &self, - name: &::Identifier, - ) -> Option<::Identifier>; - - fn is_part(&self, name: &::Identifier) -> bool; - - /// Returns whether this element matches `:empty`. - /// - /// That is, whether it does not contain any child element or any non-zero-length text node. - /// See http://dev.w3.org/csswg/selectors-3/#empty-pseudo - fn is_empty(&self) -> bool; - - /// Returns whether this element matches `:root`, - /// i.e. whether it is the root element of a document. - /// - /// Note: this can be false even if `.parent_element()` is `None` - /// if the parent node is a `DocumentFragment`. - fn is_root(&self) -> bool; - - /// Returns whether this element should ignore matching nth child - /// selector. - fn ignores_nth_child_selectors(&self) -> bool { - false - } -} diff --git a/components/selectors/visitor.rs b/components/selectors/visitor.rs deleted file mode 100644 index 785c12813a6..00000000000 --- a/components/selectors/visitor.rs +++ /dev/null @@ -1,111 +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/. */ - -//! Visitor traits for selectors. - -#![deny(missing_docs)] - -use crate::attr::NamespaceConstraint; -use crate::parser::{Combinator, Component, Selector, SelectorImpl}; - -/// A trait to visit selector properties. -/// -/// All the `visit_foo` methods return a boolean indicating whether the -/// traversal should continue or not. -pub trait SelectorVisitor: Sized { - /// The selector implementation this visitor wants to visit. - type Impl: SelectorImpl; - - /// Visit an attribute selector that may match (there are other selectors - /// that may never match, like those containing whitespace or the empty - /// string). - fn visit_attribute_selector( - &mut self, - _namespace: &NamespaceConstraint<&::NamespaceUrl>, - _local_name: &::LocalName, - _local_name_lower: &::LocalName, - ) -> bool { - true - } - - /// Visit a simple selector. - fn visit_simple_selector(&mut self, _: &Component) -> bool { - true - } - - /// Visit a nested selector list. The caller is responsible to call visit - /// into the internal selectors if / as needed. - /// - /// The default implementation does this. - fn visit_selector_list( - &mut self, - _list_kind: SelectorListKind, - list: &[Selector], - ) -> bool { - for nested in list { - if !nested.visit(self) { - return false; - } - } - true - } - - /// Visits a complex selector. - /// - /// Gets the combinator to the right of the selector, or `None` if the - /// selector is the rightmost one. - fn visit_complex_selector(&mut self, _combinator_to_right: Option) -> bool { - true - } -} - -bitflags! { - /// The kinds of components the visitor is visiting the selector list of, if any - #[derive(Default)] - pub struct SelectorListKind: u8 { - /// The visitor is inside :not(..) - const NEGATION = 1 << 0; - /// The visitor is inside :is(..) - const IS = 1 << 1; - /// The visitor is inside :where(..) - const WHERE = 1 << 2; - /// The visitor is inside :nth-child(.. of ) or - /// :nth-last-child(.. of ) - const NTH_OF = 1 << 3; - } -} - -impl SelectorListKind { - /// Construct a SelectorListKind for the corresponding component. - pub fn from_component(component: &Component) -> Self { - match component { - Component::Negation(_) => SelectorListKind::NEGATION, - Component::Is(_) => SelectorListKind::IS, - Component::Where(_) => SelectorListKind::WHERE, - Component::NthOf(_) => SelectorListKind::NTH_OF, - _ => SelectorListKind::empty(), - } - } - - /// Whether the visitor is inside :not(..) - pub fn in_negation(&self) -> bool { - self.intersects(SelectorListKind::NEGATION) - } - - /// Whether the visitor is inside :is(..) - pub fn in_is(&self) -> bool { - self.intersects(SelectorListKind::IS) - } - - /// Whether the visitor is inside :where(..) - pub fn in_where(&self) -> bool { - self.intersects(SelectorListKind::WHERE) - } - - /// Whether the visitor is inside :nth-child(.. of ) or - /// :nth-last-child(.. of ) - pub fn in_nth_of(&self) -> bool { - self.intersects(SelectorListKind::NTH_OF) - } -} diff --git a/components/servo/Cargo.toml b/components/servo/Cargo.toml index 29ae33c0d5e..7fed4386d64 100644 --- a/components/servo/Cargo.toml +++ b/components/servo/Cargo.toml @@ -72,7 +72,7 @@ servo_config = { path = "../config" } servo_geometry = { path = "../geometry" } servo_url = { path = "../url" } sparkle = { workspace = true } -style = { path = "../style", features = ["servo"] } +style = { workspace = true } style_traits = { workspace = true } surfman = { workspace = true } webdriver_server = { path = "../webdriver_server", optional = true } diff --git a/components/servo_arc/Cargo.toml b/components/servo_arc/Cargo.toml deleted file mode 100644 index c975ad55412..00000000000 --- a/components/servo_arc/Cargo.toml +++ /dev/null @@ -1,20 +0,0 @@ -[package] -name = "servo_arc" -version = "0.2.0" -authors = ["The Servo Project Developers"] -license = "MIT OR Apache-2.0" -repository = "https://github.com/servo/servo" -description = "A fork of std::sync::Arc with some extra functionality and without weak references" - -[lib] -name = "servo_arc" -path = "lib.rs" - -[features] -gecko_refcount_logging = [] -servo = ["serde"] - -[dependencies] -nodrop = { version = "0.1.8" } -serde = { workspace = true, optional = true } -stable_deref_trait = "1.0.0" diff --git a/components/servo_arc/LICENSE-APACHE b/components/servo_arc/LICENSE-APACHE deleted file mode 100644 index 16fe87b06e8..00000000000 --- a/components/servo_arc/LICENSE-APACHE +++ /dev/null @@ -1,201 +0,0 @@ - Apache License - Version 2.0, January 2004 - http://www.apache.org/licenses/ - -TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION - -1. Definitions. - - "License" shall mean the terms and conditions for use, reproduction, - and distribution as defined by Sections 1 through 9 of this document. - - "Licensor" shall mean the copyright owner or entity authorized by - the copyright owner that is granting the License. - - "Legal Entity" shall mean the union of the acting entity and all - other entities that control, are controlled by, or are under common - control with that entity. For the purposes of this definition, - "control" means (i) the power, direct or indirect, to cause the - direction or management of such entity, whether by contract or - otherwise, or (ii) ownership of fifty percent (50%) or more of the - outstanding shares, or (iii) beneficial ownership of such entity. - - "You" (or "Your") shall mean an individual or Legal Entity - exercising permissions granted by this License. - - "Source" form shall mean the preferred form for making modifications, - including but not limited to software source code, documentation - source, and configuration files. - - "Object" form shall mean any form resulting from mechanical - transformation or translation of a Source form, including but - not limited to compiled object code, generated documentation, - and conversions to other media types. - - "Work" shall mean the work of authorship, whether in Source or - Object form, made available under the License, as indicated by a - copyright notice that is included in or attached to the work - (an example is provided in the Appendix below). - - "Derivative Works" shall mean any work, whether in Source or Object - form, that is based on (or derived from) the Work and for which the - editorial revisions, annotations, elaborations, or other modifications - represent, as a whole, an original work of authorship. For the purposes - of this License, Derivative Works shall not include works that remain - separable from, or merely link (or bind by name) to the interfaces of, - the Work and Derivative Works thereof. - - "Contribution" shall mean any work of authorship, including - the original version of the Work and any modifications or additions - to that Work or Derivative Works thereof, that is intentionally - submitted to Licensor for inclusion in the Work by the copyright owner - or by an individual or Legal Entity authorized to submit on behalf of - the copyright owner. For the purposes of this definition, "submitted" - means any form of electronic, verbal, or written communication sent - to the Licensor or its representatives, including but not limited to - communication on electronic mailing lists, source code control systems, - and issue tracking systems that are managed by, or on behalf of, the - Licensor for the purpose of discussing and improving the Work, but - excluding communication that is conspicuously marked or otherwise - designated in writing by the copyright owner as "Not a Contribution." - - "Contributor" shall mean Licensor and any individual or Legal Entity - on behalf of whom a Contribution has been received by Licensor and - subsequently incorporated within the Work. - -2. Grant of Copyright License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - copyright license to reproduce, prepare Derivative Works of, - publicly display, publicly perform, sublicense, and distribute the - Work and such Derivative Works in Source or Object form. - -3. Grant of Patent License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - (except as stated in this section) patent license to make, have made, - use, offer to sell, sell, import, and otherwise transfer the Work, - where such license applies only to those patent claims licensable - by such Contributor that are necessarily infringed by their - Contribution(s) alone or by combination of their Contribution(s) - with the Work to which such Contribution(s) was submitted. If You - institute patent litigation against any entity (including a - cross-claim or counterclaim in a lawsuit) alleging that the Work - or a Contribution incorporated within the Work constitutes direct - or contributory patent infringement, then any patent licenses - granted to You under this License for that Work shall terminate - as of the date such litigation is filed. - -4. Redistribution. You may reproduce and distribute copies of the - Work or Derivative Works thereof in any medium, with or without - modifications, and in Source or Object form, provided that You - meet the following conditions: - - (a) You must give any other recipients of the Work or - Derivative Works a copy of this License; and - - (b) You must cause any modified files to carry prominent notices - stating that You changed the files; and - - (c) You must retain, in the Source form of any Derivative Works - that You distribute, all copyright, patent, trademark, and - attribution notices from the Source form of the Work, - excluding those notices that do not pertain to any part of - the Derivative Works; and - - (d) If the Work includes a "NOTICE" text file as part of its - distribution, then any Derivative Works that You distribute must - include a readable copy of the attribution notices contained - within such NOTICE file, excluding those notices that do not - pertain to any part of the Derivative Works, in at least one - of the following places: within a NOTICE text file distributed - as part of the Derivative Works; within the Source form or - documentation, if provided along with the Derivative Works; or, - within a display generated by the Derivative Works, if and - wherever such third-party notices normally appear. The contents - of the NOTICE file are for informational purposes only and - do not modify the License. You may add Your own attribution - notices within Derivative Works that You distribute, alongside - or as an addendum to the NOTICE text from the Work, provided - that such additional attribution notices cannot be construed - as modifying the License. - - You may add Your own copyright statement to Your modifications and - may provide additional or different license terms and conditions - for use, reproduction, or distribution of Your modifications, or - for any such Derivative Works as a whole, provided Your use, - reproduction, and distribution of the Work otherwise complies with - the conditions stated in this License. - -5. Submission of Contributions. Unless You explicitly state otherwise, - any Contribution intentionally submitted for inclusion in the Work - by You to the Licensor shall be under the terms and conditions of - this License, without any additional terms or conditions. - Notwithstanding the above, nothing herein shall supersede or modify - the terms of any separate license agreement you may have executed - with Licensor regarding such Contributions. - -6. Trademarks. This License does not grant permission to use the trade - names, trademarks, service marks, or product names of the Licensor, - except as required for reasonable and customary use in describing the - origin of the Work and reproducing the content of the NOTICE file. - -7. Disclaimer of Warranty. Unless required by applicable law or - agreed to in writing, Licensor provides the Work (and each - Contributor provides its Contributions) on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or - implied, including, without limitation, any warranties or conditions - of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A - PARTICULAR PURPOSE. You are solely responsible for determining the - appropriateness of using or redistributing the Work and assume any - risks associated with Your exercise of permissions under this License. - -8. Limitation of Liability. In no event and under no legal theory, - whether in tort (including negligence), contract, or otherwise, - unless required by applicable law (such as deliberate and grossly - negligent acts) or agreed to in writing, shall any Contributor be - liable to You for damages, including any direct, indirect, special, - incidental, or consequential damages of any character arising as a - result of this License or out of the use or inability to use the - Work (including but not limited to damages for loss of goodwill, - work stoppage, computer failure or malfunction, or any and all - other commercial damages or losses), even if such Contributor - has been advised of the possibility of such damages. - -9. Accepting Warranty or Additional Liability. While redistributing - the Work or Derivative Works thereof, You may choose to offer, - and charge a fee for, acceptance of support, warranty, indemnity, - or other liability obligations and/or rights consistent with this - License. However, in accepting such obligations, You may act only - on Your own behalf and on Your sole responsibility, not on behalf - of any other Contributor, and only if You agree to indemnify, - defend, and hold each Contributor harmless for any liability - incurred by, or claims asserted against, such Contributor by reason - of your accepting any such warranty or additional liability. - -END OF TERMS AND CONDITIONS - -APPENDIX: How to apply the Apache License to your work. - - To apply the Apache License to your work, attach the following - boilerplate notice, with the fields enclosed by brackets "[]" - replaced with your own identifying information. (Don't include - the brackets!) The text should be enclosed in the appropriate - comment syntax for the file format. We also recommend that a - file or class name and description of purpose be included on the - same "printed page" as the copyright notice for easier - identification within third-party archives. - -Copyright [yyyy] [name of copyright owner] - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. diff --git a/components/servo_arc/LICENSE-MIT b/components/servo_arc/LICENSE-MIT deleted file mode 100644 index 31aa79387f2..00000000000 --- a/components/servo_arc/LICENSE-MIT +++ /dev/null @@ -1,23 +0,0 @@ -Permission is hereby granted, free of charge, to any -person obtaining a copy of this software and associated -documentation files (the "Software"), to deal in the -Software without restriction, including without -limitation the rights to use, copy, modify, merge, -publish, distribute, sublicense, and/or sell copies of -the Software, and to permit persons to whom the Software -is furnished to do so, subject to the following -conditions: - -The above copyright notice and this permission notice -shall be included in all copies or substantial portions -of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF -ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED -TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A -PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT -SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY -CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION -OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR -IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER -DEALINGS IN THE SOFTWARE. diff --git a/components/servo_arc/lib.rs b/components/servo_arc/lib.rs deleted file mode 100644 index cc71827283a..00000000000 --- a/components/servo_arc/lib.rs +++ /dev/null @@ -1,1370 +0,0 @@ -// Copyright 2012-2014 The Rust Project Developers. See the COPYRIGHT -// file at the top-level directory of this distribution and at -// http://rust-lang.org/COPYRIGHT. -// -// Licensed under the Apache License, Version 2.0 or the MIT license -// , at your -// option. This file may not be copied, modified, or distributed -// except according to those terms. - -//! Fork of Arc for Servo. This has the following advantages over std::sync::Arc: -//! -//! * We don't waste storage on the weak reference count. -//! * We don't do extra RMU operations to handle the possibility of weak references. -//! * We can experiment with arena allocation (todo). -//! * We can add methods to support our custom use cases [1]. -//! * We have support for dynamically-sized types (see from_header_and_iter). -//! * We have support for thin arcs to unsized types (see ThinArc). -//! * We have support for references to static data, which don't do any -//! refcounting. -//! -//! [1]: https://bugzilla.mozilla.org/show_bug.cgi?id=1360883 - -// The semantics of `Arc` are already documented in the Rust docs, so we don't -// duplicate those here. -#![allow(missing_docs)] - -#[cfg(feature = "servo")] -extern crate serde; -extern crate stable_deref_trait; - -#[cfg(feature = "servo")] -use serde::{Deserialize, Serialize}; -use stable_deref_trait::{CloneStableDeref, StableDeref}; -use std::alloc::{self, Layout}; -use std::borrow; -use std::cmp::Ordering; -use std::convert::From; -use std::fmt; -use std::hash::{Hash, Hasher}; -use std::iter::{ExactSizeIterator, Iterator}; -use std::marker::PhantomData; -use std::mem::{self, align_of, size_of}; -use std::ops::{Deref, DerefMut}; -use std::os::raw::c_void; -use std::process; -use std::ptr; -use std::slice; -use std::sync::atomic; -use std::sync::atomic::Ordering::{Acquire, Relaxed, Release}; -use std::{isize, usize}; - -/// A soft limit on the amount of references that may be made to an `Arc`. -/// -/// Going above this limit will abort your program (although not -/// necessarily) at _exactly_ `MAX_REFCOUNT + 1` references. -const MAX_REFCOUNT: usize = (isize::MAX) as usize; - -/// Special refcount value that means the data is not reference counted, -/// and that the `Arc` is really acting as a read-only static reference. -const STATIC_REFCOUNT: usize = usize::MAX; - -/// An atomically reference counted shared pointer -/// -/// See the documentation for [`Arc`] in the standard library. Unlike the -/// standard library `Arc`, this `Arc` does not support weak reference counting. -/// -/// See the discussion in https://github.com/rust-lang/rust/pull/60594 for the -/// usage of PhantomData. -/// -/// [`Arc`]: https://doc.rust-lang.org/stable/std/sync/struct.Arc.html -/// -/// cbindgen:derive-eq=false -/// cbindgen:derive-neq=false -#[repr(C)] -pub struct Arc { - p: ptr::NonNull>, - phantom: PhantomData, -} - -/// An `Arc` that is known to be uniquely owned -/// -/// When `Arc`s are constructed, they are known to be -/// uniquely owned. In such a case it is safe to mutate -/// the contents of the `Arc`. Normally, one would just handle -/// this by mutating the data on the stack before allocating the -/// `Arc`, however it's possible the data is large or unsized -/// and you need to heap-allocate it earlier in such a way -/// that it can be freely converted into a regular `Arc` once you're -/// done. -/// -/// `UniqueArc` exists for this purpose, when constructed it performs -/// the same allocations necessary for an `Arc`, however it allows mutable access. -/// Once the mutation is finished, you can call `.shareable()` and get a regular `Arc` -/// out of it. -/// -/// Ignore the doctest below there's no way to skip building with refcount -/// logging during doc tests (see rust-lang/rust#45599). -/// -/// ```rust,ignore -/// # use servo_arc::UniqueArc; -/// let data = [1, 2, 3, 4, 5]; -/// let mut x = UniqueArc::new(data); -/// x[4] = 7; // mutate! -/// let y = x.shareable(); // y is an Arc -/// ``` -pub struct UniqueArc(Arc); - -impl UniqueArc { - #[inline] - /// Construct a new UniqueArc - pub fn new(data: T) -> Self { - UniqueArc(Arc::new(data)) - } - - /// Construct an uninitialized arc - #[inline] - pub fn new_uninit() -> UniqueArc> { - unsafe { - let layout = Layout::new::>>(); - let ptr = alloc::alloc(layout); - let mut p = ptr::NonNull::new(ptr) - .unwrap_or_else(|| alloc::handle_alloc_error(layout)) - .cast::>>(); - ptr::write(&mut p.as_mut().count, atomic::AtomicUsize::new(1)); - - #[cfg(feature = "gecko_refcount_logging")] - { - NS_LogCtor(p.as_ptr() as *mut _, b"ServoArc\0".as_ptr() as *const _, 8) - } - - UniqueArc(Arc { - p, - phantom: PhantomData, - }) - } - } - - #[inline] - /// Convert to a shareable Arc once we're done mutating it - pub fn shareable(self) -> Arc { - self.0 - } -} - -impl UniqueArc> { - /// Convert to an initialized Arc. - #[inline] - pub unsafe fn assume_init(this: Self) -> UniqueArc { - UniqueArc(Arc { - p: mem::ManuallyDrop::new(this).0.p.cast(), - phantom: PhantomData, - }) - } -} - -impl Deref for UniqueArc { - type Target = T; - fn deref(&self) -> &T { - &*self.0 - } -} - -impl DerefMut for UniqueArc { - fn deref_mut(&mut self) -> &mut T { - // We know this to be uniquely owned - unsafe { &mut (*self.0.ptr()).data } - } -} - -unsafe impl Send for Arc {} -unsafe impl Sync for Arc {} - -/// The object allocated by an Arc -#[repr(C)] -struct ArcInner { - count: atomic::AtomicUsize, - data: T, -} - -unsafe impl Send for ArcInner {} -unsafe impl Sync for ArcInner {} - -/// Computes the offset of the data field within ArcInner. -fn data_offset() -> usize { - let size = size_of::>(); - let align = align_of::(); - // https://github.com/rust-lang/rust/blob/1.36.0/src/libcore/alloc.rs#L187-L207 - size.wrapping_add(align).wrapping_sub(1) & !align.wrapping_sub(1) -} - -impl Arc { - /// Construct an `Arc` - #[inline] - pub fn new(data: T) -> Self { - let ptr = Box::into_raw(Box::new(ArcInner { - count: atomic::AtomicUsize::new(1), - data, - })); - - #[cfg(feature = "gecko_refcount_logging")] - unsafe { - // FIXME(emilio): Would be so amazing to have - // std::intrinsics::type_name() around, so that we could also report - // a real size. - NS_LogCtor(ptr as *mut _, b"ServoArc\0".as_ptr() as *const _, 8); - } - - unsafe { - Arc { - p: ptr::NonNull::new_unchecked(ptr), - phantom: PhantomData, - } - } - } - - /// Construct an intentionally-leaked arc. - #[inline] - pub fn new_leaked(data: T) -> Self { - let arc = Self::new(data); - arc.mark_as_intentionally_leaked(); - arc - } - - /// Convert the Arc to a raw pointer, suitable for use across FFI - /// - /// Note: This returns a pointer to the data T, which is offset in the allocation. - #[inline] - pub fn into_raw(this: Self) -> *const T { - let ptr = unsafe { &((*this.ptr()).data) as *const _ }; - mem::forget(this); - ptr - } - - /// Reconstruct the Arc from a raw pointer obtained from into_raw() - /// - /// Note: This raw pointer will be offset in the allocation and must be preceded - /// by the atomic count. - #[inline] - pub unsafe fn from_raw(ptr: *const T) -> Self { - // To find the corresponding pointer to the `ArcInner` we need - // to subtract the offset of the `data` field from the pointer. - let ptr = (ptr as *const u8).sub(data_offset::()); - Arc { - p: ptr::NonNull::new_unchecked(ptr as *mut ArcInner), - phantom: PhantomData, - } - } - - /// Like from_raw, but returns an addrefed arc instead. - #[inline] - pub unsafe fn from_raw_addrefed(ptr: *const T) -> Self { - let arc = Self::from_raw(ptr); - mem::forget(arc.clone()); - arc - } - - /// Create a new static Arc (one that won't reference count the object) - /// and place it in the allocation provided by the specified `alloc` - /// function. - /// - /// `alloc` must return a pointer into a static allocation suitable for - /// storing data with the `Layout` passed into it. The pointer returned by - /// `alloc` will not be freed. - #[inline] - pub unsafe fn new_static(alloc: F, data: T) -> Arc - where - F: FnOnce(Layout) -> *mut u8, - { - let ptr = alloc(Layout::new::>()) as *mut ArcInner; - - let x = ArcInner { - count: atomic::AtomicUsize::new(STATIC_REFCOUNT), - data, - }; - - ptr::write(ptr, x); - - Arc { - p: ptr::NonNull::new_unchecked(ptr), - phantom: PhantomData, - } - } - - /// Produce a pointer to the data that can be converted back - /// to an Arc. This is basically an `&Arc`, without the extra indirection. - /// It has the benefits of an `&T` but also knows about the underlying refcount - /// and can be converted into more `Arc`s if necessary. - #[inline] - pub fn borrow_arc<'a>(&'a self) -> ArcBorrow<'a, T> { - ArcBorrow(&**self) - } - - /// Returns the address on the heap of the Arc itself -- not the T within it -- for memory - /// reporting. - /// - /// If this is a static reference, this returns null. - pub fn heap_ptr(&self) -> *const c_void { - if self.inner().count.load(Relaxed) == STATIC_REFCOUNT { - ptr::null() - } else { - self.p.as_ptr() as *const ArcInner as *const c_void - } - } -} - -impl Arc { - #[inline] - fn inner(&self) -> &ArcInner { - // This unsafety is ok because while this arc is alive we're guaranteed - // that the inner pointer is valid. Furthermore, we know that the - // `ArcInner` structure itself is `Sync` because the inner data is - // `Sync` as well, so we're ok loaning out an immutable pointer to these - // contents. - unsafe { &*self.ptr() } - } - - #[inline(always)] - fn record_drop(&self) { - #[cfg(feature = "gecko_refcount_logging")] - unsafe { - NS_LogDtor(self.ptr() as *mut _, b"ServoArc\0".as_ptr() as *const _, 8); - } - } - - /// Marks this `Arc` as intentionally leaked for the purposes of refcount - /// logging. - /// - /// It's a logic error to call this more than once, but it's not unsafe, as - /// it'd just report negative leaks. - #[inline(always)] - pub fn mark_as_intentionally_leaked(&self) { - self.record_drop(); - } - - // Non-inlined part of `drop`. Just invokes the destructor and calls the - // refcount logging machinery if enabled. - #[inline(never)] - unsafe fn drop_slow(&mut self) { - self.record_drop(); - let _ = Box::from_raw(self.ptr()); - } - - /// Test pointer equality between the two Arcs, i.e. they must be the _same_ - /// allocation - #[inline] - pub fn ptr_eq(this: &Self, other: &Self) -> bool { - this.ptr() == other.ptr() - } - - fn ptr(&self) -> *mut ArcInner { - self.p.as_ptr() - } -} - -#[cfg(feature = "gecko_refcount_logging")] -extern "C" { - fn NS_LogCtor( - aPtr: *mut std::os::raw::c_void, - aTypeName: *const std::os::raw::c_char, - aSize: u32, - ); - fn NS_LogDtor( - aPtr: *mut std::os::raw::c_void, - aTypeName: *const std::os::raw::c_char, - aSize: u32, - ); -} - -impl Clone for Arc { - #[inline] - fn clone(&self) -> Self { - // NOTE(emilio): If you change anything here, make sure that the - // implementation in layout/style/ServoStyleConstsInlines.h matches! - // - // Using a relaxed ordering to check for STATIC_REFCOUNT is safe, since - // `count` never changes between STATIC_REFCOUNT and other values. - if self.inner().count.load(Relaxed) != STATIC_REFCOUNT { - // Using a relaxed ordering is alright here, as knowledge of the - // original reference prevents other threads from erroneously deleting - // the object. - // - // As explained in the [Boost documentation][1], Increasing the - // reference counter can always be done with memory_order_relaxed: New - // references to an object can only be formed from an existing - // reference, and passing an existing reference from one thread to - // another must already provide any required synchronization. - // - // [1]: (www.boost.org/doc/libs/1_55_0/doc/html/atomic/usage_examples.html) - let old_size = self.inner().count.fetch_add(1, Relaxed); - - // However we need to guard against massive refcounts in case someone - // is `mem::forget`ing Arcs. If we don't do this the count can overflow - // and users will use-after free. We racily saturate to `isize::MAX` on - // the assumption that there aren't ~2 billion threads incrementing - // the reference count at once. This branch will never be taken in - // any realistic program. - // - // We abort because such a program is incredibly degenerate, and we - // don't care to support it. - if old_size > MAX_REFCOUNT { - process::abort(); - } - } - - unsafe { - Arc { - p: ptr::NonNull::new_unchecked(self.ptr()), - phantom: PhantomData, - } - } - } -} - -impl Deref for Arc { - type Target = T; - - #[inline] - fn deref(&self) -> &T { - &self.inner().data - } -} - -impl Arc { - /// Makes a mutable reference to the `Arc`, cloning if necessary - /// - /// This is functionally equivalent to [`Arc::make_mut`][mm] from the standard library. - /// - /// If this `Arc` is uniquely owned, `make_mut()` will provide a mutable - /// reference to the contents. If not, `make_mut()` will create a _new_ `Arc` - /// with a copy of the contents, update `this` to point to it, and provide - /// a mutable reference to its contents. - /// - /// This is useful for implementing copy-on-write schemes where you wish to - /// avoid copying things if your `Arc` is not shared. - /// - /// [mm]: https://doc.rust-lang.org/stable/std/sync/struct.Arc.html#method.make_mut - #[inline] - pub fn make_mut(this: &mut Self) -> &mut T { - if !this.is_unique() { - // Another pointer exists; clone - *this = Arc::new((**this).clone()); - } - - unsafe { - // This unsafety is ok because we're guaranteed that the pointer - // returned is the *only* pointer that will ever be returned to T. Our - // reference count is guaranteed to be 1 at this point, and we required - // the Arc itself to be `mut`, so we're returning the only possible - // reference to the inner data. - &mut (*this.ptr()).data - } - } -} - -impl Arc { - /// Provides mutable access to the contents _if_ the `Arc` is uniquely owned. - #[inline] - pub fn get_mut(this: &mut Self) -> Option<&mut T> { - if this.is_unique() { - unsafe { - // See make_mut() for documentation of the threadsafety here. - Some(&mut (*this.ptr()).data) - } - } else { - None - } - } - - /// Whether or not the `Arc` is a static reference. - #[inline] - pub fn is_static(&self) -> bool { - // Using a relaxed ordering to check for STATIC_REFCOUNT is safe, since - // `count` never changes between STATIC_REFCOUNT and other values. - self.inner().count.load(Relaxed) == STATIC_REFCOUNT - } - - /// Whether or not the `Arc` is uniquely owned (is the refcount 1?) and not - /// a static reference. - #[inline] - pub fn is_unique(&self) -> bool { - // See the extensive discussion in [1] for why this needs to be Acquire. - // - // [1] https://github.com/servo/servo/issues/21186 - self.inner().count.load(Acquire) == 1 - } -} - -impl Drop for Arc { - #[inline] - fn drop(&mut self) { - // NOTE(emilio): If you change anything here, make sure that the - // implementation in layout/style/ServoStyleConstsInlines.h matches! - if self.is_static() { - return; - } - - // Because `fetch_sub` is already atomic, we do not need to synchronize - // with other threads unless we are going to delete the object. - if self.inner().count.fetch_sub(1, Release) != 1 { - return; - } - - // FIXME(bholley): Use the updated comment when [2] is merged. - // - // This load is needed to prevent reordering of use of the data and - // deletion of the data. Because it is marked `Release`, the decreasing - // of the reference count synchronizes with this `Acquire` load. This - // means that use of the data happens before decreasing the reference - // count, which happens before this load, which happens before the - // deletion of the data. - // - // As explained in the [Boost documentation][1], - // - // > It is important to enforce any possible access to the object in one - // > thread (through an existing reference) to *happen before* deleting - // > the object in a different thread. This is achieved by a "release" - // > operation after dropping a reference (any access to the object - // > through this reference must obviously happened before), and an - // > "acquire" operation before deleting the object. - // - // [1]: (www.boost.org/doc/libs/1_55_0/doc/html/atomic/usage_examples.html) - // [2]: https://github.com/rust-lang/rust/pull/41714 - self.inner().count.load(Acquire); - - unsafe { - self.drop_slow(); - } - } -} - -impl PartialEq for Arc { - fn eq(&self, other: &Arc) -> bool { - Self::ptr_eq(self, other) || *(*self) == *(*other) - } - - fn ne(&self, other: &Arc) -> bool { - !Self::ptr_eq(self, other) && *(*self) != *(*other) - } -} - -impl PartialOrd for Arc { - fn partial_cmp(&self, other: &Arc) -> Option { - (**self).partial_cmp(&**other) - } - - fn lt(&self, other: &Arc) -> bool { - *(*self) < *(*other) - } - - fn le(&self, other: &Arc) -> bool { - *(*self) <= *(*other) - } - - fn gt(&self, other: &Arc) -> bool { - *(*self) > *(*other) - } - - fn ge(&self, other: &Arc) -> bool { - *(*self) >= *(*other) - } -} -impl Ord for Arc { - fn cmp(&self, other: &Arc) -> Ordering { - (**self).cmp(&**other) - } -} -impl Eq for Arc {} - -impl fmt::Display for Arc { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - fmt::Display::fmt(&**self, f) - } -} - -impl fmt::Debug for Arc { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - fmt::Debug::fmt(&**self, f) - } -} - -impl fmt::Pointer for Arc { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - fmt::Pointer::fmt(&self.ptr(), f) - } -} - -impl Default for Arc { - fn default() -> Arc { - Arc::new(Default::default()) - } -} - -impl Hash for Arc { - fn hash(&self, state: &mut H) { - (**self).hash(state) - } -} - -impl From for Arc { - #[inline] - fn from(t: T) -> Self { - Arc::new(t) - } -} - -impl borrow::Borrow for Arc { - #[inline] - fn borrow(&self) -> &T { - &**self - } -} - -impl AsRef for Arc { - #[inline] - fn as_ref(&self) -> &T { - &**self - } -} - -unsafe impl StableDeref for Arc {} -unsafe impl CloneStableDeref for Arc {} - -#[cfg(feature = "servo")] -impl<'de, T: Deserialize<'de>> Deserialize<'de> for Arc { - fn deserialize(deserializer: D) -> Result, D::Error> - where - D: ::serde::de::Deserializer<'de>, - { - T::deserialize(deserializer).map(Arc::new) - } -} - -#[cfg(feature = "servo")] -impl Serialize for Arc { - fn serialize(&self, serializer: S) -> Result - where - S: ::serde::ser::Serializer, - { - (**self).serialize(serializer) - } -} - -/// Structure to allow Arc-managing some fixed-sized data and a variably-sized -/// slice in a single allocation. -#[derive(Debug, Eq, PartialEq, PartialOrd)] -#[repr(C)] -pub struct HeaderSlice { - /// The fixed-sized data. - pub header: H, - - /// The dynamically-sized data. - pub slice: T, -} - -#[inline(always)] -fn divide_rounding_up(dividend: usize, divisor: usize) -> usize { - (dividend + divisor - 1) / divisor -} - -impl Arc> { - /// Creates an Arc for a HeaderSlice using the given header struct and - /// iterator to generate the slice. - /// - /// `is_static` indicates whether to create a static Arc. - /// - /// `alloc` is used to get a pointer to the memory into which the - /// dynamically sized ArcInner> value will be - /// written. If `is_static` is true, then `alloc` must return a - /// pointer into some static memory allocation. If it is false, - /// then `alloc` must return an allocation that can be dellocated - /// by calling Box::from_raw::>> on it. - #[inline] - fn from_header_and_iter_alloc( - alloc: F, - header: H, - mut items: I, - num_items: usize, - is_static: bool, - ) -> Self - where - F: FnOnce(Layout) -> *mut u8, - I: Iterator, - { - assert_ne!(size_of::(), 0, "Need to think about ZST"); - - let inner_align = align_of::>>(); - debug_assert!(inner_align >= align_of::()); - - // Compute the required size for the allocation. - let size = { - // Next, synthesize a totally garbage (but properly aligned) pointer - // to a sequence of T. - let fake_slice_ptr = inner_align as *const T; - - // Convert that sequence to a fat pointer. The address component of - // the fat pointer will be garbage, but the length will be correct. - let fake_slice = unsafe { slice::from_raw_parts(fake_slice_ptr, num_items) }; - - // Pretend the garbage address points to our allocation target (with - // a trailing sequence of T), rather than just a sequence of T. - let fake_ptr = fake_slice as *const [T] as *const ArcInner>; - let fake_ref: &ArcInner> = unsafe { &*fake_ptr }; - - // Use size_of_val, which will combine static information about the - // type with the length from the fat pointer. The garbage address - // will not be used. - mem::size_of_val(fake_ref) - }; - - let ptr: *mut ArcInner>; - unsafe { - // Allocate the buffer. - let layout = if inner_align <= align_of::() { - Layout::from_size_align_unchecked(size, align_of::()) - } else if inner_align <= align_of::() { - // On 32-bit platforms may have 8 byte alignment while usize - // has 4 byte aligment. Use u64 to avoid over-alignment. - // This branch will compile away in optimized builds. - Layout::from_size_align_unchecked(size, align_of::()) - } else { - panic!("Over-aligned type not handled"); - }; - - let buffer = alloc(layout); - - // Synthesize the fat pointer. We do this by claiming we have a direct - // pointer to a [T], and then changing the type of the borrow. The key - // point here is that the length portion of the fat pointer applies - // only to the number of elements in the dynamically-sized portion of - // the type, so the value will be the same whether it points to a [T] - // or something else with a [T] as its last member. - let fake_slice: &mut [T] = slice::from_raw_parts_mut(buffer as *mut T, num_items); - ptr = fake_slice as *mut [T] as *mut ArcInner>; - - // Write the data. - // - // Note that any panics here (i.e. from the iterator) are safe, since - // we'll just leak the uninitialized memory. - let count = if is_static { - atomic::AtomicUsize::new(STATIC_REFCOUNT) - } else { - atomic::AtomicUsize::new(1) - }; - ptr::write(&mut ((*ptr).count), count); - ptr::write(&mut ((*ptr).data.header), header); - if num_items != 0 { - let mut current: *mut T = &mut (*ptr).data.slice[0]; - for _ in 0..num_items { - ptr::write( - current, - items - .next() - .expect("ExactSizeIterator over-reported length"), - ); - current = current.offset(1); - } - // We should have consumed the buffer exactly, maybe accounting - // for some padding from the alignment. - debug_assert!( - (buffer.add(size) as usize - current as *mut u8 as usize) < inner_align - ); - } - assert!( - items.next().is_none(), - "ExactSizeIterator under-reported length" - ); - } - #[cfg(feature = "gecko_refcount_logging")] - unsafe { - if !is_static { - // FIXME(emilio): Would be so amazing to have - // std::intrinsics::type_name() around. - NS_LogCtor(ptr as *mut _, b"ServoArc\0".as_ptr() as *const _, 8) - } - } - - // Return the fat Arc. - assert_eq!( - size_of::(), - size_of::() * 2, - "The Arc will be fat" - ); - unsafe { - Arc { - p: ptr::NonNull::new_unchecked(ptr), - phantom: PhantomData, - } - } - } - - /// Creates an Arc for a HeaderSlice using the given header struct and iterator to generate the - /// slice. Panics if num_items doesn't match the number of items. - #[inline] - pub fn from_header_and_iter_with_size(header: H, items: I, num_items: usize) -> Self - where - I: Iterator, - { - Arc::from_header_and_iter_alloc( - |layout| { - // align will only ever be align_of::() or align_of::() - let align = layout.align(); - unsafe { - if align == mem::align_of::() { - Self::allocate_buffer::(layout.size()) - } else { - assert_eq!(align, mem::align_of::()); - Self::allocate_buffer::(layout.size()) - } - } - }, - header, - items, - num_items, - /* is_static = */ false, - ) - } - - /// Creates an Arc for a HeaderSlice using the given header struct and - /// iterator to generate the slice. The resulting Arc will be fat. - #[inline] - pub fn from_header_and_iter(header: H, items: I) -> Self - where - I: Iterator + ExactSizeIterator, - { - let len = items.len(); - Self::from_header_and_iter_with_size(header, items, len) - } - - #[inline] - unsafe fn allocate_buffer(size: usize) -> *mut u8 { - // We use Vec because the underlying allocation machinery isn't - // available in stable Rust. To avoid alignment issues, we allocate - // words rather than bytes, rounding up to the nearest word size. - let words_to_allocate = divide_rounding_up(size, mem::size_of::()); - let mut vec = Vec::::with_capacity(words_to_allocate); - vec.set_len(words_to_allocate); - Box::into_raw(vec.into_boxed_slice()) as *mut W as *mut u8 - } -} - -/// Header data with an inline length. Consumers that use HeaderWithLength as the -/// Header type in HeaderSlice can take advantage of ThinArc. -#[derive(Debug, Eq, PartialEq, PartialOrd)] -#[repr(C)] -pub struct HeaderWithLength { - /// The fixed-sized data. - pub header: H, - - /// The slice length. - length: usize, -} - -impl HeaderWithLength { - /// Creates a new HeaderWithLength. - pub fn new(header: H, length: usize) -> Self { - HeaderWithLength { header, length } - } -} - -type HeaderSliceWithLength = HeaderSlice, T>; - -/// A "thin" `Arc` containing dynamically sized data -/// -/// This is functionally equivalent to Arc<(H, [T])> -/// -/// When you create an `Arc` containing a dynamically sized type -/// like `HeaderSlice`, the `Arc` is represented on the stack -/// as a "fat pointer", where the length of the slice is stored -/// alongside the `Arc`'s pointer. In some situations you may wish to -/// have a thin pointer instead, perhaps for FFI compatibility -/// or space efficiency. -/// -/// Note that we use `[T; 0]` in order to have the right alignment for `T`. -/// -/// `ThinArc` solves this by storing the length in the allocation itself, -/// via `HeaderSliceWithLength`. -#[repr(C)] -pub struct ThinArc { - ptr: ptr::NonNull>>, - phantom: PhantomData<(H, T)>, -} - -impl fmt::Debug for ThinArc { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - fmt::Debug::fmt(self.deref(), f) - } -} - -unsafe impl Send for ThinArc {} -unsafe impl Sync for ThinArc {} - -// Synthesize a fat pointer from a thin pointer. -// -// See the comment around the analogous operation in from_header_and_iter. -fn thin_to_thick( - thin: *mut ArcInner>, -) -> *mut ArcInner> { - let len = unsafe { (*thin).data.header.length }; - let fake_slice: *mut [T] = unsafe { slice::from_raw_parts_mut(thin as *mut T, len) }; - - fake_slice as *mut ArcInner> -} - -impl ThinArc { - /// Temporarily converts |self| into a bonafide Arc and exposes it to the - /// provided callback. The refcount is not modified. - #[inline] - pub fn with_arc(&self, f: F) -> U - where - F: FnOnce(&Arc>) -> U, - { - // Synthesize transient Arc, which never touches the refcount of the ArcInner. - let transient = unsafe { - mem::ManuallyDrop::new(Arc { - p: ptr::NonNull::new_unchecked(thin_to_thick(self.ptr.as_ptr())), - phantom: PhantomData, - }) - }; - - // Expose the transient Arc to the callback, which may clone it if it wants. - let result = f(&transient); - - // Forward the result. - result - } - - /// Creates a `ThinArc` for a HeaderSlice using the given header struct and - /// iterator to generate the slice. - pub fn from_header_and_iter(header: H, items: I) -> Self - where - I: Iterator + ExactSizeIterator, - { - let header = HeaderWithLength::new(header, items.len()); - Arc::into_thin(Arc::from_header_and_iter(header, items)) - } - - /// Create a static `ThinArc` for a HeaderSlice using the given header - /// struct and iterator to generate the slice, placing it in the allocation - /// provided by the specified `alloc` function. - /// - /// `alloc` must return a pointer into a static allocation suitable for - /// storing data with the `Layout` passed into it. The pointer returned by - /// `alloc` will not be freed. - pub unsafe fn static_from_header_and_iter(alloc: F, header: H, items: I) -> Self - where - F: FnOnce(Layout) -> *mut u8, - I: Iterator + ExactSizeIterator, - { - let len = items.len(); - let header = HeaderWithLength::new(header, len); - Arc::into_thin(Arc::from_header_and_iter_alloc( - alloc, header, items, len, /* is_static = */ true, - )) - } - - /// Returns the address on the heap of the ThinArc itself -- not the T - /// within it -- for memory reporting, and bindings. - #[inline] - pub fn ptr(&self) -> *const c_void { - self.ptr.as_ptr() as *const ArcInner as *const c_void - } - - /// If this is a static ThinArc, this returns null. - #[inline] - pub fn heap_ptr(&self) -> *const c_void { - let is_static = - ThinArc::with_arc(self, |a| a.inner().count.load(Relaxed) == STATIC_REFCOUNT); - if is_static { - ptr::null() - } else { - self.ptr() - } - } -} - -impl Deref for ThinArc { - type Target = HeaderSliceWithLength; - - #[inline] - fn deref(&self) -> &Self::Target { - unsafe { &(*thin_to_thick(self.ptr.as_ptr())).data } - } -} - -impl Clone for ThinArc { - #[inline] - fn clone(&self) -> Self { - ThinArc::with_arc(self, |a| Arc::into_thin(a.clone())) - } -} - -impl Drop for ThinArc { - #[inline] - fn drop(&mut self) { - let _ = Arc::from_thin(ThinArc { - ptr: self.ptr, - phantom: PhantomData, - }); - } -} - -impl Arc> { - /// Converts an `Arc` into a `ThinArc`. This consumes the `Arc`, so the refcount - /// is not modified. - #[inline] - pub fn into_thin(a: Self) -> ThinArc { - assert_eq!( - a.header.length, - a.slice.len(), - "Length needs to be correct for ThinArc to work" - ); - let fat_ptr: *mut ArcInner> = a.ptr(); - mem::forget(a); - let thin_ptr = fat_ptr as *mut [usize] as *mut usize; - ThinArc { - ptr: unsafe { - ptr::NonNull::new_unchecked( - thin_ptr as *mut ArcInner>, - ) - }, - phantom: PhantomData, - } - } - - /// Converts a `ThinArc` into an `Arc`. This consumes the `ThinArc`, so the refcount - /// is not modified. - #[inline] - pub fn from_thin(a: ThinArc) -> Self { - let ptr = thin_to_thick(a.ptr.as_ptr()); - mem::forget(a); - unsafe { - Arc { - p: ptr::NonNull::new_unchecked(ptr), - phantom: PhantomData, - } - } - } -} - -impl UniqueArc> { - #[inline] - pub fn from_header_and_iter(header: HeaderWithLength, items: I) -> Self - where - I: Iterator + ExactSizeIterator, - { - Self(Arc::from_header_and_iter(header, items)) - } - - #[inline] - pub fn from_header_and_iter_with_size( - header: HeaderWithLength, - items: I, - num_items: usize, - ) -> Self - where - I: Iterator, - { - Self(Arc::from_header_and_iter_with_size( - header, items, num_items, - )) - } - - /// Returns a mutable reference to the header. - pub fn header_mut(&mut self) -> &mut H { - // We know this to be uniquely owned - unsafe { &mut (*self.0.ptr()).data.header.header } - } - - /// Returns a mutable reference to the slice. - pub fn data_mut(&mut self) -> &mut [T] { - // We know this to be uniquely owned - unsafe { &mut (*self.0.ptr()).data.slice } - } - - pub fn shareable_thin(self) -> ThinArc { - Arc::into_thin(self.0) - } -} - -impl PartialEq for ThinArc { - #[inline] - fn eq(&self, other: &ThinArc) -> bool { - ThinArc::with_arc(self, |a| ThinArc::with_arc(other, |b| *a == *b)) - } -} - -impl Eq for ThinArc {} - -/// A "borrowed `Arc`". This is a pointer to -/// a T that is known to have been allocated within an -/// `Arc`. -/// -/// This is equivalent in guarantees to `&Arc`, however it is -/// a bit more flexible. To obtain an `&Arc` you must have -/// an `Arc` instance somewhere pinned down until we're done with it. -/// It's also a direct pointer to `T`, so using this involves less pointer-chasing -/// -/// However, C++ code may hand us refcounted things as pointers to T directly, -/// so we have to conjure up a temporary `Arc` on the stack each time. -/// -/// `ArcBorrow` lets us deal with borrows of known-refcounted objects -/// without needing to worry about where the `Arc` is. -#[derive(Debug, Eq, PartialEq)] -pub struct ArcBorrow<'a, T: 'a>(&'a T); - -impl<'a, T> Copy for ArcBorrow<'a, T> {} -impl<'a, T> Clone for ArcBorrow<'a, T> { - #[inline] - fn clone(&self) -> Self { - *self - } -} - -impl<'a, T> ArcBorrow<'a, T> { - /// Clone this as an `Arc`. This bumps the refcount. - #[inline] - pub fn clone_arc(&self) -> Arc { - let arc = unsafe { Arc::from_raw(self.0) }; - // addref it! - mem::forget(arc.clone()); - arc - } - - /// For constructing from a reference known to be Arc-backed, - /// e.g. if we obtain such a reference over FFI - #[inline] - pub unsafe fn from_ref(r: &'a T) -> Self { - ArcBorrow(r) - } - - /// Compare two `ArcBorrow`s via pointer equality. Will only return - /// true if they come from the same allocation - pub fn ptr_eq(this: &Self, other: &Self) -> bool { - this.0 as *const T == other.0 as *const T - } - - /// Temporarily converts |self| into a bonafide Arc and exposes it to the - /// provided callback. The refcount is not modified. - #[inline] - pub fn with_arc(&self, f: F) -> U - where - F: FnOnce(&Arc) -> U, - T: 'static, - { - // Synthesize transient Arc, which never touches the refcount. - let transient = unsafe { mem::ManuallyDrop::new(Arc::from_raw(self.0)) }; - - // Expose the transient Arc to the callback, which may clone it if it wants. - let result = f(&transient); - - // Forward the result. - result - } - - /// Similar to deref, but uses the lifetime |a| rather than the lifetime of - /// self, which is incompatible with the signature of the Deref trait. - #[inline] - pub fn get(&self) -> &'a T { - self.0 - } -} - -impl<'a, T> Deref for ArcBorrow<'a, T> { - type Target = T; - - #[inline] - fn deref(&self) -> &T { - self.0 - } -} - -/// A tagged union that can represent `Arc` or `Arc` while only consuming a -/// single word. The type is also `NonNull`, and thus can be stored in an Option -/// without increasing size. -/// -/// This is functionally equivalent to -/// `enum ArcUnion { First(Arc), Second(Arc)` but only takes up -/// up a single word of stack space. -/// -/// This could probably be extended to support four types if necessary. -pub struct ArcUnion { - p: ptr::NonNull<()>, - phantom_a: PhantomData, - phantom_b: PhantomData, -} - -unsafe impl Send for ArcUnion {} -unsafe impl Sync for ArcUnion {} - -impl PartialEq for ArcUnion { - fn eq(&self, other: &Self) -> bool { - use crate::ArcUnionBorrow::*; - match (self.borrow(), other.borrow()) { - (First(x), First(y)) => x == y, - (Second(x), Second(y)) => x == y, - (_, _) => false, - } - } -} - -/// This represents a borrow of an `ArcUnion`. -#[derive(Debug)] -pub enum ArcUnionBorrow<'a, A: 'a, B: 'a> { - First(ArcBorrow<'a, A>), - Second(ArcBorrow<'a, B>), -} - -impl ArcUnion { - unsafe fn new(ptr: *mut ()) -> Self { - ArcUnion { - p: ptr::NonNull::new_unchecked(ptr), - phantom_a: PhantomData, - phantom_b: PhantomData, - } - } - - /// Returns true if the two values are pointer-equal. - #[inline] - pub fn ptr_eq(this: &Self, other: &Self) -> bool { - this.p == other.p - } - - #[inline] - pub fn ptr(&self) -> ptr::NonNull<()> { - self.p - } - - /// Returns an enum representing a borrow of either A or B. - #[inline] - pub fn borrow(&self) -> ArcUnionBorrow { - if self.is_first() { - let ptr = self.p.as_ptr() as *const A; - let borrow = unsafe { ArcBorrow::from_ref(&*ptr) }; - ArcUnionBorrow::First(borrow) - } else { - let ptr = ((self.p.as_ptr() as usize) & !0x1) as *const B; - let borrow = unsafe { ArcBorrow::from_ref(&*ptr) }; - ArcUnionBorrow::Second(borrow) - } - } - - /// Creates an `ArcUnion` from an instance of the first type. - pub fn from_first(other: Arc) -> Self { - unsafe { Self::new(Arc::into_raw(other) as *mut _) } - } - - /// Creates an `ArcUnion` from an instance of the second type. - pub fn from_second(other: Arc) -> Self { - unsafe { Self::new(((Arc::into_raw(other) as usize) | 0x1) as *mut _) } - } - - /// Returns true if this `ArcUnion` contains the first type. - pub fn is_first(&self) -> bool { - self.p.as_ptr() as usize & 0x1 == 0 - } - - /// Returns true if this `ArcUnion` contains the second type. - pub fn is_second(&self) -> bool { - !self.is_first() - } - - /// Returns a borrow of the first type if applicable, otherwise `None`. - pub fn as_first(&self) -> Option> { - match self.borrow() { - ArcUnionBorrow::First(x) => Some(x), - ArcUnionBorrow::Second(_) => None, - } - } - - /// Returns a borrow of the second type if applicable, otherwise None. - pub fn as_second(&self) -> Option> { - match self.borrow() { - ArcUnionBorrow::First(_) => None, - ArcUnionBorrow::Second(x) => Some(x), - } - } -} - -impl Clone for ArcUnion { - fn clone(&self) -> Self { - match self.borrow() { - ArcUnionBorrow::First(x) => ArcUnion::from_first(x.clone_arc()), - ArcUnionBorrow::Second(x) => ArcUnion::from_second(x.clone_arc()), - } - } -} - -impl Drop for ArcUnion { - fn drop(&mut self) { - match self.borrow() { - ArcUnionBorrow::First(x) => unsafe { - let _ = Arc::from_raw(&*x); - }, - ArcUnionBorrow::Second(x) => unsafe { - let _ = Arc::from_raw(&*x); - }, - } - } -} - -impl fmt::Debug for ArcUnion { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - fmt::Debug::fmt(&self.borrow(), f) - } -} - -#[cfg(test)] -mod tests { - use super::{Arc, HeaderWithLength, ThinArc}; - use std::clone::Clone; - use std::ops::Drop; - use std::sync::atomic; - use std::sync::atomic::Ordering::{Acquire, SeqCst}; - - #[derive(PartialEq)] - struct Canary(*mut atomic::AtomicUsize); - - impl Drop for Canary { - fn drop(&mut self) { - unsafe { - (*self.0).fetch_add(1, SeqCst); - } - } - } - - #[test] - fn empty_thin() { - let header = HeaderWithLength::new(100u32, 0); - let x = Arc::from_header_and_iter(header, std::iter::empty::()); - let y = Arc::into_thin(x.clone()); - assert_eq!(y.header.header, 100); - assert!(y.slice.is_empty()); - assert_eq!(x.header.header, 100); - assert!(x.slice.is_empty()); - } - - #[test] - fn thin_assert_padding() { - #[derive(Clone, Default)] - #[repr(C)] - struct Padded { - i: u16, - } - - // The header will have more alignment than `Padded` - let header = HeaderWithLength::new(0i32, 2); - let items = vec![Padded { i: 0xdead }, Padded { i: 0xbeef }]; - let a = ThinArc::from_header_and_iter(header, items.into_iter()); - assert_eq!(a.slice.len(), 2); - assert_eq!(a.slice[0].i, 0xdead); - assert_eq!(a.slice[1].i, 0xbeef); - } - - #[test] - fn slices_and_thin() { - let mut canary = atomic::AtomicUsize::new(0); - let c = Canary(&mut canary as *mut atomic::AtomicUsize); - let v = vec![5, 6]; - let header = HeaderWithLength::new(c, v.len()); - { - let x = Arc::into_thin(Arc::from_header_and_iter(header, v.into_iter())); - let y = ThinArc::with_arc(&x, |q| q.clone()); - let _ = y.clone(); - let _ = x == x; - Arc::from_thin(x.clone()); - } - assert_eq!(canary.load(Acquire), 1); - } -} diff --git a/components/servo_arc/rustfmt.toml b/components/servo_arc/rustfmt.toml deleted file mode 100644 index c7ad93bafe3..00000000000 --- a/components/servo_arc/rustfmt.toml +++ /dev/null @@ -1 +0,0 @@ -disable_all_formatting = true diff --git a/components/shared/canvas/Cargo.toml b/components/shared/canvas/Cargo.toml index 6f3eca8c160..6fd52f4e3a4 100644 --- a/components/shared/canvas/Cargo.toml +++ b/components/shared/canvas/Cargo.toml @@ -20,14 +20,14 @@ cssparser = { workspace = true } euclid = { workspace = true } ipc-channel = { workspace = true } lazy_static = { workspace = true } -malloc_size_of = { path = "../../malloc_size_of" } +malloc_size_of = { workspace = true } malloc_size_of_derive = { workspace = true } pixels = { path = "../../pixels" } serde = { workspace = true } serde_bytes = { workspace = true } servo_config = { path = "../../config" } sparkle = { workspace = true } -style = { path = "../../style" } +style = { workspace = true } time = { workspace = true, optional = true } webrender_api = { workspace = true } webxr-api = { git = "https://github.com/servo/webxr", features = ["ipc"] } diff --git a/components/shared/devtools/Cargo.toml b/components/shared/devtools/Cargo.toml index d158a3117ab..c3be3bcb01b 100644 --- a/components/shared/devtools/Cargo.toml +++ b/components/shared/devtools/Cargo.toml @@ -14,7 +14,7 @@ path = "lib.rs" bitflags = { workspace = true } http = { workspace = true } ipc-channel = { workspace = true } -malloc_size_of = { path = "../../malloc_size_of" } +malloc_size_of = { workspace = true } malloc_size_of_derive = { workspace = true } msg = { workspace = true } serde = { workspace = true } diff --git a/components/shared/gfx/Cargo.toml b/components/shared/gfx/Cargo.toml index f94b5870c35..23bf75bfb07 100644 --- a/components/shared/gfx/Cargo.toml +++ b/components/shared/gfx/Cargo.toml @@ -11,7 +11,7 @@ name = "gfx_traits" path = "lib.rs" [dependencies] -malloc_size_of = { path = "../../malloc_size_of" } +malloc_size_of = { workspace = true } malloc_size_of_derive = { workspace = true } range = { path = "../../range" } serde = { workspace = true } diff --git a/components/shared/msg/Cargo.toml b/components/shared/msg/Cargo.toml index d64880b5c6f..97abd671eef 100644 --- a/components/shared/msg/Cargo.toml +++ b/components/shared/msg/Cargo.toml @@ -15,9 +15,9 @@ doctest = false [dependencies] ipc-channel = { workspace = true } lazy_static = { workspace = true } -malloc_size_of = { path = "../../malloc_size_of" } +malloc_size_of = { workspace = true } malloc_size_of_derive = { workspace = true } parking_lot = { workspace = true } serde = { workspace = true } -size_of_test = { path = "../../size_of_test" } +size_of_test = { workspace = true } webrender_api = { workspace = true } diff --git a/components/shared/net/Cargo.toml b/components/shared/net/Cargo.toml index dffc41eeb3a..50e9d4eb21d 100644 --- a/components/shared/net/Cargo.toml +++ b/components/shared/net/Cargo.toml @@ -24,7 +24,7 @@ image = { workspace = true } ipc-channel = { workspace = true } lazy_static = { workspace = true } log = { workspace = true } -malloc_size_of = { path = "../../malloc_size_of" } +malloc_size_of = { workspace = true } malloc_size_of_derive = { workspace = true } mime = { workspace = true } msg = { workspace = true } @@ -33,7 +33,7 @@ percent-encoding = { workspace = true } pixels = { path = "../../pixels" } rustls = { workspace = true } serde = { workspace = true } -servo_arc = { path = "../../servo_arc" } +servo_arc = { workspace = true } servo_rand = { path = "../../rand" } servo_url = { path = "../../url" } url = { workspace = true } diff --git a/components/shared/script/Cargo.toml b/components/shared/script/Cargo.toml index 64ea7be7e66..5cc2b1f8554 100644 --- a/components/shared/script/Cargo.toml +++ b/components/shared/script/Cargo.toml @@ -26,7 +26,7 @@ ipc-channel = { workspace = true } keyboard-types = { workspace = true } libc = { workspace = true } log = { workspace = true } -malloc_size_of = { path = "../../malloc_size_of" } +malloc_size_of = { workspace = true } malloc_size_of_derive = { workspace = true } media = { path = "../../media" } msg = { workspace = true } @@ -34,7 +34,7 @@ net_traits = { workspace = true } pixels = { path = "../../pixels" } profile_traits = { workspace = true } serde = { workspace = true } -servo_atoms = { path = "../../atoms" } +servo_atoms = { workspace = true } servo_url = { path = "../../url" } smallvec = { workspace = true } style_traits = { workspace = true } diff --git a/components/shared/script_layout/Cargo.toml b/components/shared/script_layout/Cargo.toml index 40c92e96c8e..df526c9bec9 100644 --- a/components/shared/script_layout/Cargo.toml +++ b/components/shared/script_layout/Cargo.toml @@ -21,7 +21,7 @@ gfx_traits = { workspace = true } html5ever = { workspace = true } ipc-channel = { workspace = true } libc = { workspace = true } -malloc_size_of = { path = "../../malloc_size_of" } +malloc_size_of = { workspace = true } malloc_size_of_derive = { workspace = true } metrics = { path = "../../metrics" } msg = { workspace = true } @@ -29,10 +29,10 @@ net_traits = { workspace = true } profile_traits = { workspace = true } range = { path = "../../range" } script_traits = { workspace = true } -selectors = { path = "../../selectors" } -servo_arc = { path = "../../servo_arc" } -servo_atoms = { path = "../../atoms" } +selectors = { workspace = true } +servo_arc = { workspace = true } +servo_atoms = { workspace = true } servo_url = { path = "../../url" } -style = { path = "../../style", features = ["servo"] } +style = { workspace = true } style_traits = { workspace = true } webrender_api = { workspace = true } diff --git a/components/size_of_test/Cargo.toml b/components/size_of_test/Cargo.toml deleted file mode 100644 index faea55c5c1c..00000000000 --- a/components/size_of_test/Cargo.toml +++ /dev/null @@ -1,13 +0,0 @@ -[package] -name = "size_of_test" -version = "0.0.1" -authors = ["The Servo Project Developers"] -license = "MPL-2.0" -edition = "2018" -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 deleted file mode 100644 index 18e45175e8c..00000000000 --- a/components/size_of_test/lib.rs +++ /dev/null @@ -1,14 +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/. */ - -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); - }; -} diff --git a/components/style/Cargo.toml b/components/style/Cargo.toml deleted file mode 100644 index 6040b62116b..00000000000 --- a/components/style/Cargo.toml +++ /dev/null @@ -1,95 +0,0 @@ -[package] -name = "style" -version = "0.0.1" -authors = ["The Servo Project Developers"] -license = "MPL-2.0" -publish = false - -build = "build.rs" -edition = "2018" - -# https://github.com/rust-lang/cargo/issues/3544 -links = "servo_style_crate" - -[lib] -name = "style" -path = "lib.rs" -doctest = false - -[features] -gecko = ["style_traits/gecko", "bindgen", "regex", "toml", "mozbuild"] -servo = [ - "serde", - "style_traits/servo", - "servo_atoms", - "html5ever", - "cssparser/serde", - "encoding_rs", - "malloc_size_of/servo", - "string_cache", - "to_shmem/servo", - "servo_arc/servo", - "url", -] -gecko_debug = [] -gecko_refcount_logging = [] - -[dependencies] -app_units = "0.7" -arrayvec = "0.7" -atomic_refcell = "0.1" -bitflags = "1.0" -byteorder = "1.0" -cssparser = { workspace = true } -derive_more = { version = "0.99", default-features = false, features = ["add", "add_assign", "deref", "from"] } -encoding_rs = { version = "0.8", optional = true } -euclid = "0.22" -fxhash = "0.2" -html5ever = { version = "0.26", optional = true } -indexmap = "1.0" -itertools = "0.10" -itoa = "1.0" -lazy_static = "1" -log = { version = "0.4", features = ["std"] } -malloc_size_of = { path = "../malloc_size_of" } -malloc_size_of_derive = "0.1" -mime = "0.3.13" -new_debug_unreachable = "1.0" -num-derive = "0.3" -num-integer = "0.1" -num-traits = "0.2" -num_cpus = { version = "1.1.0" } -owning_ref = "0.4" -parking_lot = "0.12" -precomputed-hash = "0.1.1" -rayon = "1" -selectors = { path = "../selectors" } -serde = { version = "1.0", optional = true, features = ["derive"] } -servo_arc = { path = "../servo_arc" } -servo_atoms = { path = "../atoms", optional = true } -smallbitvec = "2.3.0" -smallvec = "1.0" -static_assertions = "1.1" -static_prefs = { path = "../style_static_prefs" } -string_cache = { version = "0.8", optional = true } -style_config = { path = "../style_config" } -style_derive = { path = "../style_derive" } -style_traits = { path = "../style_traits" } -time = "0.1" -thin-vec = { workspace = true } -to_shmem = { path = "../to_shmem" } -to_shmem_derive = { path = "../to_shmem_derive" } -uluru = "3.0" -unicode-bidi = "0.3" -unicode-segmentation = "1.0" -url = { workspace = true, optional = true } -void = "1.0.2" - -[build-dependencies] -bindgen = { version = "0.69", optional = true, default-features = false } -lazy_static = "1" -log = "0.4" -mozbuild = { version = "0.1", optional = true } -regex = { version = "1.1", optional = true } -toml = { version = "0.5", optional = true, default-features = false } -walkdir = "2.1.4" diff --git a/components/style/README.md b/components/style/README.md deleted file mode 100644 index bdbe36e44c4..00000000000 --- a/components/style/README.md +++ /dev/null @@ -1,6 +0,0 @@ -servo-style -=========== - -Style system for Servo, using [rust-cssparser](https://github.com/servo/rust-cssparser) for parsing. - - * [Documentation](https://github.com/servo/servo/blob/main/docs/components/style.md). diff --git a/components/style/animation.rs b/components/style/animation.rs deleted file mode 100644 index aafe7067dc9..00000000000 --- a/components/style/animation.rs +++ /dev/null @@ -1,1415 +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/. */ - -//! CSS transitions and animations. - -// NOTE(emilio): This code isn't really executed in Gecko, but we don't want to -// compile it out so that people remember it exists. - -use crate::context::{CascadeInputs, SharedStyleContext}; -use crate::dom::{OpaqueNode, TDocument, TElement, TNode}; -use crate::properties::animated_properties::{AnimationValue, AnimationValueMap}; -use crate::properties::longhands::animation_direction::computed_value::single_value::T as AnimationDirection; -use crate::properties::longhands::animation_fill_mode::computed_value::single_value::T as AnimationFillMode; -use crate::properties::longhands::animation_play_state::computed_value::single_value::T as AnimationPlayState; -use crate::properties::AnimationDeclarations; -use crate::properties::{ - ComputedValues, Importance, LonghandId, LonghandIdSet, PropertyDeclarationBlock, - PropertyDeclarationId, -}; -use crate::rule_tree::CascadeLevel; -use crate::selector_parser::PseudoElement; -use crate::shared_lock::{Locked, SharedRwLock}; -use crate::style_resolver::StyleResolverForElement; -use crate::stylesheets::keyframes_rule::{KeyframesAnimation, KeyframesStep, KeyframesStepValue}; -use crate::stylesheets::layer_rule::LayerOrder; -use crate::values::animated::{Animate, Procedure}; -use crate::values::computed::{Time, TimingFunction}; -use crate::values::generics::easing::BeforeFlag; -use crate::Atom; -use fxhash::FxHashMap; -use parking_lot::RwLock; -use servo_arc::Arc; -use std::fmt; - -/// Represents an animation for a given property. -#[derive(Clone, Debug, MallocSizeOf)] -pub struct PropertyAnimation { - /// The value we are animating from. - from: AnimationValue, - - /// The value we are animating to. - to: AnimationValue, - - /// The timing function of this `PropertyAnimation`. - timing_function: TimingFunction, - - /// The duration of this `PropertyAnimation` in seconds. - pub duration: f64, -} - -impl PropertyAnimation { - /// Returns the given property longhand id. - pub fn property_id(&self) -> LonghandId { - debug_assert_eq!(self.from.id(), self.to.id()); - self.from.id() - } - - fn from_longhand( - longhand: LonghandId, - timing_function: TimingFunction, - duration: Time, - old_style: &ComputedValues, - new_style: &ComputedValues, - ) -> Option { - // FIXME(emilio): Handle the case where old_style and new_style's writing mode differ. - let longhand = longhand.to_physical(new_style.writing_mode); - let from = AnimationValue::from_computed_values(longhand, old_style)?; - let to = AnimationValue::from_computed_values(longhand, new_style)?; - let duration = duration.seconds() as f64; - - if from == to || duration == 0.0 { - return None; - } - - Some(PropertyAnimation { - from, - to, - timing_function, - duration, - }) - } - - /// The output of the timing function given the progress ration of this animation. - fn timing_function_output(&self, progress: f64) -> f64 { - let epsilon = 1. / (200. * self.duration); - // FIXME: Need to set the before flag correctly. - // In order to get the before flag, we have to know the current animation phase - // and whether the iteration is reversed. For now, we skip this calculation - // by treating as if the flag is unset at all times. - // https://drafts.csswg.org/css-easing/#step-timing-function-algo - self.timing_function - .calculate_output(progress, BeforeFlag::Unset, epsilon) - } - - /// Update the given animation at a given point of progress. - fn calculate_value(&self, progress: f64) -> Result { - let procedure = Procedure::Interpolate { - progress: self.timing_function_output(progress), - }; - self.from.animate(&self.to, procedure) - } -} - -/// This structure represents the state of an animation. -#[derive(Clone, Debug, MallocSizeOf, PartialEq)] -pub enum AnimationState { - /// The animation has been created, but is not running yet. This state - /// is also used when an animation is still in the first delay phase. - Pending, - /// This animation is currently running. - Running, - /// This animation is paused. The inner field is the percentage of progress - /// when it was paused, from 0 to 1. - Paused(f64), - /// This animation has finished. - Finished, - /// This animation has been canceled. - Canceled, -} - -impl AnimationState { - /// Whether or not this state requires its owning animation to be ticked. - fn needs_to_be_ticked(&self) -> bool { - *self == AnimationState::Running || *self == AnimationState::Pending - } -} - -/// This structure represents a keyframes animation current iteration state. -/// -/// If the iteration count is infinite, there's no other state, otherwise we -/// have to keep track the current iteration and the max iteration count. -#[derive(Clone, Debug, MallocSizeOf)] -pub enum KeyframesIterationState { - /// Infinite iterations with the current iteration count. - Infinite(f64), - /// Current and max iterations. - Finite(f64, f64), -} - -/// A temporary data structure used when calculating ComputedKeyframes for an -/// animation. This data structure is used to collapse information for steps -/// which may be spread across multiple keyframe declarations into a single -/// instance per `start_percentage`. -struct IntermediateComputedKeyframe { - declarations: PropertyDeclarationBlock, - timing_function: Option, - start_percentage: f32, -} - -impl IntermediateComputedKeyframe { - fn new(start_percentage: f32) -> Self { - IntermediateComputedKeyframe { - declarations: PropertyDeclarationBlock::new(), - timing_function: None, - start_percentage, - } - } - - /// Walk through all keyframe declarations and combine all declarations with the - /// same `start_percentage` into individual `IntermediateComputedKeyframe`s. - fn generate_for_keyframes( - animation: &KeyframesAnimation, - context: &SharedStyleContext, - base_style: &ComputedValues, - ) -> Vec { - let mut intermediate_steps: Vec = Vec::with_capacity(animation.steps.len()); - let mut current_step = IntermediateComputedKeyframe::new(0.); - for step in animation.steps.iter() { - let start_percentage = step.start_percentage.0; - if start_percentage != current_step.start_percentage { - let new_step = IntermediateComputedKeyframe::new(start_percentage); - intermediate_steps.push(std::mem::replace(&mut current_step, new_step)); - } - - current_step.update_from_step(step, context, base_style); - } - intermediate_steps.push(current_step); - - // We should always have a first and a last step, even if these are just - // generated by KeyframesStepValue::ComputedValues. - debug_assert!(intermediate_steps.first().unwrap().start_percentage == 0.); - debug_assert!(intermediate_steps.last().unwrap().start_percentage == 1.); - - intermediate_steps - } - - fn update_from_step( - &mut self, - step: &KeyframesStep, - context: &SharedStyleContext, - base_style: &ComputedValues, - ) { - // Each keyframe declaration may optionally specify a timing function, falling - // back to the one defined global for the animation. - let guard = &context.guards.author; - if let Some(timing_function) = step.get_animation_timing_function(&guard) { - self.timing_function = Some(timing_function.to_computed_value_without_context()); - } - - let block = match step.value { - KeyframesStepValue::ComputedValues => return, - KeyframesStepValue::Declarations { ref block } => block, - }; - - // Filter out !important, non-animatable properties, and the - // 'display' property (which is only animatable from SMIL). - let guard = block.read_with(&guard); - for declaration in guard.normal_declaration_iter() { - if let PropertyDeclarationId::Longhand(id) = declaration.id() { - if id == LonghandId::Display { - continue; - } - - if !id.is_animatable() { - continue; - } - } - - self.declarations.push( - declaration.to_physical(base_style.writing_mode), - Importance::Normal, - ); - } - } - - fn resolve_style( - self, - element: E, - context: &SharedStyleContext, - base_style: &Arc, - resolver: &mut StyleResolverForElement, - ) -> Arc - where - E: TElement, - { - if !self.declarations.any_normal() { - return base_style.clone(); - } - - let document = element.as_node().owner_doc(); - let locked_block = Arc::new(document.shared_lock().wrap(self.declarations)); - let mut important_rules_changed = false; - let rule_node = base_style.rules().clone(); - let new_node = context.stylist.rule_tree().update_rule_at_level( - CascadeLevel::Animations, - LayerOrder::root(), - Some(locked_block.borrow_arc()), - &rule_node, - &context.guards, - &mut important_rules_changed, - ); - - if new_node.is_none() { - return base_style.clone(); - } - - let inputs = CascadeInputs { - rules: new_node, - visited_rules: base_style.visited_rules().cloned(), - flags: base_style.flags.for_cascade_inputs(), - }; - resolver - .cascade_style_and_visited_with_default_parents(inputs) - .0 - } -} - -/// A single computed keyframe for a CSS Animation. -#[derive(Clone, MallocSizeOf)] -struct ComputedKeyframe { - /// The timing function to use for transitions between this step - /// and the next one. - timing_function: TimingFunction, - - /// The starting percentage (a number between 0 and 1) which represents - /// at what point in an animation iteration this step is. - start_percentage: f32, - - /// The animation values to transition to and from when processing this - /// keyframe animation step. - values: Vec, -} - -impl ComputedKeyframe { - fn generate_for_keyframes( - element: E, - animation: &KeyframesAnimation, - context: &SharedStyleContext, - base_style: &Arc, - default_timing_function: TimingFunction, - resolver: &mut StyleResolverForElement, - ) -> Vec - where - E: TElement, - { - let mut animating_properties = LonghandIdSet::new(); - for property in animation.properties_changed.iter() { - debug_assert!(property.is_animatable()); - animating_properties.insert(property.to_physical(base_style.writing_mode)); - } - - let animation_values_from_style: Vec = animating_properties - .iter() - .map(|property| { - AnimationValue::from_computed_values(property, &**base_style) - .expect("Unexpected non-animatable property.") - }) - .collect(); - - let intermediate_steps = - IntermediateComputedKeyframe::generate_for_keyframes(animation, context, base_style); - - let mut computed_steps: Vec = Vec::with_capacity(intermediate_steps.len()); - for (step_index, step) in intermediate_steps.into_iter().enumerate() { - let start_percentage = step.start_percentage; - let properties_changed_in_step = step.declarations.longhands().clone(); - let step_timing_function = step.timing_function.clone(); - let step_style = step.resolve_style(element, context, base_style, resolver); - let timing_function = - step_timing_function.unwrap_or_else(|| default_timing_function.clone()); - - let values = { - // If a value is not set in a property declaration we use the value from - // the style for the first and last keyframe. For intermediate ones, we - // use the value from the previous keyframe. - // - // TODO(mrobinson): According to the spec, we should use an interpolated - // value for properties missing from keyframe declarations. - let default_values = if start_percentage == 0. || start_percentage == 1.0 { - &animation_values_from_style - } else { - debug_assert!(step_index != 0); - &computed_steps[step_index - 1].values - }; - - // For each property that is animating, pull the value from the resolved - // style for this step if it's in one of the declarations. Otherwise, we - // use the default value from the set we calculated above. - animating_properties - .iter() - .zip(default_values.iter()) - .map(|(longhand, default_value)| { - if properties_changed_in_step.contains(longhand) { - AnimationValue::from_computed_values(longhand, &step_style) - .unwrap_or_else(|| default_value.clone()) - } else { - default_value.clone() - } - }) - .collect() - }; - - computed_steps.push(ComputedKeyframe { - timing_function, - start_percentage, - values, - }); - } - computed_steps - } -} - -/// A CSS Animation -#[derive(Clone, MallocSizeOf)] -pub struct Animation { - /// The name of this animation as defined by the style. - pub name: Atom, - - /// The properties that change in this animation. - properties_changed: LonghandIdSet, - - /// The computed style for each keyframe of this animation. - computed_steps: Vec, - - /// The time this animation started at, which is the current value of the animation - /// timeline when this animation was created plus any animation delay. - pub started_at: f64, - - /// The duration of this animation. - pub duration: f64, - - /// The delay of the animation. - pub delay: f64, - - /// The `animation-fill-mode` property of this animation. - pub fill_mode: AnimationFillMode, - - /// The current iteration state for the animation. - pub iteration_state: KeyframesIterationState, - - /// Whether this animation is paused. - pub state: AnimationState, - - /// The declared animation direction of this animation. - pub direction: AnimationDirection, - - /// The current animation direction. This can only be `normal` or `reverse`. - pub current_direction: AnimationDirection, - - /// The original cascade style, needed to compute the generated keyframes of - /// the animation. - #[ignore_malloc_size_of = "ComputedValues"] - pub cascade_style: Arc, - - /// Whether or not this animation is new and or has already been tracked - /// by the script thread. - pub is_new: bool, -} - -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 - .animation_name_iter() - .position(|animation_name| Some(&self.name) == animation_name.as_atom()); - let index = match index { - Some(index) => index, - None => return true, - }; - - new_ui.animation_duration_mod(index).seconds() == 0. - } - - /// Given the current time, advances this animation to the next iteration, - /// updates times, and then toggles the direction if appropriate. Otherwise - /// does nothing. Returns true if this animation has iterated. - pub fn iterate_if_necessary(&mut self, time: f64) -> bool { - if !self.iteration_over(time) { - return false; - } - - // Only iterate animations that are currently running. - if self.state != AnimationState::Running { - return false; - } - - if self.on_last_iteration() { - return false; - } - - self.iterate(); - true - } - - fn iterate(&mut self) { - debug_assert!(!self.on_last_iteration()); - - if let KeyframesIterationState::Finite(ref mut current, max) = self.iteration_state { - *current = (*current + 1.).min(max); - } - - if let AnimationState::Paused(ref mut progress) = self.state { - debug_assert!(*progress > 1.); - *progress -= 1.; - } - - // Update the next iteration direction if applicable. - self.started_at += self.duration; - match self.direction { - AnimationDirection::Alternate | AnimationDirection::AlternateReverse => { - self.current_direction = match self.current_direction { - AnimationDirection::Normal => AnimationDirection::Reverse, - AnimationDirection::Reverse => AnimationDirection::Normal, - _ => unreachable!(), - }; - }, - _ => {}, - } - } - - /// A number (> 0 and <= 1) which represents the fraction of a full iteration - /// that the current iteration of the animation lasts. This will be less than 1 - /// if the current iteration is the fractional remainder of a non-integral - /// iteration count. - pub fn current_iteration_end_progress(&self) -> f64 { - match self.iteration_state { - KeyframesIterationState::Finite(current, max) => (max - current).min(1.), - KeyframesIterationState::Infinite(_) => 1., - } - } - - /// The duration of the current iteration of this animation which may be less - /// than the animation duration if it has a non-integral iteration count. - pub fn current_iteration_duration(&self) -> f64 { - self.current_iteration_end_progress() * self.duration - } - - /// Whether or not the current iteration is over. Note that this method assumes that - /// the animation is still running. - fn iteration_over(&self, time: f64) -> bool { - time > (self.started_at + self.current_iteration_duration()) - } - - /// Assuming this animation is running, whether or not it is on the last iteration. - fn on_last_iteration(&self) -> bool { - match self.iteration_state { - KeyframesIterationState::Finite(current, max) => current >= (max - 1.), - KeyframesIterationState::Infinite(_) => false, - } - } - - /// Whether or not this animation has finished at the provided time. This does - /// not take into account canceling i.e. when an animation or transition is - /// canceled due to changes in the style. - pub fn has_ended(&self, time: f64) -> bool { - if !self.on_last_iteration() { - return false; - } - - let progress = match self.state { - AnimationState::Finished => return true, - AnimationState::Paused(progress) => progress, - AnimationState::Running => (time - self.started_at) / self.duration, - AnimationState::Pending | AnimationState::Canceled => return false, - }; - - progress >= self.current_iteration_end_progress() - } - - /// Updates the appropiate state from other animation. - /// - /// This happens when an animation is re-submitted to layout, presumably - /// because of an state change. - /// - /// There are some bits of state we can't just replace, over all taking in - /// account times, so here's that logic. - pub fn update_from_other(&mut self, other: &Self, now: f64) { - use self::AnimationState::*; - - debug!( - "KeyframesAnimationState::update_from_other({:?}, {:?})", - self, other - ); - - // NB: We shall not touch the started_at field, since we don't want to - // restart the animation. - let old_started_at = self.started_at; - let old_duration = self.duration; - let old_direction = self.current_direction; - let old_state = self.state.clone(); - let old_iteration_state = self.iteration_state.clone(); - - *self = other.clone(); - - self.started_at = old_started_at; - self.current_direction = old_direction; - - // Don't update the iteration count, just the iteration limit. - // TODO: see how changing the limit affects rendering in other browsers. - // We might need to keep the iteration count even when it's infinite. - match (&mut self.iteration_state, old_iteration_state) { - ( - &mut KeyframesIterationState::Finite(ref mut iters, _), - KeyframesIterationState::Finite(old_iters, _), - ) => *iters = old_iters, - _ => {}, - } - - // Don't pause or restart animations that should remain finished. - // We call mem::replace because `has_ended(...)` looks at `Animation::state`. - let new_state = std::mem::replace(&mut self.state, Running); - if old_state == Finished && self.has_ended(now) { - self.state = Finished; - } else { - self.state = new_state; - } - - // If we're unpausing the animation, fake the start time so we seem to - // restore it. - // - // If the animation keeps paused, keep the old value. - // - // If we're pausing the animation, compute the progress value. - match (&mut self.state, &old_state) { - (&mut Pending, &Paused(progress)) => { - self.started_at = now - (self.duration * progress); - }, - (&mut Paused(ref mut new), &Paused(old)) => *new = old, - (&mut Paused(ref mut progress), &Running) => { - *progress = (now - old_started_at) / old_duration - }, - _ => {}, - } - - // Try to detect when we should skip straight to the running phase to - // avoid sending multiple animationstart events. - if self.state == Pending && self.started_at <= now && old_state != Pending { - self.state = Running; - } - } - - /// Fill in an `AnimationValueMap` with values calculated from this animation at - /// the given time value. - fn get_property_declaration_at_time(&self, now: f64, map: &mut AnimationValueMap) { - debug_assert!(!self.computed_steps.is_empty()); - - let total_progress = match self.state { - AnimationState::Running | AnimationState::Pending | AnimationState::Finished => { - (now - self.started_at) / self.duration - }, - AnimationState::Paused(progress) => progress, - AnimationState::Canceled => return, - }; - - if total_progress < 0. && - self.fill_mode != AnimationFillMode::Backwards && - self.fill_mode != AnimationFillMode::Both - { - return; - } - if self.has_ended(now) && - self.fill_mode != AnimationFillMode::Forwards && - self.fill_mode != AnimationFillMode::Both - { - return; - } - let total_progress = total_progress - .min(self.current_iteration_end_progress()) - .max(0.0); - - // Get the indices of the previous (from) keyframe and the next (to) keyframe. - let next_keyframe_index; - let prev_keyframe_index; - let num_steps = self.computed_steps.len(); - match self.current_direction { - AnimationDirection::Normal => { - next_keyframe_index = self - .computed_steps - .iter() - .position(|step| total_progress as f32 <= step.start_percentage); - prev_keyframe_index = next_keyframe_index - .and_then(|pos| if pos != 0 { Some(pos - 1) } else { None }) - .unwrap_or(0); - }, - AnimationDirection::Reverse => { - next_keyframe_index = self - .computed_steps - .iter() - .rev() - .position(|step| total_progress as f32 <= 1. - step.start_percentage) - .map(|pos| num_steps - pos - 1); - prev_keyframe_index = next_keyframe_index - .and_then(|pos| { - if pos != num_steps - 1 { - Some(pos + 1) - } else { - None - } - }) - .unwrap_or(num_steps - 1) - }, - _ => unreachable!(), - } - - debug!( - "Animation::get_property_declaration_at_time: keyframe from {:?} to {:?}", - prev_keyframe_index, next_keyframe_index - ); - - let prev_keyframe = &self.computed_steps[prev_keyframe_index]; - let next_keyframe = match next_keyframe_index { - Some(index) => &self.computed_steps[index], - None => return, - }; - - // If we only need to take into account one keyframe, then exit early - // in order to avoid doing more work. - let mut add_declarations_to_map = |keyframe: &ComputedKeyframe| { - for value in keyframe.values.iter() { - map.insert(value.id(), value.clone()); - } - }; - if total_progress <= 0.0 { - add_declarations_to_map(&prev_keyframe); - return; - } - if total_progress >= 1.0 { - add_declarations_to_map(&next_keyframe); - return; - } - - let percentage_between_keyframes = - (next_keyframe.start_percentage - prev_keyframe.start_percentage).abs() as f64; - let duration_between_keyframes = percentage_between_keyframes * self.duration; - let direction_aware_prev_keyframe_start_percentage = match self.current_direction { - AnimationDirection::Normal => prev_keyframe.start_percentage as f64, - AnimationDirection::Reverse => 1. - prev_keyframe.start_percentage as f64, - _ => unreachable!(), - }; - let progress_between_keyframes = (total_progress - - direction_aware_prev_keyframe_start_percentage) / - percentage_between_keyframes; - - for (from, to) in prev_keyframe.values.iter().zip(next_keyframe.values.iter()) { - let animation = PropertyAnimation { - from: from.clone(), - to: to.clone(), - timing_function: prev_keyframe.timing_function.clone(), - duration: duration_between_keyframes as f64, - }; - - if let Ok(value) = animation.calculate_value(progress_between_keyframes) { - map.insert(value.id(), value); - } - } - } -} - -impl fmt::Debug for Animation { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - f.debug_struct("Animation") - .field("name", &self.name) - .field("started_at", &self.started_at) - .field("duration", &self.duration) - .field("delay", &self.delay) - .field("iteration_state", &self.iteration_state) - .field("state", &self.state) - .field("direction", &self.direction) - .field("current_direction", &self.current_direction) - .field("cascade_style", &()) - .finish() - } -} - -/// A CSS Transition -#[derive(Clone, Debug, MallocSizeOf)] -pub struct Transition { - /// The start time of this transition, which is the current value of the animation - /// timeline when this transition was created plus any animation delay. - pub start_time: f64, - - /// The delay used for this transition. - pub delay: f64, - - /// The internal style `PropertyAnimation` for this transition. - pub property_animation: PropertyAnimation, - - /// The state of this transition. - pub state: AnimationState, - - /// Whether or not this transition is new and or has already been tracked - /// by the script thread. - pub is_new: bool, - - /// If this `Transition` has been replaced by a new one this field is - /// used to help produce better reversed transitions. - pub reversing_adjusted_start_value: AnimationValue, - - /// If this `Transition` has been replaced by a new one this field is - /// used to help produce better reversed transitions. - pub reversing_shortening_factor: f64, -} - -impl Transition { - fn update_for_possibly_reversed_transition( - &mut self, - replaced_transition: &Transition, - delay: f64, - now: f64, - ) { - // If we reach here, we need to calculate a reversed transition according to - // https://drafts.csswg.org/css-transitions/#starting - // - // "...if the reversing-adjusted start value of the running transition - // is the same as the value of the property in the after-change style (see - // the section on reversing of transitions for why these case exists), - // implementations must cancel the running transition and start - // a new transition..." - if replaced_transition.reversing_adjusted_start_value != self.property_animation.to { - return; - } - - // "* reversing-adjusted start value is the end value of the running transition" - let replaced_animation = &replaced_transition.property_animation; - self.reversing_adjusted_start_value = replaced_animation.to.clone(); - - // "* reversing shortening factor is the absolute value, clamped to the - // range [0, 1], of the sum of: - // 1. the output of the timing function of the old transition at the - // time of the style change event, times the reversing shortening - // factor of the old transition - // 2. 1 minus the reversing shortening factor of the old transition." - let transition_progress = ((now - replaced_transition.start_time) / - (replaced_transition.property_animation.duration)) - .min(1.0) - .max(0.0); - let timing_function_output = replaced_animation.timing_function_output(transition_progress); - let old_reversing_shortening_factor = replaced_transition.reversing_shortening_factor; - self.reversing_shortening_factor = ((timing_function_output * - old_reversing_shortening_factor) + - (1.0 - old_reversing_shortening_factor)) - .abs() - .min(1.0) - .max(0.0); - - // "* start time is the time of the style change event plus: - // 1. if the matching transition delay is nonnegative, the matching - // transition delay, or. - // 2. if the matching transition delay is negative, the product of the new - // transition’s reversing shortening factor and the matching transition delay," - self.start_time = if delay >= 0. { - now + delay - } else { - now + (self.reversing_shortening_factor * delay) - }; - - // "* end time is the start time plus the product of the matching transition - // duration and the new transition’s reversing shortening factor," - self.property_animation.duration *= self.reversing_shortening_factor; - - // "* start value is the current value of the property in the running transition, - // * end value is the value of the property in the after-change style," - let procedure = Procedure::Interpolate { - progress: timing_function_output, - }; - match replaced_animation - .from - .animate(&replaced_animation.to, procedure) - { - Ok(new_start) => self.property_animation.from = new_start, - Err(..) => {}, - } - } - - /// Whether or not this animation has ended at the provided time. This does - /// not take into account canceling i.e. when an animation or transition is - /// canceled due to changes in the style. - pub fn has_ended(&self, time: f64) -> bool { - time >= self.start_time + (self.property_animation.duration) - } - - /// Update the given animation at a given point of progress. - pub fn calculate_value(&self, time: f64) -> Option { - let progress = (time - self.start_time) / (self.property_animation.duration); - if progress < 0.0 { - return None; - } - - self.property_animation - .calculate_value(progress.min(1.0)) - .ok() - } -} - -/// Holds the animation state for a particular element. -#[derive(Debug, Default, MallocSizeOf)] -pub struct ElementAnimationSet { - /// The animations for this element. - pub animations: Vec, - - /// The transitions for this element. - pub transitions: Vec, - - /// Whether or not this ElementAnimationSet has had animations or transitions - /// which have been added, removed, or had their state changed. - pub dirty: bool, -} - -impl ElementAnimationSet { - /// Cancel all animations in this `ElementAnimationSet`. This is typically called - /// when the element has been removed from the DOM. - pub fn cancel_all_animations(&mut self) { - self.dirty = !self.animations.is_empty(); - for animation in self.animations.iter_mut() { - animation.state = AnimationState::Canceled; - } - self.cancel_active_transitions(); - } - - fn cancel_active_transitions(&mut self) { - for transition in self.transitions.iter_mut() { - if transition.state != AnimationState::Finished { - self.dirty = true; - transition.state = AnimationState::Canceled; - } - } - } - - /// Apply all active animations. - pub fn apply_active_animations( - &self, - context: &SharedStyleContext, - style: &mut Arc, - ) { - let now = context.current_time_for_animations; - let mutable_style = Arc::make_mut(style); - if let Some(map) = self.get_value_map_for_active_animations(now) { - for value in map.values() { - value.set_in_style_for_servo(mutable_style); - } - } - - if let Some(map) = self.get_value_map_for_active_transitions(now) { - for value in map.values() { - value.set_in_style_for_servo(mutable_style); - } - } - } - - /// Clear all canceled animations and transitions from this `ElementAnimationSet`. - pub fn clear_canceled_animations(&mut self) { - self.animations - .retain(|animation| animation.state != AnimationState::Canceled); - self.transitions - .retain(|animation| animation.state != AnimationState::Canceled); - } - - /// Whether this `ElementAnimationSet` is empty, which means it doesn't - /// hold any animations in any state. - pub fn is_empty(&self) -> bool { - self.animations.is_empty() && self.transitions.is_empty() - } - - /// Whether or not this state needs animation ticks for its transitions - /// or animations. - pub fn needs_animation_ticks(&self) -> bool { - self.animations - .iter() - .any(|animation| animation.state.needs_to_be_ticked()) || - self.transitions - .iter() - .any(|transition| transition.state.needs_to_be_ticked()) - } - - /// The number of running animations and transitions for this `ElementAnimationSet`. - pub fn running_animation_and_transition_count(&self) -> usize { - self.animations - .iter() - .filter(|animation| animation.state.needs_to_be_ticked()) - .count() + - self.transitions - .iter() - .filter(|transition| transition.state.needs_to_be_ticked()) - .count() - } - - /// If this `ElementAnimationSet` has any any active animations. - pub fn has_active_animation(&self) -> bool { - self.animations - .iter() - .any(|animation| animation.state != AnimationState::Canceled) - } - - /// If this `ElementAnimationSet` has any any active transitions. - pub fn has_active_transition(&self) -> bool { - self.transitions - .iter() - .any(|transition| transition.state != AnimationState::Canceled) - } - - /// Update our animations given a new style, canceling or starting new animations - /// when appropriate. - pub fn update_animations_for_new_style( - &mut self, - element: E, - context: &SharedStyleContext, - new_style: &Arc, - resolver: &mut StyleResolverForElement, - ) where - E: TElement, - { - for animation in self.animations.iter_mut() { - if animation.is_cancelled_in_new_style(new_style) { - animation.state = AnimationState::Canceled; - } - } - - maybe_start_animations(element, &context, &new_style, self, resolver); - } - - /// Update our transitions given a new style, canceling or starting new animations - /// when appropriate. - pub fn update_transitions_for_new_style( - &mut self, - might_need_transitions_update: bool, - context: &SharedStyleContext, - old_style: Option<&Arc>, - after_change_style: &Arc, - ) { - // If this is the first style, we don't trigger any transitions and we assume - // there were no previously triggered transitions. - let mut before_change_style = match old_style { - Some(old_style) => Arc::clone(old_style), - None => return, - }; - - // If the style of this element is display:none, then cancel all active transitions. - if after_change_style.get_box().clone_display().is_none() { - self.cancel_active_transitions(); - return; - } - - if !might_need_transitions_update { - return; - } - - // We convert old values into `before-change-style` here. - if self.has_active_transition() || self.has_active_animation() { - self.apply_active_animations(context, &mut before_change_style); - } - - let transitioning_properties = start_transitions_if_applicable( - context, - &before_change_style, - after_change_style, - self, - ); - - // Cancel any non-finished transitions that have properties which no longer transition. - for transition in self.transitions.iter_mut() { - if transition.state == AnimationState::Finished { - continue; - } - if transitioning_properties.contains(transition.property_animation.property_id()) { - continue; - } - transition.state = AnimationState::Canceled; - self.dirty = true; - } - } - - fn start_transition_if_applicable( - &mut self, - context: &SharedStyleContext, - longhand_id: LonghandId, - index: usize, - old_style: &ComputedValues, - new_style: &Arc, - ) { - if !longhand_id.is_transitionable() { - return; - } - - let style = new_style.get_ui(); - let timing_function = style.transition_timing_function_mod(index); - let duration = style.transition_duration_mod(index); - let delay = style.transition_delay_mod(index).seconds() as f64; - let now = context.current_time_for_animations; - - // Only start a new transition if the style actually changes between - // the old style and the new style. - let property_animation = match PropertyAnimation::from_longhand( - longhand_id, - timing_function, - duration, - old_style, - new_style, - ) { - Some(property_animation) => property_animation, - None => return, - }; - - // Per [1], don't trigger a new transition if the end state for that - // transition is the same as that of a transition that's running or - // completed. We don't take into account any canceled animations. - // [1]: https://drafts.csswg.org/css-transitions/#starting - if self - .transitions - .iter() - .filter(|transition| transition.state != AnimationState::Canceled) - .any(|transition| transition.property_animation.to == property_animation.to) - { - return; - } - - // We are going to start a new transition, but we might have to update - // it if we are replacing a reversed transition. - let reversing_adjusted_start_value = property_animation.from.clone(); - let mut new_transition = Transition { - start_time: now + delay, - delay, - property_animation, - state: AnimationState::Pending, - is_new: true, - reversing_adjusted_start_value, - reversing_shortening_factor: 1.0, - }; - - if let Some(old_transition) = self - .transitions - .iter_mut() - .filter(|transition| transition.state == AnimationState::Running) - .find(|transition| transition.property_animation.property_id() == longhand_id) - { - // We always cancel any running transitions for the same property. - old_transition.state = AnimationState::Canceled; - new_transition.update_for_possibly_reversed_transition(old_transition, delay, now); - } - - self.transitions.push(new_transition); - self.dirty = true; - } - - /// Generate a `AnimationValueMap` for this `ElementAnimationSet`'s - /// active transitions at the given time value. - pub fn get_value_map_for_active_transitions(&self, now: f64) -> Option { - if !self.has_active_transition() { - return None; - } - - let mut map = - AnimationValueMap::with_capacity_and_hasher(self.transitions.len(), Default::default()); - for transition in &self.transitions { - if transition.state == AnimationState::Canceled { - continue; - } - let value = match transition.calculate_value(now) { - Some(value) => value, - None => continue, - }; - map.insert(value.id(), value); - } - - Some(map) - } - - /// Generate a `AnimationValueMap` for this `ElementAnimationSet`'s - /// active animations at the given time value. - pub fn get_value_map_for_active_animations(&self, now: f64) -> Option { - if !self.has_active_animation() { - return None; - } - - let mut map = Default::default(); - for animation in &self.animations { - animation.get_property_declaration_at_time(now, &mut map); - } - - Some(map) - } -} - -#[derive(Clone, Debug, Eq, Hash, MallocSizeOf, PartialEq)] -/// A key that is used to identify nodes in the `DocumentAnimationSet`. -pub struct AnimationSetKey { - /// The node for this `AnimationSetKey`. - pub node: OpaqueNode, - /// The pseudo element for this `AnimationSetKey`. If `None` this key will - /// refer to the main content for its node. - pub pseudo_element: Option, -} - -impl AnimationSetKey { - /// Create a new key given a node and optional pseudo element. - pub fn new(node: OpaqueNode, pseudo_element: Option) -> Self { - AnimationSetKey { - node, - pseudo_element, - } - } - - /// Create a new key for the main content of this node. - pub fn new_for_non_pseudo(node: OpaqueNode) -> Self { - AnimationSetKey { - node, - pseudo_element: None, - } - } - - /// Create a new key for given node and pseudo element. - pub fn new_for_pseudo(node: OpaqueNode, pseudo_element: PseudoElement) -> Self { - AnimationSetKey { - node, - pseudo_element: Some(pseudo_element), - } - } -} - -#[derive(Clone, Debug, Default, MallocSizeOf)] -/// A set of animations for a document. -pub struct DocumentAnimationSet { - /// The `ElementAnimationSet`s that this set contains. - #[ignore_malloc_size_of = "Arc is hard"] - pub sets: Arc>>, -} - -impl DocumentAnimationSet { - /// Return whether or not the provided node has active CSS animations. - pub fn has_active_animations(&self, key: &AnimationSetKey) -> bool { - self.sets - .read() - .get(key) - .map_or(false, |set| set.has_active_animation()) - } - - /// Return whether or not the provided node has active CSS transitions. - pub fn has_active_transitions(&self, key: &AnimationSetKey) -> bool { - self.sets - .read() - .get(key) - .map_or(false, |set| set.has_active_transition()) - } - - /// Return a locked PropertyDeclarationBlock with animation values for the given - /// key and time. - pub fn get_animation_declarations( - &self, - key: &AnimationSetKey, - time: f64, - shared_lock: &SharedRwLock, - ) -> Option>> { - self.sets - .read() - .get(key) - .and_then(|set| set.get_value_map_for_active_animations(time)) - .map(|map| { - let block = PropertyDeclarationBlock::from_animation_value_map(&map); - Arc::new(shared_lock.wrap(block)) - }) - } - - /// Return a locked PropertyDeclarationBlock with transition values for the given - /// key and time. - pub fn get_transition_declarations( - &self, - key: &AnimationSetKey, - time: f64, - shared_lock: &SharedRwLock, - ) -> Option>> { - self.sets - .read() - .get(key) - .and_then(|set| set.get_value_map_for_active_transitions(time)) - .map(|map| { - let block = PropertyDeclarationBlock::from_animation_value_map(&map); - Arc::new(shared_lock.wrap(block)) - }) - } - - /// Get all the animation declarations for the given key, returning an empty - /// `AnimationDeclarations` if there are no animations. - pub fn get_all_declarations( - &self, - key: &AnimationSetKey, - time: f64, - shared_lock: &SharedRwLock, - ) -> AnimationDeclarations { - let sets = self.sets.read(); - let set = match sets.get(key) { - Some(set) => set, - None => return Default::default(), - }; - - let animations = set.get_value_map_for_active_animations(time).map(|map| { - let block = PropertyDeclarationBlock::from_animation_value_map(&map); - Arc::new(shared_lock.wrap(block)) - }); - let transitions = set.get_value_map_for_active_transitions(time).map(|map| { - let block = PropertyDeclarationBlock::from_animation_value_map(&map); - Arc::new(shared_lock.wrap(block)) - }); - AnimationDeclarations { - animations, - transitions, - } - } - - /// Cancel all animations for set at the given key. - pub fn cancel_all_animations_for_key(&self, key: &AnimationSetKey) { - if let Some(set) = self.sets.write().get_mut(key) { - set.cancel_all_animations(); - } - } -} - -/// Kick off any new transitions for this node and return all of the properties that are -/// transitioning. This is at the end of calculating style for a single node. -pub fn start_transitions_if_applicable( - context: &SharedStyleContext, - old_style: &ComputedValues, - new_style: &Arc, - animation_state: &mut ElementAnimationSet, -) -> LonghandIdSet { - let mut properties_that_transition = LonghandIdSet::new(); - for transition in new_style.transition_properties() { - let physical_property = transition.longhand_id.to_physical(new_style.writing_mode); - if properties_that_transition.contains(physical_property) { - continue; - } - - properties_that_transition.insert(physical_property); - animation_state.start_transition_if_applicable( - context, - physical_property, - transition.index, - old_style, - new_style, - ); - } - - properties_that_transition -} - -/// Triggers animations for a given node looking at the animation property -/// values. -pub fn maybe_start_animations( - element: E, - context: &SharedStyleContext, - new_style: &Arc, - animation_state: &mut ElementAnimationSet, - resolver: &mut StyleResolverForElement, -) where - E: TElement, -{ - let style = new_style.get_ui(); - for (i, name) in style.animation_name_iter().enumerate() { - let name = match name.as_atom() { - Some(atom) => atom, - None => continue, - }; - - debug!("maybe_start_animations: name={}", name); - let duration = style.animation_duration_mod(i).seconds() as f64; - if duration == 0. { - continue; - } - - let keyframe_animation = match context.stylist.get_animation(name, element) { - Some(animation) => animation, - None => continue, - }; - - debug!("maybe_start_animations: animation {} found", name); - - // If this animation doesn't have any keyframe, we can just continue - // without submitting it to the compositor, since both the first and - // the second keyframes would be synthetised from the computed - // values. - if keyframe_animation.steps.is_empty() { - continue; - } - - // 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 iteration_count = style.animation_iteration_count_mod(i); - let iteration_state = if iteration_count.0.is_infinite() { - KeyframesIterationState::Infinite(0.0) - } else { - KeyframesIterationState::Finite(0.0, iteration_count.0 as f64) - }; - - let animation_direction = style.animation_direction_mod(i); - - let initial_direction = match animation_direction { - AnimationDirection::Normal | AnimationDirection::Alternate => { - AnimationDirection::Normal - }, - AnimationDirection::Reverse | AnimationDirection::AlternateReverse => { - AnimationDirection::Reverse - }, - }; - - 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) { - AnimationPlayState::Paused => AnimationState::Paused(starting_progress), - AnimationPlayState::Running => AnimationState::Pending, - }; - - let computed_steps = ComputedKeyframe::generate_for_keyframes( - element, - &keyframe_animation, - context, - new_style, - style.animation_timing_function_mod(i), - resolver, - ); - - let mut new_animation = Animation { - name: name.clone(), - properties_changed: keyframe_animation.properties_changed, - computed_steps, - started_at, - duration, - fill_mode: style.animation_fill_mode_mod(i), - delay: delay as f64, - iteration_state, - state, - direction: animation_direction, - current_direction: initial_direction, - cascade_style: new_style.clone(), - is_new: true, - }; - - // If we started with a negative delay, make sure we iterate the animation if - // the delay moves us past the first iteration. - while starting_progress > 1. && !new_animation.on_last_iteration() { - new_animation.iterate(); - starting_progress -= 1.; - } - - animation_state.dirty = true; - - // If the animation was already present in the list for the node, just update its state. - for existing_animation in animation_state.animations.iter_mut() { - if existing_animation.state == AnimationState::Canceled { - continue; - } - - if new_animation.name == existing_animation.name { - existing_animation - .update_from_other(&new_animation, context.current_time_for_animations); - return; - } - } - - animation_state.animations.push(new_animation); - } -} diff --git a/components/style/applicable_declarations.rs b/components/style/applicable_declarations.rs deleted file mode 100644 index a0dbb60da8e..00000000000 --- a/components/style/applicable_declarations.rs +++ /dev/null @@ -1,210 +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/. */ - -//! Applicable declarations management. - -use crate::properties::PropertyDeclarationBlock; -use crate::rule_tree::{CascadeLevel, StyleSource}; -use crate::shared_lock::Locked; -use crate::stylesheets::layer_rule::LayerOrder; -use servo_arc::Arc; -use smallvec::SmallVec; - -/// List of applicable declarations. This is a transient structure that shuttles -/// declarations between selector matching and inserting into the rule tree, and -/// therefore we want to avoid heap-allocation where possible. -/// -/// In measurements on wikipedia, we pretty much never have more than 8 applicable -/// declarations, so we could consider making this 8 entries instead of 16. -/// However, it may depend a lot on workload, and stack space is cheap. -pub type ApplicableDeclarationList = SmallVec<[ApplicableDeclarationBlock; 16]>; - -/// Blink uses 18 bits to store source order, and does not check overflow [1]. -/// That's a limit that could be reached in realistic webpages, so we use -/// 24 bits and enforce defined behavior in the overflow case. -/// -/// Note that right now this restriction could be lifted if wanted (because we -/// no longer stash the cascade level in the remaining bits), but we keep it in -/// place in case we come up with a use-case for them, lacking reports of the -/// current limit being too small. -/// -/// [1] https://cs.chromium.org/chromium/src/third_party/WebKit/Source/core/css/ -/// RuleSet.h?l=128&rcl=90140ab80b84d0f889abc253410f44ed54ae04f3 -const SOURCE_ORDER_BITS: usize = 24; -const SOURCE_ORDER_MAX: u32 = (1 << SOURCE_ORDER_BITS) - 1; -const SOURCE_ORDER_MASK: u32 = SOURCE_ORDER_MAX; - -/// The cascade-level+layer order of this declaration. -#[derive(Clone, Copy, Debug, Eq, Hash, MallocSizeOf, PartialEq)] -pub struct CascadePriority { - cascade_level: CascadeLevel, - layer_order: LayerOrder, -} - -const_assert_eq!( - std::mem::size_of::(), - std::mem::size_of::() -); - -impl PartialOrd for CascadePriority { - #[inline] - fn partial_cmp(&self, other: &Self) -> Option { - Some(self.cmp(other)) - } -} - -impl Ord for CascadePriority { - fn cmp(&self, other: &Self) -> std::cmp::Ordering { - self.cascade_level.cmp(&other.cascade_level).then_with(|| { - let ordering = self.layer_order.cmp(&other.layer_order); - if ordering == std::cmp::Ordering::Equal { - return ordering; - } - // https://drafts.csswg.org/css-cascade-5/#cascade-layering - // - // Cascade layers (like declarations) are ordered by order - // of appearance. When comparing declarations that belong to - // different layers, then for normal rules the declaration - // whose cascade layer is last wins, and for important rules - // the declaration whose cascade layer is first wins. - // - // But the style attribute layer for some reason is special. - if self.cascade_level.is_important() && - !self.layer_order.is_style_attribute_layer() && - !other.layer_order.is_style_attribute_layer() - { - ordering.reverse() - } else { - ordering - } - }) - } -} - -impl CascadePriority { - /// Construct a new CascadePriority for a given (level, order) pair. - pub fn new(cascade_level: CascadeLevel, layer_order: LayerOrder) -> Self { - Self { - cascade_level, - layer_order, - } - } - - /// Returns the layer order. - #[inline] - pub fn layer_order(&self) -> LayerOrder { - self.layer_order - } - - /// Returns the cascade level. - #[inline] - pub fn cascade_level(&self) -> CascadeLevel { - self.cascade_level - } - - /// Whether this declaration should be allowed if `revert` or `revert-layer` - /// have been specified on a given origin. - /// - /// `self` is the priority at which the `revert` or `revert-layer` keyword - /// have been specified. - pub fn allows_when_reverted(&self, other: &Self, origin_revert: bool) -> bool { - if origin_revert { - other.cascade_level.origin() < self.cascade_level.origin() - } else { - other.unimportant() < self.unimportant() - } - } - - /// Convert this priority from "important" to "non-important", if needed. - pub fn unimportant(&self) -> Self { - Self::new(self.cascade_level().unimportant(), self.layer_order()) - } - - /// Convert this priority from "non-important" to "important", if needed. - pub fn important(&self) -> Self { - Self::new(self.cascade_level().important(), self.layer_order()) - } -} - -/// A property declaration together with its precedence among rules of equal -/// specificity so that we can sort them. -/// -/// This represents the declarations in a given declaration block for a given -/// importance. -#[derive(Clone, Debug, MallocSizeOf, PartialEq)] -pub struct ApplicableDeclarationBlock { - /// The style source, either a style rule, or a property declaration block. - #[ignore_malloc_size_of = "Arc"] - pub source: StyleSource, - /// The bits containing the source order, cascade level, and shadow cascade - /// order. - source_order: u32, - /// The specificity of the selector. - pub specificity: u32, - /// The cascade priority of the rule. - pub cascade_priority: CascadePriority, -} - -impl ApplicableDeclarationBlock { - /// Constructs an applicable declaration block from a given property - /// declaration block and importance. - #[inline] - pub fn from_declarations( - declarations: Arc>, - level: CascadeLevel, - layer_order: LayerOrder, - ) -> Self { - ApplicableDeclarationBlock { - source: StyleSource::from_declarations(declarations), - source_order: 0, - specificity: 0, - cascade_priority: CascadePriority::new(level, layer_order), - } - } - - /// Constructs an applicable declaration block from the given components. - #[inline] - pub fn new( - source: StyleSource, - source_order: u32, - level: CascadeLevel, - specificity: u32, - layer_order: LayerOrder, - ) -> Self { - ApplicableDeclarationBlock { - source, - source_order: source_order & SOURCE_ORDER_MASK, - specificity, - cascade_priority: CascadePriority::new(level, layer_order), - } - } - - /// Returns the source order of the block. - #[inline] - pub fn source_order(&self) -> u32 { - self.source_order - } - - /// Returns the cascade level of the block. - #[inline] - pub fn level(&self) -> CascadeLevel { - self.cascade_priority.cascade_level() - } - - /// Returns the cascade level of the block. - #[inline] - pub fn layer_order(&self) -> LayerOrder { - self.cascade_priority.layer_order() - } - - /// Convenience method to consume self and return the right thing for the - /// rule tree to iterate over. - #[inline] - pub fn for_rule_tree(self) -> (StyleSource, CascadePriority) { - (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/attr.rs b/components/style/attr.rs deleted file mode 100644 index 7747921ffe9..00000000000 --- a/components/style/attr.rs +++ /dev/null @@ -1,601 +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/. */ - -//! Parsed representations of [DOM attributes][attr]. -//! -//! [attr]: https://dom.spec.whatwg.org/#interface-attr - -use crate::properties::PropertyDeclarationBlock; -use crate::shared_lock::Locked; -use crate::str::str_join; -use crate::str::{read_exponent, read_fraction, HTML_SPACE_CHARACTERS}; -use crate::str::{read_numbers, split_commas, split_html_space_chars}; -use crate::values::specified::Length; -use crate::values::AtomString; -use crate::{Atom, LocalName, Namespace, Prefix}; -use app_units::Au; -use cssparser::{self, Color, RGBA}; -use euclid::num::Zero; -use num_traits::ToPrimitive; -use selectors::attr::AttrSelectorOperation; -use servo_arc::Arc; -use std::str::FromStr; - -// Duplicated from script::dom::values. -const UNSIGNED_LONG_MAX: u32 = 2147483647; - -#[derive(Clone, Copy, Debug, PartialEq)] -#[cfg_attr(feature = "servo", derive(MallocSizeOf))] -pub enum LengthOrPercentageOrAuto { - Auto, - Percentage(f32), - Length(Au), -} - -#[derive(Clone, Debug)] -#[cfg_attr(feature = "servo", derive(MallocSizeOf))] -pub enum AttrValue { - String(String), - TokenList(String, Vec), - UInt(String, u32), - Int(String, i32), - Double(String, f64), - Atom(Atom), - Length(String, Option), - Color(String, Option), - Dimension(String, LengthOrPercentageOrAuto), - - /// Stores a URL, computed from the input string and a document's base URL. - /// - /// The URL is resolved at setting-time, so this kind of attribute value is - /// not actually suitable for most URL-reflecting IDL attributes. - ResolvedUrl( - String, - #[ignore_malloc_size_of = "Arc"] Option> - ), - - /// Note that this variant is only used transitively as a fast path to set - /// the property declaration block relevant to the style of an element when - /// set from the inline declaration of that element (that is, - /// `element.style`). - /// - /// This can, as of this writing, only correspond to the value of the - /// `style` element, and is set from its relevant CSSInlineStyleDeclaration, - /// and then converted to a string in Element::attribute_mutated. - /// - /// Note that we don't necessarily need to do that (we could just clone the - /// declaration block), but that avoids keeping a refcounted - /// declarationblock for longer than needed. - Declaration( - String, - #[ignore_malloc_size_of = "Arc"] Arc>, - ), -} - -/// Shared implementation to parse an integer according to -/// or -/// -fn do_parse_integer>(input: T) -> Result { - let mut input = input - .skip_while(|c| HTML_SPACE_CHARACTERS.iter().any(|s| s == c)) - .peekable(); - - let sign = match input.peek() { - None => return Err(()), - Some(&'-') => { - input.next(); - -1 - }, - Some(&'+') => { - input.next(); - 1 - }, - Some(_) => 1, - }; - - let (value, _) = read_numbers(input); - - value.and_then(|value| value.checked_mul(sign)).ok_or(()) -} - -/// Parse an integer according to -/// . -pub fn parse_integer>(input: T) -> Result { - do_parse_integer(input).and_then(|result| result.to_i32().ok_or(())) -} - -/// Parse an integer according to -/// -pub fn parse_unsigned_integer>(input: T) -> Result { - do_parse_integer(input).and_then(|result| result.to_u32().ok_or(())) -} - -/// Parse a floating-point number according to -/// -pub fn parse_double(string: &str) -> Result { - let trimmed = string.trim_matches(HTML_SPACE_CHARACTERS); - let mut input = trimmed.chars().peekable(); - - let (value, divisor, chars_skipped) = match input.peek() { - None => return Err(()), - Some(&'-') => { - input.next(); - (-1f64, -1f64, 1) - }, - Some(&'+') => { - input.next(); - (1f64, 1f64, 1) - }, - _ => (1f64, 1f64, 0), - }; - - let (value, value_digits) = if let Some(&'.') = input.peek() { - (0f64, 0) - } else { - let (read_val, read_digits) = read_numbers(input); - ( - value * read_val.and_then(|result| result.to_f64()).unwrap_or(1f64), - read_digits, - ) - }; - - let input = trimmed - .chars() - .skip(value_digits + chars_skipped) - .peekable(); - - let (mut value, fraction_digits) = read_fraction(input, divisor, value); - - let input = trimmed - .chars() - .skip(value_digits + chars_skipped + fraction_digits) - .peekable(); - - if let Some(exp) = read_exponent(input) { - value *= 10f64.powi(exp) - }; - - Ok(value) -} - -impl AttrValue { - pub fn from_serialized_tokenlist(tokens: String) -> AttrValue { - let atoms = - split_html_space_chars(&tokens) - .map(Atom::from) - .fold(vec![], |mut acc, atom| { - if !acc.contains(&atom) { - acc.push(atom) - } - acc - }); - AttrValue::TokenList(tokens, atoms) - } - - pub fn from_comma_separated_tokenlist(tokens: String) -> AttrValue { - let atoms = split_commas(&tokens) - .map(Atom::from) - .fold(vec![], |mut acc, atom| { - if !acc.contains(&atom) { - acc.push(atom) - } - acc - }); - AttrValue::TokenList(tokens, atoms) - } - - pub fn from_atomic_tokens(atoms: Vec) -> AttrValue { - // TODO(ajeffrey): effecient conversion of Vec to String - let tokens = String::from(str_join(&atoms, "\x20")); - AttrValue::TokenList(tokens, atoms) - } - - // https://html.spec.whatwg.org/multipage/#reflecting-content-attributes-in-idl-attributes:idl-unsigned-long - pub fn from_u32(string: String, default: u32) -> AttrValue { - let result = parse_unsigned_integer(string.chars()).unwrap_or(default); - let result = if result > UNSIGNED_LONG_MAX { - default - } else { - result - }; - AttrValue::UInt(string, result) - } - - pub fn from_i32(string: String, default: i32) -> AttrValue { - let result = parse_integer(string.chars()).unwrap_or(default); - AttrValue::Int(string, result) - } - - // https://html.spec.whatwg.org/multipage/#reflecting-content-attributes-in-idl-attributes:idl-double - pub fn from_double(string: String, default: f64) -> AttrValue { - let result = parse_double(&string).unwrap_or(default); - - if result.is_normal() { - AttrValue::Double(string, result) - } else { - AttrValue::Double(string, default) - } - } - - // https://html.spec.whatwg.org/multipage/#limited-to-only-non-negative-numbers - pub fn from_limited_i32(string: String, default: i32) -> AttrValue { - let result = parse_integer(string.chars()).unwrap_or(default); - - if result < 0 { - AttrValue::Int(string, default) - } else { - AttrValue::Int(string, result) - } - } - - // https://html.spec.whatwg.org/multipage/#limited-to-only-non-negative-numbers-greater-than-zero - pub fn from_limited_u32(string: String, default: u32) -> AttrValue { - let result = parse_unsigned_integer(string.chars()).unwrap_or(default); - let result = if result == 0 || result > UNSIGNED_LONG_MAX { - default - } else { - result - }; - AttrValue::UInt(string, result) - } - - pub fn from_atomic(string: String) -> AttrValue { - let value = Atom::from(string); - AttrValue::Atom(value) - } - - pub fn from_resolved_url(base: &Arc<::url::Url>, url: String) -> AttrValue { - let joined = base.join(&url).ok().map(Arc::new); - AttrValue::ResolvedUrl(url, joined) - } - - pub fn from_legacy_color(string: String) -> AttrValue { - let parsed = parse_legacy_color(&string).ok(); - AttrValue::Color(string, parsed) - } - - pub fn from_dimension(string: String) -> AttrValue { - let parsed = parse_length(&string); - AttrValue::Dimension(string, parsed) - } - - pub fn from_nonzero_dimension(string: String) -> AttrValue { - let parsed = parse_nonzero_length(&string); - AttrValue::Dimension(string, parsed) - } - - /// Assumes the `AttrValue` is a `TokenList` and returns its tokens - /// - /// ## Panics - /// - /// Panics if the `AttrValue` is not a `TokenList` - pub fn as_tokens(&self) -> &[Atom] { - match *self { - AttrValue::TokenList(_, ref tokens) => tokens, - _ => panic!("Tokens not found"), - } - } - - /// Assumes the `AttrValue` is an `Atom` and returns its value - /// - /// ## Panics - /// - /// Panics if the `AttrValue` is not an `Atom` - pub fn as_atom(&self) -> &Atom { - match *self { - AttrValue::Atom(ref value) => value, - _ => panic!("Atom not found"), - } - } - - /// Assumes the `AttrValue` is a `Color` and returns its value - /// - /// ## Panics - /// - /// Panics if the `AttrValue` is not a `Color` - pub fn as_color(&self) -> Option<&RGBA> { - match *self { - AttrValue::Color(_, ref color) => color.as_ref(), - _ => panic!("Color not found"), - } - } - - /// Assumes the `AttrValue` is a `Dimension` and returns its value - /// - /// ## Panics - /// - /// Panics if the `AttrValue` is not a `Dimension` - pub fn as_dimension(&self) -> &LengthOrPercentageOrAuto { - match *self { - AttrValue::Dimension(_, ref l) => l, - _ => panic!("Dimension not found"), - } - } - - /// Assumes the `AttrValue` is a `ResolvedUrl` and returns its value. - /// - /// ## Panics - /// - /// Panics if the `AttrValue` is not a `ResolvedUrl` - pub fn as_resolved_url(&self) -> Option<&Arc<::url::Url>> { - match *self { - AttrValue::ResolvedUrl(_, ref url) => url.as_ref(), - _ => panic!("Url not found"), - } - } - - /// Return the AttrValue as its integer representation, if any. - /// This corresponds to attribute values returned as `AttrValue::UInt(_)` - /// by `VirtualMethods::parse_plain_attribute()`. - /// - /// ## Panics - /// - /// Panics if the `AttrValue` is not a `UInt` - pub fn as_uint(&self) -> u32 { - if let AttrValue::UInt(_, value) = *self { - value - } else { - panic!("Uint not found"); - } - } - - /// Return the AttrValue as a dimension computed from its integer - /// representation, assuming that integer representation specifies pixels. - /// - /// This corresponds to attribute values returned as `AttrValue::UInt(_)` - /// by `VirtualMethods::parse_plain_attribute()`. - /// - /// ## Panics - /// - /// Panics if the `AttrValue` is not a `UInt` - pub fn as_uint_px_dimension(&self) -> LengthOrPercentageOrAuto { - if let AttrValue::UInt(_, value) = *self { - LengthOrPercentageOrAuto::Length(Au::from_px(value as i32)) - } else { - panic!("Uint not found"); - } - } - - pub fn eval_selector(&self, selector: &AttrSelectorOperation<&AtomString>) -> bool { - // FIXME(SimonSapin) this can be more efficient by matching on `(self, selector)` variants - // and doing Atom comparisons instead of string comparisons where possible, - // with SelectorImpl::AttrValue changed to Atom. - selector.eval_str(self) - } -} - -impl ::std::ops::Deref for AttrValue { - type Target = str; - - fn deref(&self) -> &str { - match *self { - AttrValue::String(ref value) | - AttrValue::TokenList(ref value, _) | - AttrValue::UInt(ref value, _) | - AttrValue::Double(ref value, _) | - AttrValue::Length(ref value, _) | - AttrValue::Color(ref value, _) | - AttrValue::Int(ref value, _) | - AttrValue::ResolvedUrl(ref value, _) | - AttrValue::Declaration(ref value, _) | - AttrValue::Dimension(ref value, _) => &value, - AttrValue::Atom(ref value) => &value, - } - } -} - -impl PartialEq for AttrValue { - fn eq(&self, other: &Atom) -> bool { - match *self { - AttrValue::Atom(ref value) => value == other, - _ => other == &**self, - } - } -} - -/// -pub fn parse_nonzero_length(value: &str) -> LengthOrPercentageOrAuto { - match parse_length(value) { - LengthOrPercentageOrAuto::Length(x) if x == Au::zero() => LengthOrPercentageOrAuto::Auto, - LengthOrPercentageOrAuto::Percentage(x) if x == 0. => LengthOrPercentageOrAuto::Auto, - x => x, - } -} - -/// Parses a [legacy color][color]. If unparseable, `Err` is returned. -/// -/// [color]: https://html.spec.whatwg.org/multipage/#rules-for-parsing-a-legacy-colour-value -pub fn parse_legacy_color(mut input: &str) -> Result { - // Steps 1 and 2. - if input.is_empty() { - return Err(()); - } - - // Step 3. - input = input.trim_matches(HTML_SPACE_CHARACTERS); - - // Step 4. - if input.eq_ignore_ascii_case("transparent") { - return Err(()); - } - - // Step 5. - if let Ok(Color::Rgba(rgba)) = cssparser::parse_color_keyword(input) { - return Ok(rgba); - } - - // Step 6. - if input.len() == 4 { - if let (b'#', Ok(r), Ok(g), Ok(b)) = ( - input.as_bytes()[0], - hex(input.as_bytes()[1] as char), - hex(input.as_bytes()[2] as char), - hex(input.as_bytes()[3] as char), - ) { - return Ok(RGBA::new(Some(r * 17), Some(g * 17), Some(b * 17), Some(1.0))); - } - } - - // Step 7. - let mut new_input = String::new(); - for ch in input.chars() { - if ch as u32 > 0xffff { - new_input.push_str("00") - } else { - new_input.push(ch) - } - } - let mut input = &*new_input; - - // Step 8. - for (char_count, (index, _)) in input.char_indices().enumerate() { - if char_count == 128 { - input = &input[..index]; - break; - } - } - - // Step 9. - if input.as_bytes()[0] == b'#' { - input = &input[1..] - } - - // Step 10. - let mut new_input = Vec::new(); - for ch in input.chars() { - if hex(ch).is_ok() { - new_input.push(ch as u8) - } else { - new_input.push(b'0') - } - } - let mut input = new_input; - - // Step 11. - while input.is_empty() || (input.len() % 3) != 0 { - input.push(b'0') - } - - // Step 12. - let mut length = input.len() / 3; - let (mut red, mut green, mut blue) = ( - &input[..length], - &input[length..length * 2], - &input[length * 2..], - ); - - // Step 13. - if length > 8 { - red = &red[length - 8..]; - green = &green[length - 8..]; - blue = &blue[length - 8..]; - length = 8 - } - - // Step 14. - while length > 2 && red[0] == b'0' && green[0] == b'0' && blue[0] == b'0' { - red = &red[1..]; - green = &green[1..]; - blue = &blue[1..]; - length -= 1 - } - - // Steps 15-20. - return Ok(RGBA::new( - Some(hex_string(red).unwrap()), - Some(hex_string(green).unwrap()), - Some(hex_string(blue).unwrap()), - Some(1.0), - )); - - fn hex(ch: char) -> Result { - match ch { - '0'..='9' => Ok((ch as u8) - b'0'), - 'a'..='f' => Ok((ch as u8) - b'a' + 10), - 'A'..='F' => Ok((ch as u8) - b'A' + 10), - _ => Err(()), - } - } - - fn hex_string(string: &[u8]) -> Result { - match string.len() { - 0 => Err(()), - 1 => hex(string[0] as char), - _ => { - let upper = hex(string[0] as char)?; - let lower = hex(string[1] as char)?; - Ok((upper << 4) | lower) - }, - } - } -} - -/// Parses a [dimension value][dim]. If unparseable, `Auto` is returned. -/// -/// [dim]: https://html.spec.whatwg.org/multipage/#rules-for-parsing-dimension-values -// TODO: this function can be rewritten to return Result -pub fn parse_length(mut value: &str) -> LengthOrPercentageOrAuto { - // Steps 1 & 2 are not relevant - - // Step 3 - value = value.trim_start_matches(HTML_SPACE_CHARACTERS); - - // Step 4 - match value.chars().nth(0) { - Some('0'..='9') => {}, - _ => return LengthOrPercentageOrAuto::Auto, - } - - // Steps 5 to 8 - // We trim the string length to the minimum of: - // 1. the end of the string - // 2. the first occurence of a '%' (U+0025 PERCENT SIGN) - // 3. the second occurrence of a '.' (U+002E FULL STOP) - // 4. the occurrence of a character that is neither a digit nor '%' nor '.' - // Note: Step 7.4 is directly subsumed by FromStr::from_str - let mut end_index = value.len(); - let (mut found_full_stop, mut found_percent) = (false, false); - for (i, ch) in value.chars().enumerate() { - match ch { - '0'..='9' => continue, - '%' => { - found_percent = true; - end_index = i; - break; - }, - '.' if !found_full_stop => { - found_full_stop = true; - continue; - }, - _ => { - end_index = i; - break; - }, - } - } - value = &value[..end_index]; - - if found_percent { - let result: Result = FromStr::from_str(value); - match result { - Ok(number) => return LengthOrPercentageOrAuto::Percentage((number as f32) / 100.0), - Err(_) => return LengthOrPercentageOrAuto::Auto, - } - } - - match FromStr::from_str(value) { - Ok(number) => LengthOrPercentageOrAuto::Length(Au::from_f64_px(number)), - Err(_) => LengthOrPercentageOrAuto::Auto, - } -} - -/// A struct that uniquely identifies an element's attribute. -#[derive(Clone, Debug)] -#[cfg_attr(feature = "servo", derive(MallocSizeOf))] -pub struct AttrIdentifier { - pub local_name: LocalName, - pub name: LocalName, - pub namespace: Namespace, - pub prefix: Option, -} diff --git a/components/style/author_styles.rs b/components/style/author_styles.rs deleted file mode 100644 index a0223dceccc..00000000000 --- a/components/style/author_styles.rs +++ /dev/null @@ -1,70 +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 set of author stylesheets and their computed representation, such as the -//! ones used for ShadowRoot. - -use crate::dom::TElement; -use crate::invalidation::media_queries::ToMediaListKey; -use crate::shared_lock::SharedRwLockReadGuard; -use crate::stylesheet_set::AuthorStylesheetSet; -use crate::stylesheets::StylesheetInDocument; -use crate::stylist::CascadeData; -use crate::stylist::Stylist; -use servo_arc::Arc; - -/// A set of author stylesheets and their computed representation, such as the -/// ones used for ShadowRoot. -#[derive(MallocSizeOf)] -pub struct GenericAuthorStyles -where - S: StylesheetInDocument + PartialEq + 'static, -{ - /// The sheet collection, which holds the sheet pointers, the invalidations, - /// and all that stuff. - pub stylesheets: AuthorStylesheetSet, - /// The actual cascade data computed from the stylesheets. - #[ignore_malloc_size_of = "Measured as part of the stylist"] - pub data: Arc, -} - -pub use self::GenericAuthorStyles as AuthorStyles; - -lazy_static! { - static ref EMPTY_CASCADE_DATA: Arc = Arc::new_leaked(CascadeData::new()); -} - -impl GenericAuthorStyles -where - S: StylesheetInDocument + PartialEq + 'static, -{ - /// Create an empty AuthorStyles. - #[inline] - pub fn new() -> Self { - Self { - stylesheets: AuthorStylesheetSet::new(), - data: EMPTY_CASCADE_DATA.clone(), - } - } - - /// Flush the pending sheet changes, updating `data` as appropriate. - /// - /// TODO(emilio): Need a host element and a snapshot map to do invalidation - /// properly. - #[inline] - pub fn flush(&mut self, stylist: &mut Stylist, guard: &SharedRwLockReadGuard) - where - E: TElement, - S: ToMediaListKey, - { - let flusher = self - .stylesheets - .flush::(/* host = */ None, /* snapshot_map = */ None); - - let result = stylist.rebuild_author_data(&self.data, flusher.sheets, guard); - if let Ok(Some(new_data)) = result { - self.data = new_data; - } - } -} diff --git a/components/style/bezier.rs b/components/style/bezier.rs deleted file mode 100644 index dd520ac0ed5..00000000000 --- a/components/style/bezier.rs +++ /dev/null @@ -1,176 +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/. */ - -//! Parametric Bézier curves. -//! -//! This is based on `WebCore/platform/graphics/UnitBezier.h` in WebKit. - -#![deny(missing_docs)] - -use crate::values::CSSFloat; - -const NEWTON_METHOD_ITERATIONS: u8 = 8; - -/// A unit cubic Bézier curve, used for timing functions in CSS transitions and animations. -pub struct Bezier { - ax: f64, - bx: f64, - cx: f64, - ay: f64, - by: f64, - cy: f64, -} - -impl Bezier { - /// Calculate the output of a unit cubic Bézier curve from the two middle control points. - /// - /// X coordinate is time, Y coordinate is function advancement. - /// The nominal range for both is 0 to 1. - /// - /// The start and end points are always (0, 0) and (1, 1) so that a transition or animation - /// starts at 0% and ends at 100%. - pub fn calculate_bezier_output( - progress: f64, - epsilon: f64, - x1: f32, - y1: f32, - x2: f32, - y2: f32, - ) -> f64 { - // Check for a linear curve. - if x1 == y1 && x2 == y2 { - return progress; - } - - // Ensure that we return 0 or 1 on both edges. - if progress == 0.0 { - return 0.0; - } - if progress == 1.0 { - return 1.0; - } - - // For negative values, try to extrapolate with tangent (p1 - p0) or, - // if p1 is coincident with p0, with (p2 - p0). - if progress < 0.0 { - if x1 > 0.0 { - return progress * y1 as f64 / x1 as f64; - } - if y1 == 0.0 && x2 > 0.0 { - return progress * y2 as f64 / x2 as f64; - } - // If we can't calculate a sensible tangent, don't extrapolate at all. - return 0.0; - } - - // For values greater than 1, try to extrapolate with tangent (p2 - p3) or, - // if p2 is coincident with p3, with (p1 - p3). - if progress > 1.0 { - if x2 < 1.0 { - return 1.0 + (progress - 1.0) * (y2 as f64 - 1.0) / (x2 as f64 - 1.0); - } - if y2 == 1.0 && x1 < 1.0 { - return 1.0 + (progress - 1.0) * (y1 as f64 - 1.0) / (x1 as f64 - 1.0); - } - // If we can't calculate a sensible tangent, don't extrapolate at all. - return 1.0; - } - - Bezier::new(x1, y1, x2, y2).solve(progress, epsilon) - } - - #[inline] - fn new(x1: CSSFloat, y1: CSSFloat, x2: CSSFloat, y2: CSSFloat) -> Bezier { - let cx = 3. * x1 as f64; - let bx = 3. * (x2 as f64 - x1 as f64) - cx; - - let cy = 3. * y1 as f64; - let by = 3. * (y2 as f64 - y1 as f64) - cy; - - Bezier { - ax: 1.0 - cx - bx, - bx: bx, - cx: cx, - ay: 1.0 - cy - by, - by: by, - cy: cy, - } - } - - #[inline] - fn sample_curve_x(&self, t: f64) -> f64 { - // ax * t^3 + bx * t^2 + cx * t - ((self.ax * t + self.bx) * t + self.cx) * t - } - - #[inline] - fn sample_curve_y(&self, t: f64) -> f64 { - ((self.ay * t + self.by) * t + self.cy) * t - } - - #[inline] - fn sample_curve_derivative_x(&self, t: f64) -> f64 { - (3.0 * self.ax * t + 2.0 * self.bx) * t + self.cx - } - - #[inline] - fn solve_curve_x(&self, x: f64, epsilon: f64) -> f64 { - // Fast path: Use Newton's method. - let mut t = x; - for _ in 0..NEWTON_METHOD_ITERATIONS { - let x2 = self.sample_curve_x(t); - if x2.approx_eq(x, epsilon) { - return t; - } - let dx = self.sample_curve_derivative_x(t); - if dx.approx_eq(0.0, 1e-6) { - break; - } - t -= (x2 - x) / dx; - } - - // Slow path: Use bisection. - let (mut lo, mut hi, mut t) = (0.0, 1.0, x); - - if t < lo { - return lo; - } - if t > hi { - return hi; - } - - while lo < hi { - let x2 = self.sample_curve_x(t); - if x2.approx_eq(x, epsilon) { - return t; - } - if x > x2 { - lo = t - } else { - hi = t - } - t = (hi - lo) / 2.0 + lo - } - - t - } - - /// Solve the bezier curve for a given `x` and an `epsilon`, that should be - /// between zero and one. - #[inline] - fn solve(&self, x: f64, epsilon: f64) -> f64 { - self.sample_curve_y(self.solve_curve_x(x, epsilon)) - } -} - -trait ApproxEq { - fn approx_eq(self, value: Self, epsilon: Self) -> bool; -} - -impl ApproxEq for f64 { - #[inline] - fn approx_eq(self, value: f64, epsilon: f64) -> bool { - (self - value).abs() < epsilon - } -} diff --git a/components/style/bloom.rs b/components/style/bloom.rs deleted file mode 100644 index dc722b7bdba..00000000000 --- a/components/style/bloom.rs +++ /dev/null @@ -1,401 +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/. */ - -//! The style bloom filter is used as an optimization when matching deep -//! descendant selectors. - -#![deny(missing_docs)] - -use crate::dom::{SendElement, TElement}; -use crate::LocalName; -use atomic_refcell::{AtomicRefCell, AtomicRefMut}; -use owning_ref::OwningHandle; -use selectors::bloom::BloomFilter; -use servo_arc::Arc; -use smallvec::SmallVec; -use std::mem::ManuallyDrop; - -thread_local! { - /// Bloom filters are large allocations, so we store them in thread-local storage - /// such that they can be reused across style traversals. StyleBloom is responsible - /// for ensuring that the bloom filter is zeroed when it is dropped. - /// - /// We intentionally leak this from TLS because we don't have the guarantee - /// of TLS destructors to run in worker threads. - /// - /// We could change this once https://github.com/rayon-rs/rayon/issues/688 - /// is fixed, hopefully. - static BLOOM_KEY: ManuallyDrop>> = - ManuallyDrop::new(Arc::new_leaked(Default::default())); -} - -/// A struct that allows us to fast-reject deep descendant selectors avoiding -/// selector-matching. -/// -/// This is implemented using a counting bloom filter, and it's a standard -/// optimization. See Gecko's `AncestorFilter`, and Blink's and WebKit's -/// `SelectorFilter`. -/// -/// The constraints for Servo's style system are a bit different compared to -/// traditional style systems given Servo does a parallel breadth-first -/// traversal instead of a sequential depth-first traversal. -/// -/// This implies that we need to track a bit more state than other browsers to -/// ensure we're doing the correct thing during the traversal, and being able to -/// apply this optimization effectively. -/// -/// Concretely, we have a bloom filter instance per worker thread, and we track -/// the current DOM depth in order to find a common ancestor when it doesn't -/// match the previous element we've styled. -/// -/// This is usually a pretty fast operation (we use to be one level deeper than -/// the previous one), but in the case of work-stealing, we may needed to push -/// and pop multiple elements. -/// -/// See the `insert_parents_recovering`, where most of the magic happens. -/// -/// Regarding thread-safety, this struct is safe because: -/// -/// * We clear this after a restyle. -/// * The DOM shape and attributes (and every other thing we access here) are -/// immutable during a restyle. -/// -pub struct StyleBloom { - /// A handle to the bloom filter from the thread upon which this StyleBloom - /// was created. We use AtomicRefCell so that this is all |Send|, which allows - /// StyleBloom to live in ThreadLocalStyleContext, which is dropped from the - /// parent thread. - filter: OwningHandle>, AtomicRefMut<'static, BloomFilter>>, - - /// The stack of elements that this bloom filter contains, along with the - /// number of hashes pushed for each element. - elements: SmallVec<[PushedElement; 16]>, - - /// Stack of hashes that have been pushed onto this filter. - pushed_hashes: SmallVec<[u32; 64]>, -} - -/// The very rough benchmarks in the selectors crate show clear() -/// costing about 25 times more than remove_hash(). We use this to implement -/// clear() more efficiently when only a small number of hashes have been -/// pushed. -/// -/// One subtly to note is that remove_hash() will not touch the value -/// if the filter overflowed. However, overflow can only occur if we -/// get 255 collisions on the same hash value, and 25 < 255. -const MEMSET_CLEAR_THRESHOLD: usize = 25; - -struct PushedElement { - /// The element that was pushed. - element: SendElement, - - /// The number of hashes pushed for the element. - num_hashes: usize, -} - -impl PushedElement { - fn new(el: E, num_hashes: usize) -> Self { - PushedElement { - element: unsafe { SendElement::new(el) }, - num_hashes, - } - } -} - -/// Returns whether the attribute name is excluded from the bloom filter. -/// -/// We do this for attributes that are very common but not commonly used in -/// selectors. -#[inline] -pub fn is_attr_name_excluded_from_filter(name: &LocalName) -> bool { - return *name == local_name!("class") || *name == local_name!("id") || *name == local_name!("style") -} - -fn each_relevant_element_hash(element: E, mut f: F) -where - E: TElement, - F: FnMut(u32), -{ - f(element.local_name().get_hash()); - f(element.namespace().get_hash()); - - if let Some(id) = element.id() { - f(id.get_hash()); - } - - element.each_class(|class| f(class.get_hash())); - - element.each_attr_name(|name| { - if !is_attr_name_excluded_from_filter(name) { - f(name.get_hash()) - } - }); -} - -impl Drop for StyleBloom { - fn drop(&mut self) { - // Leave the reusable bloom filter in a zeroed state. - self.clear(); - } -} - -impl StyleBloom { - /// Create an empty `StyleBloom`. Because StyleBloom acquires the thread- - /// local filter buffer, creating multiple live StyleBloom instances at - /// the same time on the same thread will panic. - - // Forced out of line to limit stack frame sizes after extra inlining from - // https://github.com/rust-lang/rust/pull/43931 - // - // See https://github.com/servo/servo/pull/18420#issuecomment-328769322 - #[inline(never)] - pub fn new() -> Self { - let bloom_arc = BLOOM_KEY.with(|b| Arc::clone(&*b)); - let filter = - OwningHandle::new_with_fn(bloom_arc, |x| unsafe { x.as_ref() }.unwrap().borrow_mut()); - debug_assert!( - filter.is_zeroed(), - "Forgot to zero the bloom filter last time" - ); - StyleBloom { - filter, - elements: Default::default(), - pushed_hashes: Default::default(), - } - } - - /// Return the bloom filter used properly by the `selectors` crate. - pub fn filter(&self) -> &BloomFilter { - &*self.filter - } - - /// Push an element to the bloom filter, knowing that it's a child of the - /// last element parent. - pub fn push(&mut self, element: E) { - if cfg!(debug_assertions) { - if self.elements.is_empty() { - assert!(element.traversal_parent().is_none()); - } - } - self.push_internal(element); - } - - /// Same as `push`, but without asserting, in order to use it from - /// `rebuild`. - fn push_internal(&mut self, element: E) { - let mut count = 0; - each_relevant_element_hash(element, |hash| { - count += 1; - self.filter.insert_hash(hash); - self.pushed_hashes.push(hash); - }); - self.elements.push(PushedElement::new(element, count)); - } - - /// Pop the last element in the bloom filter and return it. - #[inline] - fn pop(&mut self) -> Option { - let PushedElement { - element, - num_hashes, - } = self.elements.pop()?; - let popped_element = *element; - - // Verify that the pushed hashes match the ones we'd get from the element. - let mut expected_hashes = vec![]; - if cfg!(debug_assertions) { - each_relevant_element_hash(popped_element, |hash| expected_hashes.push(hash)); - } - - for _ in 0..num_hashes { - let hash = self.pushed_hashes.pop().unwrap(); - debug_assert_eq!(expected_hashes.pop().unwrap(), hash); - self.filter.remove_hash(hash); - } - - Some(popped_element) - } - - /// Returns the DOM depth of elements that can be correctly - /// matched against the bloom filter (that is, the number of - /// elements in our list). - pub fn matching_depth(&self) -> usize { - self.elements.len() - } - - /// Clears the bloom filter. - pub fn clear(&mut self) { - self.elements.clear(); - - if self.pushed_hashes.len() > MEMSET_CLEAR_THRESHOLD { - self.filter.clear(); - self.pushed_hashes.clear(); - } else { - for hash in self.pushed_hashes.drain(..) { - self.filter.remove_hash(hash); - } - debug_assert!(self.filter.is_zeroed()); - } - } - - /// Rebuilds the bloom filter up to the parent of the given element. - pub fn rebuild(&mut self, mut element: E) { - self.clear(); - - let mut parents_to_insert = SmallVec::<[E; 16]>::new(); - while let Some(parent) = element.traversal_parent() { - parents_to_insert.push(parent); - element = parent; - } - - for parent in parents_to_insert.drain(..).rev() { - self.push(parent); - } - } - - /// In debug builds, asserts that all the parents of `element` are in the - /// bloom filter. - /// - /// Goes away in release builds. - pub fn assert_complete(&self, mut element: E) { - if cfg!(debug_assertions) { - let mut checked = 0; - while let Some(parent) = element.traversal_parent() { - assert_eq!( - parent, - *(self.elements[self.elements.len() - 1 - checked].element) - ); - element = parent; - checked += 1; - } - assert_eq!(checked, self.elements.len()); - } - } - - /// Get the element that represents the chain of things inserted - /// into the filter right now. That chain is the given element - /// (if any) and its ancestors. - #[inline] - pub fn current_parent(&self) -> Option { - self.elements.last().map(|ref el| *el.element) - } - - /// Insert the parents of an element in the bloom filter, trying to recover - /// the filter if the last element inserted doesn't match. - /// - /// Gets the element depth in the dom, to make it efficient, or if not - /// provided always rebuilds the filter from scratch. - /// - /// Returns the new bloom filter depth, that the traversal code is - /// responsible to keep around if it wants to get an effective filter. - pub fn insert_parents_recovering(&mut self, element: E, element_depth: usize) { - // Easy case, we're in a different restyle, or we're empty. - if self.elements.is_empty() { - self.rebuild(element); - return; - } - - let traversal_parent = match element.traversal_parent() { - Some(parent) => parent, - None => { - // Yay, another easy case. - self.clear(); - return; - }, - }; - - if self.current_parent() == Some(traversal_parent) { - // Ta da, cache hit, we're all done. - return; - } - - if element_depth == 0 { - self.clear(); - return; - } - - // We should've early exited above. - debug_assert!( - element_depth != 0, - "We should have already cleared the bloom filter" - ); - debug_assert!(!self.elements.is_empty(), "How! We should've just rebuilt!"); - - // Now the fun begins: We have the depth of the dom and the depth of the - // last element inserted in the filter, let's try to find a common - // parent. - // - // The current depth, that is, the depth of the last element inserted in - // the bloom filter, is the number of elements _minus one_, that is: if - // there's one element, it must be the root -> depth zero. - let mut current_depth = self.elements.len() - 1; - - // If the filter represents an element too deep in the dom, we need to - // pop ancestors. - while current_depth > element_depth - 1 { - self.pop().expect("Emilio is bad at math"); - current_depth -= 1; - } - - // Now let's try to find a common parent in the bloom filter chain, - // starting with traversal_parent. - let mut common_parent = traversal_parent; - let mut common_parent_depth = element_depth - 1; - - // Let's collect the parents we are going to need to insert once we've - // found the common one. - let mut parents_to_insert = SmallVec::<[E; 16]>::new(); - - // If the bloom filter still doesn't have enough elements, the common - // parent is up in the dom. - while common_parent_depth > current_depth { - // TODO(emilio): Seems like we could insert parents here, then - // reverse the slice. - parents_to_insert.push(common_parent); - common_parent = common_parent.traversal_parent().expect("We were lied to"); - common_parent_depth -= 1; - } - - // Now the two depths are the same. - debug_assert_eq!(common_parent_depth, current_depth); - - // Happy case: The parents match, we only need to push the ancestors - // we've collected and we'll never enter in this loop. - // - // Not-so-happy case: Parent's don't match, so we need to keep going up - // until we find a common ancestor. - // - // Gecko currently models native anonymous content that conceptually - // hangs off the document (such as scrollbars) as a separate subtree - // from the document root. - // - // Thus it's possible with Gecko that we do not find any common - // ancestor. - while *(self.elements.last().unwrap().element) != common_parent { - parents_to_insert.push(common_parent); - self.pop().unwrap(); - common_parent = match common_parent.traversal_parent() { - Some(parent) => parent, - None => { - debug_assert!(self.elements.is_empty()); - if cfg!(feature = "gecko") { - break; - } else { - panic!("should have found a common ancestor"); - } - }, - } - } - - // Now the parents match, so insert the stack of elements we have been - // collecting so far. - for parent in parents_to_insert.drain(..).rev() { - self.push(parent); - } - - debug_assert_eq!(self.elements.len(), element_depth); - - // We're done! Easy. - } -} diff --git a/components/style/build.rs b/components/style/build.rs deleted file mode 100644 index eacb9b3fc99..00000000000 --- a/components/style/build.rs +++ /dev/null @@ -1,87 +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/. */ - -#[macro_use] -extern crate lazy_static; - -use std::env; -use std::path::Path; -use std::process::{exit, Command}; -use walkdir::WalkDir; - -#[cfg(feature = "gecko")] -mod build_gecko; - -#[cfg(not(feature = "gecko"))] -mod build_gecko { - pub fn generate() {} -} - -lazy_static! { - pub static ref PYTHON: String = env::var("PYTHON3").ok().unwrap_or_else(|| { - let candidates = if cfg!(windows) { - ["python.exe"] - } else { - ["python3"] - }; - for &name in &candidates { - if Command::new(name) - .arg("--version") - .output() - .ok() - .map_or(false, |out| out.status.success()) - { - return name.to_owned(); - } - } - panic!( - "Can't find python (tried {})! Try fixing PATH or setting the PYTHON3 env var", - candidates.join(", ") - ) - }); -} - -fn generate_properties(engine: &str) { - for entry in WalkDir::new("properties") { - let entry = entry.unwrap(); - match entry.path().extension().and_then(|e| e.to_str()) { - Some("mako") | Some("rs") | Some("py") | Some("zip") => { - println!("cargo:rerun-if-changed={}", entry.path().display()); - }, - _ => {}, - } - } - - let script = Path::new(&env::var_os("CARGO_MANIFEST_DIR").unwrap()) - .join("properties") - .join("build.py"); - - let status = Command::new(&*PYTHON) - .arg(&script) - .arg(engine) - .arg("style-crate") - .status() - .unwrap(); - if !status.success() { - exit(1) - } -} - -fn main() { - let gecko = cfg!(feature = "gecko"); - let servo = cfg!(feature = "servo"); - let engine = match (gecko, servo) { - (true, false) => "gecko", - (false, true) => "servo", - _ => panic!( - "\n\n\ - The style crate requires enabling one of its 'servo' or 'gecko' feature flags. \ - \n\n" - ), - }; - println!("cargo:rerun-if-changed=build.rs"); - println!("cargo:out_dir={}", env::var("OUT_DIR").unwrap()); - generate_properties(engine); - build_gecko::generate(); -} diff --git a/components/style/build_gecko.rs b/components/style/build_gecko.rs deleted file mode 100644 index a83c5dbc6d6..00000000000 --- a/components/style/build_gecko.rs +++ /dev/null @@ -1,400 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ - -use super::PYTHON; -use bindgen::{Builder, CodegenConfig}; -use regex::Regex; -use std::cmp; -use std::collections::HashSet; -use std::env; -use std::fs::{self, File}; -use std::io::{Read, Write}; -use std::path::{Path, PathBuf}; -use std::process::{exit, Command}; -use std::slice; -use std::sync::Mutex; -use std::time::SystemTime; -use toml; -use toml::value::Table; - -lazy_static! { - static ref OUTDIR_PATH: PathBuf = PathBuf::from(env::var_os("OUT_DIR").unwrap()).join("gecko"); -} - -const STRUCTS_FILE: &'static str = "structs.rs"; - -fn read_config(path: &PathBuf) -> Table { - println!("cargo:rerun-if-changed={}", path.to_str().unwrap()); - update_last_modified(&path); - - let mut contents = String::new(); - File::open(path) - .expect("Failed to open config file") - .read_to_string(&mut contents) - .expect("Failed to read config file"); - match toml::from_str::(&contents) { - Ok(result) => result, - Err(e) => panic!("Failed to parse config file: {}", e), - } -} - -lazy_static! { - static ref CONFIG: Table = { - // Load Gecko's binding generator config from the source tree. - let path = mozbuild::TOPSRCDIR.join("layout/style/ServoBindings.toml"); - read_config(&path) - }; - static ref BINDGEN_FLAGS: Vec = { - // Load build-specific config overrides. - let path = mozbuild::TOPOBJDIR.join("layout/style/extra-bindgen-flags"); - println!("cargo:rerun-if-changed={}", path.to_str().unwrap()); - fs::read_to_string(path).expect("Failed to read extra-bindgen-flags file") - .split_whitespace() - .map(std::borrow::ToOwned::to_owned) - .collect() - }; - static ref INCLUDE_RE: Regex = Regex::new(r#"#include\s*"(.+?)""#).unwrap(); - static ref DISTDIR_PATH: PathBuf = mozbuild::TOPOBJDIR.join("dist"); - static ref SEARCH_PATHS: Vec = vec![ - DISTDIR_PATH.join("include"), - DISTDIR_PATH.join("include/nspr"), - ]; - static ref ADDED_PATHS: Mutex> = Mutex::new(HashSet::new()); - static ref LAST_MODIFIED: Mutex = - Mutex::new(get_modified_time(&env::current_exe().unwrap()) - .expect("Failed to get modified time of executable")); -} - -fn get_modified_time(file: &Path) -> Option { - file.metadata().and_then(|m| m.modified()).ok() -} - -fn update_last_modified(file: &Path) { - let modified = get_modified_time(file).expect("Couldn't get file modification time"); - let mut last_modified = LAST_MODIFIED.lock().unwrap(); - *last_modified = cmp::max(modified, *last_modified); -} - -fn search_include(name: &str) -> Option { - for path in SEARCH_PATHS.iter() { - let file = path.join(name); - if file.is_file() { - update_last_modified(&file); - return Some(file); - } - } - None -} - -fn add_headers_recursively(path: PathBuf, added_paths: &mut HashSet) { - if added_paths.contains(&path) { - return; - } - let mut file = File::open(&path).unwrap(); - let mut content = String::new(); - file.read_to_string(&mut content).unwrap(); - added_paths.insert(path); - // Find all includes and add them recursively - for cap in INCLUDE_RE.captures_iter(&content) { - if let Some(path) = search_include(cap.get(1).unwrap().as_str()) { - add_headers_recursively(path, added_paths); - } - } -} - -fn add_include(name: &str) -> String { - let mut added_paths = ADDED_PATHS.lock().unwrap(); - let file = match search_include(name) { - Some(file) => file, - None => panic!("Include not found: {}", name), - }; - let result = String::from(file.to_str().unwrap()); - add_headers_recursively(file, &mut *added_paths); - result -} - -trait BuilderExt { - fn get_initial_builder() -> Builder; - fn include>(self, file: T) -> Builder; -} - -impl BuilderExt for Builder { - fn get_initial_builder() -> Builder { - // Disable rust unions, because we replace some types inside of - // them. - let mut builder = Builder::default() - .size_t_is_usize(true) - .disable_untagged_union(); - - let rustfmt_path = env::var_os("RUSTFMT") - // This can be replaced with - // > .filter(|p| !p.is_empty()).map(PathBuf::from) - // once we can use 1.27+. - .and_then(|p| { - if p.is_empty() { - None - } else { - Some(PathBuf::from(p)) - } - }); - if let Some(path) = rustfmt_path { - builder = builder.with_rustfmt(path); - } - - for dir in SEARCH_PATHS.iter() { - builder = builder.clang_arg("-I").clang_arg(dir.to_str().unwrap()); - } - - builder = builder.include(add_include("mozilla-config.h")); - - if env::var("CARGO_FEATURE_GECKO_DEBUG").is_ok() { - builder = builder.clang_arg("-DDEBUG=1").clang_arg("-DJS_DEBUG=1"); - } - - for item in &*BINDGEN_FLAGS { - builder = builder.clang_arg(item); - } - - builder - } - fn include>(self, file: T) -> Builder { - self.clang_arg("-include").clang_arg(file) - } -} - -struct Fixup { - pat: String, - rep: String, -} - -fn write_binding_file(builder: Builder, file: &str, fixups: &[Fixup]) { - let out_file = OUTDIR_PATH.join(file); - if let Some(modified) = get_modified_time(&out_file) { - // Don't generate the file if nothing it depends on was modified. - let last_modified = LAST_MODIFIED.lock().unwrap(); - if *last_modified <= modified { - return; - } - } - let command_line_opts = builder.command_line_flags(); - let result = builder.generate(); - let mut result = match result { - Ok(bindings) => bindings.to_string(), - Err(_) => { - panic!( - "Failed to generate bindings, flags: {:?}", - command_line_opts - ); - }, - }; - - for fixup in fixups.iter() { - result = Regex::new(&fixup.pat) - .unwrap() - .replace_all(&result, &*fixup.rep) - .into_owned() - .into(); - } - let bytes = result.into_bytes(); - File::create(&out_file) - .unwrap() - .write_all(&bytes) - .expect("Unable to write output"); -} - -struct BuilderWithConfig<'a> { - builder: Builder, - config: &'a Table, - used_keys: HashSet<&'static str>, -} -impl<'a> BuilderWithConfig<'a> { - fn new(builder: Builder, config: &'a Table) -> Self { - BuilderWithConfig { - builder, - config, - used_keys: HashSet::new(), - } - } - - fn handle_list(self, key: &'static str, func: F) -> BuilderWithConfig<'a> - where - F: FnOnce(Builder, slice::Iter<'a, toml::Value>) -> Builder, - { - let mut builder = self.builder; - let config = self.config; - let mut used_keys = self.used_keys; - if let Some(list) = config.get(key) { - used_keys.insert(key); - builder = func(builder, list.as_array().unwrap().as_slice().iter()); - } - BuilderWithConfig { - builder, - config, - used_keys, - } - } - fn handle_items(self, key: &'static str, mut func: F) -> BuilderWithConfig<'a> - where - F: FnMut(Builder, &'a toml::Value) -> Builder, - { - self.handle_list(key, |b, iter| iter.fold(b, |b, item| func(b, item))) - } - fn handle_str_items(self, key: &'static str, mut func: F) -> BuilderWithConfig<'a> - where - F: FnMut(Builder, &'a str) -> Builder, - { - self.handle_items(key, |b, item| func(b, item.as_str().unwrap())) - } - fn handle_table_items(self, key: &'static str, mut func: F) -> BuilderWithConfig<'a> - where - F: FnMut(Builder, &'a Table) -> Builder, - { - self.handle_items(key, |b, item| func(b, item.as_table().unwrap())) - } - fn handle_common(self, fixups: &mut Vec) -> BuilderWithConfig<'a> { - self.handle_str_items("headers", |b, item| b.header(add_include(item))) - .handle_str_items("raw-lines", |b, item| b.raw_line(item)) - .handle_str_items("hide-types", |b, item| b.blocklist_type(item)) - .handle_table_items("fixups", |builder, item| { - fixups.push(Fixup { - pat: item["pat"].as_str().unwrap().into(), - rep: item["rep"].as_str().unwrap().into(), - }); - builder - }) - } - - fn get_builder(self) -> Builder { - for key in self.config.keys() { - if !self.used_keys.contains(key.as_str()) { - panic!("Unknown key: {}", key); - } - } - self.builder - } -} - -fn generate_structs() { - let builder = Builder::get_initial_builder() - .enable_cxx_namespaces() - .with_codegen_config(CodegenConfig::TYPES | CodegenConfig::VARS | CodegenConfig::FUNCTIONS); - 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("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("opaque-types", |b, item| b.opaque_type(item)) - .handle_table_items("cbindgen-types", |b, item| { - let gecko = item["gecko"].as_str().unwrap(); - let servo = item["servo"].as_str().unwrap(); - b.blocklist_type(format!("mozilla::{}", gecko)) - .module_raw_line("root::mozilla", format!("pub use {} as {};", servo, gecko)) - }) - .handle_table_items("mapped-generic-types", |builder, item| { - let generic = item["generic"].as_bool().unwrap(); - let gecko = item["gecko"].as_str().unwrap(); - let servo = item["servo"].as_str().unwrap(); - let gecko_name = gecko.rsplit("::").next().unwrap(); - let gecko = gecko - .split("::") - .map(|s| format!("\\s*{}\\s*", s)) - .collect::>() - .join("::"); - - fixups.push(Fixup { - pat: format!("\\broot\\s*::\\s*{}\\b", gecko), - rep: format!("crate::gecko_bindings::structs::{}", gecko_name), - }); - builder.blocklist_type(gecko).raw_line(format!( - "pub type {0}{2} = {1}{2};", - gecko_name, - servo, - if generic { "" } else { "" } - )) - }) - .get_builder(); - write_binding_file(builder, STRUCTS_FILE, &fixups); -} - -fn setup_logging() -> bool { - struct BuildLogger { - file: Option>, - filter: String, - } - - impl log::Log for BuildLogger { - fn enabled(&self, meta: &log::Metadata) -> bool { - self.file.is_some() && meta.target().contains(&self.filter) - } - - fn log(&self, record: &log::Record) { - if !self.enabled(record.metadata()) { - return; - } - - let mut file = self.file.as_ref().unwrap().lock().unwrap(); - let _ = writeln!( - file, - "{} - {} - {} @ {}:{}", - record.level(), - record.target(), - record.args(), - record.file().unwrap_or(""), - record.line().unwrap_or(0) - ); - } - - fn flush(&self) { - if let Some(ref file) = self.file { - file.lock().unwrap().flush().unwrap(); - } - } - } - - if let Some(path) = env::var_os("STYLO_BUILD_LOG") { - log::set_max_level(log::LevelFilter::Debug); - log::set_boxed_logger(Box::new(BuildLogger { - file: fs::File::create(path).ok().map(Mutex::new), - filter: env::var("STYLO_BUILD_FILTER") - .ok() - .unwrap_or_else(|| "bindgen".to_owned()), - })) - .expect("Failed to set logger."); - - true - } else { - false - } -} - -fn generate_atoms() { - let script = PathBuf::from(env::var_os("CARGO_MANIFEST_DIR").unwrap()) - .join("gecko") - .join("regen_atoms.py"); - println!("cargo:rerun-if-changed={}", script.display()); - let status = Command::new(&*PYTHON) - .arg(&script) - .arg(DISTDIR_PATH.as_os_str()) - .arg(OUTDIR_PATH.as_os_str()) - .status() - .unwrap(); - if !status.success() { - exit(1); - } -} - -pub fn generate() { - println!("cargo:rerun-if-changed=build_gecko.rs"); - fs::create_dir_all(&*OUTDIR_PATH).unwrap(); - setup_logging(); - generate_structs(); - generate_atoms(); - - for path in ADDED_PATHS.lock().unwrap().iter() { - println!("cargo:rerun-if-changed={}", path.to_str().unwrap()); - } -} diff --git a/components/style/color/convert.rs b/components/style/color/convert.rs deleted file mode 100644 index 4fa037f9d6d..00000000000 --- a/components/style/color/convert.rs +++ /dev/null @@ -1,888 +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/. */ - -//! Color conversion algorithms. -//! -//! Algorithms, matrices and constants are from the [color-4] specification, -//! unless otherwise specified: -//! -//! https://drafts.csswg.org/css-color-4/#color-conversion-code -//! -//! NOTE: Matrices has to be transposed from the examples in the spec for use -//! with the `euclid` library. - -use crate::color::ColorComponents; -use std::f32::consts::PI; - -type Transform = euclid::default::Transform3D; -type Vector = euclid::default::Vector3D; - -const RAD_PER_DEG: f32 = PI / 180.0; -const DEG_PER_RAD: f32 = 180.0 / PI; - -/// Normalize hue into [0, 360). -#[inline] -fn normalize_hue(hue: f32) -> f32 { - hue - 360. * (hue / 360.).floor() -} - -/// Calculate the hue from RGB components and return it along with the min and -/// max RGB values. -#[inline] -fn rgb_to_hue_min_max(red: f32, green: f32, blue: f32) -> (f32, f32, f32) { - let max = red.max(green).max(blue); - let min = red.min(green).min(blue); - - let delta = max - min; - - let hue = if delta != 0.0 { - 60.0 * if max == red { - (green - blue) / delta + if green < blue { 6.0 } else { 0.0 } - } else if max == green { - (blue - red) / delta + 2.0 - } else { - (red - green) / delta + 4.0 - } - } else { - f32::NAN - }; - - (hue, min, max) -} - -/// Convert a hue value into red, green, blue components. -#[inline] -fn hue_to_rgb(t1: f32, t2: f32, hue: f32) -> f32 { - let hue = normalize_hue(hue); - - if hue * 6.0 < 360.0 { - t1 + (t2 - t1) * hue / 60.0 - } else if hue * 2.0 < 360.0 { - t2 - } else if hue * 3.0 < 720.0 { - t1 + (t2 - t1) * (240.0 - hue) / 60.0 - } else { - t1 - } -} - -/// Convert from HSL notation to RGB notation. -/// https://drafts.csswg.org/css-color-4/#hsl-to-rgb -#[inline] -pub fn hsl_to_rgb(from: &ColorComponents) -> ColorComponents { - let ColorComponents(hue, saturation, lightness) = *from; - - let t2 = if lightness <= 0.5 { - lightness * (saturation + 1.0) - } else { - lightness + saturation - lightness * saturation - }; - let t1 = lightness * 2.0 - t2; - - ColorComponents( - hue_to_rgb(t1, t2, hue + 120.0), - hue_to_rgb(t1, t2, hue), - hue_to_rgb(t1, t2, hue - 120.0), - ) -} - -/// Convert from RGB notation to HSL notation. -/// https://drafts.csswg.org/css-color-4/#rgb-to-hsl -pub fn rgb_to_hsl(from: &ColorComponents) -> ColorComponents { - let ColorComponents(red, green, blue) = *from; - - let (hue, min, max) = rgb_to_hue_min_max(red, green, blue); - - let lightness = (min + max) / 2.0; - let delta = max - min; - - let saturation = if delta != 0.0 { - if lightness == 0.0 || lightness == 1.0 { - 0.0 - } else { - (max - lightness) / lightness.min(1.0 - lightness) - } - } else { - 0.0 - }; - - ColorComponents(hue, saturation, lightness) -} - -/// Convert from HWB notation to RGB notation. -/// https://drafts.csswg.org/css-color-4/#hwb-to-rgb -#[inline] -pub fn hwb_to_rgb(from: &ColorComponents) -> ColorComponents { - let ColorComponents(hue, whiteness, blackness) = *from; - - if whiteness + blackness > 1.0 { - let gray = whiteness / (whiteness + blackness); - return ColorComponents(gray, gray, gray); - } - - let x = 1.0 - whiteness - blackness; - hsl_to_rgb(&ColorComponents(hue, 1.0, 0.5)).map(|v| v * x + whiteness) -} - -/// Convert from RGB notation to HWB notation. -/// https://drafts.csswg.org/css-color-4/#rgb-to-hwb -#[inline] -pub fn rgb_to_hwb(from: &ColorComponents) -> ColorComponents { - let ColorComponents(red, green, blue) = *from; - - let (hue, min, max) = rgb_to_hue_min_max(red, green, blue); - - let whiteness = min; - let blackness = 1.0 - max; - - ColorComponents(hue, whiteness, blackness) -} - -/// Convert from Lab to Lch. This calculation works for both Lab and Olab. -/// -#[inline] -pub fn lab_to_lch(from: &ColorComponents) -> ColorComponents { - let ColorComponents(lightness, a, b) = *from; - - let hue = normalize_hue(b.atan2(a) * 180.0 / PI); - let chroma = (a.powf(2.0) + b.powf(2.0)).sqrt(); - - ColorComponents(lightness, chroma, hue) -} - -/// Convert from Lch to Lab. This calculation works for both Lch and Oklch. -/// -#[inline] -pub fn lch_to_lab(from: &ColorComponents) -> ColorComponents { - let ColorComponents(lightness, chroma, hue) = *from; - - let a = chroma * (hue * PI / 180.0).cos(); - let b = chroma * (hue * PI / 180.0).sin(); - - ColorComponents(lightness, a, b) -} - -#[inline] -fn transform(from: &ColorComponents, mat: &Transform) -> ColorComponents { - let result = mat.transform_vector3d(Vector::new(from.0, from.1, from.2)); - ColorComponents(result.x, result.y, result.z) -} - -fn xyz_d65_to_xyz_d50(from: &ColorComponents) -> ColorComponents { - #[rustfmt::skip] - const MAT: Transform = Transform::new( - 1.0479298208405488, 0.029627815688159344, -0.009243058152591178, 0.0, - 0.022946793341019088, 0.990434484573249, 0.015055144896577895, 0.0, - -0.05019222954313557, -0.01707382502938514, 0.7518742899580008, 0.0, - 0.0, 0.0, 0.0, 1.0, - ); - - transform(from, &MAT) -} - -fn xyz_d50_to_xyz_d65(from: &ColorComponents) -> ColorComponents { - #[rustfmt::skip] - const MAT: Transform = Transform::new( - 0.9554734527042182, -0.028369706963208136, 0.012314001688319899, 0.0, - -0.023098536874261423, 1.0099954580058226, -0.020507696433477912, 0.0, - 0.0632593086610217, 0.021041398966943008, 1.3303659366080753, 0.0, - 0.0, 0.0, 0.0, 1.0, - ); - - transform(from, &MAT) -} - -/// A reference white that is used during color conversion. -pub enum WhitePoint { - /// D50 white reference. - D50, - /// D65 white reference. - D65, -} - -fn convert_white_point(from: WhitePoint, to: WhitePoint, components: &mut ColorComponents) { - match (from, to) { - (WhitePoint::D50, WhitePoint::D65) => *components = xyz_d50_to_xyz_d65(components), - (WhitePoint::D65, WhitePoint::D50) => *components = xyz_d65_to_xyz_d50(components), - - _ => {}, - } -} - -/// A trait that allows conversion of color spaces to and from XYZ coordinate -/// space with a specified white point. -/// -/// Allows following the specified method of converting between color spaces: -/// - Convert to values to sRGB linear light. -/// - Convert to XYZ coordinate space. -/// - Adjust white point to target white point. -/// - Convert to sRGB linear light in target color space. -/// - Convert to sRGB gamma encoded in target color space. -/// -/// https://drafts.csswg.org/css-color-4/#color-conversion -pub trait ColorSpaceConversion { - /// The white point that the implementer is represented in. - const WHITE_POINT: WhitePoint; - - /// Convert the components from sRGB gamma encoded values to sRGB linear - /// light values. - fn to_linear_light(from: &ColorComponents) -> ColorComponents; - - /// Convert the components from sRGB linear light values to XYZ coordinate - /// space. - fn to_xyz(from: &ColorComponents) -> ColorComponents; - - /// Convert the components from XYZ coordinate space to sRGB linear light - /// values. - fn from_xyz(from: &ColorComponents) -> ColorComponents; - - /// Convert the components from sRGB linear light values to sRGB gamma - /// encoded values. - fn to_gamma_encoded(from: &ColorComponents) -> ColorComponents; -} - -/// Convert the color components from the specified color space to XYZ and -/// return the components and the white point they are in. -pub fn to_xyz(from: &ColorComponents) -> (ColorComponents, WhitePoint) { - // Convert the color components where in-gamut values are in the range - // [0 - 1] to linear light (un-companded) form. - let result = From::to_linear_light(from); - - // Convert the color components from the source color space to XYZ. - (From::to_xyz(&result), From::WHITE_POINT) -} - -/// Convert the color components from XYZ at the given white point to the -/// specified color space. -pub fn from_xyz( - from: &ColorComponents, - white_point: WhitePoint, -) -> ColorComponents { - let mut xyz = from.clone(); - - // Convert the white point if needed. - convert_white_point(white_point, To::WHITE_POINT, &mut xyz); - - // Convert the color from XYZ to the target color space. - let result = To::from_xyz(&xyz); - - // Convert the color components of linear-light values in the range - // [0 - 1] to a gamma corrected form. - To::to_gamma_encoded(&result) -} - -/// The sRGB color space. -/// https://drafts.csswg.org/css-color-4/#predefined-sRGB -pub struct Srgb; - -impl Srgb { - #[rustfmt::skip] - const TO_XYZ: Transform = Transform::new( - 0.4123907992659595, 0.21263900587151036, 0.01933081871559185, 0.0, - 0.35758433938387796, 0.7151686787677559, 0.11919477979462599, 0.0, - 0.1804807884018343, 0.07219231536073371, 0.9505321522496606, 0.0, - 0.0, 0.0, 0.0, 1.0, - ); - - #[rustfmt::skip] - const FROM_XYZ: Transform = Transform::new( - 3.2409699419045213, -0.9692436362808798, 0.05563007969699361, 0.0, - -1.5373831775700935, 1.8759675015077206, -0.20397695888897657, 0.0, - -0.4986107602930033, 0.04155505740717561, 1.0569715142428786, 0.0, - 0.0, 0.0, 0.0, 1.0, - ); -} - -impl ColorSpaceConversion for Srgb { - const WHITE_POINT: WhitePoint = WhitePoint::D65; - - fn to_linear_light(from: &ColorComponents) -> ColorComponents { - from.clone().map(|value| { - let abs = value.abs(); - - if abs < 0.04045 { - value / 12.92 - } else { - value.signum() * ((abs + 0.055) / 1.055).powf(2.4) - } - }) - } - - fn to_xyz(from: &ColorComponents) -> ColorComponents { - transform(from, &Self::TO_XYZ) - } - - fn from_xyz(from: &ColorComponents) -> ColorComponents { - transform(from, &Self::FROM_XYZ) - } - - fn to_gamma_encoded(from: &ColorComponents) -> ColorComponents { - from.clone().map(|value| { - let abs = value.abs(); - - if abs > 0.0031308 { - value.signum() * (1.055 * abs.powf(1.0 / 2.4) - 0.055) - } else { - 12.92 * value - } - }) - } -} - -/// Color specified with hue, saturation and lightness components. -pub struct Hsl; - -impl ColorSpaceConversion for Hsl { - const WHITE_POINT: WhitePoint = Srgb::WHITE_POINT; - - fn to_linear_light(from: &ColorComponents) -> ColorComponents { - Srgb::to_linear_light(&hsl_to_rgb(from)) - } - - #[inline] - fn to_xyz(from: &ColorComponents) -> ColorComponents { - Srgb::to_xyz(from) - } - - #[inline] - fn from_xyz(from: &ColorComponents) -> ColorComponents { - Srgb::from_xyz(from) - } - - fn to_gamma_encoded(from: &ColorComponents) -> ColorComponents { - rgb_to_hsl(&Srgb::to_gamma_encoded(from)) - } -} - -/// Color specified with hue, whiteness and blackness components. -pub struct Hwb; - -impl ColorSpaceConversion for Hwb { - const WHITE_POINT: WhitePoint = Srgb::WHITE_POINT; - - fn to_linear_light(from: &ColorComponents) -> ColorComponents { - Srgb::to_linear_light(&hwb_to_rgb(from)) - } - - #[inline] - fn to_xyz(from: &ColorComponents) -> ColorComponents { - Srgb::to_xyz(from) - } - - #[inline] - fn from_xyz(from: &ColorComponents) -> ColorComponents { - Srgb::from_xyz(from) - } - - fn to_gamma_encoded(from: &ColorComponents) -> ColorComponents { - rgb_to_hwb(&Srgb::to_gamma_encoded(from)) - } -} - -/// The same as sRGB color space, except the transfer function is linear light. -/// https://drafts.csswg.org/css-color-4/#predefined-sRGB-linear -pub struct SrgbLinear; - -impl ColorSpaceConversion for SrgbLinear { - const WHITE_POINT: WhitePoint = Srgb::WHITE_POINT; - - fn to_linear_light(from: &ColorComponents) -> ColorComponents { - // Already in linear light form. - from.clone() - } - - fn to_xyz(from: &ColorComponents) -> ColorComponents { - Srgb::to_xyz(from) - } - - fn from_xyz(from: &ColorComponents) -> ColorComponents { - Srgb::from_xyz(from) - } - - fn to_gamma_encoded(from: &ColorComponents) -> ColorComponents { - // Stay in linear light form. - from.clone() - } -} - -/// The Display-P3 color space. -/// https://drafts.csswg.org/css-color-4/#predefined-display-p3 -pub struct DisplayP3; - -impl DisplayP3 { - #[rustfmt::skip] - const TO_XYZ: Transform = Transform::new( - 0.48657094864821626, 0.22897456406974884, 0.0, 0.0, - 0.26566769316909294, 0.6917385218365062, 0.045113381858902575, 0.0, - 0.1982172852343625, 0.079286914093745, 1.0439443689009757, 0.0, - 0.0, 0.0, 0.0, 1.0, - ); - - #[rustfmt::skip] - const FROM_XYZ: Transform = Transform::new( - 2.4934969119414245, -0.829488969561575, 0.035845830243784335, 0.0, - -0.9313836179191236, 1.7626640603183468, -0.07617238926804171, 0.0, - -0.40271078445071684, 0.02362468584194359, 0.9568845240076873, 0.0, - 0.0, 0.0, 0.0, 1.0, - ); -} - -impl ColorSpaceConversion for DisplayP3 { - const WHITE_POINT: WhitePoint = WhitePoint::D65; - - fn to_linear_light(from: &ColorComponents) -> ColorComponents { - Srgb::to_linear_light(from) - } - - fn to_xyz(from: &ColorComponents) -> ColorComponents { - transform(from, &Self::TO_XYZ) - } - - fn from_xyz(from: &ColorComponents) -> ColorComponents { - transform(from, &Self::FROM_XYZ) - } - - fn to_gamma_encoded(from: &ColorComponents) -> ColorComponents { - Srgb::to_gamma_encoded(from) - } -} - -/// The a98-rgb color space. -/// https://drafts.csswg.org/css-color-4/#predefined-a98-rgb -pub struct A98Rgb; - -impl A98Rgb { - #[rustfmt::skip] - const TO_XYZ: Transform = Transform::new( - 0.5766690429101308, 0.29734497525053616, 0.027031361386412378, 0.0, - 0.18555823790654627, 0.627363566255466, 0.07068885253582714, 0.0, - 0.18822864623499472, 0.07529145849399789, 0.9913375368376389, 0.0, - 0.0, 0.0, 0.0, 1.0, - ); - - #[rustfmt::skip] - const FROM_XYZ: Transform = Transform::new( - 2.041587903810746, -0.9692436362808798, 0.013444280632031024, 0.0, - -0.5650069742788596, 1.8759675015077206, -0.11836239223101824, 0.0, - -0.3447313507783295, 0.04155505740717561, 1.0151749943912054, 0.0, - 0.0, 0.0, 0.0, 1.0, - ); -} - -impl ColorSpaceConversion for A98Rgb { - const WHITE_POINT: WhitePoint = WhitePoint::D65; - - fn to_linear_light(from: &ColorComponents) -> ColorComponents { - from.clone().map(|v| v.signum() * v.abs().powf(2.19921875)) - } - - fn to_xyz(from: &ColorComponents) -> ColorComponents { - transform(from, &Self::TO_XYZ) - } - - fn from_xyz(from: &ColorComponents) -> ColorComponents { - transform(from, &Self::FROM_XYZ) - } - - fn to_gamma_encoded(from: &ColorComponents) -> ColorComponents { - from.clone() - .map(|v| v.signum() * v.abs().powf(0.4547069271758437)) - } -} - -/// The ProPhoto RGB color space. -/// https://drafts.csswg.org/css-color-4/#predefined-prophoto-rgb -pub struct ProphotoRgb; - -impl ProphotoRgb { - #[rustfmt::skip] - const TO_XYZ: Transform = Transform::new( - 0.7977604896723027, 0.2880711282292934, 0.0, 0.0, - 0.13518583717574031, 0.7118432178101014, 0.0, 0.0, - 0.0313493495815248, 0.00008565396060525902, 0.8251046025104601, 0.0, - 0.0, 0.0, 0.0, 1.0, - ); - - #[rustfmt::skip] - const FROM_XYZ: Transform = Transform::new( - 1.3457989731028281, -0.5446224939028347, 0.0, 0.0, - -0.25558010007997534, 1.5082327413132781, 0.0, 0.0, - -0.05110628506753401, 0.02053603239147973, 1.2119675456389454, 0.0, - 0.0, 0.0, 0.0, 1.0, - ); -} - -impl ColorSpaceConversion for ProphotoRgb { - const WHITE_POINT: WhitePoint = WhitePoint::D50; - - fn to_linear_light(from: &ColorComponents) -> ColorComponents { - from.clone().map(|value| { - const ET2: f32 = 16.0 / 512.0; - - let abs = value.abs(); - - if abs <= ET2 { - value / 16.0 - } else { - value.signum() * abs.powf(1.8) - } - }) - } - - fn to_xyz(from: &ColorComponents) -> ColorComponents { - transform(from, &Self::TO_XYZ) - } - - fn from_xyz(from: &ColorComponents) -> ColorComponents { - transform(from, &Self::FROM_XYZ) - } - - fn to_gamma_encoded(from: &ColorComponents) -> ColorComponents { - const ET: f32 = 1.0 / 512.0; - - from.clone().map(|v| { - let abs = v.abs(); - if abs >= ET { - v.signum() * abs.powf(1.0 / 1.8) - } else { - 16.0 * v - } - }) - } -} - -/// The Rec.2020 color space. -/// https://drafts.csswg.org/css-color-4/#predefined-rec2020 -pub struct Rec2020; - -impl Rec2020 { - const ALPHA: f32 = 1.09929682680944; - const BETA: f32 = 0.018053968510807; - - #[rustfmt::skip] - const TO_XYZ: Transform = Transform::new( - 0.6369580483012913, 0.26270021201126703, 0.0, 0.0, - 0.14461690358620838, 0.677998071518871, 0.028072693049087508, 0.0, - 0.16888097516417205, 0.059301716469861945, 1.0609850577107909, 0.0, - 0.0, 0.0, 0.0, 1.0, - ); - - #[rustfmt::skip] - const FROM_XYZ: Transform = Transform::new( - 1.7166511879712676, -0.666684351832489, 0.017639857445310915, 0.0, - -0.3556707837763924, 1.616481236634939, -0.042770613257808655, 0.0, - -0.2533662813736598, 0.01576854581391113, 0.942103121235474, 0.0, - 0.0, 0.0, 0.0, 1.0, - ); -} - -impl ColorSpaceConversion for Rec2020 { - const WHITE_POINT: WhitePoint = WhitePoint::D65; - - fn to_linear_light(from: &ColorComponents) -> ColorComponents { - from.clone().map(|value| { - let abs = value.abs(); - - if abs < Self::BETA * 4.5 { - value / 4.5 - } else { - value.signum() * ((abs + Self::ALPHA - 1.0) / Self::ALPHA).powf(1.0 / 0.45) - } - }) - } - - fn to_xyz(from: &ColorComponents) -> ColorComponents { - transform(from, &Self::TO_XYZ) - } - - fn from_xyz(from: &ColorComponents) -> ColorComponents { - transform(from, &Self::FROM_XYZ) - } - - fn to_gamma_encoded(from: &ColorComponents) -> ColorComponents { - from.clone().map(|v| { - let abs = v.abs(); - - if abs > Self::BETA { - v.signum() * (Self::ALPHA * abs.powf(0.45) - (Self::ALPHA - 1.0)) - } else { - 4.5 * v - } - }) - } -} - -/// A color in the XYZ coordinate space with a D50 white reference. -/// https://drafts.csswg.org/css-color-4/#predefined-xyz -pub struct XyzD50; - -impl ColorSpaceConversion for XyzD50 { - const WHITE_POINT: WhitePoint = WhitePoint::D50; - - fn to_linear_light(from: &ColorComponents) -> ColorComponents { - from.clone() - } - - fn to_xyz(from: &ColorComponents) -> ColorComponents { - from.clone() - } - - fn from_xyz(from: &ColorComponents) -> ColorComponents { - from.clone() - } - - fn to_gamma_encoded(from: &ColorComponents) -> ColorComponents { - from.clone() - } -} - -/// A color in the XYZ coordinate space with a D65 white reference. -/// https://drafts.csswg.org/css-color-4/#predefined-xyz -pub struct XyzD65; - -impl ColorSpaceConversion for XyzD65 { - const WHITE_POINT: WhitePoint = WhitePoint::D65; - - fn to_linear_light(from: &ColorComponents) -> ColorComponents { - from.clone() - } - - fn to_xyz(from: &ColorComponents) -> ColorComponents { - from.clone() - } - - fn from_xyz(from: &ColorComponents) -> ColorComponents { - from.clone() - } - - fn to_gamma_encoded(from: &ColorComponents) -> ColorComponents { - from.clone() - } -} - -/// The Lab color space. -/// https://drafts.csswg.org/css-color-4/#specifying-lab-lch -pub struct Lab; - -impl Lab { - const KAPPA: f32 = 24389.0 / 27.0; - const EPSILON: f32 = 216.0 / 24389.0; - const WHITE: ColorComponents = ColorComponents(0.96422, 1.0, 0.82521); -} - -impl ColorSpaceConversion for Lab { - const WHITE_POINT: WhitePoint = WhitePoint::D50; - - fn to_linear_light(from: &ColorComponents) -> ColorComponents { - // No need for conversion. - from.clone() - } - - /// Convert a CIELAB color to XYZ as specified in [1] and [2]. - /// - /// [1]: https://drafts.csswg.org/css-color/#lab-to-predefined - /// [2]: https://drafts.csswg.org/css-color/#color-conversion-code - fn to_xyz(from: &ColorComponents) -> ColorComponents { - let f1 = (from.0 + 16.0) / 116.0; - let f0 = (from.1 / 500.0) + f1; - let f2 = f1 - from.2 / 200.0; - - let x = if f0.powf(3.0) > Self::EPSILON { - f0.powf(3.) - } else { - (116.0 * f0 - 16.0) / Self::KAPPA - }; - let y = if from.0 > Self::KAPPA * Self::EPSILON { - ((from.0 + 16.0) / 116.0).powf(3.0) - } else { - from.0 / Self::KAPPA - }; - let z = if f2.powf(3.0) > Self::EPSILON { - f2.powf(3.0) - } else { - (116.0 * f2 - 16.0) / Self::KAPPA - }; - - ColorComponents(x * Self::WHITE.0, y * Self::WHITE.1, z * Self::WHITE.2) - } - - /// 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_xyz(from: &ColorComponents) -> ColorComponents { - macro_rules! compute_f { - ($value:expr) => {{ - if $value > Self::EPSILON { - $value.cbrt() - } else { - (Self::KAPPA * $value + 16.0) / 116.0 - } - }}; - } - - // 4. Convert D50-adapted XYZ to Lab. - let f = [ - compute_f!(from.0 / Self::WHITE.0), - compute_f!(from.1 / Self::WHITE.1), - compute_f!(from.2 / Self::WHITE.2), - ]; - - let lightness = 116.0 * f[1] - 16.0; - let a = 500.0 * (f[0] - f[1]); - let b = 200.0 * (f[1] - f[2]); - - ColorComponents(lightness, a, b) - } - - fn to_gamma_encoded(from: &ColorComponents) -> ColorComponents { - // No need for conversion. - from.clone() - } -} - -/// The Lch color space. -/// https://drafts.csswg.org/css-color-4/#specifying-lab-lch -pub struct Lch; - -impl ColorSpaceConversion for Lch { - const WHITE_POINT: WhitePoint = Lab::WHITE_POINT; - - fn to_linear_light(from: &ColorComponents) -> ColorComponents { - // No need for conversion. - from.clone() - } - - fn to_xyz(from: &ColorComponents) -> ColorComponents { - // Convert LCH to Lab first. - let hue = from.2 * RAD_PER_DEG; - let a = from.1 * hue.cos(); - let b = from.1 * hue.sin(); - - let lab = ColorComponents(from.0, a, b); - - // Then convert the Lab to XYZ. - Lab::to_xyz(&lab) - } - - fn from_xyz(from: &ColorComponents) -> ColorComponents { - // First convert the XYZ to LAB. - let ColorComponents(lightness, a, b) = Lab::from_xyz(&from); - - // Then conver the Lab to LCH. - let hue = b.atan2(a) * DEG_PER_RAD; - let chroma = (a * a + b * b).sqrt(); - - ColorComponents(lightness, chroma, hue) - } - - fn to_gamma_encoded(from: &ColorComponents) -> ColorComponents { - // No need for conversion. - from.clone() - } -} - -/// The Oklab color space. -/// https://drafts.csswg.org/css-color-4/#specifying-oklab-oklch -pub struct Oklab; - -impl Oklab { - #[rustfmt::skip] - const XYZ_TO_LMS: Transform = Transform::new( - 0.8190224432164319, 0.0329836671980271, 0.048177199566046255, 0.0, - 0.3619062562801221, 0.9292868468965546, 0.26423952494422764, 0.0, - -0.12887378261216414, 0.03614466816999844, 0.6335478258136937, 0.0, - 0.0, 0.0, 0.0, 1.0, - ); - - #[rustfmt::skip] - const LMS_TO_OKLAB: Transform = Transform::new( - 0.2104542553, 1.9779984951, 0.0259040371, 0.0, - 0.7936177850, -2.4285922050, 0.7827717662, 0.0, - -0.0040720468, 0.4505937099, -0.8086757660, 0.0, - 0.0, 0.0, 0.0, 1.0, - ); - - #[rustfmt::skip] - const LMS_TO_XYZ: Transform = Transform::new( - 1.2268798733741557, -0.04057576262431372, -0.07637294974672142, 0.0, - -0.5578149965554813, 1.1122868293970594, -0.4214933239627914, 0.0, - 0.28139105017721583, -0.07171106666151701, 1.5869240244272418, 0.0, - 0.0, 0.0, 0.0, 1.0, - ); - - #[rustfmt::skip] - const OKLAB_TO_LMS: Transform = Transform::new( - 0.99999999845051981432, 1.0000000088817607767, 1.0000000546724109177, 0.0, - 0.39633779217376785678, -0.1055613423236563494, -0.089484182094965759684, 0.0, - 0.21580375806075880339, -0.063854174771705903402, -1.2914855378640917399, 0.0, - 0.0, 0.0, 0.0, 1.0, - ); -} - -impl ColorSpaceConversion for Oklab { - const WHITE_POINT: WhitePoint = WhitePoint::D65; - - fn to_linear_light(from: &ColorComponents) -> ColorComponents { - // No need for conversion. - from.clone() - } - - fn to_xyz(from: &ColorComponents) -> ColorComponents { - let lms = transform(&from, &Self::OKLAB_TO_LMS); - let lms = lms.map(|v| v.powf(3.0)); - transform(&lms, &Self::LMS_TO_XYZ) - } - - fn from_xyz(from: &ColorComponents) -> ColorComponents { - let lms = transform(&from, &Self::XYZ_TO_LMS); - let lms = lms.map(|v| v.cbrt()); - transform(&lms, &Self::LMS_TO_OKLAB) - } - - fn to_gamma_encoded(from: &ColorComponents) -> ColorComponents { - // No need for conversion. - from.clone() - } -} - -/// The Oklch color space. -/// https://drafts.csswg.org/css-color-4/#specifying-oklab-oklch -pub struct Oklch; - -impl ColorSpaceConversion for Oklch { - const WHITE_POINT: WhitePoint = Oklab::WHITE_POINT; - - fn to_linear_light(from: &ColorComponents) -> ColorComponents { - // No need for conversion. - from.clone() - } - - fn to_xyz(from: &ColorComponents) -> ColorComponents { - // First convert OkLCH to Oklab. - let hue = from.2 * RAD_PER_DEG; - let a = from.1 * hue.cos(); - let b = from.1 * hue.sin(); - let oklab = ColorComponents(from.0, a, b); - - // Then convert Oklab to XYZ. - Oklab::to_xyz(&oklab) - } - - fn from_xyz(from: &ColorComponents) -> ColorComponents { - // First convert XYZ to Oklab. - let ColorComponents(lightness, a, b) = Oklab::from_xyz(&from); - - // Then convert Oklab to OkLCH. - let hue = b.atan2(a) * DEG_PER_RAD; - let chroma = (a * a + b * b).sqrt(); - - ColorComponents(lightness, chroma, hue) - } - - fn to_gamma_encoded(from: &ColorComponents) -> ColorComponents { - // No need for conversion. - from.clone() - } -} diff --git a/components/style/color/mix.rs b/components/style/color/mix.rs deleted file mode 100644 index 455d0252659..00000000000 --- a/components/style/color/mix.rs +++ /dev/null @@ -1,475 +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/. */ - -//! Color mixing/interpolation. - -use super::{AbsoluteColor, ColorComponents, ColorFlags, ColorSpace}; -use crate::parser::{Parse, ParserContext}; -use cssparser::Parser; -use std::fmt::{self, Write}; -use style_traits::{CssWriter, ParseError, ToCss}; - -/// 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 const fn srgb() -> Self { - Self { - space: ColorSpace::Srgb, - hue: HueInterpolationMethod::Shorter, - } - } - - /// Return the oklab interpolation method used for default color - /// interpolcation. - pub const fn oklab() -> Self { - Self { - space: ColorSpace::Oklab, - hue: HueInterpolationMethod::Shorter, - } - } - - /// Decides the best method for interpolating between the given colors. - /// https://drafts.csswg.org/css-color-4/#interpolation-space - pub fn best_interpolation_between(left: &AbsoluteColor, right: &AbsoluteColor) -> Self { - // The preferred color space to use for interpolating colors is Oklab. - // However, if either of the colors are in legacy rgb(), hsl() or hwb(), - // then interpolation is done in sRGB. - if !left.is_legacy_color() || !right.is_legacy_color() { - Self::oklab() - } else { - Self::srgb() - } - } -} - -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(()) - } -} - -/// Mix two colors into one. -pub fn mix( - interpolation: ColorInterpolationMethod, - left_color: &AbsoluteColor, - mut left_weight: f32, - right_color: &AbsoluteColor, - mut right_weight: f32, - normalize_weights: bool, -) -> AbsoluteColor { - // 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; - } - } - } - - mix_in( - interpolation.space, - left_color, - left_weight, - right_color, - right_weight, - interpolation.hue, - alpha_multiplier, - ) -} - -/// What the outcome of each component should be in a mix result. -#[derive(Clone, Copy)] -#[repr(u8)] -enum ComponentMixOutcome { - /// Mix the left and right sides to give the result. - Mix, - /// Carry the left side forward to the result. - UseLeft, - /// Carry the right side forward to the result. - UseRight, - /// The resulting component should also be none. - None, -} - -impl ComponentMixOutcome { - fn from_colors( - left: &AbsoluteColor, - right: &AbsoluteColor, - flags_to_check: ColorFlags, - ) -> Self { - match ( - left.flags.contains(flags_to_check), - right.flags.contains(flags_to_check), - ) { - (true, true) => Self::None, - (true, false) => Self::UseRight, - (false, true) => Self::UseLeft, - (false, false) => Self::Mix, - } - } -} - -fn mix_in( - color_space: ColorSpace, - left_color: &AbsoluteColor, - left_weight: f32, - right_color: &AbsoluteColor, - right_weight: f32, - hue_interpolation: HueInterpolationMethod, - alpha_multiplier: f32, -) -> AbsoluteColor { - let outcomes = [ - ComponentMixOutcome::from_colors(left_color, right_color, ColorFlags::C1_IS_NONE), - ComponentMixOutcome::from_colors(left_color, right_color, ColorFlags::C2_IS_NONE), - ComponentMixOutcome::from_colors(left_color, right_color, ColorFlags::C3_IS_NONE), - ComponentMixOutcome::from_colors(left_color, right_color, ColorFlags::ALPHA_IS_NONE), - ]; - - // Convert both colors into the interpolation color space. - let left = left_color.to_color_space(color_space); - let left = left.raw_components(); - - let right = right_color.to_color_space(color_space); - let right = right.raw_components(); - - let (result, result_flags) = interpolate_premultiplied( - &left, - left_weight, - &right, - right_weight, - color_space.hue_index(), - hue_interpolation, - &outcomes, - ); - - let alpha = if alpha_multiplier != 1.0 { - result[3] * alpha_multiplier - } else { - result[3] - }; - - // FIXME: In rare cases we end up with 0.999995 in the alpha channel, - // so we reduce the precision to avoid serializing to - // rgba(?, ?, ?, 1). This is not ideal, so we should look into - // ways to avoid it. Maybe pre-multiply all color components and - // then divide after calculations? - let alpha = (alpha * 1000.0).round() / 1000.0; - - let mut result = AbsoluteColor::new( - color_space, - ColorComponents(result[0], result[1], result[2]), - alpha, - ); - - result.flags = result_flags; - // If both sides are legacy RGB, then the result stays in legacy RGB. - if !left_color.is_legacy_color() || !right_color.is_legacy_color() { - result.flags.insert(ColorFlags::AS_COLOR_FUNCTION); - } - - result -} - -fn interpolate_premultiplied_component( - left: f32, - left_weight: f32, - left_alpha: f32, - right: f32, - right_weight: f32, - right_alpha: f32, -) -> f32 { - left * left_weight * left_alpha + right * right_weight * right_alpha -} - -// Normalize hue into [0, 360) -#[inline] -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; - } - } 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 -} - -struct InterpolatedAlpha { - /// The adjusted left alpha value. - left: f32, - /// The adjusted right alpha value. - right: f32, - /// The interpolated alpha value. - interpolated: f32, - /// Whether the alpha component should be `none`. - is_none: bool, -} - -fn interpolate_alpha( - left: f32, - left_weight: f32, - right: f32, - right_weight: f32, - outcome: ComponentMixOutcome, -) -> InterpolatedAlpha { - // - let mut result = match outcome { - ComponentMixOutcome::Mix => { - let interpolated = left * left_weight + right * right_weight; - InterpolatedAlpha { - left, - right, - interpolated, - is_none: false, - } - }, - ComponentMixOutcome::UseLeft => InterpolatedAlpha { - left, - right: left, - interpolated: left, - is_none: false, - }, - ComponentMixOutcome::UseRight => InterpolatedAlpha { - left: right, - right, - interpolated: right, - is_none: false, - }, - ComponentMixOutcome::None => InterpolatedAlpha { - left: 1.0, - right: 1.0, - interpolated: 0.0, - is_none: true, - }, - }; - - // Clip all alpha values to [0.0..1.0]. - result.left = result.left.clamp(0.0, 1.0); - result.right = result.right.clamp(0.0, 1.0); - result.interpolated = result.interpolated.clamp(0.0, 1.0); - - result -} - -fn interpolate_premultiplied( - left: &[f32; 4], - left_weight: f32, - right: &[f32; 4], - right_weight: f32, - hue_index: Option, - hue_interpolation: HueInterpolationMethod, - outcomes: &[ComponentMixOutcome; 4], -) -> ([f32; 4], ColorFlags) { - let alpha = interpolate_alpha(left[3], left_weight, right[3], right_weight, outcomes[3]); - let mut flags = if alpha.is_none { - ColorFlags::ALPHA_IS_NONE - } else { - ColorFlags::empty() - }; - - let mut result = [0.; 4]; - - for i in 0..3 { - match outcomes[i] { - ComponentMixOutcome::Mix => { - let is_hue = hue_index == Some(i); - result[i] = if is_hue { - normalize_hue(interpolate_hue( - left[i], - left_weight, - right[i], - right_weight, - hue_interpolation, - )) - } else { - let interpolated = interpolate_premultiplied_component( - left[i], - left_weight, - alpha.left, - right[i], - right_weight, - alpha.right, - ); - - if alpha.interpolated == 0.0 { - interpolated - } else { - interpolated / alpha.interpolated - } - }; - }, - ComponentMixOutcome::UseLeft => result[i] = left[i], - ComponentMixOutcome::UseRight => result[i] = right[i], - ComponentMixOutcome::None => { - result[i] = 0.0; - match i { - 0 => flags.insert(ColorFlags::C1_IS_NONE), - 1 => flags.insert(ColorFlags::C2_IS_NONE), - 2 => flags.insert(ColorFlags::C3_IS_NONE), - _ => unreachable!(), - } - }, - } - } - result[3] = alpha.interpolated; - - (result, flags) -} diff --git a/components/style/color/mod.rs b/components/style/color/mod.rs deleted file mode 100644 index f5a36aba2e8..00000000000 --- a/components/style/color/mod.rs +++ /dev/null @@ -1,465 +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/. */ - -//! Color support functions. - -/// cbindgen:ignore -pub mod convert; -pub mod mix; - -use std::fmt::{self, Write}; -use style_traits::{CssWriter, ToCss}; - -/// The 3 components that make up a color. (Does not include the alpha component) -#[derive(Copy, Clone, Debug, MallocSizeOf, PartialEq, ToShmem)] -#[repr(C)] -pub struct ColorComponents(pub f32, pub f32, pub f32); - -impl ColorComponents { - /// Apply a function to each of the 3 components of the color. - pub fn map(self, f: impl Fn(f32) -> f32) -> Self { - Self(f(self.0), f(self.1), f(self.2)) - } -} - -/// A color space representation in the CSS specification. -/// -/// 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 { - /// A color specified in the sRGB color space with either the rgb/rgba(..) - /// functions or the newer color(srgb ..) function. If the color(..) - /// function is used, the AS_COLOR_FUNCTION flag will be set. Examples: - /// "color(srgb 0.691 0.139 0.259)", "rgb(176, 35, 66)" - Srgb = 0, - /// A color specified in the Hsl notation in the sRGB color space, e.g. - /// "hsl(289.18 93.136% 65.531%)" - /// https://drafts.csswg.org/css-color-4/#the-hsl-notation - Hsl, - /// A color specified in the Hwb notation in the sRGB color space, e.g. - /// "hwb(740deg 20% 30%)" - /// https://drafts.csswg.org/css-color-4/#the-hwb-notation - Hwb, - /// A color specified in the Lab color format, e.g. - /// "lab(29.2345% 39.3825 20.0664)". - /// https://w3c.github.io/csswg-drafts/css-color-4/#lab-colors - Lab, - /// A color specified in the Lch color format, e.g. - /// "lch(29.2345% 44.2 27)". - /// https://w3c.github.io/csswg-drafts/css-color-4/#lch-colors - Lch, - /// A color specified in the Oklab color format, e.g. - /// "oklab(40.101% 0.1147 0.0453)". - /// https://w3c.github.io/csswg-drafts/css-color-4/#lab-colors - Oklab, - /// A color specified in the Oklch color format, e.g. - /// "oklch(40.101% 0.12332 21.555)". - /// https://w3c.github.io/csswg-drafts/css-color-4/#lch-colors - Oklch, - /// A color specified with the color(..) function and the "srgb-linear" - /// color space, e.g. "color(srgb-linear 0.435 0.017 0.055)". - SrgbLinear, - /// A color specified with the color(..) function and the "display-p3" - /// color space, e.g. "color(display-p3 0.84 0.19 0.72)". - DisplayP3, - /// A color specified with the color(..) function and the "a98-rgb" color - /// space, e.g. "color(a98-rgb 0.44091 0.49971 0.37408)". - A98Rgb, - /// A color specified with the color(..) function and the "prophoto-rgb" - /// color space, e.g. "color(prophoto-rgb 0.36589 0.41717 0.31333)". - ProphotoRgb, - /// A color specified with the color(..) function and the "rec2020" color - /// space, e.g. "color(rec2020 0.42210 0.47580 0.35605)". - Rec2020, - /// A color specified with the color(..) function and the "xyz-d50" color - /// space, e.g. "color(xyz-d50 0.2005 0.14089 0.4472)". - XyzD50, - /// A color specified with the color(..) function and the "xyz-d65" or "xyz" - /// color space, e.g. "color(xyz-d65 0.21661 0.14602 0.59452)". - /// NOTE: https://drafts.csswg.org/css-color-4/#resolving-color-function-values - /// specifies that `xyz` is an alias for the `xyz-d65` color space. - #[parse(aliases = "xyz")] - XyzD65, -} - -impl ColorSpace { - /// Returns whether this is a ``. - #[inline] - pub fn is_rectangular(&self) -> bool { - !self.is_polar() - } - - /// Returns whether this is a ``. - #[inline] - pub fn is_polar(&self) -> bool { - matches!(self, Self::Hsl | Self::Hwb | Self::Lch | Self::Oklch) - } - - /// Returns an index of the hue component in the color space, otherwise - /// `None`. - #[inline] - pub fn hue_index(&self) -> Option { - match self { - Self::Hsl | Self::Hwb => Some(0), - Self::Lch | Self::Oklch => Some(2), - - _ => { - debug_assert!(!self.is_polar()); - None - }, - } - } -} - -bitflags! { - /// Flags used when serializing colors. - #[derive(Default, MallocSizeOf, ToShmem)] - #[repr(C)] - pub struct ColorFlags : u8 { - /// If set, serializes sRGB colors into `color(srgb ...)` instead of - /// `rgba(...)`. - const AS_COLOR_FUNCTION = 1 << 0; - /// Whether the 1st color component is `none`. - const C1_IS_NONE = 1 << 1; - /// Whether the 2nd color component is `none`. - const C2_IS_NONE = 1 << 2; - /// Whether the 3rd color component is `none`. - const C3_IS_NONE = 1 << 3; - /// Whether the alpha component is `none`. - const ALPHA_IS_NONE = 1 << 4; - } -} - -/// An absolutely specified color, using either rgb(), rgba(), lab(), lch(), -/// oklab(), oklch() or color(). -#[derive(Copy, Clone, Debug, MallocSizeOf, PartialEq, ToShmem)] -#[repr(C)] -pub struct AbsoluteColor { - /// The 3 components that make up colors in any color space. - pub components: ColorComponents, - /// The alpha component of the color. - pub alpha: f32, - /// The current color space that the components represent. - pub color_space: ColorSpace, - /// Extra flags used durring serialization of this color. - pub flags: ColorFlags, -} - -/// Given an [`AbsoluteColor`], return the 4 float components as the type given, -/// e.g.: -/// -/// ```rust -/// let srgb = AbsoluteColor::new(ColorSpace::Srgb, 1.0, 0.0, 0.0, 0.0); -/// let floats = color_components_as!(&srgb, [f32; 4]); // [1.0, 0.0, 0.0, 0.0] -/// ``` -macro_rules! color_components_as { - ($c:expr, $t:ty) => {{ - // This macro is not an inline function, because we can't use the - // generic type ($t) in a constant expression as per: - // https://github.com/rust-lang/rust/issues/76560 - const_assert_eq!(std::mem::size_of::<$t>(), std::mem::size_of::<[f32; 4]>()); - const_assert_eq!(std::mem::align_of::<$t>(), std::mem::align_of::<[f32; 4]>()); - const_assert!(std::mem::size_of::() >= std::mem::size_of::<$t>()); - const_assert_eq!( - std::mem::align_of::(), - std::mem::align_of::<$t>() - ); - - std::mem::transmute::<&ColorComponents, &$t>(&$c.components) - }}; -} - -impl AbsoluteColor { - /// Create a new [`AbsoluteColor`] with the given [`ColorSpace`] and - /// components. - pub fn new(color_space: ColorSpace, components: ColorComponents, alpha: f32) -> Self { - let mut components = components; - - // Lightness must not be less than 0. - if matches!( - color_space, - ColorSpace::Lab | ColorSpace::Lch | ColorSpace::Oklab | ColorSpace::Oklch - ) { - components.0 = components.0.max(0.0); - } - - // Chroma must not be less than 0. - if matches!(color_space, ColorSpace::Lch | ColorSpace::Oklch) { - components.1 = components.1.max(0.0); - } - - Self { - components, - alpha: alpha.clamp(0.0, 1.0), - color_space, - flags: ColorFlags::empty(), - } - } - - /// Create a new [`AbsoluteColor`] from rgba values in the sRGB color space. - pub fn srgb(red: f32, green: f32, blue: f32, alpha: f32) -> Self { - Self::new(ColorSpace::Srgb, ColorComponents(red, green, blue), alpha) - } - - /// Create a new transparent color. - pub fn transparent() -> Self { - Self::srgb(0.0, 0.0, 0.0, 0.0) - } - - /// Create a new opaque black color. - pub fn black() -> Self { - Self::srgb(0.0, 0.0, 0.0, 1.0) - } - - /// Create a new opaque white color. - pub fn white() -> Self { - Self::srgb(1.0, 1.0, 1.0, 1.0) - } - - /// Return all the components of the color in an array. (Includes alpha) - #[inline] - pub fn raw_components(&self) -> &[f32; 4] { - unsafe { color_components_as!(self, [f32; 4]) } - } - - /// Returns true if this color is in one of the legacy color formats. - #[inline] - pub fn is_legacy_color(&self) -> bool { - // rgb(), rgba(), hsl(), hsla(), hwb(), hwba() - match self.color_space { - ColorSpace::Srgb => !self.flags.contains(ColorFlags::AS_COLOR_FUNCTION), - ColorSpace::Hsl | ColorSpace::Hwb => true, - _ => false, - } - } - - /// Return the alpha component. - #[inline] - pub fn alpha(&self) -> f32 { - self.alpha - } - - /// Convert this color to the specified color space. - pub fn to_color_space(&self, color_space: ColorSpace) -> Self { - use ColorSpace::*; - - if self.color_space == color_space { - return self.clone(); - } - - // We have simplified conversions that do not need to convert to XYZ - // first. This improves performance, because it skips 2 matrix - // multiplications and reduces float rounding errors. - match (self.color_space, color_space) { - (Srgb, Hsl) => { - return Self::new( - color_space, - convert::rgb_to_hsl(&self.components), - self.alpha, - ); - }, - - (Srgb, Hwb) => { - return Self::new( - color_space, - convert::rgb_to_hwb(&self.components), - self.alpha, - ); - }, - - (Hsl, Srgb) => { - return Self::new( - color_space, - convert::hsl_to_rgb(&self.components), - self.alpha, - ); - }, - - (Hwb, Srgb) => { - return Self::new( - color_space, - convert::hwb_to_rgb(&self.components), - self.alpha, - ); - }, - - (Lab, Lch) | (Oklab, Oklch) => { - return Self::new( - color_space, - convert::lab_to_lch(&self.components), - self.alpha, - ); - }, - - (Lch, Lab) | (Oklch, Oklab) => { - return Self::new( - color_space, - convert::lch_to_lab(&self.components), - self.alpha, - ); - }, - - _ => {}, - } - - let (xyz, white_point) = match self.color_space { - Lab => convert::to_xyz::(&self.components), - Lch => convert::to_xyz::(&self.components), - Oklab => convert::to_xyz::(&self.components), - Oklch => convert::to_xyz::(&self.components), - Srgb => convert::to_xyz::(&self.components), - Hsl => convert::to_xyz::(&self.components), - Hwb => convert::to_xyz::(&self.components), - SrgbLinear => convert::to_xyz::(&self.components), - DisplayP3 => convert::to_xyz::(&self.components), - A98Rgb => convert::to_xyz::(&self.components), - ProphotoRgb => convert::to_xyz::(&self.components), - Rec2020 => convert::to_xyz::(&self.components), - XyzD50 => convert::to_xyz::(&self.components), - XyzD65 => convert::to_xyz::(&self.components), - }; - - let result = match color_space { - Lab => convert::from_xyz::(&xyz, white_point), - Lch => convert::from_xyz::(&xyz, white_point), - Oklab => convert::from_xyz::(&xyz, white_point), - Oklch => convert::from_xyz::(&xyz, white_point), - Srgb => convert::from_xyz::(&xyz, white_point), - Hsl => convert::from_xyz::(&xyz, white_point), - Hwb => convert::from_xyz::(&xyz, white_point), - SrgbLinear => convert::from_xyz::(&xyz, white_point), - DisplayP3 => convert::from_xyz::(&xyz, white_point), - A98Rgb => convert::from_xyz::(&xyz, white_point), - ProphotoRgb => convert::from_xyz::(&xyz, white_point), - Rec2020 => convert::from_xyz::(&xyz, white_point), - XyzD50 => convert::from_xyz::(&xyz, white_point), - XyzD65 => convert::from_xyz::(&xyz, white_point), - }; - - Self::new(color_space, result, self.alpha) - } -} - -impl From for ColorSpace { - fn from(value: cssparser::PredefinedColorSpace) -> Self { - match value { - cssparser::PredefinedColorSpace::Srgb => ColorSpace::Srgb, - cssparser::PredefinedColorSpace::SrgbLinear => ColorSpace::SrgbLinear, - cssparser::PredefinedColorSpace::DisplayP3 => ColorSpace::DisplayP3, - cssparser::PredefinedColorSpace::A98Rgb => ColorSpace::A98Rgb, - cssparser::PredefinedColorSpace::ProphotoRgb => ColorSpace::ProphotoRgb, - cssparser::PredefinedColorSpace::Rec2020 => ColorSpace::Rec2020, - cssparser::PredefinedColorSpace::XyzD50 => ColorSpace::XyzD50, - cssparser::PredefinedColorSpace::XyzD65 => ColorSpace::XyzD65, - } - } -} - -impl ToCss for AbsoluteColor { - fn to_css(&self, dest: &mut CssWriter) -> fmt::Result - where - W: Write, - { - macro_rules! value_or_none { - ($v:expr,$flag:tt) => {{ - if self.flags.contains(ColorFlags::$flag) { - None - } else { - Some($v) - } - }}; - } - - let maybe_c1 = value_or_none!(self.components.0, C1_IS_NONE); - let maybe_c2 = value_or_none!(self.components.1, C2_IS_NONE); - let maybe_c3 = value_or_none!(self.components.2, C3_IS_NONE); - let maybe_alpha = value_or_none!(self.alpha, ALPHA_IS_NONE); - - match self.color_space { - ColorSpace::Hsl => { - let rgb = convert::hsl_to_rgb(&self.components); - Self::new(ColorSpace::Srgb, rgb, self.alpha).to_css(dest) - }, - - ColorSpace::Hwb => { - let rgb = convert::hwb_to_rgb(&self.components); - - Self::new(ColorSpace::Srgb, rgb, self.alpha).to_css(dest) - }, - - ColorSpace::Srgb if !self.flags.contains(ColorFlags::AS_COLOR_FUNCTION) => { - // Althought we are passing Option<_> in here, the to_css fn - // knows that the "none" keyword is not supported in the - // rgb/rgba legacy syntax. - cssparser::ToCss::to_css( - &cssparser::RGBA::from_floats(maybe_c1, maybe_c2, maybe_c3, maybe_alpha), - dest, - ) - }, - ColorSpace::Lab => cssparser::ToCss::to_css( - &cssparser::Lab::new(maybe_c1, maybe_c2, maybe_c3, maybe_alpha), - dest, - ), - ColorSpace::Lch => cssparser::ToCss::to_css( - &cssparser::Lch::new(maybe_c1, maybe_c2, maybe_c3, maybe_alpha), - dest, - ), - ColorSpace::Oklab => cssparser::ToCss::to_css( - &cssparser::Oklab::new(maybe_c1, maybe_c2, maybe_c3, maybe_alpha), - dest, - ), - ColorSpace::Oklch => cssparser::ToCss::to_css( - &cssparser::Oklch::new(maybe_c1, maybe_c2, maybe_c3, maybe_alpha), - dest, - ), - _ => { - let color_space = match self.color_space { - ColorSpace::Srgb => { - debug_assert!( - self.flags.contains(ColorFlags::AS_COLOR_FUNCTION), - "The case without this flag should be handled in the wrapping match case!!" - ); - - cssparser::PredefinedColorSpace::Srgb - }, - ColorSpace::SrgbLinear => cssparser::PredefinedColorSpace::SrgbLinear, - ColorSpace::DisplayP3 => cssparser::PredefinedColorSpace::DisplayP3, - ColorSpace::A98Rgb => cssparser::PredefinedColorSpace::A98Rgb, - ColorSpace::ProphotoRgb => cssparser::PredefinedColorSpace::ProphotoRgb, - ColorSpace::Rec2020 => cssparser::PredefinedColorSpace::Rec2020, - ColorSpace::XyzD50 => cssparser::PredefinedColorSpace::XyzD50, - ColorSpace::XyzD65 => cssparser::PredefinedColorSpace::XyzD65, - - _ => { - unreachable!("other color spaces do not support color() syntax") - }, - }; - - let color_function = cssparser::ColorFunction { - color_space, - c1: maybe_c1, - c2: maybe_c2, - c3: maybe_c3, - alpha: maybe_alpha, - }; - let color = cssparser::Color::ColorFunction(color_function); - cssparser::ToCss::to_css(&color, dest) - }, - } - } -} diff --git a/components/style/context.rs b/components/style/context.rs deleted file mode 100644 index 8f717eca61f..00000000000 --- a/components/style/context.rs +++ /dev/null @@ -1,698 +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/. */ - -//! The context within which style is calculated. - -#[cfg(feature = "servo")] -use crate::animation::DocumentAnimationSet; -use crate::bloom::StyleBloom; -use crate::computed_value_flags::ComputedValueFlags; -use crate::data::{EagerPseudoStyles, ElementData}; -use crate::dom::{SendElement, TElement}; -#[cfg(feature = "gecko")] -use crate::gecko_bindings::structs; -use crate::parallel::{STACK_SAFETY_MARGIN_KB, STYLE_THREAD_STACK_SIZE_KB}; -use crate::properties::ComputedValues; -#[cfg(feature = "servo")] -use crate::properties::PropertyId; -use crate::rule_cache::RuleCache; -use crate::rule_tree::StrongRuleNode; -use crate::selector_parser::{SnapshotMap, EAGER_PSEUDO_COUNT}; -use crate::shared_lock::StylesheetGuards; -use crate::sharing::StyleSharingCache; -use crate::stylist::Stylist; -use crate::thread_state::{self, ThreadState}; -use crate::traversal::DomTraversal; -use crate::traversal_flags::TraversalFlags; -use app_units::Au; -use euclid::default::Size2D; -use euclid::Scale; -#[cfg(feature = "servo")] -use fxhash::FxHashMap; -use selectors::NthIndexCache; -#[cfg(feature = "gecko")] -use servo_arc::Arc; -#[cfg(feature = "servo")] -use servo_atoms::Atom; -use std::fmt; -use std::ops; -use style_traits::CSSPixel; -use style_traits::DevicePixel; -#[cfg(feature = "servo")] -use style_traits::SpeculativePainter; -use time; - -pub use selectors::matching::QuirksMode; - -/// A global options structure for the style system. We use this instead of -/// opts to abstract across Gecko and Servo. -#[derive(Clone)] -pub struct StyleSystemOptions { - /// Whether the style sharing cache is disabled. - pub disable_style_sharing_cache: bool, - /// Whether we should dump statistics about the style system. - pub dump_style_statistics: bool, - /// The minimum number of elements that must be traversed to trigger a dump - /// of style statistics. - pub style_statistics_threshold: usize, -} - -#[cfg(feature = "gecko")] -fn get_env_bool(name: &str) -> bool { - use std::env; - match env::var(name) { - Ok(s) => !s.is_empty(), - Err(_) => false, - } -} - -const DEFAULT_STATISTICS_THRESHOLD: usize = 50; - -#[cfg(feature = "gecko")] -fn get_env_usize(name: &str) -> Option { - use std::env; - env::var(name).ok().map(|s| { - s.parse::() - .expect("Couldn't parse environmental variable as usize") - }) -} - -/// A global variable holding the state of -/// `StyleSystemOptions::default().disable_style_sharing_cache`. -/// See [#22854](https://github.com/servo/servo/issues/22854). -#[cfg(feature = "servo")] -pub static DEFAULT_DISABLE_STYLE_SHARING_CACHE: std::sync::atomic::AtomicBool = - std::sync::atomic::AtomicBool::new(false); - -/// A global variable holding the state of -/// `StyleSystemOptions::default().dump_style_statistics`. -/// See [#22854](https://github.com/servo/servo/issues/22854). -#[cfg(feature = "servo")] -pub static DEFAULT_DUMP_STYLE_STATISTICS: std::sync::atomic::AtomicBool = - std::sync::atomic::AtomicBool::new(false); - -impl Default for StyleSystemOptions { - #[cfg(feature = "servo")] - fn default() -> Self { - use std::sync::atomic::Ordering; - - StyleSystemOptions { - disable_style_sharing_cache: DEFAULT_DISABLE_STYLE_SHARING_CACHE - .load(Ordering::Relaxed), - dump_style_statistics: DEFAULT_DUMP_STYLE_STATISTICS.load(Ordering::Relaxed), - style_statistics_threshold: DEFAULT_STATISTICS_THRESHOLD, - } - } - - #[cfg(feature = "gecko")] - fn default() -> Self { - StyleSystemOptions { - disable_style_sharing_cache: get_env_bool("DISABLE_STYLE_SHARING_CACHE"), - dump_style_statistics: get_env_bool("DUMP_STYLE_STATISTICS"), - style_statistics_threshold: get_env_usize("STYLE_STATISTICS_THRESHOLD") - .unwrap_or(DEFAULT_STATISTICS_THRESHOLD), - } - } -} - -/// A shared style context. -/// -/// There's exactly one of these during a given restyle traversal, and it's -/// shared among the worker threads. -pub struct SharedStyleContext<'a> { - /// The CSS selector stylist. - pub stylist: &'a Stylist, - - /// Whether visited styles are enabled. - /// - /// They may be disabled when Gecko's pref layout.css.visited_links_enabled - /// is false, or when in private browsing mode. - pub visited_styles_enabled: bool, - - /// Configuration options. - pub options: StyleSystemOptions, - - /// Guards for pre-acquired locks - pub guards: StylesheetGuards<'a>, - - /// The current time for transitions and animations. This is needed to ensure - /// a consistent sampling time and also to adjust the time for testing. - pub current_time_for_animations: f64, - - /// Flags controlling how we traverse the tree. - pub traversal_flags: TraversalFlags, - - /// A map with our snapshots in order to handle restyle hints. - pub snapshot_map: &'a SnapshotMap, - - /// The state of all animations for our styled elements. - #[cfg(feature = "servo")] - pub animations: DocumentAnimationSet, - - /// Paint worklets - #[cfg(feature = "servo")] - pub registered_speculative_painters: &'a dyn RegisteredSpeculativePainters, -} - -impl<'a> SharedStyleContext<'a> { - /// Return a suitable viewport size in order to be used for viewport units. - pub fn viewport_size(&self) -> Size2D { - self.stylist.device().au_viewport_size() - } - - /// The device pixel ratio - pub fn device_pixel_ratio(&self) -> Scale { - self.stylist.device().device_pixel_ratio() - } - - /// The quirks mode of the document. - pub fn quirks_mode(&self) -> QuirksMode { - self.stylist.quirks_mode() - } -} - -/// The structure holds various intermediate inputs that are eventually used by -/// by the cascade. -/// -/// The matching and cascading process stores them in this format temporarily -/// within the `CurrentElementInfo`. At the end of the cascade, they are folded -/// down into the main `ComputedValues` to reduce memory usage per element while -/// still remaining accessible. -#[derive(Clone, Debug, Default)] -pub struct CascadeInputs { - /// The rule node representing the ordered list of rules matched for this - /// node. - pub rules: Option, - - /// The rule node representing the ordered list of rules matched for this - /// node if visited, only computed if there's a relevant link for this - /// element. A element's "relevant link" is the element being matched if it - /// is a link or the nearest ancestor link. - pub visited_rules: Option, - - /// The set of flags from container queries that we need for invalidation. - pub flags: ComputedValueFlags, -} - -impl CascadeInputs { - /// Construct inputs from previous cascade results, if any. - pub fn new_from_style(style: &ComputedValues) -> Self { - Self { - rules: style.rules.clone(), - visited_rules: style.visited_style().and_then(|v| v.rules.clone()), - flags: style.flags.for_cascade_inputs(), - } - } -} - -/// A list of cascade inputs for eagerly-cascaded pseudo-elements. -/// The list is stored inline. -#[derive(Debug)] -pub struct EagerPseudoCascadeInputs(Option<[Option; EAGER_PSEUDO_COUNT]>); - -// Manually implement `Clone` here because the derived impl of `Clone` for -// array types assumes the value inside is `Copy`. -impl Clone for EagerPseudoCascadeInputs { - fn clone(&self) -> Self { - if self.0.is_none() { - return EagerPseudoCascadeInputs(None); - } - let self_inputs = self.0.as_ref().unwrap(); - let mut inputs: [Option; EAGER_PSEUDO_COUNT] = Default::default(); - for i in 0..EAGER_PSEUDO_COUNT { - inputs[i] = self_inputs[i].clone(); - } - EagerPseudoCascadeInputs(Some(inputs)) - } -} - -impl EagerPseudoCascadeInputs { - /// Construct inputs from previous cascade results, if any. - fn new_from_style(styles: &EagerPseudoStyles) -> Self { - EagerPseudoCascadeInputs(styles.as_optional_array().map(|styles| { - let mut inputs: [Option; EAGER_PSEUDO_COUNT] = Default::default(); - for i in 0..EAGER_PSEUDO_COUNT { - inputs[i] = styles[i].as_ref().map(|s| CascadeInputs::new_from_style(s)); - } - inputs - })) - } - - /// Returns the list of rules, if they exist. - pub fn into_array(self) -> Option<[Option; EAGER_PSEUDO_COUNT]> { - self.0 - } -} - -/// The cascade inputs associated with a node, including those for any -/// pseudo-elements. -/// -/// The matching and cascading process stores them in this format temporarily -/// within the `CurrentElementInfo`. At the end of the cascade, they are folded -/// down into the main `ComputedValues` to reduce memory usage per element while -/// still remaining accessible. -#[derive(Clone, Debug)] -pub struct ElementCascadeInputs { - /// The element's cascade inputs. - pub primary: CascadeInputs, - /// A list of the inputs for the element's eagerly-cascaded pseudo-elements. - pub pseudos: EagerPseudoCascadeInputs, -} - -impl ElementCascadeInputs { - /// Construct inputs from previous cascade results, if any. - #[inline] - pub fn new_from_element_data(data: &ElementData) -> Self { - debug_assert!(data.has_styles()); - ElementCascadeInputs { - primary: CascadeInputs::new_from_style(data.styles.primary()), - pseudos: EagerPseudoCascadeInputs::new_from_style(&data.styles.pseudos), - } - } -} - -/// Statistics gathered during the traversal. We gather statistics on each -/// thread and then combine them after the threads join via the Add -/// implementation below. -#[derive(AddAssign, Clone, Default)] -pub struct PerThreadTraversalStatistics { - /// The total number of elements traversed. - pub elements_traversed: u32, - /// The number of elements where has_styles() went from false to true. - pub elements_styled: u32, - /// The number of elements for which we performed selector matching. - pub elements_matched: u32, - /// The number of cache hits from the StyleSharingCache. - pub styles_shared: u32, - /// The number of styles reused via rule node comparison from the - /// StyleSharingCache. - pub styles_reused: u32, -} - -/// Statistics gathered during the traversal plus some information from -/// other sources including stylist. -#[derive(Default)] -pub struct TraversalStatistics { - /// Aggregated statistics gathered during the traversal. - pub aggregated: PerThreadTraversalStatistics, - /// The number of selectors in the stylist. - pub selectors: u32, - /// The number of revalidation selectors. - pub revalidation_selectors: u32, - /// The number of state/attr dependencies in the dependency set. - pub dependency_selectors: u32, - /// The number of declarations in the stylist. - pub declarations: u32, - /// The number of times the stylist was rebuilt. - pub stylist_rebuilds: u32, - /// Time spent in the traversal, in milliseconds. - pub traversal_time_ms: f64, - /// Whether this was a parallel traversal. - pub is_parallel: bool, - /// Whether this is a "large" traversal. - pub is_large: bool, -} - -/// Format the statistics in a way that the performance test harness understands. -/// See https://bugzilla.mozilla.org/show_bug.cgi?id=1331856#c2 -impl fmt::Display for TraversalStatistics { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - debug_assert!( - self.traversal_time_ms != 0.0, - "should have set traversal time" - ); - writeln!(f, "[PERF] perf block start")?; - writeln!( - f, - "[PERF],traversal,{}", - if self.is_parallel { - "parallel" - } else { - "sequential" - } - )?; - writeln!( - f, - "[PERF],elements_traversed,{}", - self.aggregated.elements_traversed - )?; - writeln!( - f, - "[PERF],elements_styled,{}", - self.aggregated.elements_styled - )?; - writeln!( - f, - "[PERF],elements_matched,{}", - self.aggregated.elements_matched - )?; - writeln!(f, "[PERF],styles_shared,{}", self.aggregated.styles_shared)?; - writeln!(f, "[PERF],styles_reused,{}", self.aggregated.styles_reused)?; - writeln!(f, "[PERF],selectors,{}", self.selectors)?; - writeln!( - f, - "[PERF],revalidation_selectors,{}", - self.revalidation_selectors - )?; - writeln!( - f, - "[PERF],dependency_selectors,{}", - self.dependency_selectors - )?; - writeln!(f, "[PERF],declarations,{}", self.declarations)?; - writeln!(f, "[PERF],stylist_rebuilds,{}", self.stylist_rebuilds)?; - writeln!(f, "[PERF],traversal_time_ms,{}", self.traversal_time_ms)?; - writeln!(f, "[PERF] perf block end") - } -} - -impl TraversalStatistics { - /// Generate complete traversal statistics. - /// - /// The traversal time is computed given the start time in seconds. - pub fn new( - aggregated: PerThreadTraversalStatistics, - traversal: &D, - parallel: bool, - start: f64, - ) -> TraversalStatistics - where - E: TElement, - D: DomTraversal, - { - let threshold = traversal - .shared_context() - .options - .style_statistics_threshold; - let stylist = traversal.shared_context().stylist; - let is_large = aggregated.elements_traversed as usize >= threshold; - TraversalStatistics { - aggregated, - selectors: stylist.num_selectors() as u32, - revalidation_selectors: stylist.num_revalidation_selectors() as u32, - dependency_selectors: stylist.num_invalidations() as u32, - declarations: stylist.num_declarations() as u32, - stylist_rebuilds: stylist.num_rebuilds() as u32, - traversal_time_ms: (time::precise_time_s() - start) * 1000.0, - is_parallel: parallel, - is_large, - } - } -} - -#[cfg(feature = "gecko")] -bitflags! { - /// Represents which tasks are performed in a SequentialTask of - /// UpdateAnimations which is a result of normal restyle. - pub struct UpdateAnimationsTasks: u8 { - /// Update CSS Animations. - const CSS_ANIMATIONS = structs::UpdateAnimationsTasks_CSSAnimations; - /// Update CSS Transitions. - const CSS_TRANSITIONS = structs::UpdateAnimationsTasks_CSSTransitions; - /// Update effect properties. - const EFFECT_PROPERTIES = structs::UpdateAnimationsTasks_EffectProperties; - /// Update animation cacade results for animations running on the compositor. - const CASCADE_RESULTS = structs::UpdateAnimationsTasks_CascadeResults; - /// Display property was changed from none. - /// Script animations keep alive on display:none elements, so we need to trigger - /// the second animation restyles for the script animations in the case where - /// the display property was changed from 'none' to others. - const DISPLAY_CHANGED_FROM_NONE = structs::UpdateAnimationsTasks_DisplayChangedFromNone; - /// Update CSS named scroll progress timelines. - const SCROLL_TIMELINES = structs::UpdateAnimationsTasks_ScrollTimelines; - /// Update CSS named view progress timelines. - const VIEW_TIMELINES = structs::UpdateAnimationsTasks_ViewTimelines; - } -} - -#[cfg(feature = "gecko")] -bitflags! { - /// Represents which tasks are performed in a SequentialTask as a result of - /// animation-only restyle. - pub struct PostAnimationTasks: u8 { - /// Display property was changed from none in animation-only restyle so - /// that we need to resolve styles for descendants in a subsequent - /// normal restyle. - const DISPLAY_CHANGED_FROM_NONE_FOR_SMIL = 0x01; - } -} - -/// A task to be run in sequential mode on the parent (non-worker) thread. This -/// is used by the style system to queue up work which is not safe to do during -/// the parallel traversal. -pub enum SequentialTask { - /// Entry to avoid an unused type parameter error on servo. - Unused(SendElement), - - /// Performs one of a number of possible tasks related to updating - /// animations based on the |tasks| field. These include updating CSS - /// animations/transitions that changed as part of the non-animation style - /// traversal, and updating the computed effect properties. - #[cfg(feature = "gecko")] - UpdateAnimations { - /// The target element or pseudo-element. - el: SendElement, - /// The before-change style for transitions. We use before-change style - /// as the initial value of its Keyframe. Required if |tasks| includes - /// CSSTransitions. - before_change_style: Option>, - /// The tasks which are performed in this SequentialTask. - tasks: UpdateAnimationsTasks, - }, - - /// Performs one of a number of possible tasks as a result of animation-only - /// restyle. - /// - /// Currently we do only process for resolving descendant elements that were - /// display:none subtree for SMIL animation. - #[cfg(feature = "gecko")] - PostAnimation { - /// The target element. - el: SendElement, - /// The tasks which are performed in this SequentialTask. - tasks: PostAnimationTasks, - }, -} - -impl SequentialTask { - /// Executes this task. - pub fn execute(self) { - use self::SequentialTask::*; - debug_assert!(thread_state::get().contains(ThreadState::LAYOUT)); - match self { - Unused(_) => unreachable!(), - #[cfg(feature = "gecko")] - UpdateAnimations { - el, - before_change_style, - tasks, - } => { - el.update_animations(before_change_style, tasks); - }, - #[cfg(feature = "gecko")] - PostAnimation { el, tasks } => { - el.process_post_animation(tasks); - }, - } - } - - /// Creates a task to update various animation-related state on a given - /// (pseudo-)element. - #[cfg(feature = "gecko")] - pub fn update_animations( - el: E, - before_change_style: Option>, - tasks: UpdateAnimationsTasks, - ) -> Self { - use self::SequentialTask::*; - UpdateAnimations { - el: unsafe { SendElement::new(el) }, - before_change_style, - tasks, - } - } - - /// Creates a task to do post-process for a given element as a result of - /// animation-only restyle. - #[cfg(feature = "gecko")] - pub fn process_post_animation(el: E, tasks: PostAnimationTasks) -> Self { - use self::SequentialTask::*; - PostAnimation { - el: unsafe { SendElement::new(el) }, - tasks, - } - } -} - -/// A list of SequentialTasks that get executed on Drop. -pub struct SequentialTaskList(Vec>) -where - E: TElement; - -impl ops::Deref for SequentialTaskList -where - E: TElement, -{ - type Target = Vec>; - - fn deref(&self) -> &Self::Target { - &self.0 - } -} - -impl ops::DerefMut for SequentialTaskList -where - E: TElement, -{ - fn deref_mut(&mut self) -> &mut Self::Target { - &mut self.0 - } -} - -impl Drop for SequentialTaskList -where - E: TElement, -{ - fn drop(&mut self) { - debug_assert!(thread_state::get().contains(ThreadState::LAYOUT)); - for task in self.0.drain(..) { - task.execute() - } - } -} - -/// A helper type for stack limit checking. This assumes that stacks grow -/// down, which is true for all non-ancient CPU architectures. -pub struct StackLimitChecker { - lower_limit: usize, -} - -impl StackLimitChecker { - /// Create a new limit checker, for this thread, allowing further use - /// of up to |stack_size| bytes beyond (below) the current stack pointer. - #[inline(never)] - pub fn new(stack_size_limit: usize) -> Self { - StackLimitChecker { - lower_limit: StackLimitChecker::get_sp() - stack_size_limit, - } - } - - /// Checks whether the previously stored stack limit has now been exceeded. - #[inline(never)] - pub fn limit_exceeded(&self) -> bool { - let curr_sp = StackLimitChecker::get_sp(); - - // Do some sanity-checking to ensure that our invariants hold, even in - // the case where we've exceeded the soft limit. - // - // The correctness of depends on the assumption that no stack wraps - // around the end of the address space. - if cfg!(debug_assertions) { - // Compute the actual bottom of the stack by subtracting our safety - // margin from our soft limit. Note that this will be slightly below - // the actual bottom of the stack, because there are a few initial - // frames on the stack before we do the measurement that computes - // the limit. - let stack_bottom = self.lower_limit - STACK_SAFETY_MARGIN_KB * 1024; - - // The bottom of the stack should be below the current sp. If it - // isn't, that means we've either waited too long to check the limit - // and burned through our safety margin (in which case we probably - // would have segfaulted by now), or we're using a limit computed for - // a different thread. - debug_assert!(stack_bottom < curr_sp); - - // Compute the distance between the current sp and the bottom of - // the stack, and compare it against the current stack. It should be - // no further from us than the total stack size. We allow some slop - // to handle the fact that stack_bottom is a bit further than the - // bottom of the stack, as discussed above. - let distance_to_stack_bottom = curr_sp - stack_bottom; - let max_allowable_distance = (STYLE_THREAD_STACK_SIZE_KB + 10) * 1024; - debug_assert!(distance_to_stack_bottom <= max_allowable_distance); - } - - // The actual bounds check. - curr_sp <= self.lower_limit - } - - // Technically, rustc can optimize this away, but shouldn't for now. - // We should fix this once black_box is stable. - #[inline(always)] - fn get_sp() -> usize { - let mut foo: usize = 42; - (&mut foo as *mut usize) as usize - } -} - -/// A thread-local style context. -/// -/// This context contains data that needs to be used during restyling, but is -/// not required to be unique among worker threads, so we create one per worker -/// thread in order to be able to mutate it without locking. -pub struct ThreadLocalStyleContext { - /// A cache to share style among siblings. - pub sharing_cache: StyleSharingCache, - /// A cache from matched properties to elements that match those. - pub rule_cache: RuleCache, - /// The bloom filter used to fast-reject selector-matching. - pub bloom_filter: StyleBloom, - /// A set of tasks to be run (on the parent thread) in sequential mode after - /// the rest of the styling is complete. This is useful for - /// infrequently-needed non-threadsafe operations. - /// - /// It's important that goes after the style sharing cache and the bloom - /// filter, to ensure they're dropped before we execute the tasks, which - /// could create another ThreadLocalStyleContext for style computation. - pub tasks: SequentialTaskList, - /// Statistics about the traversal. - pub statistics: PerThreadTraversalStatistics, - /// A checker used to ensure that parallel.rs does not recurse indefinitely - /// even on arbitrarily deep trees. See Gecko bug 1376883. - pub stack_limit_checker: StackLimitChecker, - /// A cache for nth-index-like selectors. - pub nth_index_cache: NthIndexCache, -} - -impl ThreadLocalStyleContext { - /// Creates a new `ThreadLocalStyleContext` - pub fn new() -> Self { - ThreadLocalStyleContext { - sharing_cache: StyleSharingCache::new(), - rule_cache: RuleCache::new(), - bloom_filter: StyleBloom::new(), - tasks: SequentialTaskList(Vec::new()), - statistics: PerThreadTraversalStatistics::default(), - stack_limit_checker: StackLimitChecker::new( - (STYLE_THREAD_STACK_SIZE_KB - STACK_SAFETY_MARGIN_KB) * 1024, - ), - nth_index_cache: NthIndexCache::default(), - } - } -} - -/// 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> { - /// The shared style context reference. - pub shared: &'a SharedStyleContext<'a>, - /// The thread-local style context (mutable) reference. - pub thread_local: &'a mut ThreadLocalStyleContext, -} - -/// A registered painter -#[cfg(feature = "servo")] -pub trait RegisteredSpeculativePainter: SpeculativePainter { - /// The name it was registered with - fn name(&self) -> Atom; - /// The properties it was registered with - fn properties(&self) -> &FxHashMap; -} - -/// A set of registered painters -#[cfg(feature = "servo")] -pub trait RegisteredSpeculativePainters: Sync { - /// Look up a speculative painter - fn get(&self, name: &Atom) -> Option<&dyn RegisteredSpeculativePainter>; -} diff --git a/components/style/counter_style/mod.rs b/components/style/counter_style/mod.rs deleted file mode 100644 index 65143d69906..00000000000 --- a/components/style/counter_style/mod.rs +++ /dev/null @@ -1,697 +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/. */ - -//! The [`@counter-style`][counter-style] at-rule. -//! -//! [counter-style]: https://drafts.csswg.org/css-counter-styles/ - -use crate::error_reporting::ContextualParseError; -use crate::parser::{Parse, ParserContext}; -use crate::shared_lock::{SharedRwLockReadGuard, ToCssWithGuard}; -use crate::str::CssStringWriter; -use crate::values::specified::Integer; -use crate::values::CustomIdent; -use crate::Atom; -use cssparser::{ - AtRuleParser, DeclarationParser, QualifiedRuleParser, RuleBodyItemParser, RuleBodyParser, -}; -use cssparser::{CowRcStr, Parser, SourceLocation, Token}; -use selectors::parser::SelectorParseErrorKind; -use std::fmt::{self, Write}; -use std::mem; -use std::num::Wrapping; -use style_traits::{Comma, CssWriter, OneOrMoreSeparated, ParseError}; -use style_traits::{StyleParseErrorKind, ToCss}; - -/// Parse a counter style name reference. -/// -/// This allows the reserved counter style names "decimal" and "disc". -pub fn parse_counter_style_name<'i, 't>( - input: &mut Parser<'i, 't>, -) -> Result> { - macro_rules! predefined { - ($($name: expr,)+) => { - { - ascii_case_insensitive_phf_map! { - // FIXME: use static atoms https://github.com/rust-lang/rust/issues/33156 - predefined -> &'static str = { - $( - $name => $name, - )+ - } - } - - let location = input.current_source_location(); - let ident = input.expect_ident()?; - if let Some(&lower_cased) = predefined(&ident) { - Ok(CustomIdent(Atom::from(lower_cased))) - } else { - // none is always an invalid value. - CustomIdent::from_ident(location, ident, &["none"]) - } - } - } - } - include!("predefined.rs") -} - -fn is_valid_name_definition(ident: &CustomIdent) -> bool { - ident.0 != atom!("decimal") && - ident.0 != atom!("disc") && - ident.0 != atom!("circle") && - ident.0 != atom!("square") && - ident.0 != atom!("disclosure-closed") && - ident.0 != atom!("disclosure-open") -} - -/// Parse the prelude of an @counter-style rule -pub fn parse_counter_style_name_definition<'i, 't>( - input: &mut Parser<'i, 't>, -) -> Result> { - parse_counter_style_name(input).and_then(|ident| { - if !is_valid_name_definition(&ident) { - Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError)) - } else { - Ok(ident) - } - }) -} - -/// Parse the body (inside `{}`) of an @counter-style rule -pub fn parse_counter_style_body<'i, 't>( - name: CustomIdent, - context: &ParserContext, - input: &mut Parser<'i, 't>, - location: SourceLocation, -) -> Result> { - let start = input.current_source_location(); - let mut rule = CounterStyleRuleData::empty(name, location); - { - let mut parser = CounterStyleRuleParser { - context, - rule: &mut rule, - }; - let mut iter = RuleBodyParser::new(input, &mut parser); - while let Some(declaration) = iter.next() { - if let Err((error, slice)) = declaration { - let location = error.location; - let error = ContextualParseError::UnsupportedCounterStyleDescriptorDeclaration( - slice, error, - ); - context.log_css_error(location, error) - } - } - } - let error = match *rule.resolved_system() { - ref system @ System::Cyclic | - ref system @ System::Fixed { .. } | - ref system @ System::Symbolic | - ref system @ System::Alphabetic | - ref system @ System::Numeric - if rule.symbols.is_none() => - { - let system = system.to_css_string(); - Some(ContextualParseError::InvalidCounterStyleWithoutSymbols( - system, - )) - }, - ref system @ System::Alphabetic | ref system @ System::Numeric - if rule.symbols().unwrap().0.len() < 2 => - { - let system = system.to_css_string(); - Some(ContextualParseError::InvalidCounterStyleNotEnoughSymbols( - system, - )) - }, - System::Additive if rule.additive_symbols.is_none() => { - Some(ContextualParseError::InvalidCounterStyleWithoutAdditiveSymbols) - }, - System::Extends(_) if rule.symbols.is_some() => { - Some(ContextualParseError::InvalidCounterStyleExtendsWithSymbols) - }, - System::Extends(_) if rule.additive_symbols.is_some() => { - Some(ContextualParseError::InvalidCounterStyleExtendsWithAdditiveSymbols) - }, - _ => None, - }; - if let Some(error) = error { - context.log_css_error(start, error); - Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError)) - } else { - Ok(rule) - } -} - -struct CounterStyleRuleParser<'a, 'b: 'a> { - context: &'a ParserContext<'b>, - rule: &'a mut CounterStyleRuleData, -} - -/// Default methods reject all at rules. -impl<'a, 'b, 'i> AtRuleParser<'i> for CounterStyleRuleParser<'a, 'b> { - type Prelude = (); - type AtRule = (); - type Error = StyleParseErrorKind<'i>; -} - -impl<'a, 'b, 'i> QualifiedRuleParser<'i> for CounterStyleRuleParser<'a, 'b> { - type Prelude = (); - type QualifiedRule = (); - type Error = StyleParseErrorKind<'i>; -} - -impl<'a, 'b, 'i> RuleBodyItemParser<'i, (), StyleParseErrorKind<'i>> - for CounterStyleRuleParser<'a, 'b> -{ - fn parse_qualified(&self) -> bool { - false - } - fn parse_declarations(&self) -> bool { - true - } -} - -macro_rules! checker { - ($self:ident._($value:ident)) => {}; - ($self:ident. $checker:ident($value:ident)) => { - if !$self.$checker(&$value) { - return false; - } - }; -} - -macro_rules! counter_style_descriptors { - ( - $( #[$doc: meta] $name: tt $ident: ident / $setter: ident [$checker: tt]: $ty: ty, )+ - ) => { - /// An @counter-style rule - #[derive(Clone, Debug, ToShmem)] - pub struct CounterStyleRuleData { - name: CustomIdent, - generation: Wrapping, - $( - #[$doc] - $ident: Option<$ty>, - )+ - /// Line and column of the @counter-style rule source code. - pub source_location: SourceLocation, - } - - impl CounterStyleRuleData { - fn empty(name: CustomIdent, source_location: SourceLocation) -> Self { - CounterStyleRuleData { - name: name, - generation: Wrapping(0), - $( - $ident: None, - )+ - source_location, - } - } - - $( - #[$doc] - pub fn $ident(&self) -> Option<&$ty> { - self.$ident.as_ref() - } - )+ - - $( - #[$doc] - pub fn $setter(&mut self, value: $ty) -> bool { - checker!(self.$checker(value)); - self.$ident = Some(value); - self.generation += Wrapping(1); - true - } - )+ - } - - impl<'a, 'b, 'i> DeclarationParser<'i> for CounterStyleRuleParser<'a, 'b> { - type Declaration = (); - type Error = StyleParseErrorKind<'i>; - - fn parse_value<'t>( - &mut self, - name: CowRcStr<'i>, - input: &mut Parser<'i, 't>, - ) -> Result<(), ParseError<'i>> { - match_ignore_ascii_case! { &*name, - $( - $name => { - // DeclarationParser also calls parse_entirely so we’d normally not - // need to, but in this case we do because we set the value as a side - // effect rather than returning it. - let value = input.parse_entirely(|i| Parse::parse(self.context, i))?; - self.rule.$ident = Some(value) - }, - )* - _ => return Err(input.new_custom_error(SelectorParseErrorKind::UnexpectedIdent(name.clone()))), - } - Ok(()) - } - } - - impl ToCssWithGuard for CounterStyleRuleData { - fn to_css(&self, _guard: &SharedRwLockReadGuard, dest: &mut CssStringWriter) -> fmt::Result { - dest.write_str("@counter-style ")?; - self.name.to_css(&mut CssWriter::new(dest))?; - dest.write_str(" { ")?; - $( - if let Some(ref value) = self.$ident { - dest.write_str(concat!($name, ": "))?; - ToCss::to_css(value, &mut CssWriter::new(dest))?; - dest.write_str("; ")?; - } - )+ - dest.write_char('}') - } - } - } -} - -counter_style_descriptors! { - /// - "system" system / set_system [check_system]: System, - - /// - "negative" negative / set_negative [_]: Negative, - - /// - "prefix" prefix / set_prefix [_]: Symbol, - - /// - "suffix" suffix / set_suffix [_]: Symbol, - - /// - "range" range / set_range [_]: CounterRanges, - - /// - "pad" pad / set_pad [_]: Pad, - - /// - "fallback" fallback / set_fallback [_]: Fallback, - - /// - "symbols" symbols / set_symbols [check_symbols]: Symbols, - - /// - "additive-symbols" additive_symbols / - set_additive_symbols [check_additive_symbols]: AdditiveSymbols, - - /// - "speak-as" speak_as / set_speak_as [_]: SpeakAs, -} - -// Implements the special checkers for some setters. -// See -impl CounterStyleRuleData { - /// Check that the system is effectively not changed. Only params - /// of system descriptor is changeable. - fn check_system(&self, value: &System) -> bool { - mem::discriminant(self.resolved_system()) == mem::discriminant(value) - } - - fn check_symbols(&self, value: &Symbols) -> bool { - match *self.resolved_system() { - // These two systems require at least two symbols. - System::Numeric | System::Alphabetic => value.0.len() >= 2, - // No symbols should be set for extends system. - System::Extends(_) => false, - _ => true, - } - } - - fn check_additive_symbols(&self, _value: &AdditiveSymbols) -> bool { - match *self.resolved_system() { - // No additive symbols should be set for extends system. - System::Extends(_) => false, - _ => true, - } - } -} - -impl CounterStyleRuleData { - /// Get the name of the counter style rule. - pub fn name(&self) -> &CustomIdent { - &self.name - } - - /// Set the name of the counter style rule. Caller must ensure that - /// the name is valid. - pub fn set_name(&mut self, name: CustomIdent) { - debug_assert!(is_valid_name_definition(&name)); - self.name = name; - } - - /// Get the current generation of the counter style rule. - pub fn generation(&self) -> u32 { - self.generation.0 - } - - /// Get the system of this counter style rule, default to - /// `symbolic` if not specified. - pub fn resolved_system(&self) -> &System { - match self.system { - Some(ref system) => system, - None => &System::Symbolic, - } - } -} - -/// -#[derive(Clone, Debug, ToShmem)] -pub enum System { - /// 'cyclic' - Cyclic, - /// 'numeric' - Numeric, - /// 'alphabetic' - Alphabetic, - /// 'symbolic' - Symbolic, - /// 'additive' - Additive, - /// 'fixed ?' - Fixed { - /// '?' - first_symbol_value: Option, - }, - /// 'extends ' - Extends(CustomIdent), -} - -impl Parse for System { - fn parse<'i, 't>( - context: &ParserContext, - input: &mut Parser<'i, 't>, - ) -> Result> { - try_match_ident_ignore_ascii_case! { input, - "cyclic" => Ok(System::Cyclic), - "numeric" => Ok(System::Numeric), - "alphabetic" => Ok(System::Alphabetic), - "symbolic" => Ok(System::Symbolic), - "additive" => Ok(System::Additive), - "fixed" => { - let first_symbol_value = input.try_parse(|i| Integer::parse(context, i)).ok(); - Ok(System::Fixed { first_symbol_value }) - }, - "extends" => { - let other = parse_counter_style_name(input)?; - Ok(System::Extends(other)) - }, - } - } -} - -impl ToCss for System { - fn to_css(&self, dest: &mut CssWriter) -> fmt::Result - where - W: Write, - { - match *self { - System::Cyclic => dest.write_str("cyclic"), - System::Numeric => dest.write_str("numeric"), - System::Alphabetic => dest.write_str("alphabetic"), - System::Symbolic => dest.write_str("symbolic"), - System::Additive => dest.write_str("additive"), - System::Fixed { first_symbol_value } => { - if let Some(value) = first_symbol_value { - dest.write_str("fixed ")?; - value.to_css(dest) - } else { - dest.write_str("fixed") - } - }, - System::Extends(ref other) => { - dest.write_str("extends ")?; - other.to_css(dest) - }, - } - } -} - -/// -#[derive( - Clone, Debug, Eq, MallocSizeOf, PartialEq, ToComputedValue, ToResolvedValue, ToCss, ToShmem, -)] -#[repr(u8)] -pub enum Symbol { - /// - String(crate::OwnedStr), - /// - Ident(CustomIdent), - // Not implemented: - // /// - // Image(Image), -} - -impl Parse for Symbol { - fn parse<'i, 't>( - _context: &ParserContext, - input: &mut Parser<'i, 't>, - ) -> Result> { - let location = input.current_source_location(); - match *input.next()? { - Token::QuotedString(ref s) => Ok(Symbol::String(s.as_ref().to_owned().into())), - Token::Ident(ref s) => Ok(Symbol::Ident(CustomIdent::from_ident(location, s, &[])?)), - ref t => Err(location.new_unexpected_token_error(t.clone())), - } - } -} - -impl Symbol { - /// Returns whether this symbol is allowed in symbols() function. - pub fn is_allowed_in_symbols(&self) -> bool { - match self { - // Identifier is not allowed. - &Symbol::Ident(_) => false, - _ => true, - } - } -} - -/// -#[derive(Clone, Debug, ToCss, ToShmem)] -pub struct Negative(pub Symbol, pub Option); - -impl Parse for Negative { - fn parse<'i, 't>( - context: &ParserContext, - input: &mut Parser<'i, 't>, - ) -> Result> { - Ok(Negative( - Symbol::parse(context, input)?, - input.try_parse(|input| Symbol::parse(context, input)).ok(), - )) - } -} - -/// -#[derive(Clone, Debug, ToCss, ToShmem)] -pub struct CounterRange { - /// The start of the range. - pub start: CounterBound, - /// The end of the range. - pub end: CounterBound, -} - -/// -/// -/// Empty represents 'auto' -#[derive(Clone, Debug, ToCss, ToShmem)] -#[css(comma)] -pub struct CounterRanges(#[css(iterable, if_empty = "auto")] pub crate::OwnedSlice); - -/// A bound found in `CounterRanges`. -#[derive(Clone, Copy, Debug, ToCss, ToShmem)] -pub enum CounterBound { - /// An integer bound. - Integer(Integer), - /// The infinite bound. - Infinite, -} - -impl Parse for CounterRanges { - fn parse<'i, 't>( - context: &ParserContext, - input: &mut Parser<'i, 't>, - ) -> Result> { - if input - .try_parse(|input| input.expect_ident_matching("auto")) - .is_ok() - { - return Ok(CounterRanges(Default::default())); - } - - let ranges = input.parse_comma_separated(|input| { - let start = parse_bound(context, input)?; - let end = parse_bound(context, input)?; - if let (CounterBound::Integer(start), CounterBound::Integer(end)) = (start, end) { - if start > end { - return Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError)); - } - } - Ok(CounterRange { start, end }) - })?; - - Ok(CounterRanges(ranges.into())) - } -} - -fn parse_bound<'i, 't>( - context: &ParserContext, - input: &mut Parser<'i, 't>, -) -> Result> { - if let Ok(integer) = input.try_parse(|input| Integer::parse(context, input)) { - return Ok(CounterBound::Integer(integer)); - } - input.expect_ident_matching("infinite")?; - Ok(CounterBound::Infinite) -} - -/// -#[derive(Clone, Debug, ToCss, ToShmem)] -pub struct Pad(pub Integer, pub Symbol); - -impl Parse for Pad { - fn parse<'i, 't>( - context: &ParserContext, - input: &mut Parser<'i, 't>, - ) -> Result> { - let pad_with = input.try_parse(|input| Symbol::parse(context, input)); - let min_length = Integer::parse_non_negative(context, input)?; - let pad_with = pad_with.or_else(|_| Symbol::parse(context, input))?; - Ok(Pad(min_length, pad_with)) - } -} - -/// -#[derive(Clone, Debug, ToCss, ToShmem)] -pub struct Fallback(pub CustomIdent); - -impl Parse for Fallback { - fn parse<'i, 't>( - _context: &ParserContext, - input: &mut Parser<'i, 't>, - ) -> Result> { - Ok(Fallback(parse_counter_style_name(input)?)) - } -} - -/// -#[derive( - Clone, Debug, Eq, MallocSizeOf, PartialEq, ToComputedValue, ToResolvedValue, ToCss, ToShmem, -)] -#[repr(C)] -pub struct Symbols(#[css(iterable)] pub crate::OwnedSlice); - -impl Parse for Symbols { - fn parse<'i, 't>( - context: &ParserContext, - input: &mut Parser<'i, 't>, - ) -> Result> { - let mut symbols = Vec::new(); - while let Ok(s) = input.try_parse(|input| Symbol::parse(context, input)) { - symbols.push(s); - } - if symbols.is_empty() { - return Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError)); - } - Ok(Symbols(symbols.into())) - } -} - -/// -#[derive(Clone, Debug, ToCss, ToShmem)] -#[css(comma)] -pub struct AdditiveSymbols(#[css(iterable)] pub crate::OwnedSlice); - -impl Parse for AdditiveSymbols { - fn parse<'i, 't>( - context: &ParserContext, - input: &mut Parser<'i, 't>, - ) -> Result> { - let tuples = Vec::::parse(context, input)?; - // FIXME maybe? https://github.com/w3c/csswg-drafts/issues/1220 - if tuples - .windows(2) - .any(|window| window[0].weight <= window[1].weight) - { - return Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError)); - } - Ok(AdditiveSymbols(tuples.into())) - } -} - -/// && -#[derive(Clone, Debug, ToCss, ToShmem)] -pub struct AdditiveTuple { - /// - pub weight: Integer, - /// - pub symbol: Symbol, -} - -impl OneOrMoreSeparated for AdditiveTuple { - type S = Comma; -} - -impl Parse for AdditiveTuple { - fn parse<'i, 't>( - context: &ParserContext, - input: &mut Parser<'i, 't>, - ) -> Result> { - let symbol = input.try_parse(|input| Symbol::parse(context, input)); - let weight = Integer::parse_non_negative(context, input)?; - let symbol = symbol.or_else(|_| Symbol::parse(context, input))?; - Ok(Self { weight, symbol }) - } -} - -/// -#[derive(Clone, Debug, ToCss, ToShmem)] -pub enum SpeakAs { - /// auto - Auto, - /// bullets - Bullets, - /// numbers - Numbers, - /// words - Words, - // /// spell-out, not supported, see bug 1024178 - // SpellOut, - /// - Other(CustomIdent), -} - -impl Parse for SpeakAs { - fn parse<'i, 't>( - _context: &ParserContext, - input: &mut Parser<'i, 't>, - ) -> Result> { - let mut is_spell_out = false; - let result = input.try_parse(|input| { - let ident = input.expect_ident().map_err(|_| ())?; - match_ignore_ascii_case! { &*ident, - "auto" => Ok(SpeakAs::Auto), - "bullets" => Ok(SpeakAs::Bullets), - "numbers" => Ok(SpeakAs::Numbers), - "words" => Ok(SpeakAs::Words), - "spell-out" => { - is_spell_out = true; - Err(()) - }, - _ => Err(()), - } - }); - if is_spell_out { - // spell-out is not supported, but don’t parse it as a . - // See bug 1024178. - return Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError)); - } - result.or_else(|_| Ok(SpeakAs::Other(parse_counter_style_name(input)?))) - } -} diff --git a/components/style/counter_style/predefined.rs b/components/style/counter_style/predefined.rs deleted file mode 100644 index 7243e3b3f32..00000000000 --- a/components/style/counter_style/predefined.rs +++ /dev/null @@ -1,61 +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/. */ - -predefined! { - "decimal", - "decimal-leading-zero", - "arabic-indic", - "armenian", - "upper-armenian", - "lower-armenian", - "bengali", - "cambodian", - "khmer", - "cjk-decimal", - "devanagari", - "georgian", - "gujarati", - "gurmukhi", - "hebrew", - "kannada", - "lao", - "malayalam", - "mongolian", - "myanmar", - "oriya", - "persian", - "lower-roman", - "upper-roman", - "tamil", - "telugu", - "thai", - "tibetan", - "lower-alpha", - "lower-latin", - "upper-alpha", - "upper-latin", - "cjk-earthly-branch", - "cjk-heavenly-stem", - "lower-greek", - "hiragana", - "hiragana-iroha", - "katakana", - "katakana-iroha", - "disc", - "circle", - "square", - "disclosure-open", - "disclosure-closed", - "japanese-informal", - "japanese-formal", - "korean-hangul-formal", - "korean-hanja-informal", - "korean-hanja-formal", - "simp-chinese-informal", - "simp-chinese-formal", - "trad-chinese-informal", - "trad-chinese-formal", - "cjk-ideographic", - "ethiopic-numeric", -} diff --git a/components/style/counter_style/update_predefined.py b/components/style/counter_style/update_predefined.py deleted file mode 100755 index 1523958ff3e..00000000000 --- a/components/style/counter_style/update_predefined.py +++ /dev/null @@ -1,35 +0,0 @@ -#!/usr/bin/env python - -# 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/. */ - -import os.path -import re -import urllib - - -def main(filename): - names = [ - re.search('>([^>]+)(| VariableValue; - -struct EnvironmentVariable { - name: Atom, - evaluator: EnvironmentEvaluator, -} - -macro_rules! make_variable { - ($name:expr, $evaluator:expr) => {{ - EnvironmentVariable { - name: $name, - evaluator: $evaluator, - } - }}; -} - -fn get_safearea_inset_top(device: &Device) -> VariableValue { - VariableValue::pixels(device.safe_area_insets().top) -} - -fn get_safearea_inset_bottom(device: &Device) -> VariableValue { - VariableValue::pixels(device.safe_area_insets().bottom) -} - -fn get_safearea_inset_left(device: &Device) -> VariableValue { - VariableValue::pixels(device.safe_area_insets().left) -} - -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") -} - -fn get_scrollbar_inline_size(device: &Device) -> VariableValue { - VariableValue::pixels(device.scrollbar_inline_size().px()) -} - -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), - make_variable!(atom!("safe-area-inset-left"), get_safearea_inset_left), - make_variable!(atom!("safe-area-inset-right"), get_safearea_inset_right), -]; - -#[cfg(feature = "gecko")] -macro_rules! lnf_int { - ($id:ident) => { - unsafe { - crate::gecko_bindings::bindings::Gecko_GetLookAndFeelInt( - crate::gecko_bindings::bindings::LookAndFeel_IntID::$id as i32, - ) - } - }; -} - -#[cfg(feature = "servo")] -macro_rules! lnf_int { - ($id:ident) => { - // TODO: implement this. - 0 - }; -} - -macro_rules! lnf_int_variable { - ($atom:expr, $id:ident, $ctor:ident) => {{ - fn __eval(_: &Device) -> VariableValue { - VariableValue::$ctor(lnf_int!($id)) - } - make_variable!($atom, __eval) - }}; -} - -static CHROME_ENVIRONMENT_VARIABLES: [EnvironmentVariable; 6] = [ - lnf_int_variable!( - atom!("-moz-gtk-csd-titlebar-radius"), - TitlebarRadius, - int_pixels - ), - lnf_int_variable!( - atom!("-moz-gtk-csd-close-button-position"), - GTKCSDCloseButtonPosition, - integer - ), - lnf_int_variable!( - atom!("-moz-gtk-csd-minimize-button-position"), - GTKCSDMinimizeButtonPosition, - integer - ), - lnf_int_variable!( - atom!("-moz-gtk-csd-maximize-button-position"), - GTKCSDMaximizeButtonPosition, - integer - ), - make_variable!( - atom!("-moz-content-preferred-color-scheme"), - get_content_preferred_color_scheme - ), - make_variable!(atom!("scrollbar-inline-size"), get_scrollbar_inline_size), -]; - -impl CssEnvironment { - #[inline] - fn get(&self, name: &Atom, device: &Device) -> Option { - if let Some(var) = ENVIRONMENT_VARIABLES.iter().find(|var| var.name == *name) { - return Some((var.evaluator)(device)); - } - if !device.chrome_rules_enabled_for_document() { - return None; - } - let var = CHROME_ENVIRONMENT_VARIABLES - .iter() - .find(|var| var.name == *name)?; - Some((var.evaluator)(device)) - } -} - -/// A custom property name is just an `Atom`. -/// -/// Note that this does not include the `--` prefix -pub type Name = Atom; - -/// Parse a custom property name. -/// -/// -pub fn parse_name(s: &str) -> Result<&str, ()> { - if s.starts_with("--") && s.len() > 2 { - Ok(&s[2..]) - } else { - Err(()) - } -} - -/// A value for a custom property is just a set of tokens. -/// -/// We preserve the original CSS for serialization, and also the variable -/// references to other custom property names. -#[derive(Clone, Debug, MallocSizeOf, PartialEq, ToShmem)] -pub struct VariableValue { - css: String, - - first_token_type: TokenSerializationType, - last_token_type: TokenSerializationType, - - /// Whether a variable value has a reference to an environment variable. - /// - /// If this is the case, we need to perform variable substitution on the - /// value. - references_environment: bool, - - /// Custom property names in var() functions. - references: Box<[Name]>, -} - -impl ToCss for SpecifiedValue { - fn to_css(&self, dest: &mut CssWriter) -> fmt::Result - where - W: Write, - { - dest.write_str(&self.css) - } -} - -/// A map from CSS variable names to CSS variable computed values, used for -/// resolving. -/// -/// A consistent ordering is required for CSSDeclaration objects in the -/// DOM. CSSDeclarations expose property names as indexed properties, which -/// need to be stable. So we keep an array of property names which order is -/// determined on the order that they are added to the name-value map. -/// -/// The variable values are guaranteed to not have references to other -/// properties. -pub type CustomPropertiesMap = - IndexMap, BuildHasherDefault>; - -/// Both specified and computed values are VariableValues, the difference is -/// whether var() functions are expanded. -pub type SpecifiedValue = VariableValue; -/// Both specified and computed values are VariableValues, the difference is -/// whether var() functions are expanded. -pub type ComputedValue = VariableValue; - -/// A struct holding information about the external references to that a custom -/// property value may have. -#[derive(Default)] -struct VarOrEnvReferences { - custom_property_references: PrecomputedHashSet, - references_environment: bool, -} - -impl VariableValue { - fn empty() -> Self { - Self { - css: String::new(), - last_token_type: TokenSerializationType::nothing(), - first_token_type: TokenSerializationType::nothing(), - references: Default::default(), - references_environment: false, - } - } - - fn push<'i>( - &mut self, - input: &Parser<'i, '_>, - css: &str, - css_first_token_type: TokenSerializationType, - css_last_token_type: TokenSerializationType, - ) -> Result<(), ParseError<'i>> { - /// Prevent values from getting terribly big since you can use custom - /// properties exponentially. - /// - /// This number (2MB) is somewhat arbitrary, but silly enough that no - /// reasonable page should hit it. We could limit by number of total - /// substitutions, but that was very easy to work around in practice - /// (just choose a larger initial value and boom). - const MAX_VALUE_LENGTH_IN_BYTES: usize = 2 * 1024 * 1024; - - if self.css.len() + css.len() > MAX_VALUE_LENGTH_IN_BYTES { - return Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError)); - } - - // This happens e.g. between two subsequent var() functions: - // `var(--a)var(--b)`. - // - // In that case, css_*_token_type is nonsensical. - if css.is_empty() { - return Ok(()); - } - - self.first_token_type.set_if_nothing(css_first_token_type); - // If self.first_token_type was nothing, - // self.last_token_type is also nothing and this will be false: - if self - .last_token_type - .needs_separator_when_before(css_first_token_type) - { - self.css.push_str("/**/") - } - self.css.push_str(css); - self.last_token_type = css_last_token_type; - Ok(()) - } - - fn push_from<'i>( - &mut self, - input: &Parser<'i, '_>, - position: (SourcePosition, TokenSerializationType), - last_token_type: TokenSerializationType, - ) -> Result<(), ParseError<'i>> { - self.push( - input, - input.slice_from(position.0), - position.1, - last_token_type, - ) - } - - fn push_variable<'i>( - &mut self, - input: &Parser<'i, '_>, - variable: &ComputedValue, - ) -> Result<(), ParseError<'i>> { - debug_assert!(variable.references.is_empty()); - self.push( - input, - &variable.css, - variable.first_token_type, - variable.last_token_type, - ) - } - - /// Parse a custom property value. - pub fn parse<'i, 't>(input: &mut Parser<'i, 't>) -> Result, ParseError<'i>> { - let mut references = VarOrEnvReferences::default(); - - let (first_token_type, css, last_token_type) = - parse_self_contained_declaration_value(input, Some(&mut references))?; - - let custom_property_references = references - .custom_property_references - .into_iter() - .collect::>() - .into_boxed_slice(); - - let mut css = css.into_owned(); - css.shrink_to_fit(); - - Ok(Arc::new(VariableValue { - css, - first_token_type, - last_token_type, - references: custom_property_references, - references_environment: references.references_environment, - })) - } - - /// Create VariableValue from an int. - fn integer(number: i32) -> Self { - Self::from_token(Token::Number { - has_sign: false, - value: number as f32, - int_value: Some(number), - }) - } - - /// 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): - // No way to get TokenSerializationType::Dimension without creating - // Token object. - Self::from_token(Token::Dimension { - has_sign: false, - value: number, - int_value: None, - unit: CowRcStr::from("px"), - }) - } - - /// Create VariableValue from an integer amount of CSS pixels. - fn int_pixels(number: i32) -> Self { - Self::from_token(Token::Dimension { - has_sign: false, - value: number as f32, - int_value: Some(number), - unit: CowRcStr::from("px"), - }) - } - - fn from_token(token: Token) -> Self { - let token_type = token.serialization_type(); - let mut css = token.to_css_string(); - css.shrink_to_fit(); - - VariableValue { - css, - first_token_type: token_type, - last_token_type: token_type, - references: Default::default(), - references_environment: false, - } - } -} - -/// Parse the value of a non-custom property that contains `var()` references. -pub fn parse_non_custom_with_var<'i, 't>( - input: &mut Parser<'i, 't>, -) -> Result<(TokenSerializationType, Cow<'i, str>), ParseError<'i>> { - let (first_token_type, css, _) = parse_self_contained_declaration_value(input, None)?; - Ok((first_token_type, css)) -} - -fn parse_self_contained_declaration_value<'i, 't>( - input: &mut Parser<'i, 't>, - references: Option<&mut VarOrEnvReferences>, -) -> Result<(TokenSerializationType, Cow<'i, str>, TokenSerializationType), ParseError<'i>> { - let start_position = input.position(); - let mut missing_closing_characters = String::new(); - let (first, last) = - parse_declaration_value(input, references, &mut missing_closing_characters)?; - let mut css: Cow = input.slice_from(start_position).into(); - if !missing_closing_characters.is_empty() { - // Unescaped backslash at EOF in a quoted string is ignored. - if css.ends_with("\\") && matches!(missing_closing_characters.as_bytes()[0], b'"' | b'\'') { - css.to_mut().pop(); - } - css.to_mut().push_str(&missing_closing_characters); - } - Ok((first, css, last)) -} - -/// -fn parse_declaration_value<'i, 't>( - input: &mut Parser<'i, 't>, - references: Option<&mut VarOrEnvReferences>, - missing_closing_characters: &mut String, -) -> Result<(TokenSerializationType, TokenSerializationType), ParseError<'i>> { - input.parse_until_before(Delimiter::Bang | Delimiter::Semicolon, |input| { - parse_declaration_value_block(input, references, missing_closing_characters) - }) -} - -/// Like parse_declaration_value, but accept `!` and `;` since they are only -/// invalid at the top level -fn parse_declaration_value_block<'i, 't>( - input: &mut Parser<'i, 't>, - mut references: Option<&mut VarOrEnvReferences>, - missing_closing_characters: &mut String, -) -> Result<(TokenSerializationType, TokenSerializationType), ParseError<'i>> { - input.skip_whitespace(); - let mut token_start = input.position(); - let mut token = match input.next_including_whitespace_and_comments() { - Ok(token) => token, - Err(_) => { - return Ok(( - TokenSerializationType::nothing(), - TokenSerializationType::nothing(), - )); - }, - }; - let first_token_type = token.serialization_type(); - loop { - macro_rules! nested { - () => { - input.parse_nested_block(|input| { - parse_declaration_value_block( - input, - references.as_mut().map(|r| &mut **r), - missing_closing_characters, - ) - })? - }; - } - macro_rules! check_closed { - ($closing:expr) => { - if !input.slice_from(token_start).ends_with($closing) { - missing_closing_characters.push_str($closing) - } - }; - } - let last_token_type = match *token { - Token::Comment(_) => { - let serialization_type = token.serialization_type(); - let token_slice = input.slice_from(token_start); - if !token_slice.ends_with("*/") { - missing_closing_characters.push_str(if token_slice.ends_with('*') { - "/" - } else { - "*/" - }) - } - serialization_type - }, - Token::BadUrl(ref u) => { - let e = StyleParseErrorKind::BadUrlInDeclarationValueBlock(u.clone()); - return Err(input.new_custom_error(e)); - }, - Token::BadString(ref s) => { - let e = StyleParseErrorKind::BadStringInDeclarationValueBlock(s.clone()); - return Err(input.new_custom_error(e)); - }, - Token::CloseParenthesis => { - let e = StyleParseErrorKind::UnbalancedCloseParenthesisInDeclarationValueBlock; - return Err(input.new_custom_error(e)); - }, - Token::CloseSquareBracket => { - let e = StyleParseErrorKind::UnbalancedCloseSquareBracketInDeclarationValueBlock; - return Err(input.new_custom_error(e)); - }, - Token::CloseCurlyBracket => { - let e = StyleParseErrorKind::UnbalancedCloseCurlyBracketInDeclarationValueBlock; - return Err(input.new_custom_error(e)); - }, - Token::Function(ref name) => { - if name.eq_ignore_ascii_case("var") { - let args_start = input.state(); - input.parse_nested_block(|input| { - parse_var_function(input, references.as_mut().map(|r| &mut **r)) - })?; - input.reset(&args_start); - } else if name.eq_ignore_ascii_case("env") { - let args_start = input.state(); - input.parse_nested_block(|input| { - parse_env_function(input, references.as_mut().map(|r| &mut **r)) - })?; - input.reset(&args_start); - } - nested!(); - check_closed!(")"); - Token::CloseParenthesis.serialization_type() - }, - Token::ParenthesisBlock => { - nested!(); - check_closed!(")"); - Token::CloseParenthesis.serialization_type() - }, - Token::CurlyBracketBlock => { - nested!(); - check_closed!("}"); - Token::CloseCurlyBracket.serialization_type() - }, - Token::SquareBracketBlock => { - nested!(); - check_closed!("]"); - Token::CloseSquareBracket.serialization_type() - }, - Token::QuotedString(_) => { - let serialization_type = token.serialization_type(); - let token_slice = input.slice_from(token_start); - let quote = &token_slice[..1]; - debug_assert!(matches!(quote, "\"" | "'")); - if !(token_slice.ends_with(quote) && token_slice.len() > 1) { - missing_closing_characters.push_str(quote) - } - serialization_type - }, - Token::Ident(ref value) | - Token::AtKeyword(ref value) | - Token::Hash(ref value) | - Token::IDHash(ref value) | - Token::UnquotedUrl(ref value) | - Token::Dimension { - unit: ref value, .. - } => { - let serialization_type = token.serialization_type(); - let is_unquoted_url = matches!(token, Token::UnquotedUrl(_)); - if value.ends_with("�") && input.slice_from(token_start).ends_with("\\") { - // Unescaped backslash at EOF in these contexts is interpreted as U+FFFD - // Check the value in case the final backslash was itself escaped. - // Serialize as escaped U+FFFD, which is also interpreted as U+FFFD. - // (Unescaped U+FFFD would also work, but removing the backslash is annoying.) - missing_closing_characters.push_str("�") - } - if is_unquoted_url { - check_closed!(")"); - } - serialization_type - }, - _ => token.serialization_type(), - }; - - token_start = input.position(); - token = match input.next_including_whitespace_and_comments() { - Ok(token) => token, - Err(..) => return Ok((first_token_type, last_token_type)), - }; - } -} - -fn parse_fallback<'i, 't>(input: &mut Parser<'i, 't>) -> Result<(), ParseError<'i>> { - // Exclude `!` and `;` at the top level - // https://drafts.csswg.org/css-syntax/#typedef-declaration-value - input.parse_until_before(Delimiter::Bang | Delimiter::Semicolon, |input| { - // Skip until the end. - while input.next_including_whitespace_and_comments().is_ok() {} - Ok(()) - }) -} - -// If the var function is valid, return Ok((custom_property_name, fallback)) -fn parse_var_function<'i, 't>( - input: &mut Parser<'i, 't>, - references: Option<&mut VarOrEnvReferences>, -) -> Result<(), ParseError<'i>> { - let name = input.expect_ident_cloned()?; - let name = parse_name(&name).map_err(|()| { - input.new_custom_error(SelectorParseErrorKind::UnexpectedIdent(name.clone())) - })?; - if input.try_parse(|input| input.expect_comma()).is_ok() { - parse_fallback(input)?; - } - if let Some(refs) = references { - refs.custom_property_references.insert(Atom::from(name)); - } - Ok(()) -} - -fn parse_env_function<'i, 't>( - input: &mut Parser<'i, 't>, - references: Option<&mut VarOrEnvReferences>, -) -> Result<(), ParseError<'i>> { - // TODO(emilio): This should be per spec, but no other - // browser does that, see https://github.com/w3c/csswg-drafts/issues/3262. - input.expect_ident()?; - if input.try_parse(|input| input.expect_comma()).is_ok() { - parse_fallback(input)?; - } - if let Some(references) = references { - references.references_environment = true; - } - Ok(()) -} - -/// A struct that takes care of encapsulating the cascade process for custom -/// properties. -pub struct CustomPropertiesBuilder<'a> { - seen: PrecomputedHashSet<&'a Name>, - may_have_cycles: bool, - custom_properties: Option, - inherited: Option<&'a Arc>, - reverted: PrecomputedHashMap<&'a Name, (CascadePriority, bool)>, - device: &'a Device, -} - -impl<'a> CustomPropertiesBuilder<'a> { - /// Create a new builder, inheriting from a given custom properties map. - pub fn new(inherited: Option<&'a Arc>, device: &'a Device) -> Self { - Self { - seen: PrecomputedHashSet::default(), - reverted: Default::default(), - may_have_cycles: false, - custom_properties: None, - inherited, - device, - } - } - - /// Cascade a given custom property declaration. - pub fn cascade(&mut self, declaration: &'a CustomDeclaration, priority: CascadePriority) { - let CustomDeclaration { - ref name, - ref value, - } = *declaration; - - if let Some(&(reverted_priority, is_origin_revert)) = self.reverted.get(&name) { - if !reverted_priority.allows_when_reverted(&priority, is_origin_revert) { - return; - } - } - - let was_already_present = !self.seen.insert(name); - if was_already_present { - return; - } - - if !self.value_may_affect_style(name, value) { - return; - } - - if self.custom_properties.is_none() { - self.custom_properties = Some(match self.inherited { - Some(inherited) => (**inherited).clone(), - None => CustomPropertiesMap::default(), - }); - } - - let map = self.custom_properties.as_mut().unwrap(); - match *value { - CustomDeclarationValue::Value(ref unparsed_value) => { - let has_references = !unparsed_value.references.is_empty(); - self.may_have_cycles |= has_references; - - // If the variable value has no references and it has an - // environment variable here, perform substitution here instead - // of forcing a full traversal in `substitute_all` afterwards. - let value = if !has_references && unparsed_value.references_environment { - let result = substitute_references_in_value(unparsed_value, &map, &self.device); - match result { - Ok(new_value) => new_value, - Err(..) => { - map.remove(name); - return; - }, - } - } else { - (*unparsed_value).clone() - }; - map.insert(name.clone(), value); - }, - CustomDeclarationValue::CSSWideKeyword(keyword) => match keyword { - CSSWideKeyword::RevertLayer | CSSWideKeyword::Revert => { - let origin_revert = keyword == CSSWideKeyword::Revert; - self.seen.remove(name); - self.reverted.insert(name, (priority, origin_revert)); - }, - CSSWideKeyword::Initial => { - map.remove(name); - }, - // handled in value_may_affect_style - CSSWideKeyword::Unset | CSSWideKeyword::Inherit => unreachable!(), - }, - } - } - - fn value_may_affect_style(&self, name: &Name, value: &CustomDeclarationValue) -> bool { - match *value { - CustomDeclarationValue::CSSWideKeyword(CSSWideKeyword::Unset) | - CustomDeclarationValue::CSSWideKeyword(CSSWideKeyword::Inherit) => { - // Custom properties are inherited by default. So - // explicit 'inherit' or 'unset' means we can just use - // any existing value in the inherited CustomPropertiesMap. - return false; - }, - _ => {}, - } - - let existing_value = self - .custom_properties - .as_ref() - .and_then(|m| m.get(name)) - .or_else(|| self.inherited.and_then(|m| m.get(name))); - - match (existing_value, value) { - (None, &CustomDeclarationValue::CSSWideKeyword(CSSWideKeyword::Initial)) => { - // The initial value of a custom property is the same as it - // not existing in the map. - return false; - }, - (Some(existing_value), &CustomDeclarationValue::Value(ref value)) => { - // Don't bother overwriting an existing inherited value with - // the same specified value. - if existing_value == value { - return false; - } - }, - _ => {}, - } - - true - } - - fn inherited_properties_match(&self, map: &CustomPropertiesMap) -> bool { - let inherited = match self.inherited { - Some(inherited) => inherited, - None => return false, - }; - if inherited.len() != map.len() { - return false; - } - for name in self.seen.iter() { - if inherited.get(*name) != map.get(*name) { - return false; - } - } - true - } - - /// Returns the final map of applicable custom properties. - /// - /// If there was any specified property, we've created a new map and now we - /// need to remove any potential cycles, and wrap it in an arc. - /// - /// Otherwise, just use the inherited custom properties map. - pub fn build(mut self) -> Option> { - let mut map = match self.custom_properties.take() { - Some(m) => m, - None => return self.inherited.cloned(), - }; - - if self.may_have_cycles { - substitute_all(&mut map, &self.seen, self.device); - } - - // Some pages apply a lot of redundant custom properties, see e.g. - // bug 1758974 comment 5. Try to detect the case where the values - // haven't really changed, and save some memory by reusing the inherited - // map in that case. - if self.inherited_properties_match(&map) { - return self.inherited.cloned(); - } - - map.shrink_to_fit(); - Some(Arc::new(map)) - } -} - -/// Resolve all custom properties to either substituted, invalid, or unset -/// (meaning we should use the inherited value). -/// -/// It does cycle dependencies removal at the same time as substitution. -fn substitute_all( - custom_properties_map: &mut CustomPropertiesMap, - seen: &PrecomputedHashSet<&Name>, - device: &Device, -) { - // The cycle dependencies removal in this function is a variant - // of Tarjan's algorithm. It is mostly based on the pseudo-code - // listed in - // https://en.wikipedia.org/w/index.php? - // title=Tarjan%27s_strongly_connected_components_algorithm&oldid=801728495 - - /// Struct recording necessary information for each variable. - #[derive(Debug)] - struct VarInfo { - /// The name of the variable. It will be taken to save addref - /// when the corresponding variable is popped from the stack. - /// This also serves as a mark for whether the variable is - /// currently in the stack below. - name: Option, - /// If the variable is in a dependency cycle, lowlink represents - /// a smaller index which corresponds to a variable in the same - /// strong connected component, which is known to be accessible - /// from this variable. It is not necessarily the root, though. - lowlink: usize, - } - /// Context struct for traversing the variable graph, so that we can - /// avoid referencing all the fields multiple times. - #[derive(Debug)] - struct Context<'a> { - /// Number of variables visited. This is used as the order index - /// when we visit a new unresolved variable. - count: usize, - /// The map from custom property name to its order index. - index_map: PrecomputedHashMap, - /// Information of each variable indexed by the order index. - var_info: SmallVec<[VarInfo; 5]>, - /// The stack of order index of visited variables. It contains - /// all unfinished strong connected components. - stack: SmallVec<[usize; 5]>, - map: &'a mut CustomPropertiesMap, - /// To resolve the environment to substitute `env()` variables. - device: &'a Device, - } - - /// This function combines the traversal for cycle removal and value - /// substitution. It returns either a signal None if this variable - /// has been fully resolved (to either having no reference or being - /// marked invalid), or the order index for the given name. - /// - /// When it returns, the variable corresponds to the name would be - /// in one of the following states: - /// * It is still in context.stack, which means it is part of an - /// potentially incomplete dependency circle. - /// * It has been removed from the map. It can be either that the - /// substitution failed, or it is inside a dependency circle. - /// When this function removes a variable from the map because - /// of dependency circle, it would put all variables in the same - /// strong connected component to the set together. - /// * It doesn't have any reference, because either this variable - /// doesn't have reference at all in specified value, or it has - /// been completely resolved. - /// * There is no such variable at all. - fn traverse<'a>(name: &Name, context: &mut Context<'a>) -> Option { - // Some shortcut checks. - let (name, value) = { - let value = context.map.get(name)?; - - // Nothing to resolve. - if value.references.is_empty() { - debug_assert!( - !value.references_environment, - "Should've been handled earlier" - ); - return None; - } - - // Whether this variable has been visited in this traversal. - let key; - match context.index_map.entry(name.clone()) { - Entry::Occupied(entry) => { - return Some(*entry.get()); - }, - Entry::Vacant(entry) => { - key = entry.key().clone(); - entry.insert(context.count); - }, - } - - // Hold a strong reference to the value so that we don't - // need to keep reference to context.map. - (key, value.clone()) - }; - - // Add new entry to the information table. - let index = context.count; - context.count += 1; - debug_assert_eq!(index, context.var_info.len()); - context.var_info.push(VarInfo { - name: Some(name), - lowlink: index, - }); - context.stack.push(index); - - let mut self_ref = false; - let mut lowlink = index; - for next in value.references.iter() { - let next_index = match traverse(next, context) { - Some(index) => index, - // There is nothing to do if the next variable has been - // fully resolved at this point. - None => { - continue; - }, - }; - let next_info = &context.var_info[next_index]; - if next_index > index { - // The next variable has a larger index than us, so it - // must be inserted in the recursive call above. We want - // to get its lowlink. - lowlink = cmp::min(lowlink, next_info.lowlink); - } else if next_index == index { - self_ref = true; - } else if next_info.name.is_some() { - // The next variable has a smaller order index and it is - // in the stack, so we are at the same component. - lowlink = cmp::min(lowlink, next_index); - } - } - - context.var_info[index].lowlink = lowlink; - if lowlink != index { - // This variable is in a loop, but it is not the root of - // this strong connected component. We simply return for - // now, and the root would remove it from the map. - // - // This cannot be removed from the map here, because - // otherwise the shortcut check at the beginning of this - // function would return the wrong value. - return Some(index); - } - - // This is the root of a strong-connected component. - let mut in_loop = self_ref; - let name; - loop { - let var_index = context - .stack - .pop() - .expect("The current variable should still be in stack"); - let var_info = &mut context.var_info[var_index]; - // We should never visit the variable again, so it's safe - // to take the name away, so that we don't do additional - // reference count. - let var_name = var_info - .name - .take() - .expect("Variable should not be poped from stack twice"); - if var_index == index { - name = var_name; - break; - } - // Anything here is in a loop which can traverse to the - // variable we are handling, so remove it from the map, it's invalid - // at computed-value time. - context.map.remove(&var_name); - in_loop = true; - } - if in_loop { - // This variable is in loop. Resolve to invalid. - context.map.remove(&name); - return None; - } - - // Now we have shown that this variable is not in a loop, and all of its - // dependencies should have been resolved. We can start substitution - // now. - let result = substitute_references_in_value(&value, &context.map, &context.device); - match result { - Ok(computed_value) => { - context.map.insert(name, computed_value); - }, - Err(..) => { - // This is invalid, reset it to the guaranteed-invalid value. - context.map.remove(&name); - }, - } - - // All resolved, so return the signal value. - None - } - - // Note that `seen` doesn't contain names inherited from our parent, but - // those can't have variable references (since we inherit the computed - // variables) so we don't want to spend cycles traversing them anyway. - for name in seen { - let mut context = Context { - count: 0, - index_map: PrecomputedHashMap::default(), - stack: SmallVec::new(), - var_info: SmallVec::new(), - map: custom_properties_map, - device, - }; - traverse(name, &mut context); - } -} - -/// Replace `var()` and `env()` functions in a pre-existing variable value. -fn substitute_references_in_value<'i>( - value: &'i VariableValue, - custom_properties: &CustomPropertiesMap, - device: &Device, -) -> Result, ParseError<'i>> { - debug_assert!(!value.references.is_empty() || value.references_environment); - - let mut input = ParserInput::new(&value.css); - let mut input = Parser::new(&mut input); - let mut position = (input.position(), value.first_token_type); - let mut computed_value = ComputedValue::empty(); - - let last_token_type = substitute_block( - &mut input, - &mut position, - &mut computed_value, - custom_properties, - device, - )?; - - computed_value.push_from(&input, position, last_token_type)?; - computed_value.css.shrink_to_fit(); - Ok(Arc::new(computed_value)) -} - -/// Replace `var()` functions in an arbitrary bit of input. -/// -/// If the variable has its initial value, the callback should return `Err(())` -/// and leave `partial_computed_value` unchanged. -/// -/// Otherwise, it should push the value of the variable (with its own `var()` functions replaced) -/// to `partial_computed_value` and return `Ok(last_token_type of what was pushed)` -/// -/// Return `Err(())` if `input` is invalid at computed-value time. -/// or `Ok(last_token_type that was pushed to partial_computed_value)` otherwise. -fn substitute_block<'i>( - input: &mut Parser<'i, '_>, - position: &mut (SourcePosition, TokenSerializationType), - partial_computed_value: &mut ComputedValue, - custom_properties: &CustomPropertiesMap, - device: &Device, -) -> Result> { - let mut last_token_type = TokenSerializationType::nothing(); - let mut set_position_at_next_iteration = false; - loop { - let before_this_token = input.position(); - let next = input.next_including_whitespace_and_comments(); - if set_position_at_next_iteration { - *position = ( - before_this_token, - match next { - Ok(token) => token.serialization_type(), - Err(_) => TokenSerializationType::nothing(), - }, - ); - set_position_at_next_iteration = false; - } - let token = match next { - Ok(token) => token, - Err(..) => break, - }; - match token { - Token::Function(ref name) - if name.eq_ignore_ascii_case("var") || name.eq_ignore_ascii_case("env") => - { - let is_env = name.eq_ignore_ascii_case("env"); - - partial_computed_value.push( - input, - input.slice(position.0..before_this_token), - position.1, - last_token_type, - )?; - input.parse_nested_block(|input| { - // parse_var_function() / parse_env_function() ensure neither .unwrap() will fail. - let name = { - let name = input.expect_ident().unwrap(); - if is_env { - Atom::from(&**name) - } else { - Atom::from(parse_name(&name).unwrap()) - } - }; - - let env_value; - let value = if is_env { - if let Some(v) = device.environment().get(&name, device) { - env_value = v; - Some(&env_value) - } else { - None - } - } else { - custom_properties.get(&name).map(|v| &**v) - }; - - if let Some(v) = value { - last_token_type = v.last_token_type; - partial_computed_value.push_variable(input, v)?; - // Skip over the fallback, as `parse_nested_block` would return `Err` - // if we don't consume all of `input`. - // FIXME: Add a specialized method to cssparser to do this with less work. - while input.next().is_ok() {} - } else { - input.expect_comma()?; - input.skip_whitespace(); - let after_comma = input.state(); - let first_token_type = input - .next_including_whitespace_and_comments() - .ok() - .map_or_else(TokenSerializationType::nothing, |t| { - t.serialization_type() - }); - input.reset(&after_comma); - let mut position = (after_comma.position(), first_token_type); - last_token_type = substitute_block( - input, - &mut position, - partial_computed_value, - custom_properties, - device, - )?; - partial_computed_value.push_from(input, position, last_token_type)?; - } - Ok(()) - })?; - set_position_at_next_iteration = true - }, - Token::Function(_) | - Token::ParenthesisBlock | - Token::CurlyBracketBlock | - Token::SquareBracketBlock => { - input.parse_nested_block(|input| { - substitute_block( - input, - position, - partial_computed_value, - custom_properties, - device, - ) - })?; - // It's the same type for CloseCurlyBracket and CloseSquareBracket. - last_token_type = Token::CloseParenthesis.serialization_type(); - }, - - _ => last_token_type = token.serialization_type(), - } - } - // FIXME: deal with things being implicitly closed at the end of the input. E.g. - // ```html - //
- //

- //
- // ``` - Ok(last_token_type) -} - -/// Replace `var()` and `env()` functions for a non-custom property. -/// -/// Return `Err(())` for invalid at computed time. -pub fn substitute<'i>( - input: &'i str, - first_token_type: TokenSerializationType, - computed_values_map: Option<&Arc>, - device: &Device, -) -> Result> { - let mut substituted = ComputedValue::empty(); - let mut input = ParserInput::new(input); - let mut input = Parser::new(&mut input); - let mut position = (input.position(), first_token_type); - let empty_map = CustomPropertiesMap::default(); - let custom_properties = match computed_values_map { - Some(m) => &**m, - None => &empty_map, - }; - let last_token_type = substitute_block( - &mut input, - &mut position, - &mut substituted, - &custom_properties, - device, - )?; - substituted.push_from(&input, position, last_token_type)?; - Ok(substituted.css) -} diff --git a/components/style/data.rs b/components/style/data.rs deleted file mode 100644 index 62dff225f8f..00000000000 --- a/components/style/data.rs +++ /dev/null @@ -1,545 +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/. */ - -//! Per-node data used in style calculation. - -use crate::computed_value_flags::ComputedValueFlags; -use crate::context::{SharedStyleContext, StackLimitChecker}; -use crate::dom::TElement; -use crate::invalidation::element::invalidator::InvalidationResult; -use crate::invalidation::element::restyle_hints::RestyleHint; -use crate::properties::ComputedValues; -use crate::selector_parser::{PseudoElement, RestyleDamage, EAGER_PSEUDO_COUNT}; -use crate::style_resolver::{PrimaryStyle, ResolvedElementStyles, ResolvedStyle}; -#[cfg(feature = "gecko")] -use malloc_size_of::MallocSizeOfOps; -use selectors::NthIndexCache; -use servo_arc::Arc; -use std::fmt; -use std::mem; -use std::ops::{Deref, DerefMut}; - -bitflags! { - /// Various flags stored on ElementData. - #[derive(Default)] - pub struct ElementDataFlags: u8 { - /// Whether the styles changed for this restyle. - const WAS_RESTYLED = 1 << 0; - /// Whether the last traversal of this element did not do - /// any style computation. This is not true during the initial - /// styling pass, nor is it true when we restyle (in which case - /// WAS_RESTYLED is set). - /// - /// This bit always corresponds to the last time the element was - /// traversed, so each traversal simply updates it with the appropriate - /// value. - const TRAVERSED_WITHOUT_STYLING = 1 << 1; - - /// Whether the primary style of this element data was reused from - /// another element via a rule node comparison. This allows us to - /// differentiate between elements that shared styles because they met - /// all the criteria of the style sharing cache, compared to elements - /// that reused style structs via rule node identity. - /// - /// The former gives us stronger transitive guarantees that allows us to - /// apply the style sharing cache to cousins. - const PRIMARY_STYLE_REUSED_VIA_RULE_NODE = 1 << 2; - } -} - -/// A lazily-allocated list of styles for eagerly-cascaded pseudo-elements. -/// -/// We use an Arc so that sharing these styles via the style sharing cache does -/// not require duplicate allocations. We leverage the copy-on-write semantics of -/// Arc::make_mut(), which is free (i.e. does not require atomic RMU operations) -/// in servo_arc. -#[derive(Clone, Debug, Default)] -pub struct EagerPseudoStyles(Option>); - -#[derive(Default)] -struct EagerPseudoArray(EagerPseudoArrayInner); -type EagerPseudoArrayInner = [Option>; EAGER_PSEUDO_COUNT]; - -impl Deref for EagerPseudoArray { - type Target = EagerPseudoArrayInner; - fn deref(&self) -> &Self::Target { - &self.0 - } -} - -impl DerefMut for EagerPseudoArray { - fn deref_mut(&mut self) -> &mut Self::Target { - &mut self.0 - } -} - -// Manually implement `Clone` here because the derived impl of `Clone` for -// array types assumes the value inside is `Copy`. -impl Clone for EagerPseudoArray { - fn clone(&self) -> Self { - let mut clone = Self::default(); - for i in 0..EAGER_PSEUDO_COUNT { - clone[i] = self.0[i].clone(); - } - clone - } -} - -// Override Debug to print which pseudos we have, and substitute the rule node -// for the much-more-verbose ComputedValues stringification. -impl fmt::Debug for EagerPseudoArray { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!(f, "EagerPseudoArray {{ ")?; - for i in 0..EAGER_PSEUDO_COUNT { - if let Some(ref values) = self[i] { - write!( - f, - "{:?}: {:?}, ", - PseudoElement::from_eager_index(i), - &values.rules - )?; - } - } - write!(f, "}}") - } -} - -// Can't use [None; EAGER_PSEUDO_COUNT] here because it complains -// about Copy not being implemented for our Arc type. -#[cfg(feature = "gecko")] -const EMPTY_PSEUDO_ARRAY: &'static EagerPseudoArrayInner = &[None, None, None, None]; -#[cfg(feature = "servo")] -const EMPTY_PSEUDO_ARRAY: &'static EagerPseudoArrayInner = &[None, None, None]; - -impl EagerPseudoStyles { - /// Returns whether there are any pseudo styles. - pub fn is_empty(&self) -> bool { - self.0.is_none() - } - - /// Grabs a reference to the list of styles, if they exist. - pub fn as_optional_array(&self) -> Option<&EagerPseudoArrayInner> { - match self.0 { - None => None, - Some(ref x) => Some(&x.0), - } - } - - /// Grabs a reference to the list of styles or a list of None if - /// there are no styles to be had. - pub fn as_array(&self) -> &EagerPseudoArrayInner { - self.as_optional_array().unwrap_or(EMPTY_PSEUDO_ARRAY) - } - - /// Returns a reference to the style for a given eager pseudo, if it exists. - pub fn get(&self, pseudo: &PseudoElement) -> Option<&Arc> { - debug_assert!(pseudo.is_eager()); - self.0 - .as_ref() - .and_then(|p| p[pseudo.eager_index()].as_ref()) - } - - /// Sets the style for the eager pseudo. - pub fn set(&mut self, pseudo: &PseudoElement, value: Arc) { - if self.0.is_none() { - self.0 = Some(Arc::new(Default::default())); - } - let arr = Arc::make_mut(self.0.as_mut().unwrap()); - arr[pseudo.eager_index()] = Some(value); - } -} - -/// The styles associated with a node, including the styles for any -/// pseudo-elements. -#[derive(Clone, Default)] -pub struct ElementStyles { - /// The element's style. - pub primary: Option>, - /// A list of the styles for the element's eagerly-cascaded pseudo-elements. - pub pseudos: EagerPseudoStyles, -} - -// There's one of these per rendered elements so it better be small. -size_of_test!(ElementStyles, 16); - -/// Information on how this element uses viewport units. -#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)] -pub enum ViewportUnitUsage { - /// No viewport units are used. - None = 0, - /// There are viewport units used from regular style rules (which means we - /// should re-cascade). - FromDeclaration, - /// There are viewport units used from container queries (which means we - /// need to re-selector-match). - FromQuery, -} - -impl ElementStyles { - /// Returns the primary style. - pub fn get_primary(&self) -> Option<&Arc> { - self.primary.as_ref() - } - - /// Returns the primary style. Panic if no style available. - pub fn primary(&self) -> &Arc { - self.primary.as_ref().unwrap() - } - - /// Whether this element `display` value is `none`. - pub fn is_display_none(&self) -> bool { - self.primary().get_box().clone_display().is_none() - } - - /// Whether this element uses viewport units. - pub fn viewport_unit_usage(&self) -> ViewportUnitUsage { - fn usage_from_flags(flags: ComputedValueFlags) -> ViewportUnitUsage { - if flags.intersects(ComputedValueFlags::USES_VIEWPORT_UNITS_ON_CONTAINER_QUERIES) { - return ViewportUnitUsage::FromQuery; - } - if flags.intersects(ComputedValueFlags::USES_VIEWPORT_UNITS) { - return ViewportUnitUsage::FromDeclaration; - } - ViewportUnitUsage::None - } - - let mut usage = usage_from_flags(self.primary().flags); - for pseudo_style in self.pseudos.as_array() { - if let Some(ref pseudo_style) = pseudo_style { - usage = std::cmp::max(usage, usage_from_flags(pseudo_style.flags)); - } - } - - usage - } - - #[cfg(feature = "gecko")] - fn size_of_excluding_cvs(&self, _ops: &mut MallocSizeOfOps) -> usize { - // As the method name suggests, we don't measures the ComputedValues - // here, because they are measured on the C++ side. - - // XXX: measure the EagerPseudoArray itself, but not the ComputedValues - // within it. - - 0 - } -} - -// We manually implement Debug for ElementStyles so that we can avoid the -// verbose stringification of every property in the ComputedValues. We -// substitute the rule node instead. -impl fmt::Debug for ElementStyles { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!( - f, - "ElementStyles {{ primary: {:?}, pseudos: {:?} }}", - self.primary.as_ref().map(|x| &x.rules), - self.pseudos - ) - } -} - -/// Style system data associated with an Element. -/// -/// In Gecko, this hangs directly off the Element. Servo, this is embedded -/// inside of layout data, which itself hangs directly off the Element. In -/// both cases, it is wrapped inside an AtomicRefCell to ensure thread safety. -#[derive(Debug, Default)] -pub struct ElementData { - /// The styles for the element and its pseudo-elements. - pub styles: ElementStyles, - - /// The restyle damage, indicating what kind of layout changes are required - /// afte restyling. - pub damage: RestyleDamage, - - /// The restyle hint, which indicates whether selectors need to be rematched - /// for this element, its children, and its descendants. - pub hint: RestyleHint, - - /// Flags. - 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 { - /// We need to run selector matching plus re-cascade, that is, a full - /// restyle. - MatchAndCascade, - /// We need to recascade with some replacement rule, such as the style - /// attribute, or animation rules. - CascadeWithReplacements(RestyleHint), - /// We only need to recascade, for example, because only inherited - /// properties in the parent changed. - CascadeOnly, -} - -impl ElementData { - /// Invalidates style for this element, its descendants, and later siblings, - /// based on the snapshot of the element that we took when attributes or - /// state changed. - pub fn invalidate_style_if_needed<'a, E: TElement>( - &mut self, - element: E, - shared_context: &SharedStyleContext, - stack_limit_checker: Option<&StackLimitChecker>, - nth_index_cache: &mut NthIndexCache, - ) -> InvalidationResult { - // In animation-only restyle we shouldn't touch snapshot at all. - if shared_context.traversal_flags.for_animation_only() { - return InvalidationResult::empty(); - } - - use crate::invalidation::element::invalidator::TreeStyleInvalidator; - use crate::invalidation::element::state_and_attributes::StateAndAttrInvalidationProcessor; - - debug!( - "invalidate_style_if_needed: {:?}, flags: {:?}, has_snapshot: {}, \ - handled_snapshot: {}, pseudo: {:?}", - element, - shared_context.traversal_flags, - element.has_snapshot(), - element.handled_snapshot(), - element.implemented_pseudo_element() - ); - - if !element.has_snapshot() || element.handled_snapshot() { - return InvalidationResult::empty(); - } - - let mut processor = - StateAndAttrInvalidationProcessor::new(shared_context, element, self, nth_index_cache); - - let invalidator = TreeStyleInvalidator::new(element, stack_limit_checker, &mut processor); - - let result = invalidator.invalidate(); - - unsafe { element.set_handled_snapshot() } - debug_assert!(element.handled_snapshot()); - - result - } - - /// Returns true if this element has styles. - #[inline] - pub fn has_styles(&self) -> bool { - self.styles.primary.is_some() - } - - /// Returns this element's styles as resolved styles to use for sharing. - pub fn share_styles(&self) -> ResolvedElementStyles { - ResolvedElementStyles { - primary: self.share_primary_style(), - pseudos: self.styles.pseudos.clone(), - } - } - - /// Returns this element's primary style as a resolved style to use for sharing. - pub fn share_primary_style(&self) -> PrimaryStyle { - let reused_via_rule_node = self - .flags - .contains(ElementDataFlags::PRIMARY_STYLE_REUSED_VIA_RULE_NODE); - - PrimaryStyle { - style: ResolvedStyle(self.styles.primary().clone()), - reused_via_rule_node, - } - } - - /// Sets a new set of styles, returning the old ones. - pub fn set_styles(&mut self, new_styles: ResolvedElementStyles) -> ElementStyles { - if new_styles.primary.reused_via_rule_node { - self.flags - .insert(ElementDataFlags::PRIMARY_STYLE_REUSED_VIA_RULE_NODE); - } else { - self.flags - .remove(ElementDataFlags::PRIMARY_STYLE_REUSED_VIA_RULE_NODE); - } - mem::replace(&mut self.styles, new_styles.into()) - } - - /// Returns the kind of restyling that we're going to need to do on this - /// element, based of the stored restyle hint. - pub fn restyle_kind(&self, shared_context: &SharedStyleContext) -> Option { - if shared_context.traversal_flags.for_animation_only() { - return self.restyle_kind_for_animation(shared_context); - } - - let style = match self.styles.primary { - Some(ref s) => s, - None => return Some(RestyleKind::MatchAndCascade), - }; - - let hint = self.hint; - if hint.is_empty() { - return None; - } - - let needs_to_match_self = hint.intersects(RestyleHint::RESTYLE_SELF) || - (hint.intersects(RestyleHint::RESTYLE_SELF_IF_PSEUDO) && style.is_pseudo_style()); - if needs_to_match_self { - return Some(RestyleKind::MatchAndCascade); - } - - if hint.has_replacements() { - debug_assert!( - !hint.has_animation_hint(), - "Animation only restyle hint should have already processed" - ); - return Some(RestyleKind::CascadeWithReplacements( - hint & RestyleHint::replacements(), - )); - } - - let needs_to_recascade_self = hint.intersects(RestyleHint::RECASCADE_SELF) || - (hint.intersects(RestyleHint::RECASCADE_SELF_IF_INHERIT_RESET_STYLE) && - style - .flags - .contains(ComputedValueFlags::INHERITS_RESET_STYLE)); - if needs_to_recascade_self { - return Some(RestyleKind::CascadeOnly); - } - - None - } - - /// Returns the kind of restyling for animation-only restyle. - fn restyle_kind_for_animation( - &self, - shared_context: &SharedStyleContext, - ) -> Option { - debug_assert!(shared_context.traversal_flags.for_animation_only()); - debug_assert!( - self.has_styles(), - "animation traversal doesn't care about unstyled elements" - ); - - // FIXME: We should ideally restyle here, but it is a hack to work around our weird - // animation-only traversal stuff: If we're display: none and the rules we could - // match could change, we consider our style up-to-date. This is because re-cascading with - // and old style doesn't guarantee returning the correct animation style (that's - // bug 1393323). So if our display changed, and it changed from display: none, we would - // incorrectly forget about it and wouldn't be able to correctly style our descendants - // later. - // XXX Figure out if this still makes sense. - let hint = self.hint; - if self.styles.is_display_none() && hint.intersects(RestyleHint::RESTYLE_SELF) { - return None; - } - - let style = self.styles.primary(); - // Return either CascadeWithReplacements or CascadeOnly in case of - // animation-only restyle. I.e. animation-only restyle never does - // selector matching. - if hint.has_animation_hint() { - return Some(RestyleKind::CascadeWithReplacements( - hint & RestyleHint::for_animations(), - )); - } - - let needs_to_recascade_self = hint.intersects(RestyleHint::RECASCADE_SELF) || - (hint.intersects(RestyleHint::RECASCADE_SELF_IF_INHERIT_RESET_STYLE) && - style - .flags - .contains(ComputedValueFlags::INHERITS_RESET_STYLE)); - if needs_to_recascade_self { - return Some(RestyleKind::CascadeOnly); - } - return None; - } - - /// Drops any restyle state from the element. - /// - /// FIXME(bholley): The only caller of this should probably just assert that - /// the hint is empty and call clear_flags_and_damage(). - #[inline] - pub fn clear_restyle_state(&mut self) { - self.hint = RestyleHint::empty(); - self.clear_restyle_flags_and_damage(); - } - - /// Drops restyle flags and damage from the element. - #[inline] - pub fn clear_restyle_flags_and_damage(&mut self) { - self.damage = RestyleDamage::empty(); - self.flags.remove(ElementDataFlags::WAS_RESTYLED); - } - - /// Mark this element as restyled, which is useful to know whether we need - /// to do a post-traversal. - pub fn set_restyled(&mut self) { - self.flags.insert(ElementDataFlags::WAS_RESTYLED); - self.flags - .remove(ElementDataFlags::TRAVERSED_WITHOUT_STYLING); - } - - /// Returns true if this element was restyled. - #[inline] - pub fn is_restyle(&self) -> bool { - self.flags.contains(ElementDataFlags::WAS_RESTYLED) - } - - /// Mark that we traversed this element without computing any style for it. - pub fn set_traversed_without_styling(&mut self) { - self.flags - .insert(ElementDataFlags::TRAVERSED_WITHOUT_STYLING); - } - - /// Returns whether this element has been part of a restyle. - #[inline] - pub fn contains_restyle_data(&self) -> bool { - self.is_restyle() || !self.hint.is_empty() || !self.damage.is_empty() - } - - /// Returns whether it is safe to perform cousin sharing based on the ComputedValues - /// identity of the primary style in this ElementData. There are a few subtle things - /// to check. - /// - /// First, if a parent element was already styled and we traversed past it without - /// restyling it, that may be because our clever invalidation logic was able to prove - /// that the styles of that element would remain unchanged despite changes to the id - /// or class attributes. However, style sharing relies on the strong guarantee that all - /// the classes and ids up the respective parent chains are identical. As such, if we - /// skipped styling for one (or both) of the parents on this traversal, we can't share - /// styles across cousins. Note that this is a somewhat conservative check. We could - /// tighten it by having the invalidation logic explicitly flag elements for which it - /// ellided styling. - /// - /// Second, we want to only consider elements whose ComputedValues match due to a hit - /// in the style sharing cache, rather than due to the rule-node-based reuse that - /// happens later in the styling pipeline. The former gives us the stronger guarantees - /// we need for style sharing, the latter does not. - pub fn safe_for_cousin_sharing(&self) -> bool { - if self.flags.intersects( - ElementDataFlags::TRAVERSED_WITHOUT_STYLING | - ElementDataFlags::PRIMARY_STYLE_REUSED_VIA_RULE_NODE, - ) { - return false; - } - if !self - .styles - .primary() - .get_box() - .clone_container_type() - .is_normal() - { - return false; - } - true - } - - /// Measures memory usage. - #[cfg(feature = "gecko")] - pub fn size_of_excluding_cvs(&self, ops: &mut MallocSizeOfOps) -> usize { - let n = self.styles.size_of_excluding_cvs(ops); - - // We may measure more fields in the future if DMD says it's worth it. - - n - } -} diff --git a/components/style/dom.rs b/components/style/dom.rs deleted file mode 100644 index 7fc29280e69..00000000000 --- a/components/style/dom.rs +++ /dev/null @@ -1,940 +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/. */ - -//! Types and traits used to access the DOM from style calculation. - -#![allow(unsafe_code)] -#![deny(missing_docs)] - -use crate::applicable_declarations::ApplicableDeclarationBlock; -use crate::context::SharedStyleContext; -#[cfg(feature = "gecko")] -use crate::context::{PostAnimationTasks, UpdateAnimationsTasks}; -use crate::data::ElementData; -use crate::media_queries::Device; -use crate::properties::{AnimationDeclarations, ComputedValues, PropertyDeclarationBlock}; -use crate::selector_parser::{AttrValue, Lang, PseudoElement, SelectorImpl}; -use crate::shared_lock::{Locked, SharedRwLock}; -use crate::stylist::CascadeData; -use crate::values::computed::Display; -use crate::values::AtomIdent; -use crate::{LocalName, WeakAtom}; -use atomic_refcell::{AtomicRef, AtomicRefMut}; -use selectors::matching::{QuirksMode, VisitedHandlingMode}; -use selectors::sink::Push; -use selectors::Element as SelectorsElement; -use servo_arc::{Arc, ArcBorrow}; -use std::fmt; -use std::fmt::Debug; -use std::hash::Hash; -use std::ops::Deref; -use style_traits::dom::ElementState; - -pub use style_traits::dom::OpaqueNode; - -/// Simple trait to provide basic information about the type of an element. -/// -/// We avoid exposing the full type id, since computing it in the general case -/// would be difficult for Gecko nodes. -pub trait NodeInfo { - /// Whether this node is an element. - fn is_element(&self) -> bool; - /// Whether this node is a text node. - fn is_text_node(&self) -> bool; -} - -/// A node iterator that only returns node that don't need layout. -pub struct LayoutIterator(pub T); - -impl Iterator for LayoutIterator -where - T: Iterator, - N: NodeInfo, -{ - type Item = N; - - fn next(&mut self) -> Option { - loop { - let n = self.0.next()?; - // Filter out nodes that layout should ignore. - if n.is_text_node() || n.is_element() { - return Some(n); - } - } - } -} - -/// An iterator over the DOM children of a node. -pub struct DomChildren(Option); -impl Iterator for DomChildren -where - N: TNode, -{ - type Item = N; - - fn next(&mut self) -> Option { - let n = self.0.take()?; - self.0 = n.next_sibling(); - Some(n) - } -} - -/// An iterator over the DOM descendants of a node in pre-order. -pub struct DomDescendants { - previous: Option, - scope: N, -} - -impl Iterator for DomDescendants -where - N: TNode, -{ - type Item = N; - - #[inline] - fn next(&mut self) -> Option { - let prev = self.previous.take()?; - self.previous = prev.next_in_preorder(self.scope); - self.previous - } -} - -/// The `TDocument` trait, to represent a document node. -pub trait TDocument: Sized + Copy + Clone { - /// The concrete `TNode` type. - type ConcreteNode: TNode; - - /// Get this document as a `TNode`. - fn as_node(&self) -> Self::ConcreteNode; - - /// Returns whether this document is an HTML document. - fn is_html_document(&self) -> bool; - - /// Returns the quirks mode of this document. - fn quirks_mode(&self) -> QuirksMode; - - /// Get a list of elements with a given ID in this document, sorted by - /// tree position. - /// - /// Can return an error to signal that this list is not available, or also - /// return an empty slice. - fn elements_with_id<'a>( - &self, - _id: &AtomIdent, - ) -> Result<&'a [::ConcreteElement], ()> - where - Self: 'a, - { - Err(()) - } - - /// This document's shared lock. - fn shared_lock(&self) -> &SharedRwLock; -} - -/// The `TNode` trait. This is the main generic trait over which the style -/// system can be implemented. -pub trait TNode: Sized + Copy + Clone + Debug + NodeInfo + PartialEq { - /// The concrete `TElement` type. - type ConcreteElement: TElement; - - /// The concrete `TDocument` type. - type ConcreteDocument: TDocument; - - /// The concrete `TShadowRoot` type. - type ConcreteShadowRoot: TShadowRoot; - - /// Get this node's parent node. - fn parent_node(&self) -> Option; - - /// Get this node's first child. - fn first_child(&self) -> Option; - - /// Get this node's last child. - fn last_child(&self) -> Option; - - /// Get this node's previous sibling. - fn prev_sibling(&self) -> Option; - - /// Get this node's next sibling. - fn next_sibling(&self) -> Option; - - /// Get the owner document of this node. - fn owner_doc(&self) -> Self::ConcreteDocument; - - /// Iterate over the DOM children of a node. - #[inline(always)] - fn dom_children(&self) -> DomChildren { - DomChildren(self.first_child()) - } - - /// Returns whether the node is attached to a document. - fn is_in_document(&self) -> bool; - - /// Iterate over the DOM children of a node, in preorder. - #[inline(always)] - fn dom_descendants(&self) -> DomDescendants { - DomDescendants { - previous: Some(*self), - scope: *self, - } - } - - /// Returns the next node after this one, in a pre-order tree-traversal of - /// the subtree rooted at scoped_to. - #[inline] - fn next_in_preorder(&self, scoped_to: Self) -> Option { - if let Some(c) = self.first_child() { - return Some(c); - } - - let mut current = *self; - loop { - if current == scoped_to { - return None; - } - - if let Some(s) = current.next_sibling() { - return Some(s); - } - - debug_assert!( - current.parent_node().is_some(), - "Not a descendant of the scope?" - ); - current = current.parent_node()?; - } - } - - /// Get this node's parent element from the perspective of a restyle - /// traversal. - fn traversal_parent(&self) -> Option; - - /// Get this node's parent element if present. - fn parent_element(&self) -> Option { - self.parent_node().and_then(|n| n.as_element()) - } - - /// Get this node's parent element, or shadow host if it's a shadow root. - fn parent_element_or_host(&self) -> Option { - let parent = self.parent_node()?; - if let Some(e) = parent.as_element() { - return Some(e); - } - if let Some(root) = parent.as_shadow_root() { - return Some(root.host()); - } - None - } - - /// Converts self into an `OpaqueNode`. - fn opaque(&self) -> OpaqueNode; - - /// A debug id, only useful, mm... for debugging. - fn debug_id(self) -> usize; - - /// Get this node as an element, if it's one. - fn as_element(&self) -> Option; - - /// Get this node as a document, if it's one. - fn as_document(&self) -> Option; - - /// Get this node as a ShadowRoot, if it's one. - fn as_shadow_root(&self) -> Option; -} - -/// Wrapper to output the subtree rather than the single node when formatting -/// for Debug. -pub struct ShowSubtree(pub N); -impl Debug for ShowSubtree { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - writeln!(f, "DOM Subtree:")?; - fmt_subtree(f, &|f, n| write!(f, "{:?}", n), self.0, 1) - } -} - -/// Wrapper to output the subtree along with the ElementData when formatting -/// for Debug. -pub struct ShowSubtreeData(pub N); -impl Debug for ShowSubtreeData { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - writeln!(f, "DOM Subtree:")?; - fmt_subtree(f, &|f, n| fmt_with_data(f, n), self.0, 1) - } -} - -/// Wrapper to output the subtree along with the ElementData and primary -/// ComputedValues when formatting for Debug. This is extremely verbose. -#[cfg(feature = "servo")] -pub struct ShowSubtreeDataAndPrimaryValues(pub N); -#[cfg(feature = "servo")] -impl Debug for ShowSubtreeDataAndPrimaryValues { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - writeln!(f, "DOM Subtree:")?; - fmt_subtree(f, &|f, n| fmt_with_data_and_primary_values(f, n), self.0, 1) - } -} - -fn fmt_with_data(f: &mut fmt::Formatter, n: N) -> fmt::Result { - if let Some(el) = n.as_element() { - write!( - f, - "{:?} dd={} aodd={} data={:?}", - el, - el.has_dirty_descendants(), - el.has_animation_only_dirty_descendants(), - el.borrow_data(), - ) - } else { - write!(f, "{:?}", n) - } -} - -#[cfg(feature = "servo")] -fn fmt_with_data_and_primary_values(f: &mut fmt::Formatter, n: N) -> fmt::Result { - if let Some(el) = n.as_element() { - let dd = el.has_dirty_descendants(); - let aodd = el.has_animation_only_dirty_descendants(); - let data = el.borrow_data(); - let values = data.as_ref().and_then(|d| d.styles.get_primary()); - write!( - f, - "{:?} dd={} aodd={} data={:?} values={:?}", - el, dd, aodd, &data, values - ) - } else { - write!(f, "{:?}", n) - } -} - -fn fmt_subtree(f: &mut fmt::Formatter, stringify: &F, n: N, indent: u32) -> fmt::Result -where - F: Fn(&mut fmt::Formatter, N) -> fmt::Result, -{ - for _ in 0..indent { - write!(f, " ")?; - } - stringify(f, n)?; - if let Some(e) = n.as_element() { - for kid in e.traversal_children() { - writeln!(f, "")?; - fmt_subtree(f, stringify, kid, indent + 1)?; - } - } - - Ok(()) -} - -/// The ShadowRoot trait. -pub trait TShadowRoot: Sized + Copy + Clone + Debug + PartialEq { - /// The concrete node type. - type ConcreteNode: TNode; - - /// Get this ShadowRoot as a node. - fn as_node(&self) -> Self::ConcreteNode; - - /// Get the shadow host that hosts this ShadowRoot. - fn host(&self) -> ::ConcreteElement; - - /// Get the style data for this ShadowRoot. - fn style_data<'a>(&self) -> Option<&'a CascadeData> - where - Self: 'a; - - /// Get the list of shadow parts for this shadow root. - fn parts<'a>(&self) -> &[::ConcreteElement] - where - Self: 'a, - { - &[] - } - - /// Get a list of elements with a given ID in this shadow root, sorted by - /// tree position. - /// - /// Can return an error to signal that this list is not available, or also - /// return an empty slice. - fn elements_with_id<'a>( - &self, - _id: &AtomIdent, - ) -> Result<&'a [::ConcreteElement], ()> - where - Self: 'a, - { - Err(()) - } -} - -/// The element trait, the main abstraction the style crate acts over. -pub trait TElement: - Eq + PartialEq + Debug + Hash + Sized + Copy + Clone + SelectorsElement -{ - /// The concrete node type. - type ConcreteNode: TNode; - - /// A concrete children iterator type in order to iterate over the `Node`s. - /// - /// TODO(emilio): We should eventually replace this with the `impl Trait` - /// syntax. - type TraversalChildrenIterator: Iterator; - - /// Get this element as a node. - fn as_node(&self) -> Self::ConcreteNode; - - /// A debug-only check that the device's owner doc matches the actual doc - /// we're the root of. - /// - /// Otherwise we may set document-level state incorrectly, like the root - /// font-size used for rem units. - fn owner_doc_matches_for_testing(&self, _: &Device) -> bool { - true - } - - /// Whether this element should match user and content rules. - /// - /// We use this for Native Anonymous Content in Gecko. - fn matches_user_and_content_rules(&self) -> bool { - true - } - - /// Returns the depth of this element in the DOM. - fn depth(&self) -> usize { - let mut depth = 0; - let mut curr = *self; - while let Some(parent) = curr.traversal_parent() { - depth += 1; - curr = parent; - } - - depth - } - - /// Get this node's parent element from the perspective of a restyle - /// traversal. - fn traversal_parent(&self) -> Option { - self.as_node().traversal_parent() - } - - /// Get this node's children from the perspective of a restyle traversal. - fn traversal_children(&self) -> LayoutIterator; - - /// Returns the parent element we should inherit from. - /// - /// This is pretty much always the parent element itself, except in the case - /// of Gecko's Native Anonymous Content, which uses the traversal parent - /// (i.e. the flattened tree parent) and which also may need to find the - /// closest non-NAC ancestor. - fn inheritance_parent(&self) -> Option { - self.parent_element() - } - - /// The ::before pseudo-element of this element, if it exists. - fn before_pseudo_element(&self) -> Option { - None - } - - /// The ::after pseudo-element of this element, if it exists. - fn after_pseudo_element(&self) -> Option { - None - } - - /// The ::marker pseudo-element of this element, if it exists. - fn marker_pseudo_element(&self) -> Option { - None - } - - /// Execute `f` for each anonymous content child (apart from ::before and - /// ::after) whose originating element is `self`. - fn each_anonymous_content_child(&self, _f: F) - where - F: FnMut(Self), - { - } - - /// Return whether this element is an element in the HTML namespace. - fn is_html_element(&self) -> bool; - - /// Return whether this element is an element in the MathML namespace. - fn is_mathml_element(&self) -> bool; - - /// Return whether this element is an element in the SVG namespace. - fn is_svg_element(&self) -> bool; - - /// Return whether this element is an element in the XUL namespace. - fn is_xul_element(&self) -> bool { - false - } - - /// Return the list of slotted nodes of this node. - fn slotted_nodes(&self) -> &[Self::ConcreteNode] { - &[] - } - - /// Get this element's style attribute. - fn style_attribute(&self) -> Option>>; - - /// Unset the style attribute's dirty bit. - /// Servo doesn't need to manage ditry bit for style attribute. - fn unset_dirty_style_attribute(&self) {} - - /// Get this element's SMIL override declarations. - fn smil_override(&self) -> Option>> { - None - } - - /// Get the combined animation and transition rules. - /// - /// FIXME(emilio): Is this really useful? - fn animation_declarations(&self, context: &SharedStyleContext) -> AnimationDeclarations { - if !self.may_have_animations() { - return Default::default(); - } - - AnimationDeclarations { - animations: self.animation_rule(context), - transitions: self.transition_rule(context), - } - } - - /// Get this element's animation rule. - fn animation_rule( - &self, - _: &SharedStyleContext, - ) -> Option>>; - - /// Get this element's transition rule. - fn transition_rule( - &self, - context: &SharedStyleContext, - ) -> Option>>; - - /// Get this element's state, for non-tree-structural pseudos. - fn state(&self) -> ElementState; - - /// Returns whether this element has a `part` attribute. - fn has_part_attr(&self) -> bool; - - /// Returns whether this element exports any part from its shadow tree. - fn exports_any_part(&self) -> bool; - - /// The ID for this element. - fn id(&self) -> Option<&WeakAtom>; - - /// Internal iterator for the classes of this element. - fn each_class(&self, callback: F) - where - F: FnMut(&AtomIdent); - - /// Internal iterator for the part names of this element. - fn each_part(&self, _callback: F) - where - F: FnMut(&AtomIdent), - { - } - - /// Internal iterator for the attribute names of this element. - fn each_attr_name(&self, callback: F) - where - F: FnMut(&LocalName); - - /// Internal iterator for the part names that this element exports for a - /// given part name. - fn each_exported_part(&self, _name: &AtomIdent, _callback: F) - where - F: FnMut(&AtomIdent), - { - } - - /// Whether a given element may generate a pseudo-element. - /// - /// This is useful to avoid computing, for example, pseudo styles for - /// `::-first-line` or `::-first-letter`, when we know it won't affect us. - /// - /// TODO(emilio, bz): actually implement the logic for it. - fn may_generate_pseudo(&self, pseudo: &PseudoElement, _primary_style: &ComputedValues) -> bool { - // ::before/::after are always supported for now, though we could try to - // optimize out leaf elements. - - // ::first-letter and ::first-line are only supported for block-inside - // things, and only in Gecko, not Servo. Unfortunately, Gecko has - // block-inside things that might have any computed display value due to - // things like fieldsets, legends, etc. Need to figure out how this - // should work. - debug_assert!( - pseudo.is_eager(), - "Someone called may_generate_pseudo with a non-eager pseudo." - ); - true - } - - /// Returns true if this element may have a descendant needing style processing. - /// - /// Note that we cannot guarantee the existence of such an element, because - /// it may have been removed from the DOM between marking it for restyle and - /// the actual restyle traversal. - fn has_dirty_descendants(&self) -> bool; - - /// Returns whether state or attributes that may change style have changed - /// on the element, and thus whether the element has been snapshotted to do - /// restyle hint computation. - fn has_snapshot(&self) -> bool; - - /// Returns whether the current snapshot if present has been handled. - fn handled_snapshot(&self) -> bool; - - /// Flags this element as having handled already its snapshot. - unsafe fn set_handled_snapshot(&self); - - /// Returns whether the element's styles are up-to-date after traversal - /// (i.e. in post traversal). - fn has_current_styles(&self, data: &ElementData) -> bool { - if self.has_snapshot() && !self.handled_snapshot() { - return false; - } - - data.has_styles() && - // TODO(hiro): When an animating element moved into subtree of - // contenteditable element, there remains animation restyle hints in - // post traversal. It's generally harmless since the hints will be - // processed in a next styling but ideally it should be processed soon. - // - // Without this, we get failures in: - // layout/style/crashtests/1383319.html - // layout/style/crashtests/1383001.html - // - // https://bugzilla.mozilla.org/show_bug.cgi?id=1389675 tracks fixing - // this. - !data.hint.has_non_animation_invalidations() - } - - /// Flag that this element has a descendant for style processing. - /// - /// Only safe to call with exclusive access to the element. - unsafe fn set_dirty_descendants(&self); - - /// Flag that this element has no descendant for style processing. - /// - /// Only safe to call with exclusive access to the element. - unsafe fn unset_dirty_descendants(&self); - - /// Similar to the dirty_descendants but for representing a descendant of - /// the element needs to be updated in animation-only traversal. - fn has_animation_only_dirty_descendants(&self) -> bool { - false - } - - /// Flag that this element has a descendant for animation-only restyle - /// processing. - /// - /// Only safe to call with exclusive access to the element. - unsafe fn set_animation_only_dirty_descendants(&self) {} - - /// Flag that this element has no descendant for animation-only restyle processing. - /// - /// Only safe to call with exclusive access to the element. - unsafe fn unset_animation_only_dirty_descendants(&self) {} - - /// Clear all bits related describing the dirtiness of descendants. - /// - /// In Gecko, this corresponds to the regular dirty descendants bit, the - /// animation-only dirty descendants bit, and the lazy frame construction - /// descendants bit. - unsafe fn clear_descendant_bits(&self) { - self.unset_dirty_descendants(); - } - - /// Returns true if this element is a visited link. - /// - /// Servo doesn't support visited styles yet. - fn is_visited_link(&self) -> bool { - false - } - - /// Returns the pseudo-element implemented by this element, if any. - /// - /// Gecko traverses pseudo-elements during the style traversal, and we need - /// to know this so we can properly grab the pseudo-element style from the - /// parent element. - /// - /// Note that we still need to compute the pseudo-elements before-hand, - /// given otherwise we don't know if we need to create an element or not. - /// - /// Servo doesn't have to deal with this. - fn implemented_pseudo_element(&self) -> Option { - None - } - - /// Atomically stores the number of children of this node that we will - /// need to process during bottom-up traversal. - fn store_children_to_process(&self, n: isize); - - /// Atomically notes that a child has been processed during bottom-up - /// traversal. Returns the number of children left to process. - fn did_process_child(&self) -> isize; - - /// Gets a reference to the ElementData container, or creates one. - /// - /// Unsafe because it can race to allocate and leak if not used with - /// exclusive access to the element. - unsafe fn ensure_data(&self) -> AtomicRefMut; - - /// Clears the element data reference, if any. - /// - /// Unsafe following the same reasoning as ensure_data. - unsafe fn clear_data(&self); - - /// Whether there is an ElementData container. - fn has_data(&self) -> bool; - - /// Immutably borrows the ElementData. - fn borrow_data(&self) -> Option>; - - /// Mutably borrows the ElementData. - fn mutate_data(&self) -> Option>; - - /// Whether we should skip any root- or item-based display property - /// blockification on this element. (This function exists so that Gecko - /// native anonymous content can opt out of this style fixup.) - fn skip_item_display_fixup(&self) -> 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. - fn may_have_animations(&self) -> bool; - - /// Creates a task to update various animation state on a given (pseudo-)element. - #[cfg(feature = "gecko")] - fn update_animations( - &self, - before_change_style: Option>, - tasks: UpdateAnimationsTasks, - ); - - /// Creates a task to process post animation on a given element. - #[cfg(feature = "gecko")] - fn process_post_animation(&self, tasks: PostAnimationTasks); - - /// Returns true if the element has relevant animations. Relevant - /// animations are those animations that are affecting the element's style - /// or are scheduled to do so in the future. - fn has_animations(&self, context: &SharedStyleContext) -> bool; - - /// Returns true if the element has a CSS animation. The `context` and `pseudo_element` - /// arguments are only used by Servo, since it stores animations globally and pseudo-elements - /// are not in the DOM. - fn has_css_animations( - &self, - context: &SharedStyleContext, - pseudo_element: Option, - ) -> bool; - - /// Returns true if the element has a CSS transition (including running transitions and - /// completed transitions). The `context` and `pseudo_element` arguments are only used - /// by Servo, since it stores animations globally and pseudo-elements are not in the DOM. - fn has_css_transitions( - &self, - context: &SharedStyleContext, - pseudo_element: Option, - ) -> bool; - - /// Returns true if the element has animation restyle hints. - fn has_animation_restyle_hints(&self) -> bool { - let data = match self.borrow_data() { - Some(d) => d, - None => return false, - }; - return data.hint.has_animation_hint(); - } - - /// The shadow root this element is a host of. - fn shadow_root(&self) -> Option<::ConcreteShadowRoot>; - - /// The shadow root which roots the subtree this element is contained in. - fn containing_shadow(&self) -> Option<::ConcreteShadowRoot>; - - /// Return the element which we can use to look up rules in the selector - /// maps. - /// - /// This is always the element itself, except in the case where we are an - /// element-backed pseudo-element, in which case we return the originating - /// element. - fn rule_hash_target(&self) -> Self { - if self.is_pseudo_element() { - self.pseudo_element_originating_element() - .expect("Trying to collect rules for a detached pseudo-element") - } else { - *self - } - } - - /// Executes the callback for each applicable style rule data which isn't - /// the main document's data (which stores UA / author rules). - /// - /// The element passed to the callback is the containing shadow host for the - /// data if it comes from Shadow DOM. - /// - /// Returns whether normal document author rules should apply. - /// - /// TODO(emilio): We could separate the invalidation data for elements - /// matching in other scopes to avoid over-invalidation. - fn each_applicable_non_document_style_rule_data<'a, F>(&self, mut f: F) -> bool - where - Self: 'a, - F: FnMut(&'a CascadeData, Self), - { - use crate::rule_collector::containing_shadow_ignoring_svg_use; - - let target = self.rule_hash_target(); - let matches_user_and_content_rules = target.matches_user_and_content_rules(); - let mut doc_rules_apply = matches_user_and_content_rules; - - // Use the same rules to look for the containing host as we do for rule - // collection. - if let Some(shadow) = containing_shadow_ignoring_svg_use(target) { - doc_rules_apply = false; - if let Some(data) = shadow.style_data() { - f(data, shadow.host()); - } - } - - if let Some(shadow) = target.shadow_root() { - if let Some(data) = shadow.style_data() { - f(data, shadow.host()); - } - } - - let mut current = target.assigned_slot(); - while let Some(slot) = current { - // Slots can only have assigned nodes when in a shadow tree. - let shadow = slot.containing_shadow().unwrap(); - if let Some(data) = shadow.style_data() { - if data.any_slotted_rule() { - f(data, shadow.host()); - } - } - current = slot.assigned_slot(); - } - - if target.has_part_attr() { - if let Some(mut inner_shadow) = target.containing_shadow() { - loop { - let inner_shadow_host = inner_shadow.host(); - match inner_shadow_host.containing_shadow() { - Some(shadow) => { - if let Some(data) = shadow.style_data() { - if data.any_part_rule() { - f(data, shadow.host()) - } - } - // TODO: Could be more granular. - if !inner_shadow_host.exports_any_part() { - break; - } - inner_shadow = shadow; - }, - None => { - // TODO(emilio): Should probably distinguish with - // MatchesDocumentRules::{No,Yes,IfPart} or something so that we could - // skip some work. - doc_rules_apply = matches_user_and_content_rules; - break; - }, - } - } - } - } - - doc_rules_apply - } - - /// Returns true if one of the transitions needs to be updated on this element. We check all - /// the transition properties to make sure that updating transitions is necessary. - /// This method should only be called if might_needs_transitions_update returns true when - /// passed the same parameters. - #[cfg(feature = "gecko")] - fn needs_transitions_update( - &self, - before_change_style: &ComputedValues, - after_change_style: &ComputedValues, - ) -> bool; - - /// Returns the value of the `xml:lang=""` attribute (or, if appropriate, - /// the `lang=""` attribute) on this element. - fn lang_attr(&self) -> Option; - - /// Returns whether this element's language matches the language tag - /// `value`. If `override_lang` is not `None`, it specifies the value - /// of the `xml:lang=""` or `lang=""` attribute to use in place of - /// looking at the element and its ancestors. (This argument is used - /// to implement matching of `:lang()` against snapshots.) - fn match_element_lang(&self, override_lang: Option>, value: &Lang) -> bool; - - /// Returns whether this element is the main body element of the HTML - /// document it is on. - fn is_html_document_body_element(&self) -> bool; - - /// Generate the proper applicable declarations due to presentational hints, - /// and insert them into `hints`. - fn synthesize_presentational_hints_for_legacy_attributes( - &self, - visited_handling: VisitedHandlingMode, - hints: &mut V, - ) where - V: Push; - - /// Returns element's local name. - fn local_name(&self) -> &::BorrowedLocalName; - - /// Returns element's namespace. - fn namespace(&self) - -> &::BorrowedNamespaceUrl; - - /// Returns the size of the element to be used in container size queries. - /// This will usually be the size of the content area of the primary box, - /// but can be None if there is no box or if some axis lacks size containment. - fn query_container_size( - &self, - display: &Display, - ) -> euclid::default::Size2D>; -} - -/// TNode and TElement aren't Send because we want to be careful and explicit -/// about our parallel traversal. However, there are certain situations -/// (including but not limited to the traversal) where we need to send DOM -/// objects to other threads. -/// -/// That's the reason why `SendNode` exists. -#[derive(Clone, Debug, PartialEq)] -pub struct SendNode(N); -unsafe impl Send for SendNode {} -impl SendNode { - /// Unsafely construct a SendNode. - pub unsafe fn new(node: N) -> Self { - SendNode(node) - } -} -impl Deref for SendNode { - type Target = N; - fn deref(&self) -> &N { - &self.0 - } -} - -/// Same reason as for the existence of SendNode, SendElement does the proper -/// things for a given `TElement`. -#[derive(Debug, Eq, Hash, PartialEq)] -pub struct SendElement(E); -unsafe impl Send for SendElement {} -impl SendElement { - /// Unsafely construct a SendElement. - pub unsafe fn new(el: E) -> Self { - SendElement(el) - } -} -impl Deref for SendElement { - type Target = E; - fn deref(&self) -> &E { - &self.0 - } -} diff --git a/components/style/dom_apis.rs b/components/style/dom_apis.rs deleted file mode 100644 index da5dc387f73..00000000000 --- a/components/style/dom_apis.rs +++ /dev/null @@ -1,767 +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/. */ - -//! Generic implementations of some DOM APIs so they can be shared between Servo -//! and Gecko. - -use crate::context::QuirksMode; -use crate::dom::{TDocument, TElement, TNode, TShadowRoot}; -use crate::invalidation::element::invalidation_map::Dependency; -use crate::invalidation::element::invalidator::{DescendantInvalidationLists, Invalidation}; -use crate::invalidation::element::invalidator::{InvalidationProcessor, InvalidationVector}; -use crate::selector_parser::SelectorImpl; -use crate::values::AtomIdent; -use selectors::attr::CaseSensitivity; -use selectors::matching::{self, MatchingContext, MatchingMode, NeedsSelectorFlags}; -use selectors::parser::{Combinator, Component, LocalName}; -use selectors::{Element, SelectorList}; -use smallvec::SmallVec; - -/// -pub fn element_matches( - element: &E, - selector_list: &SelectorList, - quirks_mode: QuirksMode, -) -> bool -where - E: Element, -{ - let mut nth_index_cache = Default::default(); - - let mut context = MatchingContext::new( - MatchingMode::Normal, - None, - &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()); - matching::matches_selector_list(selector_list, element, &mut context) -} - -/// -pub fn element_closest( - element: E, - selector_list: &SelectorList, - quirks_mode: QuirksMode, -) -> Option -where - E: Element, -{ - let mut nth_index_cache = Default::default(); - - let mut context = MatchingContext::new( - MatchingMode::Normal, - None, - &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()); - - let mut current = Some(element); - while let Some(element) = current.take() { - if matching::matches_selector_list(selector_list, &element, &mut context) { - return Some(element); - } - current = element.parent_element(); - } - - return None; -} - -/// A selector query abstraction, in order to be generic over QuerySelector and -/// QuerySelectorAll. -pub trait SelectorQuery { - /// The output of the query. - type Output; - - /// Whether the query should stop after the first element has been matched. - fn should_stop_after_first_match() -> bool; - - /// Append an element matching after the first query. - fn append_element(output: &mut Self::Output, element: E); - - /// Returns true if the output is empty. - fn is_empty(output: &Self::Output) -> bool; -} - -/// The result of a querySelectorAll call. -pub type QuerySelectorAllResult = SmallVec<[E; 128]>; - -/// A query for all the elements in a subtree. -pub struct QueryAll; - -impl SelectorQuery for QueryAll { - type Output = QuerySelectorAllResult; - - fn should_stop_after_first_match() -> bool { - false - } - - fn append_element(output: &mut Self::Output, element: E) { - output.push(element); - } - - fn is_empty(output: &Self::Output) -> bool { - output.is_empty() - } -} - -/// A query for the first in-tree match of all the elements in a subtree. -pub struct QueryFirst; - -impl SelectorQuery for QueryFirst { - type Output = Option; - - fn should_stop_after_first_match() -> bool { - true - } - - fn append_element(output: &mut Self::Output, element: E) { - if output.is_none() { - *output = Some(element) - } - } - - fn is_empty(output: &Self::Output) -> bool { - output.is_none() - } -} - -struct QuerySelectorProcessor<'a, E, Q> -where - E: TElement + 'a, - Q: SelectorQuery, - Q::Output: 'a, -{ - results: &'a mut Q::Output, - matching_context: MatchingContext<'a, E::Impl>, - dependencies: &'a [Dependency], -} - -impl<'a, E, Q> InvalidationProcessor<'a, E> for QuerySelectorProcessor<'a, E, Q> -where - E: TElement + 'a, - Q: SelectorQuery, - Q::Output: 'a, -{ - fn light_tree_only(&self) -> bool { - true - } - - fn check_outer_dependency(&mut self, _: &Dependency, _: E) -> bool { - debug_assert!( - false, - "How? We should only have parent-less dependencies here!" - ); - true - } - - fn collect_invalidations( - &mut self, - element: E, - self_invalidations: &mut InvalidationVector<'a>, - descendant_invalidations: &mut DescendantInvalidationLists<'a>, - _sibling_invalidations: &mut InvalidationVector<'a>, - ) -> bool { - // TODO(emilio): If the element is not a root element, and - // selector_list has any descendant combinator, we need to do extra work - // in order to handle properly things like: - // - //
- //
- //
- //
- //
- // - // b.querySelector('#a div'); // Should return "c". - // - // For now, assert it's a root element. - debug_assert!(element.parent_element().is_none()); - - let target_vector = if self.matching_context.scope_element.is_some() { - &mut descendant_invalidations.dom_descendants - } else { - self_invalidations - }; - - for dependency in self.dependencies.iter() { - target_vector.push(Invalidation::new( - dependency, - self.matching_context.current_host.clone(), - )) - } - - false - } - - fn matching_context(&mut self) -> &mut MatchingContext<'a, E::Impl> { - &mut self.matching_context - } - - fn should_process_descendants(&mut self, _: E) -> bool { - if Q::should_stop_after_first_match() { - return Q::is_empty(&self.results); - } - - true - } - - fn invalidated_self(&mut self, e: E) { - Q::append_element(self.results, e); - } - - fn invalidated_sibling(&mut self, e: E, _of: E) { - Q::append_element(self.results, e); - } - - fn recursion_limit_exceeded(&mut self, _e: E) {} - fn invalidated_descendants(&mut self, _e: E, _child: E) {} -} - -fn collect_all_elements(root: E::ConcreteNode, results: &mut Q::Output, mut filter: F) -where - E: TElement, - Q: SelectorQuery, - F: FnMut(E) -> bool, -{ - for node in root.dom_descendants() { - let element = match node.as_element() { - Some(e) => e, - None => continue, - }; - - if !filter(element) { - continue; - } - - Q::append_element(results, element); - if Q::should_stop_after_first_match() { - return; - } - } -} - -/// Returns whether a given element connected to `root` is descendant of `root`. -/// -/// NOTE(emilio): if root == element, this returns false. -fn connected_element_is_descendant_of(element: E, root: E::ConcreteNode) -> bool -where - E: TElement, -{ - // Optimize for when the root is a document or a shadow root and the element - // is connected to that root. - if root.as_document().is_some() { - debug_assert!(element.as_node().is_in_document(), "Not connected?"); - debug_assert_eq!( - root, - root.owner_doc().as_node(), - "Where did this element come from?", - ); - return true; - } - - if root.as_shadow_root().is_some() { - debug_assert_eq!( - element.containing_shadow().unwrap().as_node(), - root, - "Not connected?" - ); - return true; - } - - let mut current = element.as_node().parent_node(); - while let Some(n) = current.take() { - if n == root { - return true; - } - - current = n.parent_node(); - } - false -} - -/// Fast path for iterating over every element with a given id in the document -/// or shadow root that `root` is connected to. -fn fast_connected_elements_with_id<'a, N>( - root: N, - id: &AtomIdent, - case_sensitivity: CaseSensitivity, -) -> Result<&'a [N::ConcreteElement], ()> -where - N: TNode + 'a, -{ - if case_sensitivity != CaseSensitivity::CaseSensitive { - return Err(()); - } - - if root.is_in_document() { - return root.owner_doc().elements_with_id(id); - } - - if let Some(shadow) = root.as_shadow_root() { - return shadow.elements_with_id(id); - } - - if let Some(shadow) = root.as_element().and_then(|e| e.containing_shadow()) { - return shadow.elements_with_id(id); - } - - Err(()) -} - -/// Collects elements with a given id under `root`, that pass `filter`. -fn collect_elements_with_id( - root: E::ConcreteNode, - id: &AtomIdent, - results: &mut Q::Output, - class_and_id_case_sensitivity: CaseSensitivity, - mut filter: F, -) where - E: TElement, - Q: SelectorQuery, - F: FnMut(E) -> bool, -{ - let elements = match fast_connected_elements_with_id(root, id, class_and_id_case_sensitivity) { - Ok(elements) => elements, - Err(()) => { - collect_all_elements::(root, results, |e| { - e.has_id(id, class_and_id_case_sensitivity) && filter(e) - }); - - return; - }, - }; - - for element in elements { - // If the element is not an actual descendant of the root, even though - // it's connected, we don't really care about it. - if !connected_element_is_descendant_of(*element, root) { - continue; - } - - if !filter(*element) { - continue; - } - - Q::append_element(results, *element); - if Q::should_stop_after_first_match() { - break; - } - } -} - -fn has_attr(element: E, local_name: &crate::LocalName) -> bool -where - E: TElement, -{ - let mut found = false; - element.each_attr_name(|name| found |= name == local_name); - found -} - -#[inline(always)] -fn local_name_matches(element: E, local_name: &LocalName) -> bool -where - E: TElement, -{ - let LocalName { - ref name, - ref lower_name, - } = *local_name; - - let chosen_name = if name == lower_name || element.is_html_element_in_html_document() { - lower_name - } else { - name - }; - - element.local_name() == &**chosen_name -} - -fn get_attr_name(component: &Component) -> Option<&crate::LocalName> { - let (name, name_lower) = match component { - Component::AttributeInNoNamespace { ref local_name, .. } => return Some(local_name), - Component::AttributeInNoNamespaceExists { - ref local_name, - ref local_name_lower, - .. - } => (local_name, local_name_lower), - Component::AttributeOther(ref attr) => (&attr.local_name, &attr.local_name_lower), - _ => return None, - }; - if name != name_lower { - return None; // TODO: Maybe optimize this? - } - Some(name) -} - -fn get_id(component: &Component) -> Option<&AtomIdent> { - use selectors::attr::AttrSelectorOperator; - Some(match component { - Component::ID(ref id) => id, - Component::AttributeInNoNamespace { - ref operator, - ref local_name, - ref value, - .. - } => { - if *local_name != local_name!("id") { - return None; - } - if *operator != AttrSelectorOperator::Equal { - return None; - } - AtomIdent::cast(&value.0) - }, - _ => return None, - }) -} - -/// Fast paths for querySelector with a single simple selector. -fn query_selector_single_query( - root: E::ConcreteNode, - component: &Component, - results: &mut Q::Output, - class_and_id_case_sensitivity: CaseSensitivity, -) -> Result<(), ()> -where - E: TElement, - Q: SelectorQuery, -{ - // TODO: Maybe we could implement a fast path for [name=""]? - match *component { - Component::ExplicitUniversalType => { - collect_all_elements::(root, results, |_| true) - }, - Component::Class(ref class) => collect_all_elements::(root, results, |element| { - element.has_class(class, class_and_id_case_sensitivity) - }), - Component::LocalName(ref local_name) => { - collect_all_elements::(root, results, |element| { - local_name_matches(element, local_name) - }) - }, - ref other => { - let id = match get_id(other) { - Some(id) => id, - // TODO(emilio): More fast paths? - None => return Err(()), - }; - collect_elements_with_id::( - root, - id, - results, - class_and_id_case_sensitivity, - |_| true, - ); - }, - } - - Ok(()) -} - -enum SimpleFilter<'a> { - Class(&'a AtomIdent), - Attr(&'a crate::LocalName), - LocalName(&'a LocalName), -} - -/// Fast paths for a given selector query. -/// -/// When there's only one component, we go directly to -/// `query_selector_single_query`, otherwise, we try to optimize by looking just -/// at the subtrees rooted at ids in the selector, and otherwise we try to look -/// up by class name or local name in the rightmost compound. -/// -/// FIXME(emilio, nbp): This may very well be a good candidate for code to be -/// replaced by HolyJit :) -fn query_selector_fast( - root: E::ConcreteNode, - selector_list: &SelectorList, - results: &mut Q::Output, - matching_context: &mut MatchingContext, -) -> Result<(), ()> -where - E: TElement, - Q: SelectorQuery, -{ - // We need to return elements in document order, and reordering them - // afterwards is kinda silly. - if selector_list.0.len() > 1 { - return Err(()); - } - - let selector = &selector_list.0[0]; - let class_and_id_case_sensitivity = matching_context.classes_and_ids_case_sensitivity(); - // Let's just care about the easy cases for now. - if selector.len() == 1 { - if query_selector_single_query::( - root, - selector.iter().next().unwrap(), - results, - class_and_id_case_sensitivity, - ) - .is_ok() - { - return Ok(()); - } - } - - let mut iter = selector.iter(); - let mut combinator: Option = None; - - // We want to optimize some cases where there's no id involved whatsoever, - // like `.foo .bar`, but we don't want to make `#foo .bar` slower because of - // that. - let mut simple_filter = None; - - 'selector_loop: loop { - debug_assert!(combinator.map_or(true, |c| !c.is_sibling())); - - 'component_loop: for component in &mut iter { - match *component { - Component::Class(ref class) => { - if combinator.is_none() { - simple_filter = Some(SimpleFilter::Class(class)); - } - }, - Component::LocalName(ref local_name) => { - if combinator.is_none() { - // Prefer to look at class rather than local-name if - // both are present. - if let Some(SimpleFilter::Class(..)) = simple_filter { - continue; - } - simple_filter = Some(SimpleFilter::LocalName(local_name)); - } - }, - ref other => { - if let Some(id) = get_id(other) { - if combinator.is_none() { - // In the rightmost compound, just find descendants of root that match - // the selector list with that id. - collect_elements_with_id::( - root, - id, - results, - class_and_id_case_sensitivity, - |e| { - matching::matches_selector_list( - selector_list, - &e, - matching_context, - ) - }, - ); - return Ok(()); - } - - let elements = fast_connected_elements_with_id( - root, - id, - class_and_id_case_sensitivity, - )?; - if elements.is_empty() { - return Ok(()); - } - - // Results need to be in document order. Let's not bother - // reordering or deduplicating nodes, which we would need to - // do if one element with the given id were a descendant of - // another element with that given id. - if !Q::should_stop_after_first_match() && elements.len() > 1 { - continue; - } - - for element in elements { - // If the element is not a descendant of the root, then - // it may have descendants that match our selector that - // _are_ descendants of the root, and other descendants - // that match our selector that are _not_. - // - // So we can't just walk over the element's descendants - // and match the selector against all of them, nor can - // we skip looking at this element's descendants. - // - // Give up on trying to optimize based on this id and - // keep walking our selector. - if !connected_element_is_descendant_of(*element, root) { - continue 'component_loop; - } - - query_selector_slow::( - element.as_node(), - selector_list, - results, - matching_context, - ); - - if Q::should_stop_after_first_match() && !Q::is_empty(&results) { - break; - } - } - - return Ok(()); - } - if combinator.is_none() && simple_filter.is_none() { - if let Some(attr_name) = get_attr_name(other) { - simple_filter = Some(SimpleFilter::Attr(attr_name)); - } - } - }, - } - } - - loop { - let next_combinator = match iter.next_sequence() { - None => break 'selector_loop, - Some(c) => c, - }; - - // We don't want to scan stuff affected by sibling combinators, - // given we scan the subtree of elements with a given id (and we - // don't want to care about scanning the siblings' subtrees). - if next_combinator.is_sibling() { - // Advance to the next combinator. - for _ in &mut iter {} - continue; - } - - combinator = Some(next_combinator); - break; - } - } - - // We got here without finding any ID or such that we could handle. Try to - // use one of the simple filters. - let simple_filter = match simple_filter { - Some(f) => f, - None => return Err(()), - }; - - match simple_filter { - SimpleFilter::Class(ref class) => { - collect_all_elements::(root, results, |element| { - element.has_class(class, class_and_id_case_sensitivity) && - matching::matches_selector_list(selector_list, &element, matching_context) - }); - }, - SimpleFilter::LocalName(ref local_name) => { - collect_all_elements::(root, results, |element| { - local_name_matches(element, local_name) && - matching::matches_selector_list(selector_list, &element, matching_context) - }); - }, - SimpleFilter::Attr(ref local_name) => { - collect_all_elements::(root, results, |element| { - has_attr(element, local_name) && - matching::matches_selector_list(selector_list, &element, matching_context) - }); - }, - } - - Ok(()) -} - -// Slow path for a given selector query. -fn query_selector_slow( - root: E::ConcreteNode, - selector_list: &SelectorList, - results: &mut Q::Output, - matching_context: &mut MatchingContext, -) where - E: TElement, - Q: SelectorQuery, -{ - collect_all_elements::(root, results, |element| { - matching::matches_selector_list(selector_list, &element, matching_context) - }); -} - -/// Whether the invalidation machinery should be used for this query. -#[derive(PartialEq)] -pub enum MayUseInvalidation { - /// We may use it if we deem it useful. - Yes, - /// Don't use it. - No, -} - -/// -pub fn query_selector( - root: E::ConcreteNode, - selector_list: &SelectorList, - results: &mut Q::Output, - may_use_invalidation: MayUseInvalidation, -) where - E: TElement, - Q: SelectorQuery, -{ - use crate::invalidation::element::invalidator::TreeStyleInvalidator; - - let mut nth_index_cache = Default::default(); - let quirks_mode = root.owner_doc().quirks_mode(); - - let mut matching_context = MatchingContext::new( - MatchingMode::Normal, - None, - &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 { - Some(root) => root.containing_shadow_host().map(|host| host.opaque()), - None => root.as_shadow_root().map(|root| root.host().opaque()), - }; - - let fast_result = - query_selector_fast::(root, selector_list, results, &mut matching_context); - - if fast_result.is_ok() { - return; - } - - // Slow path: Use the invalidation machinery if we're a root, and tree - // traversal otherwise. - // - // See the comment in collect_invalidations to see why only if we're a root. - // - // The invalidation mechanism is only useful in presence of combinators. - // - // We could do that check properly here, though checking the length of the - // selectors is a good heuristic. - // - // A selector with a combinator needs to have a length of at least 3: A - // simple selector, a combinator, and another simple selector. - let invalidation_may_be_useful = may_use_invalidation == MayUseInvalidation::Yes && - selector_list.0.iter().any(|s| s.len() > 2); - - if root_element.is_some() || !invalidation_may_be_useful { - query_selector_slow::(root, selector_list, results, &mut matching_context); - } else { - let dependencies = selector_list - .0 - .iter() - .map(|selector| Dependency::for_full_selector_invalidation(selector.clone())) - .collect::>(); - let mut processor = QuerySelectorProcessor:: { - results, - matching_context, - dependencies: &dependencies, - }; - - for node in root.dom_children() { - if let Some(e) = node.as_element() { - TreeStyleInvalidator::new(e, /* stack_limit_checker = */ None, &mut processor) - .invalidate(); - } - } - } -} diff --git a/components/style/driver.rs b/components/style/driver.rs deleted file mode 100644 index cf48d831fdd..00000000000 --- a/components/style/driver.rs +++ /dev/null @@ -1,161 +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/. */ - -//! Implements traversal over the DOM tree. The traversal starts in sequential -//! mode, and optionally parallelizes as it discovers work. - -#![deny(missing_docs)] - -use crate::context::{PerThreadTraversalStatistics, StyleContext}; -use crate::context::{ThreadLocalStyleContext, TraversalStatistics}; -use crate::dom::{SendNode, TElement, TNode}; -use crate::parallel; -use crate::scoped_tls::ScopedTLS; -use crate::traversal::{DomTraversal, PerLevelTraversalData, PreTraverseToken}; -use rayon; -use std::collections::VecDeque; -use std::mem; -use time; - -#[cfg(feature = "servo")] -fn should_report_statistics() -> bool { - false -} - -#[cfg(feature = "gecko")] -fn should_report_statistics() -> bool { - unsafe { crate::gecko_bindings::structs::ServoTraversalStatistics_sActive } -} - -#[cfg(feature = "servo")] -fn report_statistics(_stats: &PerThreadTraversalStatistics) { - unreachable!("Servo never report stats"); -} - -#[cfg(feature = "gecko")] -fn report_statistics(stats: &PerThreadTraversalStatistics) { - // This should only be called in the main thread, or it may be racy - // to update the statistics in a global variable. - debug_assert!(unsafe { crate::gecko_bindings::bindings::Gecko_IsMainThread() }); - let gecko_stats = - unsafe { &mut crate::gecko_bindings::structs::ServoTraversalStatistics_sSingleton }; - gecko_stats.mElementsTraversed += stats.elements_traversed; - gecko_stats.mElementsStyled += stats.elements_styled; - gecko_stats.mElementsMatched += stats.elements_matched; - gecko_stats.mStylesShared += stats.styles_shared; - gecko_stats.mStylesReused += stats.styles_reused; -} - -fn with_pool_in_place_scope<'scope, R>( - work_unit_max: usize, - pool: Option<&rayon::ThreadPool>, - closure: impl FnOnce(Option<&rayon::ScopeFifo<'scope>>) -> R, -) -> R { - if work_unit_max == 0 || pool.is_none() { - closure(None) - } else { - pool.unwrap().in_place_scope_fifo(|scope| { - closure(Some(scope)) - }) - } -} - -/// See documentation of the pref for performance characteristics. -fn work_unit_max() -> usize { - static_prefs::pref!("layout.css.stylo-work-unit-size") as usize -} - -/// Do a DOM traversal for top-down and (optionally) bottom-up processing, generic over `D`. -/// -/// We use an adaptive traversal strategy. We start out with simple sequential processing, until we -/// arrive at a wide enough level in the DOM that the parallel traversal would parallelize it. -/// If a thread pool is provided, we then transfer control over to the parallel traversal. -/// -/// Returns true if the traversal was parallel, and also returns the statistics object containing -/// information on nodes traversed (on nightly only). Not all of its fields will be initialized -/// since we don't call finish(). -pub fn traverse_dom( - traversal: &D, - token: PreTraverseToken, - pool: Option<&rayon::ThreadPool>, -) -> E -where - E: TElement, - D: DomTraversal, -{ - let root = token - .traversal_root() - .expect("Should've ensured we needed to traverse"); - - let report_stats = should_report_statistics(); - let dump_stats = traversal.shared_context().options.dump_style_statistics; - let start_time = if dump_stats { - Some(time::precise_time_s()) - } else { - None - }; - - // Declare the main-thread context, as well as the worker-thread contexts, - // which we may or may not instantiate. It's important to declare the worker- - // thread contexts first, so that they get dropped second. This matters because: - // * ThreadLocalContexts borrow AtomicRefCells in TLS. - // * Dropping a ThreadLocalContext can run SequentialTasks. - // * Sequential tasks may call into functions like - // Servo_StyleSet_GetBaseComputedValuesForElement, which instantiate a - // ThreadLocalStyleContext on the main thread. If the main thread - // ThreadLocalStyleContext has not released its TLS borrow by that point, - // we'll panic on double-borrow. - let mut scoped_tls = pool.map(ScopedTLS::>::new); - let mut tlc = ThreadLocalStyleContext::new(); - let mut context = StyleContext { - shared: traversal.shared_context(), - thread_local: &mut tlc, - }; - - // Process the nodes breadth-first. This helps keep similar traversal characteristics for the - // style sharing cache. - let work_unit_max = work_unit_max(); - with_pool_in_place_scope(work_unit_max, pool, |maybe_scope| { - let mut discovered = VecDeque::with_capacity(work_unit_max * 2); - discovered.push_back(unsafe { SendNode::new(root.as_node()) }); - parallel::style_trees( - &mut context, - discovered, - root.as_node().opaque(), - work_unit_max, - static_prefs::pref!("layout.css.stylo-local-work-queue.in-main-thread") as usize, - PerLevelTraversalData { current_dom_depth: root.depth() }, - maybe_scope, - traversal, - scoped_tls.as_ref(), - ); - }); - - // Collect statistics from thread-locals if requested. - if dump_stats || report_stats { - let mut aggregate = mem::replace(&mut context.thread_local.statistics, Default::default()); - let parallel = pool.is_some(); - if let Some(ref mut tls) = scoped_tls { - for slot in tls.slots() { - if let Some(cx) = slot.get_mut() { - aggregate += cx.statistics.clone(); - } - } - } - - if report_stats { - report_statistics(&aggregate); - } - // dump statistics to stdout if requested - if dump_stats { - let stats = - TraversalStatistics::new(aggregate, traversal, parallel, start_time.unwrap()); - if stats.is_large { - println!("{}", stats); - } - } - } - - root -} diff --git a/components/style/encoding_support.rs b/components/style/encoding_support.rs deleted file mode 100644 index c144ad0b3bc..00000000000 --- a/components/style/encoding_support.rs +++ /dev/null @@ -1,105 +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 stylesheets from bytes (not `&str`). - -use crate::context::QuirksMode; -use crate::error_reporting::ParseErrorReporter; -use crate::media_queries::MediaList; -use crate::shared_lock::SharedRwLock; -use crate::stylesheets::{AllowImportRules, Origin, Stylesheet, StylesheetLoader, UrlExtraData}; -use cssparser::{stylesheet_encoding, EncodingSupport}; -use servo_arc::Arc; -use std::borrow::Cow; -use std::str; - -struct EncodingRs; - -impl EncodingSupport for EncodingRs { - type Encoding = &'static encoding_rs::Encoding; - - fn utf8() -> Self::Encoding { - encoding_rs::UTF_8 - } - - fn is_utf16_be_or_le(encoding: &Self::Encoding) -> bool { - *encoding == encoding_rs::UTF_16LE || *encoding == encoding_rs::UTF_16BE - } - - fn from_label(ascii_label: &[u8]) -> Option { - encoding_rs::Encoding::for_label(ascii_label) - } -} - -fn decode_stylesheet_bytes<'a>( - css: &'a [u8], - protocol_encoding_label: Option<&str>, - environment_encoding: Option<&'static encoding_rs::Encoding>, -) -> Cow<'a, str> { - let fallback_encoding = stylesheet_encoding::( - css, - protocol_encoding_label.map(str::as_bytes), - environment_encoding, - ); - let (result, _used_encoding, _) = fallback_encoding.decode(&css); - // FIXME record used encoding for environment encoding of @import - result -} - -impl Stylesheet { - /// Parse a stylesheet from a set of bytes, potentially received over the - /// network. - /// - /// Takes care of decoding the network bytes and forwards the resulting - /// string to `Stylesheet::from_str`. - pub fn from_bytes( - bytes: &[u8], - url_data: UrlExtraData, - protocol_encoding_label: Option<&str>, - environment_encoding: Option<&'static encoding_rs::Encoding>, - origin: Origin, - media: MediaList, - shared_lock: SharedRwLock, - stylesheet_loader: Option<&dyn StylesheetLoader>, - error_reporter: Option<&dyn ParseErrorReporter>, - quirks_mode: QuirksMode, - ) -> Stylesheet { - let string = decode_stylesheet_bytes(bytes, protocol_encoding_label, environment_encoding); - Stylesheet::from_str( - &string, - url_data, - origin, - Arc::new(shared_lock.wrap(media)), - shared_lock, - stylesheet_loader, - error_reporter, - quirks_mode, - 0, - AllowImportRules::Yes, - ) - } - - /// Updates an empty stylesheet with a set of bytes that reached over the - /// network. - pub fn update_from_bytes( - existing: &Stylesheet, - bytes: &[u8], - protocol_encoding_label: Option<&str>, - environment_encoding: Option<&'static encoding_rs::Encoding>, - url_data: UrlExtraData, - stylesheet_loader: Option<&dyn StylesheetLoader>, - error_reporter: Option<&dyn ParseErrorReporter>, - ) { - let string = decode_stylesheet_bytes(bytes, protocol_encoding_label, environment_encoding); - Self::update_from_str( - existing, - &string, - url_data, - stylesheet_loader, - error_reporter, - 0, - AllowImportRules::Yes, - ) - } -} diff --git a/components/style/error_reporting.rs b/components/style/error_reporting.rs deleted file mode 100644 index 258c7c44ef4..00000000000 --- a/components/style/error_reporting.rs +++ /dev/null @@ -1,283 +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/. */ - -//! Types used to report parsing errors. - -#![deny(missing_docs)] - -use crate::selector_parser::SelectorImpl; -use crate::stylesheets::UrlExtraData; -use cssparser::{BasicParseErrorKind, ParseErrorKind, SourceLocation, Token}; -use selectors::SelectorList; -use std::fmt; -use style_traits::ParseError; - -/// Errors that can be encountered while parsing CSS. -#[derive(Debug)] -pub enum ContextualParseError<'a> { - /// A property declaration was not recognized. - UnsupportedPropertyDeclaration( - &'a str, - ParseError<'a>, - Option<&'a SelectorList>, - ), - /// A property descriptor was not recognized. - UnsupportedPropertyDescriptor(&'a str, ParseError<'a>), - /// A font face descriptor was not recognized. - UnsupportedFontFaceDescriptor(&'a str, ParseError<'a>), - /// A font feature values descriptor was not recognized. - UnsupportedFontFeatureValuesDescriptor(&'a str, ParseError<'a>), - /// A font palette values descriptor was not recognized. - UnsupportedFontPaletteValuesDescriptor(&'a str, ParseError<'a>), - /// A keyframe rule was not valid. - InvalidKeyframeRule(&'a str, ParseError<'a>), - /// A font feature values rule was not valid. - InvalidFontFeatureValuesRule(&'a str, ParseError<'a>), - /// A keyframe property declaration was not recognized. - UnsupportedKeyframePropertyDeclaration(&'a str, ParseError<'a>), - /// A rule was invalid for some reason. - InvalidRule(&'a str, ParseError<'a>), - /// A rule was not recognized. - UnsupportedRule(&'a str, ParseError<'a>), - /// A viewport descriptor declaration was not recognized. - UnsupportedViewportDescriptorDeclaration(&'a str, ParseError<'a>), - /// A counter style descriptor declaration was not recognized. - UnsupportedCounterStyleDescriptorDeclaration(&'a str, ParseError<'a>), - /// A counter style rule had no symbols. - InvalidCounterStyleWithoutSymbols(String), - /// A counter style rule had less than two symbols. - InvalidCounterStyleNotEnoughSymbols(String), - /// A counter style rule did not have additive-symbols. - InvalidCounterStyleWithoutAdditiveSymbols, - /// A counter style rule had extends with symbols. - InvalidCounterStyleExtendsWithSymbols, - /// A counter style rule had extends with additive-symbols. - InvalidCounterStyleExtendsWithAdditiveSymbols, - /// A media rule was invalid for some reason. - InvalidMediaRule(&'a str, ParseError<'a>), - /// A value was not recognized. - UnsupportedValue(&'a str, ParseError<'a>), - /// A never-matching `:host` selector was found. - NeverMatchingHostSelector(String), -} - -impl<'a> fmt::Display for ContextualParseError<'a> { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - fn token_to_str(t: &Token, f: &mut fmt::Formatter) -> fmt::Result { - match *t { - Token::Ident(ref i) => write!(f, "identifier {}", i), - Token::AtKeyword(ref kw) => write!(f, "keyword @{}", kw), - Token::Hash(ref h) => write!(f, "hash #{}", h), - Token::IDHash(ref h) => write!(f, "id selector #{}", h), - Token::QuotedString(ref s) => write!(f, "quoted string \"{}\"", s), - Token::UnquotedUrl(ref u) => write!(f, "url {}", u), - Token::Delim(ref d) => write!(f, "delimiter {}", d), - Token::Number { - int_value: Some(i), .. - } => write!(f, "number {}", i), - Token::Number { value, .. } => write!(f, "number {}", value), - Token::Percentage { - int_value: Some(i), .. - } => write!(f, "percentage {}", i), - Token::Percentage { unit_value, .. } => { - write!(f, "percentage {}", unit_value * 100.) - }, - Token::Dimension { - value, ref unit, .. - } => write!(f, "dimension {}{}", value, unit), - Token::WhiteSpace(_) => write!(f, "whitespace"), - Token::Comment(_) => write!(f, "comment"), - Token::Colon => write!(f, "colon (:)"), - Token::Semicolon => write!(f, "semicolon (;)"), - Token::Comma => write!(f, "comma (,)"), - Token::IncludeMatch => write!(f, "include match (~=)"), - Token::DashMatch => write!(f, "dash match (|=)"), - Token::PrefixMatch => write!(f, "prefix match (^=)"), - Token::SuffixMatch => write!(f, "suffix match ($=)"), - Token::SubstringMatch => write!(f, "substring match (*=)"), - Token::CDO => write!(f, "CDO ()"), - Token::Function(ref name) => write!(f, "function {}", name), - Token::ParenthesisBlock => write!(f, "parenthesis ("), - Token::SquareBracketBlock => write!(f, "square bracket ["), - Token::CurlyBracketBlock => write!(f, "curly bracket {{"), - Token::BadUrl(ref _u) => write!(f, "bad url parse error"), - Token::BadString(ref _s) => write!(f, "bad string parse error"), - Token::CloseParenthesis => write!(f, "unmatched close parenthesis"), - Token::CloseSquareBracket => write!(f, "unmatched close square bracket"), - Token::CloseCurlyBracket => write!(f, "unmatched close curly bracket"), - } - } - - fn parse_error_to_str(err: &ParseError, f: &mut fmt::Formatter) -> fmt::Result { - match err.kind { - ParseErrorKind::Basic(BasicParseErrorKind::UnexpectedToken(ref t)) => { - write!(f, "found unexpected ")?; - token_to_str(t, f) - }, - ParseErrorKind::Basic(BasicParseErrorKind::EndOfInput) => { - write!(f, "unexpected end of input") - }, - ParseErrorKind::Basic(BasicParseErrorKind::AtRuleInvalid(ref i)) => { - write!(f, "@ rule invalid: {}", i) - }, - ParseErrorKind::Basic(BasicParseErrorKind::AtRuleBodyInvalid) => { - write!(f, "@ rule invalid") - }, - ParseErrorKind::Basic(BasicParseErrorKind::QualifiedRuleInvalid) => { - write!(f, "qualified rule invalid") - }, - ParseErrorKind::Custom(ref err) => write!(f, "{:?}", err), - } - } - - match *self { - ContextualParseError::UnsupportedPropertyDeclaration(decl, ref err, _selectors) => { - write!(f, "Unsupported property declaration: '{}', ", decl)?; - parse_error_to_str(err, f) - }, - ContextualParseError::UnsupportedPropertyDescriptor(decl, ref err) => { - write!( - f, - "Unsupported @property descriptor declaration: '{}', ", - decl - )?; - parse_error_to_str(err, f) - }, - ContextualParseError::UnsupportedFontFaceDescriptor(decl, ref err) => { - write!( - f, - "Unsupported @font-face descriptor declaration: '{}', ", - decl - )?; - parse_error_to_str(err, f) - }, - ContextualParseError::UnsupportedFontFeatureValuesDescriptor(decl, ref err) => { - write!( - f, - "Unsupported @font-feature-values descriptor declaration: '{}', ", - decl - )?; - parse_error_to_str(err, f) - }, - ContextualParseError::UnsupportedFontPaletteValuesDescriptor(decl, ref err) => { - write!( - f, - "Unsupported @font-palette-values descriptor declaration: '{}', ", - decl - )?; - parse_error_to_str(err, f) - }, - ContextualParseError::InvalidKeyframeRule(rule, ref err) => { - write!(f, "Invalid keyframe rule: '{}', ", rule)?; - parse_error_to_str(err, f) - }, - ContextualParseError::InvalidFontFeatureValuesRule(rule, ref err) => { - write!(f, "Invalid font feature value rule: '{}', ", rule)?; - parse_error_to_str(err, f) - }, - ContextualParseError::UnsupportedKeyframePropertyDeclaration(decl, ref err) => { - write!(f, "Unsupported keyframe property declaration: '{}', ", decl)?; - parse_error_to_str(err, f) - }, - ContextualParseError::InvalidRule(rule, ref err) => { - write!(f, "Invalid rule: '{}', ", rule)?; - parse_error_to_str(err, f) - }, - ContextualParseError::UnsupportedRule(rule, ref err) => { - write!(f, "Unsupported rule: '{}', ", rule)?; - parse_error_to_str(err, f) - }, - ContextualParseError::UnsupportedViewportDescriptorDeclaration(decl, ref err) => { - write!( - f, - "Unsupported @viewport descriptor declaration: '{}', ", - decl - )?; - parse_error_to_str(err, f) - }, - ContextualParseError::UnsupportedCounterStyleDescriptorDeclaration(decl, ref err) => { - write!( - f, - "Unsupported @counter-style descriptor declaration: '{}', ", - decl - )?; - parse_error_to_str(err, f) - }, - ContextualParseError::InvalidCounterStyleWithoutSymbols(ref system) => write!( - f, - "Invalid @counter-style rule: 'system: {}' without 'symbols'", - system - ), - ContextualParseError::InvalidCounterStyleNotEnoughSymbols(ref system) => write!( - f, - "Invalid @counter-style rule: 'system: {}' less than two 'symbols'", - system - ), - ContextualParseError::InvalidCounterStyleWithoutAdditiveSymbols => write!( - f, - "Invalid @counter-style rule: 'system: additive' without 'additive-symbols'" - ), - ContextualParseError::InvalidCounterStyleExtendsWithSymbols => write!( - f, - "Invalid @counter-style rule: 'system: extends …' with 'symbols'" - ), - ContextualParseError::InvalidCounterStyleExtendsWithAdditiveSymbols => write!( - f, - "Invalid @counter-style rule: 'system: extends …' with 'additive-symbols'" - ), - ContextualParseError::InvalidMediaRule(media_rule, ref err) => { - write!(f, "Invalid media rule: {}, ", media_rule)?; - parse_error_to_str(err, f) - }, - ContextualParseError::UnsupportedValue(_value, ref err) => parse_error_to_str(err, f), - ContextualParseError::NeverMatchingHostSelector(ref selector) => { - write!(f, ":host selector is not featureless: {}", selector) - }, - } - } -} - -/// A generic trait for an error reporter. -pub trait ParseErrorReporter { - /// Called when the style engine detects an error. - /// - /// Returns the current input being parsed, the source location it was - /// reported from, and a message. - fn report_error( - &self, - url: &UrlExtraData, - location: SourceLocation, - error: ContextualParseError, - ); -} - -/// An error reporter that uses [the `log` crate](https://github.com/rust-lang-nursery/log) -/// at `info` level. -/// -/// This logging is silent by default, and can be enabled with a `RUST_LOG=style=info` -/// environment variable. -/// (See [`env_logger`](https://rust-lang-nursery.github.io/log/env_logger/).) -#[cfg(feature = "servo")] -pub struct RustLogReporter; - -#[cfg(feature = "servo")] -impl ParseErrorReporter for RustLogReporter { - fn report_error( - &self, - url: &UrlExtraData, - location: SourceLocation, - error: ContextualParseError, - ) { - if log_enabled!(log::Level::Info) { - info!( - "Url:\t{}\n{}:{} {}", - url.as_str(), - location.line, - location.column, - error - ) - } - } -} diff --git a/components/style/font_face.rs b/components/style/font_face.rs deleted file mode 100644 index 1e193d3eb79..00000000000 --- a/components/style/font_face.rs +++ /dev/null @@ -1,835 +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/. */ - -//! The [`@font-face`][ff] at-rule. -//! -//! [ff]: https://drafts.csswg.org/css-fonts/#at-font-face-rule - -use crate::error_reporting::ContextualParseError; -use crate::parser::{Parse, ParserContext}; -#[cfg(feature = "gecko")] -use crate::properties::longhands::font_language_override; -use crate::shared_lock::{SharedRwLockReadGuard, ToCssWithGuard}; -use crate::str::CssStringWriter; -use crate::values::computed::font::{FamilyName, FontStretch}; -use crate::values::generics::font::FontStyle as GenericFontStyle; -#[cfg(feature = "gecko")] -use crate::values::specified::font::MetricsOverride; -use crate::values::specified::font::SpecifiedFontStyle; -use crate::values::specified::font::{AbsoluteFontWeight, FontStretch as SpecifiedFontStretch}; -#[cfg(feature = "gecko")] -use crate::values::specified::font::{FontFeatureSettings, FontVariationSettings}; -use crate::values::specified::url::SpecifiedUrl; -use crate::values::specified::Angle; -#[cfg(feature = "gecko")] -use crate::values::specified::NonNegativePercentage; -#[cfg(feature = "gecko")] -use cssparser::UnicodeRange; -use cssparser::{ - AtRuleParser, CowRcStr, DeclarationParser, Parser, QualifiedRuleParser, RuleBodyItemParser, - RuleBodyParser, SourceLocation, -}; -use selectors::parser::SelectorParseErrorKind; -use std::fmt::{self, Write}; -use style_traits::{CssWriter, ParseError}; -use style_traits::{StyleParseErrorKind, ToCss}; - -/// A source for a font-face rule. -#[cfg_attr(feature = "servo", derive(Deserialize, Serialize))] -#[derive(Clone, Debug, Eq, PartialEq, ToCss, ToShmem)] -pub enum Source { - /// A `url()` source. - Url(UrlSource), - /// A `local()` source. - #[css(function)] - Local(FamilyName), -} - -/// A list of sources for the font-face src descriptor. -#[derive(Clone, Debug, Eq, PartialEq, ToCss, ToShmem)] -#[css(comma)] -pub struct SourceList(#[css(iterable)] pub Vec); - -// We can't just use OneOrMoreSeparated to derive Parse for the Source list, -// because we want to filter out components that parsed as None, then fail if no -// valid components remain. So we provide our own implementation here. -impl Parse for SourceList { - fn parse<'i, 't>( - context: &ParserContext, - input: &mut Parser<'i, 't>, - ) -> Result> { - // Parse the comma-separated list, then let filter_map discard any None items. - let list = input - .parse_comma_separated(|input| { - let s = input.parse_entirely(|input| Source::parse(context, input)); - while input.next().is_ok() {} - Ok(s.ok()) - })? - .into_iter() - .filter_map(|s| s) - .collect::>(); - if list.is_empty() { - Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError)) - } else { - Ok(SourceList(list)) - } - } -} - -/// Keywords for the font-face src descriptor's format() function. -/// ('None' and 'Unknown' are for internal use in gfx, not exposed to CSS.) -#[derive(Clone, Copy, Debug, Eq, Parse, PartialEq, ToCss, ToShmem)] -#[cfg_attr(feature = "servo", derive(Deserialize, Serialize))] -#[repr(u8)] -#[allow(missing_docs)] -pub enum FontFaceSourceFormatKeyword { - #[css(skip)] - None, - Collection, - EmbeddedOpentype, - Opentype, - Svg, - Truetype, - Woff, - Woff2, - #[css(skip)] - Unknown, -} - -bitflags! { - /// Flags for the @font-face tech() function, indicating font technologies - /// required by the resource. - #[derive(ToShmem)] - #[cfg_attr(feature = "servo", derive(Deserialize, Serialize))] - #[repr(C)] - pub struct FontFaceSourceTechFlags: u16 { - /// Font requires OpenType feature support. - const FEATURES_OPENTYPE = 1 << 0; - /// Font requires Apple Advanced Typography support. - const FEATURES_AAT = 1 << 1; - /// Font requires Graphite shaping support. - const FEATURES_GRAPHITE = 1 << 2; - /// Font requires COLRv0 rendering support (simple list of colored layers). - const COLOR_COLRV0 = 1 << 3; - /// Font requires COLRv1 rendering support (graph of paint operations). - const COLOR_COLRV1 = 1 << 4; - /// Font requires SVG glyph rendering support. - const COLOR_SVG = 1 << 5; - /// Font has bitmap glyphs in 'sbix' format. - const COLOR_SBIX = 1 << 6; - /// Font has bitmap glyphs in 'CBDT' format. - const COLOR_CBDT = 1 << 7; - /// Font requires OpenType Variations support. - const VARIATIONS = 1 << 8; - /// Font requires CPAL palette selection support. - const PALETTES = 1 << 9; - /// Font requires support for incremental downloading. - const INCREMENTAL = 1 << 10; - } -} - -impl FontFaceSourceTechFlags { - /// Parse a single font-technology keyword and return its flag. - pub fn parse_one<'i, 't>(input: &mut Parser<'i, 't>) -> Result> { - Ok(try_match_ident_ignore_ascii_case! { input, - "features-opentype" => Self::FEATURES_OPENTYPE, - "features-aat" => Self::FEATURES_AAT, - "features-graphite" => Self::FEATURES_GRAPHITE, - "color-colrv0" => Self::COLOR_COLRV0, - "color-colrv1" => Self::COLOR_COLRV1, - "color-svg" => Self::COLOR_SVG, - "color-sbix" => Self::COLOR_SBIX, - "color-cbdt" => Self::COLOR_CBDT, - "variations" => Self::VARIATIONS, - "palettes" => Self::PALETTES, - "incremental" => Self::INCREMENTAL, - }) - } -} - -impl Parse for FontFaceSourceTechFlags { - fn parse<'i, 't>( - _context: &ParserContext, - input: &mut Parser<'i, 't>, - ) -> Result> { - let location = input.current_source_location(); - // We don't actually care about the return value of parse_comma_separated, - // because we insert the flags into result as we go. - let mut result = Self::empty(); - input.parse_comma_separated(|input| { - let flag = Self::parse_one(input)?; - result.insert(flag); - Ok(()) - })?; - if !result.is_empty() { - Ok(result) - } else { - Err(location.new_custom_error(StyleParseErrorKind::UnspecifiedError)) - } - } -} - -#[allow(unused_assignments)] -impl ToCss for FontFaceSourceTechFlags { - fn to_css(&self, dest: &mut CssWriter) -> fmt::Result - where - W: fmt::Write, - { - let mut first = true; - - macro_rules! write_if_flag { - ($s:expr => $f:ident) => { - if self.contains(Self::$f) { - if first { - first = false; - } else { - dest.write_str(", ")?; - } - dest.write_str($s)?; - } - }; - } - - write_if_flag!("features-opentype" => FEATURES_OPENTYPE); - write_if_flag!("features-aat" => FEATURES_AAT); - write_if_flag!("features-graphite" => FEATURES_GRAPHITE); - write_if_flag!("color-colrv0" => COLOR_COLRV0); - write_if_flag!("color-colrv1" => COLOR_COLRV1); - write_if_flag!("color-svg" => COLOR_SVG); - write_if_flag!("color-sbix" => COLOR_SBIX); - write_if_flag!("color-cbdt" => COLOR_CBDT); - write_if_flag!("variations" => VARIATIONS); - write_if_flag!("palettes" => PALETTES); - write_if_flag!("incremental" => INCREMENTAL); - - Ok(()) - } -} - -/// A POD representation for Gecko. All pointers here are non-owned and as such -/// can't outlive the rule they came from, but we can't enforce that via C++. -/// -/// All the strings are of course utf8. -#[cfg(feature = "gecko")] -#[derive(Clone, Copy, Debug, Eq, PartialEq)] -#[repr(u8)] -#[allow(missing_docs)] -pub enum FontFaceSourceListComponent { - Url(*const crate::gecko::url::CssUrl), - Local(*mut crate::gecko_bindings::structs::nsAtom), - FormatHintKeyword(FontFaceSourceFormatKeyword), - FormatHintString { - length: usize, - utf8_bytes: *const u8, - }, - TechFlags(FontFaceSourceTechFlags), -} - -#[derive(Clone, Debug, Eq, PartialEq, ToCss, ToShmem)] -#[cfg_attr(feature = "servo", derive(Deserialize, Serialize))] -#[repr(u8)] -#[allow(missing_docs)] -pub enum FontFaceSourceFormat { - Keyword(FontFaceSourceFormatKeyword), - String(String), -} - -/// A `UrlSource` represents a font-face source that has been specified with a -/// `url()` function. -/// -/// -#[cfg_attr(feature = "servo", derive(Deserialize, Serialize))] -#[derive(Clone, Debug, Eq, PartialEq, ToShmem)] -pub struct UrlSource { - /// The specified url. - pub url: SpecifiedUrl, - /// The format hint specified with the `format()` function, if present. - pub format_hint: Option, - /// The font technology flags specified with the `tech()` function, if any. - pub tech_flags: FontFaceSourceTechFlags, -} - -impl ToCss for UrlSource { - fn to_css(&self, dest: &mut CssWriter) -> fmt::Result - where - W: fmt::Write, - { - self.url.to_css(dest)?; - if let Some(hint) = &self.format_hint { - dest.write_str(" format(")?; - hint.to_css(dest)?; - dest.write_char(')')?; - } - if !self.tech_flags.is_empty() { - dest.write_str(" tech(")?; - self.tech_flags.to_css(dest)?; - dest.write_char(')')?; - } - Ok(()) - } -} - -/// A font-display value for a @font-face rule. -/// The font-display descriptor determines how a font face is displayed based -/// on whether and when it is downloaded and ready to use. -#[allow(missing_docs)] -#[cfg_attr(feature = "servo", derive(Deserialize, Serialize))] -#[derive( - Clone, Copy, Debug, Eq, MallocSizeOf, Parse, PartialEq, ToComputedValue, ToCss, ToShmem, -)] -#[repr(u8)] -pub enum FontDisplay { - Auto, - Block, - Swap, - Fallback, - Optional, -} - -macro_rules! impl_range { - ($range:ident, $component:ident) => { - impl Parse for $range { - fn parse<'i, 't>( - context: &ParserContext, - input: &mut Parser<'i, 't>, - ) -> Result> { - let first = $component::parse(context, input)?; - let second = input - .try_parse(|input| $component::parse(context, input)) - .unwrap_or_else(|_| first.clone()); - Ok($range(first, second)) - } - } - impl ToCss for $range { - fn to_css(&self, dest: &mut CssWriter) -> fmt::Result - where - W: fmt::Write, - { - self.0.to_css(dest)?; - if self.0 != self.1 { - dest.write_char(' ')?; - self.1.to_css(dest)?; - } - Ok(()) - } - } - }; -} - -/// The font-weight descriptor: -/// -/// https://drafts.csswg.org/css-fonts-4/#descdef-font-face-font-weight -#[derive(Clone, Debug, PartialEq, ToShmem)] -pub struct FontWeightRange(pub AbsoluteFontWeight, pub AbsoluteFontWeight); -impl_range!(FontWeightRange, AbsoluteFontWeight); - -/// The computed representation of the above so Gecko can read them easily. -/// -/// This one is needed because cbindgen doesn't know how to generate -/// specified::Number. -#[repr(C)] -#[allow(missing_docs)] -pub struct ComputedFontWeightRange(f32, f32); - -#[inline] -fn sort_range(a: T, b: T) -> (T, T) { - if a > b { - (b, a) - } else { - (a, b) - } -} - -impl FontWeightRange { - /// Returns a computed font-stretch range. - pub fn compute(&self) -> ComputedFontWeightRange { - let (min, max) = sort_range(self.0.compute().value(), self.1.compute().value()); - ComputedFontWeightRange(min, max) - } -} - -/// The font-stretch descriptor: -/// -/// https://drafts.csswg.org/css-fonts-4/#descdef-font-face-font-stretch -#[derive(Clone, Debug, PartialEq, ToShmem)] -pub struct FontStretchRange(pub SpecifiedFontStretch, pub SpecifiedFontStretch); -impl_range!(FontStretchRange, SpecifiedFontStretch); - -/// The computed representation of the above, so that Gecko can read them -/// easily. -#[repr(C)] -#[allow(missing_docs)] -pub struct ComputedFontStretchRange(FontStretch, FontStretch); - -impl FontStretchRange { - /// Returns a computed font-stretch range. - pub fn compute(&self) -> ComputedFontStretchRange { - fn compute_stretch(s: &SpecifiedFontStretch) -> FontStretch { - match *s { - SpecifiedFontStretch::Keyword(ref kw) => kw.compute(), - SpecifiedFontStretch::Stretch(ref p) => FontStretch::from_percentage(p.0.get()), - SpecifiedFontStretch::System(..) => unreachable!(), - } - } - - let (min, max) = sort_range(compute_stretch(&self.0), compute_stretch(&self.1)); - ComputedFontStretchRange(min, max) - } -} - -/// The font-style descriptor: -/// -/// https://drafts.csswg.org/css-fonts-4/#descdef-font-face-font-style -#[derive(Clone, Debug, PartialEq, ToShmem)] -#[allow(missing_docs)] -pub enum FontStyle { - Normal, - Italic, - Oblique(Angle, Angle), -} - -/// The computed representation of the above, with angles in degrees, so that -/// Gecko can read them easily. -#[repr(u8)] -#[allow(missing_docs)] -pub enum ComputedFontStyleDescriptor { - Normal, - Italic, - Oblique(f32, f32), -} - -impl Parse for FontStyle { - fn parse<'i, 't>( - context: &ParserContext, - input: &mut Parser<'i, 't>, - ) -> Result> { - let style = SpecifiedFontStyle::parse(context, input)?; - Ok(match style { - GenericFontStyle::Normal => FontStyle::Normal, - GenericFontStyle::Italic => FontStyle::Italic, - GenericFontStyle::Oblique(angle) => { - let second_angle = input - .try_parse(|input| SpecifiedFontStyle::parse_angle(context, input)) - .unwrap_or_else(|_| angle.clone()); - - FontStyle::Oblique(angle, second_angle) - }, - }) - } -} - -impl ToCss for FontStyle { - fn to_css(&self, dest: &mut CssWriter) -> fmt::Result - where - W: fmt::Write, - { - match *self { - FontStyle::Normal => dest.write_str("normal"), - FontStyle::Italic => dest.write_str("italic"), - FontStyle::Oblique(ref first, ref second) => { - dest.write_str("oblique")?; - if *first != SpecifiedFontStyle::default_angle() || first != second { - dest.write_char(' ')?; - first.to_css(dest)?; - } - if first != second { - dest.write_char(' ')?; - second.to_css(dest)?; - } - Ok(()) - }, - } - } -} - -impl FontStyle { - /// Returns a computed font-style descriptor. - pub fn compute(&self) -> ComputedFontStyleDescriptor { - match *self { - FontStyle::Normal => ComputedFontStyleDescriptor::Normal, - FontStyle::Italic => ComputedFontStyleDescriptor::Italic, - FontStyle::Oblique(ref first, ref second) => { - let (min, max) = sort_range( - SpecifiedFontStyle::compute_angle_degrees(first), - SpecifiedFontStyle::compute_angle_degrees(second), - ); - ComputedFontStyleDescriptor::Oblique(min, max) - }, - } - } -} - -/// Parse the block inside a `@font-face` rule. -/// -/// Note that the prelude parsing code lives in the `stylesheets` module. -pub fn parse_font_face_block( - context: &ParserContext, - input: &mut Parser, - location: SourceLocation, -) -> FontFaceRuleData { - let mut rule = FontFaceRuleData::empty(location); - { - let mut parser = FontFaceRuleParser { - context, - rule: &mut rule, - }; - let mut iter = RuleBodyParser::new(input, &mut parser); - while let Some(declaration) = iter.next() { - if let Err((error, slice)) = declaration { - let location = error.location; - let error = ContextualParseError::UnsupportedFontFaceDescriptor(slice, error); - context.log_css_error(location, error) - } - } - } - rule -} - -/// A @font-face rule that is known to have font-family and src declarations. -#[cfg(feature = "servo")] -pub struct FontFace<'a>(&'a FontFaceRuleData); - -/// A list of effective sources that we send over through IPC to the font cache. -#[cfg(feature = "servo")] -#[derive(Clone, Debug)] -#[cfg_attr(feature = "servo", derive(Deserialize, Serialize))] -pub struct EffectiveSources(Vec); - -#[cfg(feature = "servo")] -impl<'a> FontFace<'a> { - /// Returns the list of effective sources for that font-face, that is the - /// sources which don't list any format hint, or the ones which list at - /// least "truetype" or "opentype". - pub fn effective_sources(&self) -> EffectiveSources { - EffectiveSources( - self.sources() - .0 - .iter() - .rev() - .filter(|source| { - if let Source::Url(ref url_source) = **source { - // We support only opentype fonts and truetype is an alias for - // that format. Sources without format hints need to be - // downloaded in case we support them. - url_source - .format_hint - .as_ref() - .map_or(true, |hint| match hint { - FontFaceSourceFormat::Keyword( - FontFaceSourceFormatKeyword::Truetype - | FontFaceSourceFormatKeyword::Opentype - | FontFaceSourceFormatKeyword::Woff, - ) => true, - FontFaceSourceFormat::String(s) => { - s == "truetype" || s == "opentype" || s == "woff" - } - _ => false, - }) - } else { - true - } - }) - .cloned() - .collect(), - ) - } -} - -#[cfg(feature = "servo")] -impl Iterator for EffectiveSources { - type Item = Source; - fn next(&mut self) -> Option { - self.0.pop() - } - - fn size_hint(&self) -> (usize, Option) { - (self.0.len(), Some(self.0.len())) - } -} - -struct FontFaceRuleParser<'a, 'b: 'a> { - context: &'a ParserContext<'b>, - rule: &'a mut FontFaceRuleData, -} - -/// Default methods reject all at rules. -impl<'a, 'b, 'i> AtRuleParser<'i> for FontFaceRuleParser<'a, 'b> { - type Prelude = (); - type AtRule = (); - type Error = StyleParseErrorKind<'i>; -} - -impl<'a, 'b, 'i> QualifiedRuleParser<'i> for FontFaceRuleParser<'a, 'b> { - type Prelude = (); - type QualifiedRule = (); - type Error = StyleParseErrorKind<'i>; -} - -impl<'a, 'b, 'i> RuleBodyItemParser<'i, (), StyleParseErrorKind<'i>> - for FontFaceRuleParser<'a, 'b> -{ - fn parse_qualified(&self) -> bool { - false - } - fn parse_declarations(&self) -> bool { - true - } -} - -impl Parse for Source { - fn parse<'i, 't>( - context: &ParserContext, - input: &mut Parser<'i, 't>, - ) -> Result> { - if input - .try_parse(|input| input.expect_function_matching("local")) - .is_ok() - { - return input - .parse_nested_block(|input| FamilyName::parse(context, input)) - .map(Source::Local); - } - - let url = SpecifiedUrl::parse(context, input)?; - - // Parsing optional format() - let format_hint = if input - .try_parse(|input| input.expect_function_matching("format")) - .is_ok() - { - input.parse_nested_block(|input| { - if let Ok(kw) = input.try_parse(FontFaceSourceFormatKeyword::parse) { - Ok(Some(FontFaceSourceFormat::Keyword(kw))) - } else { - let s = input.expect_string()?.as_ref().to_owned(); - Ok(Some(FontFaceSourceFormat::String(s))) - } - })? - } else { - None - }; - - // Parse optional tech() - let tech_flags = if static_prefs::pref!("layout.css.font-tech.enabled") && - input - .try_parse(|input| input.expect_function_matching("tech")) - .is_ok() - { - input.parse_nested_block(|input| FontFaceSourceTechFlags::parse(context, input))? - } else { - FontFaceSourceTechFlags::empty() - }; - - Ok(Source::Url(UrlSource { - url, - format_hint, - tech_flags, - })) - } -} - -macro_rules! is_descriptor_enabled { - ("font-display") => { - static_prefs::pref!("layout.css.font-display.enabled") - }; - ("font-variation-settings") => { - static_prefs::pref!("layout.css.font-variations.enabled") - }; - ("ascent-override") => { - static_prefs::pref!("layout.css.font-metrics-overrides.enabled") - }; - ("descent-override") => { - static_prefs::pref!("layout.css.font-metrics-overrides.enabled") - }; - ("line-gap-override") => { - static_prefs::pref!("layout.css.font-metrics-overrides.enabled") - }; - ("size-adjust") => { - static_prefs::pref!("layout.css.size-adjust.enabled") - }; - ($name:tt) => { - true - }; -} - -macro_rules! font_face_descriptors_common { - ( - $( #[$doc: meta] $name: tt $ident: ident / $gecko_ident: ident: $ty: ty, )* - ) => { - /// Data inside a `@font-face` rule. - /// - /// - #[derive(Clone, Debug, PartialEq, ToShmem)] - pub struct FontFaceRuleData { - $( - #[$doc] - pub $ident: Option<$ty>, - )* - /// Line and column of the @font-face rule source code. - pub source_location: SourceLocation, - } - - impl FontFaceRuleData { - /// Create an empty font-face rule - pub fn empty(location: SourceLocation) -> Self { - FontFaceRuleData { - $( - $ident: None, - )* - source_location: location, - } - } - - /// Serialization of declarations in the FontFaceRule - pub fn decl_to_css(&self, dest: &mut CssStringWriter) -> fmt::Result { - $( - if let Some(ref value) = self.$ident { - dest.write_str(concat!($name, ": "))?; - value.to_css(&mut CssWriter::new(dest))?; - dest.write_str("; ")?; - } - )* - Ok(()) - } - } - - impl<'a, 'b, 'i> DeclarationParser<'i> for FontFaceRuleParser<'a, 'b> { - type Declaration = (); - type Error = StyleParseErrorKind<'i>; - - fn parse_value<'t>( - &mut self, - name: CowRcStr<'i>, - input: &mut Parser<'i, 't>, - ) -> Result<(), ParseError<'i>> { - match_ignore_ascii_case! { &*name, - $( - $name if is_descriptor_enabled!($name) => { - // DeclarationParser also calls parse_entirely - // so we’d normally not need to, - // but in this case we do because we set the value as a side effect - // rather than returning it. - let value = input.parse_entirely(|i| Parse::parse(self.context, i))?; - self.rule.$ident = Some(value) - }, - )* - _ => return Err(input.new_custom_error(SelectorParseErrorKind::UnexpectedIdent(name.clone()))), - } - Ok(()) - } - } - } -} - -impl ToCssWithGuard for FontFaceRuleData { - // Serialization of FontFaceRule is not specced. - fn to_css(&self, _guard: &SharedRwLockReadGuard, dest: &mut CssStringWriter) -> fmt::Result { - dest.write_str("@font-face { ")?; - self.decl_to_css(dest)?; - dest.write_char('}') - } -} - -macro_rules! font_face_descriptors { - ( - mandatory descriptors = [ - $( #[$m_doc: meta] $m_name: tt $m_ident: ident / $m_gecko_ident: ident: $m_ty: ty, )* - ] - optional descriptors = [ - $( #[$o_doc: meta] $o_name: tt $o_ident: ident / $o_gecko_ident: ident: $o_ty: ty, )* - ] - ) => { - font_face_descriptors_common! { - $( #[$m_doc] $m_name $m_ident / $m_gecko_ident: $m_ty, )* - $( #[$o_doc] $o_name $o_ident / $o_gecko_ident: $o_ty, )* - } - - impl FontFaceRuleData { - /// Per https://github.com/w3c/csswg-drafts/issues/1133 an @font-face rule - /// is valid as far as the CSS parser is concerned even if it doesn’t have - /// a font-family or src declaration. - /// - /// However both are required for the rule to represent an actual font face. - #[cfg(feature = "servo")] - pub fn font_face(&self) -> Option { - if $( self.$m_ident.is_some() )&&* { - Some(FontFace(self)) - } else { - None - } - } - } - - #[cfg(feature = "servo")] - impl<'a> FontFace<'a> { - $( - #[$m_doc] - pub fn $m_ident(&self) -> &$m_ty { - self.0 .$m_ident.as_ref().unwrap() - } - )* - } - } -} - -#[cfg(feature = "gecko")] -font_face_descriptors! { - mandatory descriptors = [ - /// The name of this font face - "font-family" family / mFamily: FamilyName, - - /// The alternative sources for this font face. - "src" sources / mSrc: SourceList, - ] - optional descriptors = [ - /// The style of this font face. - "font-style" style / mStyle: FontStyle, - - /// The weight of this font face. - "font-weight" weight / mWeight: FontWeightRange, - - /// The stretch of this font face. - "font-stretch" stretch / mStretch: FontStretchRange, - - /// The display of this font face. - "font-display" display / mDisplay: FontDisplay, - - /// The ranges of code points outside of which this font face should not be used. - "unicode-range" unicode_range / mUnicodeRange: Vec, - - /// The feature settings of this font face. - "font-feature-settings" feature_settings / mFontFeatureSettings: FontFeatureSettings, - - /// The variation settings of this font face. - "font-variation-settings" variation_settings / mFontVariationSettings: FontVariationSettings, - - /// The language override of this font face. - "font-language-override" language_override / mFontLanguageOverride: font_language_override::SpecifiedValue, - - /// The ascent override for this font face. - "ascent-override" ascent_override / mAscentOverride: MetricsOverride, - - /// The descent override for this font face. - "descent-override" descent_override / mDescentOverride: MetricsOverride, - - /// The line-gap override for this font face. - "line-gap-override" line_gap_override / mLineGapOverride: MetricsOverride, - - /// The size adjustment for this font face. - "size-adjust" size_adjust / mSizeAdjust: NonNegativePercentage, - ] -} - -#[cfg(feature = "servo")] -font_face_descriptors! { - mandatory descriptors = [ - /// The name of this font face - "font-family" family / mFamily: FamilyName, - - /// The alternative sources for this font face. - "src" sources / mSrc: SourceList, - ] - optional descriptors = [ - ] -} diff --git a/components/style/font_metrics.rs b/components/style/font_metrics.rs deleted file mode 100644 index 391d3653ee6..00000000000 --- a/components/style/font_metrics.rs +++ /dev/null @@ -1,58 +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/. */ - -//! Access to font metrics from the style system. - -#![deny(missing_docs)] - -use crate::values::computed::Length; - -/// Represents the font metrics that style needs from a font to compute the -/// value of certain CSS units like `ex`. -#[derive(Clone, Debug, PartialEq)] -pub struct FontMetrics { - /// The x-height of the font. - pub x_height: Option, - /// The zero advance. This is usually writing mode dependent - pub zero_advance_measure: Option, - /// The cap-height of the font. - pub cap_height: Option, - /// The ideographic-width of the font. - pub ic_width: Option, - /// The ascent of the font (a value is always available for this). - pub ascent: Length, - /// Script scale down factor for math-depth 1. - /// https://w3c.github.io/mathml-core/#dfn-scriptpercentscaledown - pub script_percent_scale_down: Option, - /// Script scale down factor for math-depth 2. - /// https://w3c.github.io/mathml-core/#dfn-scriptscriptpercentscaledown - pub script_script_percent_scale_down: Option, -} - -impl Default for FontMetrics { - fn default() -> Self { - FontMetrics { - x_height: None, - zero_advance_measure: None, - cap_height: None, - ic_width: None, - ascent: Length::new(0.0), - script_percent_scale_down: None, - script_script_percent_scale_down: None, - } - } -} - -/// Type of font metrics to retrieve. -#[derive(Clone, Debug, PartialEq)] -pub enum FontMetricsOrientation { - /// Get metrics for horizontal or vertical according to the Context's - /// writing mode, using horizontal metrics for vertical/mixed - MatchContextPreferHorizontal, - /// Get metrics for horizontal or vertical according to the Context's - /// writing mode, using vertical metrics for vertical/mixed - MatchContextPreferVertical, - /// Force getting horizontal metrics. - Horizontal, -} diff --git a/components/style/gecko/arc_types.rs b/components/style/gecko/arc_types.rs deleted file mode 100644 index 24bf22d69a7..00000000000 --- a/components/style/gecko/arc_types.rs +++ /dev/null @@ -1,171 +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/. */ - -//! This file lists all arc FFI types and defines corresponding addref and release functions. This -//! list loosely corresponds to ServoLockedArcTypeList.h file in Gecko. - -#![allow(non_snake_case, missing_docs)] - -use crate::gecko::url::CssUrlData; -use crate::media_queries::MediaList; -use crate::properties::animated_properties::AnimationValue; -use crate::properties::{ComputedValues, PropertyDeclarationBlock}; -use crate::shared_lock::Locked; -use crate::stylesheets::keyframes_rule::Keyframe; -use crate::stylesheets::{ - ContainerRule, CounterStyleRule, CssRules, DocumentRule, FontFaceRule, FontFeatureValuesRule, - FontPaletteValuesRule, ImportRule, KeyframesRule, LayerBlockRule, LayerStatementRule, - MediaRule, NamespaceRule, PageRule, PropertyRule, StyleRule, StylesheetContents, SupportsRule, -}; -use servo_arc::Arc; - -macro_rules! impl_simple_arc_ffi { - ($ty:ty, $addref:ident, $release:ident) => { - #[no_mangle] - pub unsafe extern "C" fn $addref(obj: *const $ty) { - std::mem::forget(Arc::from_raw_addrefed(obj)); - } - - #[no_mangle] - pub unsafe extern "C" fn $release(obj: *const $ty) { - let _ = Arc::from_raw(obj); - } - }; -} - -macro_rules! impl_locked_arc_ffi { - ($servo_type:ty, $alias:ident, $addref:ident, $release:ident) => { - /// A simple alias for a locked type. - pub type $alias = Locked<$servo_type>; - impl_simple_arc_ffi!($alias, $addref, $release); - }; -} - -impl_locked_arc_ffi!( - CssRules, - LockedCssRules, - Servo_CssRules_AddRef, - Servo_CssRules_Release -); -impl_locked_arc_ffi!( - PropertyDeclarationBlock, - LockedDeclarationBlock, - Servo_DeclarationBlock_AddRef, - Servo_DeclarationBlock_Release -); -impl_locked_arc_ffi!( - StyleRule, - LockedStyleRule, - Servo_StyleRule_AddRef, - Servo_StyleRule_Release -); -impl_locked_arc_ffi!( - ImportRule, - LockedImportRule, - Servo_ImportRule_AddRef, - Servo_ImportRule_Release -); -impl_locked_arc_ffi!( - Keyframe, - LockedKeyframe, - Servo_Keyframe_AddRef, - Servo_Keyframe_Release -); -impl_locked_arc_ffi!( - KeyframesRule, - LockedKeyframesRule, - Servo_KeyframesRule_AddRef, - Servo_KeyframesRule_Release -); -impl_simple_arc_ffi!( - LayerBlockRule, - Servo_LayerBlockRule_AddRef, - Servo_LayerBlockRule_Release -); -impl_simple_arc_ffi!( - LayerStatementRule, - Servo_LayerStatementRule_AddRef, - Servo_LayerStatementRule_Release -); -impl_locked_arc_ffi!( - MediaList, - LockedMediaList, - Servo_MediaList_AddRef, - Servo_MediaList_Release -); -impl_simple_arc_ffi!(MediaRule, Servo_MediaRule_AddRef, Servo_MediaRule_Release); -impl_simple_arc_ffi!( - NamespaceRule, - Servo_NamespaceRule_AddRef, - Servo_NamespaceRule_Release -); -impl_locked_arc_ffi!( - PageRule, - LockedPageRule, - Servo_PageRule_AddRef, - Servo_PageRule_Release -); -impl_simple_arc_ffi!( - PropertyRule, - Servo_PropertyRule_AddRef, - Servo_PropertyRule_Release -); -impl_simple_arc_ffi!( - SupportsRule, - Servo_SupportsRule_AddRef, - Servo_SupportsRule_Release -); -impl_simple_arc_ffi!( - ContainerRule, - Servo_ContainerRule_AddRef, - Servo_ContainerRule_Release -); -impl_simple_arc_ffi!( - DocumentRule, - Servo_DocumentRule_AddRef, - Servo_DocumentRule_Release -); -impl_simple_arc_ffi!( - FontFeatureValuesRule, - Servo_FontFeatureValuesRule_AddRef, - Servo_FontFeatureValuesRule_Release -); -impl_simple_arc_ffi!( - FontPaletteValuesRule, - Servo_FontPaletteValuesRule_AddRef, - Servo_FontPaletteValuesRule_Release -); -impl_locked_arc_ffi!( - FontFaceRule, - LockedFontFaceRule, - Servo_FontFaceRule_AddRef, - Servo_FontFaceRule_Release -); -impl_locked_arc_ffi!( - CounterStyleRule, - LockedCounterStyleRule, - Servo_CounterStyleRule_AddRef, - Servo_CounterStyleRule_Release -); - -impl_simple_arc_ffi!( - StylesheetContents, - Servo_StyleSheetContents_AddRef, - Servo_StyleSheetContents_Release -); -impl_simple_arc_ffi!( - CssUrlData, - Servo_CssUrlData_AddRef, - Servo_CssUrlData_Release -); -impl_simple_arc_ffi!( - ComputedValues, - Servo_ComputedStyle_AddRef, - Servo_ComputedStyle_Release -); -impl_simple_arc_ffi!( - AnimationValue, - Servo_AnimationValue_AddRef, - Servo_AnimationValue_Release -); diff --git a/components/style/gecko/conversions.rs b/components/style/gecko/conversions.rs deleted file mode 100644 index ea3700a3235..00000000000 --- a/components/style/gecko/conversions.rs +++ /dev/null @@ -1,59 +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/. */ - -//! This module contains conversion helpers between Servo and Gecko types -//! Ideally, it would be in geckolib itself, but coherence -//! forces us to keep the traits and implementations here -//! -//! FIXME(emilio): This file should generally just die. - -#![allow(unsafe_code)] - -use crate::gecko_bindings::structs::{nsresult, Matrix4x4Components}; -use crate::stylesheets::RulesMutateError; -use crate::values::computed::transform::Matrix3D; - -impl From for nsresult { - fn from(other: RulesMutateError) -> Self { - match other { - RulesMutateError::Syntax => nsresult::NS_ERROR_DOM_SYNTAX_ERR, - RulesMutateError::IndexSize => nsresult::NS_ERROR_DOM_INDEX_SIZE_ERR, - RulesMutateError::HierarchyRequest => nsresult::NS_ERROR_DOM_HIERARCHY_REQUEST_ERR, - RulesMutateError::InvalidState => nsresult::NS_ERROR_DOM_INVALID_STATE_ERR, - } - } -} - -impl<'a> From<&'a Matrix4x4Components> for Matrix3D { - fn from(m: &'a Matrix4x4Components) -> Matrix3D { - Matrix3D { - m11: m[0], - m12: m[1], - m13: m[2], - m14: m[3], - m21: m[4], - m22: m[5], - m23: m[6], - m24: m[7], - m31: m[8], - m32: m[9], - m33: m[10], - m34: m[11], - m41: m[12], - m42: m[13], - m43: m[14], - m44: m[15], - } - } -} - -impl From for Matrix4x4Components { - fn from(matrix: Matrix3D) -> Self { - [ - matrix.m11, matrix.m12, matrix.m13, matrix.m14, matrix.m21, matrix.m22, matrix.m23, - matrix.m24, matrix.m31, matrix.m32, matrix.m33, matrix.m34, matrix.m41, matrix.m42, - matrix.m43, matrix.m44, - ] - } -} diff --git a/components/style/gecko/data.rs b/components/style/gecko/data.rs deleted file mode 100644 index c4a5554c5e5..00000000000 --- a/components/style/gecko/data.rs +++ /dev/null @@ -1,198 +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/. */ - -//! Data needed to style a Gecko document. - -use crate::dom::TElement; -use crate::gecko_bindings::bindings; -use crate::gecko_bindings::structs::{ - self, ServoStyleSetSizes, StyleSheet as DomStyleSheet, StyleSheetInfo, -}; -use crate::invalidation::media_queries::{MediaListKey, ToMediaListKey}; -use crate::media_queries::{Device, MediaList}; -use crate::properties::ComputedValues; -use crate::selector_parser::SnapshotMap; -use crate::shared_lock::{SharedRwLockReadGuard, StylesheetGuards}; -use crate::stylesheets::{StylesheetContents, StylesheetInDocument}; -use crate::stylist::Stylist; -use atomic_refcell::{AtomicRef, AtomicRefCell, AtomicRefMut}; -use malloc_size_of::MallocSizeOfOps; -use servo_arc::Arc; -use std::fmt; - -/// Little wrapper to a Gecko style sheet. -#[derive(Eq, PartialEq)] -pub struct GeckoStyleSheet(*const DomStyleSheet); - -// NOTE(emilio): These are kind of a lie. We allow to make these Send + Sync so that other data -// structures can also be Send and Sync, but Gecko's stylesheets are main-thread-reference-counted. -// -// We assert that we reference-count in the right thread (in the Addref/Release implementations). -// Sending these to a different thread can't really happen (it could theoretically really happen if -// we allowed @import rules inside a nested style rule, but that can't happen per spec and would be -// a parser bug, caught by the asserts). -unsafe impl Send for GeckoStyleSheet {} -unsafe impl Sync for GeckoStyleSheet {} - -impl fmt::Debug for GeckoStyleSheet { - fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result { - let contents = self.contents(); - formatter - .debug_struct("GeckoStyleSheet") - .field("origin", &contents.origin) - .field("url_data", &*contents.url_data.read()) - .finish() - } -} - -impl ToMediaListKey for crate::gecko::data::GeckoStyleSheet { - fn to_media_list_key(&self) -> MediaListKey { - use std::mem; - unsafe { MediaListKey::from_raw(mem::transmute(self.0)) } - } -} - -impl GeckoStyleSheet { - /// Create a `GeckoStyleSheet` from a raw `DomStyleSheet` pointer. - #[inline] - pub unsafe fn new(s: *const DomStyleSheet) -> Self { - debug_assert!(!s.is_null()); - bindings::Gecko_StyleSheet_AddRef(s); - Self::from_addrefed(s) - } - - /// Create a `GeckoStyleSheet` from a raw `DomStyleSheet` pointer that - /// already holds a strong reference. - #[inline] - pub unsafe fn from_addrefed(s: *const DomStyleSheet) -> Self { - assert!(!s.is_null()); - GeckoStyleSheet(s) - } - - /// HACK(emilio): This is so that we can avoid crashing release due to - /// bug 1719963 and can hopefully get a useful report from fuzzers. - #[inline] - pub fn hack_is_null(&self) -> bool { - self.0.is_null() - } - - /// Get the raw `StyleSheet` that we're wrapping. - pub fn raw(&self) -> &DomStyleSheet { - unsafe { &*self.0 } - } - - fn inner(&self) -> &StyleSheetInfo { - unsafe { &*(self.raw().mInner as *const StyleSheetInfo) } - } -} - -impl Drop for GeckoStyleSheet { - fn drop(&mut self) { - unsafe { bindings::Gecko_StyleSheet_Release(self.0) }; - } -} - -impl Clone for GeckoStyleSheet { - fn clone(&self) -> Self { - unsafe { bindings::Gecko_StyleSheet_AddRef(self.0) }; - GeckoStyleSheet(self.0) - } -} - -impl StylesheetInDocument for GeckoStyleSheet { - fn media<'a>(&'a self, guard: &'a SharedRwLockReadGuard) -> Option<&'a MediaList> { - use crate::gecko_bindings::structs::mozilla::dom::MediaList as DomMediaList; - unsafe { - let dom_media_list = self.raw().mMedia.mRawPtr as *const DomMediaList; - if dom_media_list.is_null() { - return None; - } - let list = &*(*dom_media_list).mRawList.mRawPtr; - Some(list.read_with(guard)) - } - } - - // All the stylesheets Servo knows about are enabled, because that state is - // handled externally by Gecko. - #[inline] - fn enabled(&self) -> bool { - true - } - - #[inline] - fn contents(&self) -> &StylesheetContents { - debug_assert!(!self.inner().mContents.mRawPtr.is_null()); - unsafe { &*self.inner().mContents.mRawPtr } - } -} - -/// The container for data that a Servo-backed Gecko document needs to style -/// itself. -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 -/// and unexpected races while trying to mutate it. -pub struct PerDocumentStyleData(AtomicRefCell); - -impl PerDocumentStyleData { - /// Create a `PerDocumentStyleData`. - pub fn new(document: *const structs::Document) -> Self { - let device = Device::new(document); - let quirks_mode = device.document().mCompatMode; - - PerDocumentStyleData(AtomicRefCell::new(PerDocumentStyleDataImpl { - stylist: Stylist::new(device, quirks_mode.into()), - undisplayed_style_cache: Default::default(), - undisplayed_style_cache_generation: 0, - })) - } - - /// Get an immutable reference to this style data. - pub fn borrow(&self) -> AtomicRef { - self.0.borrow() - } - - /// Get an mutable reference to this style data. - pub fn borrow_mut(&self) -> AtomicRefMut { - self.0.borrow_mut() - } -} - -impl PerDocumentStyleDataImpl { - /// Recreate the style data if the stylesheets have changed. - pub fn flush_stylesheets( - &mut self, - guard: &SharedRwLockReadGuard, - document_element: Option, - snapshots: Option<&SnapshotMap>, - ) -> bool - where - E: TElement, - { - self.stylist - .flush(&StylesheetGuards::same(guard), document_element, snapshots) - } - - /// Get the default computed values for this document. - pub fn default_computed_values(&self) -> &Arc { - self.stylist.device().default_computed_values_arc() - } - - /// Measure heap usage. - pub fn add_size_of(&self, ops: &mut MallocSizeOfOps, sizes: &mut ServoStyleSetSizes) { - self.stylist.add_size_of(ops, sizes); - } -} - -/// The gecko-specific AuthorStyles instantiation. -pub type AuthorStyles = crate::author_styles::AuthorStyles; diff --git a/components/style/gecko/media_features.rs b/components/style/gecko/media_features.rs deleted file mode 100644 index 4ca746ea84d..00000000000 --- a/components/style/gecko/media_features.rs +++ /dev/null @@ -1,1028 +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/. */ - -//! Gecko's media feature list and evaluator. - -use crate::gecko_bindings::bindings; -use crate::gecko_bindings::structs; -use crate::gecko_bindings::structs::ScreenColorGamut; -use crate::media_queries::{Device, MediaType}; -use crate::queries::feature::{AllowsRanges, Evaluator, FeatureFlags, QueryFeatureDescription}; -use crate::queries::values::Orientation; -use crate::values::computed::{CSSPixelLength, Context, Ratio, Resolution}; -use app_units::Au; -use euclid::default::Size2D; - -fn device_size(device: &Device) -> Size2D { - let mut width = 0; - let mut height = 0; - unsafe { - bindings::Gecko_MediaFeatures_GetDeviceSize(device.document(), &mut width, &mut height); - } - Size2D::new(Au(width), Au(height)) -} - -/// https://drafts.csswg.org/mediaqueries-4/#width -fn eval_width(context: &Context) -> CSSPixelLength { - CSSPixelLength::new(context.device().au_viewport_size().width.to_f32_px()) -} - -/// https://drafts.csswg.org/mediaqueries-4/#device-width -fn eval_device_width(context: &Context) -> CSSPixelLength { - CSSPixelLength::new(device_size(context.device()).width.to_f32_px()) -} - -/// https://drafts.csswg.org/mediaqueries-4/#height -fn eval_height(context: &Context) -> CSSPixelLength { - CSSPixelLength::new(context.device().au_viewport_size().height.to_f32_px()) -} - -/// https://drafts.csswg.org/mediaqueries-4/#device-height -fn eval_device_height(context: &Context) -> CSSPixelLength { - CSSPixelLength::new(device_size(context.device()).height.to_f32_px()) -} - -fn eval_aspect_ratio_for(context: &Context, get_size: F) -> Ratio -where - F: FnOnce(&Device) -> Size2D, -{ - let size = get_size(context.device()); - Ratio::new(size.width.0 as f32, size.height.0 as f32) -} - -/// https://drafts.csswg.org/mediaqueries-4/#aspect-ratio -fn eval_aspect_ratio(context: &Context) -> Ratio { - eval_aspect_ratio_for(context, Device::au_viewport_size) -} - -/// https://drafts.csswg.org/mediaqueries-4/#device-aspect-ratio -fn eval_device_aspect_ratio(context: &Context) -> Ratio { - eval_aspect_ratio_for(context, device_size) -} - -/// https://compat.spec.whatwg.org/#css-media-queries-webkit-device-pixel-ratio -fn eval_device_pixel_ratio(context: &Context) -> f32 { - eval_resolution(context).dppx() -} - -/// https://drafts.csswg.org/mediaqueries-4/#orientation -fn eval_orientation(context: &Context, value: Option) -> bool { - Orientation::eval(context.device().au_viewport_size(), value) -} - -/// FIXME: There's no spec for `-moz-device-orientation`. -fn eval_device_orientation(context: &Context, value: Option) -> bool { - Orientation::eval(device_size(context.device()), value) -} - -/// Values for the display-mode media feature. -#[derive(Clone, Copy, Debug, FromPrimitive, Parse, PartialEq, ToCss)] -#[repr(u8)] -#[allow(missing_docs)] -pub enum DisplayMode { - Browser = 0, - MinimalUi, - Standalone, - Fullscreen, -} - -/// https://w3c.github.io/manifest/#the-display-mode-media-feature -fn eval_display_mode(context: &Context, query_value: Option) -> bool { - match query_value { - Some(v) => { - v == unsafe { - bindings::Gecko_MediaFeatures_GetDisplayMode(context.device().document()) - } - }, - None => true, - } -} - -/// https://drafts.csswg.org/mediaqueries-4/#grid -fn eval_grid(_: &Context) -> bool { - // Gecko doesn't support grid devices (e.g., ttys), so the 'grid' feature - // is always 0. - false -} - -/// https://compat.spec.whatwg.org/#css-media-queries-webkit-transform-3d -fn eval_transform_3d(_: &Context) -> bool { - true -} - -#[derive(Clone, Copy, Debug, FromPrimitive, Parse, ToCss)] -#[repr(u8)] -enum Scan { - Progressive, - Interlace, -} - -/// https://drafts.csswg.org/mediaqueries-4/#scan -fn eval_scan(_: &Context, _: Option) -> bool { - // Since Gecko doesn't support the 'tv' media type, the 'scan' feature never - // matches. - false -} - -/// https://drafts.csswg.org/mediaqueries-4/#color -fn eval_color(context: &Context) -> i32 { - unsafe { bindings::Gecko_MediaFeatures_GetColorDepth(context.device().document()) } -} - -/// https://drafts.csswg.org/mediaqueries-4/#color-index -fn eval_color_index(_: &Context) -> i32 { - // We should return zero if the device does not use a color lookup table. - 0 -} - -/// https://drafts.csswg.org/mediaqueries-4/#monochrome -fn eval_monochrome(context: &Context) -> i32 { - // For color devices we should return 0. - unsafe { bindings::Gecko_MediaFeatures_GetMonochromeBitsPerPixel(context.device().document()) } -} - -/// Values for the color-gamut media feature. -/// This implements PartialOrd so that lower values will correctly match -/// higher capabilities. -#[derive(Clone, Copy, Debug, FromPrimitive, Parse, PartialEq, PartialOrd, ToCss)] -#[repr(u8)] -enum ColorGamut { - /// The sRGB gamut. - Srgb, - /// The gamut specified by the Display P3 Color Space. - P3, - /// The gamut specified by the ITU-R Recommendation BT.2020 Color Space. - Rec2020, -} - -/// https://drafts.csswg.org/mediaqueries-4/#color-gamut -fn eval_color_gamut(context: &Context, query_value: Option) -> bool { - let query_value = match query_value { - Some(v) => v, - None => return false, - }; - let color_gamut = - unsafe { bindings::Gecko_MediaFeatures_ColorGamut(context.device().document()) }; - // Match if our color gamut is at least as wide as the query value - query_value <= - match color_gamut { - // EndGuard_ is not a valid color gamut, so the default color-gamut is used. - ScreenColorGamut::Srgb | ScreenColorGamut::EndGuard_ => ColorGamut::Srgb, - ScreenColorGamut::P3 => ColorGamut::P3, - ScreenColorGamut::Rec2020 => ColorGamut::Rec2020, - } -} - -/// 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) -} - -#[derive(Clone, Copy, Debug, FromPrimitive, Parse, ToCss)] -#[repr(u8)] -enum PrefersReducedMotion { - NoPreference, - Reduce, -} - -#[derive(Clone, Copy, Debug, FromPrimitive, Parse, ToCss)] -#[repr(u8)] -enum PrefersReducedTransparency { - NoPreference, - Reduce, -} - -/// Values for the prefers-color-scheme media feature. -#[derive(Clone, Copy, Debug, FromPrimitive, Parse, PartialEq, ToCss)] -#[repr(u8)] -#[allow(missing_docs)] -pub enum PrefersColorScheme { - Light, - 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 { - let prefers_reduced = - unsafe { bindings::Gecko_MediaFeatures_PrefersReducedMotion(context.device().document()) }; - let query_value = match query_value { - Some(v) => v, - None => return prefers_reduced, - }; - - match query_value { - PrefersReducedMotion::NoPreference => !prefers_reduced, - PrefersReducedMotion::Reduce => prefers_reduced, - } -} - -/// https://drafts.csswg.org/mediaqueries-5/#prefers-reduced-transparency -fn eval_prefers_reduced_transparency( - context: &Context, - query_value: Option, -) -> bool { - let prefers_reduced = unsafe { - bindings::Gecko_MediaFeatures_PrefersReducedTransparency(context.device().document()) - }; - let query_value = match query_value { - Some(v) => v, - None => return prefers_reduced, - }; - - match query_value { - PrefersReducedTransparency::NoPreference => !prefers_reduced, - PrefersReducedTransparency::Reduce => prefers_reduced, - } -} - -/// Possible values for prefers-contrast media query. -/// https://drafts.csswg.org/mediaqueries-5/#prefers-contrast -#[derive(Clone, Copy, Debug, FromPrimitive, Parse, PartialEq, ToCss)] -#[repr(u8)] -pub enum PrefersContrast { - /// More contrast is preferred. - More, - /// Low contrast is preferred. - Less, - /// Custom (not more, not less). - Custom, - /// The default value if neither high or low contrast is enabled. - NoPreference, -} - -/// https://drafts.csswg.org/mediaqueries-5/#prefers-contrast -fn eval_prefers_contrast(context: &Context, query_value: Option) -> bool { - let prefers_contrast = - unsafe { bindings::Gecko_MediaFeatures_PrefersContrast(context.device().document()) }; - match query_value { - Some(v) => v == prefers_contrast, - None => prefers_contrast != PrefersContrast::NoPreference, - } -} - -/// Possible values for the forced-colors media query. -/// https://drafts.csswg.org/mediaqueries-5/#forced-colors -#[derive(Clone, Copy, Debug, FromPrimitive, Parse, PartialEq, ToCss)] -#[repr(u8)] -pub enum ForcedColors { - /// Page colors are not being forced. - None, - /// Page colors are being forced. - Active, -} - -/// 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(); - match query_value { - Some(query_value) => forced == (query_value == ForcedColors::Active), - None => forced, - } -} - -/// Possible values for the inverted-colors media query. -/// https://drafts.csswg.org/mediaqueries-5/#inverted -#[derive(Clone, Copy, Debug, FromPrimitive, Parse, ToCss)] -#[repr(u8)] -enum InvertedColors { - /// Colors are displayed normally. - None, - /// All pixels within the displayed area have been inverted. - Inverted, -} - -/// https://drafts.csswg.org/mediaqueries-5/#inverted -fn eval_inverted_colors(context: &Context, query_value: Option) -> bool { - let inverted_colors = - unsafe { bindings::Gecko_MediaFeatures_InvertedColors(context.device().document()) }; - let query_value = match query_value { - Some(v) => v, - None => return inverted_colors, - }; - - match query_value { - InvertedColors::None => !inverted_colors, - InvertedColors::Inverted => inverted_colors, - } -} - -#[derive(Clone, Copy, Debug, FromPrimitive, Parse, ToCss)] -#[repr(u8)] -enum OverflowBlock { - None, - Scroll, - Paged, -} - -/// https://drafts.csswg.org/mediaqueries-4/#mf-overflow-block -fn eval_overflow_block(context: &Context, query_value: Option) -> bool { - // For the time being, assume that printing (including previews) - // is the only time when we paginate, and we are otherwise always - // scrolling. This is true at the moment in Firefox, but may need - // updating in the future (e.g., ebook readers built with Stylo, a - // 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 query_value = match query_value { - Some(v) => v, - None => return true, - }; - - match query_value { - OverflowBlock::None => false, - OverflowBlock::Scroll => scrolling, - OverflowBlock::Paged => !scrolling, - } -} - -#[derive(Clone, Copy, Debug, FromPrimitive, Parse, ToCss)] -#[repr(u8)] -enum OverflowInline { - None, - Scroll, -} - -/// https://drafts.csswg.org/mediaqueries-4/#mf-overflow-inline -fn eval_overflow_inline(context: &Context, query_value: Option) -> bool { - // See the note in eval_overflow_block. - let scrolling = context.device().media_type() != MediaType::print(); - let query_value = match query_value { - Some(v) => v, - None => return scrolling, - }; - - match query_value { - OverflowInline::None => !scrolling, - OverflowInline::Scroll => scrolling, - } -} - -#[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, - use_content: bool, - query_value: Option, -) -> bool { - let prefers_color_scheme = unsafe { - bindings::Gecko_MediaFeatures_PrefersColorScheme(context.device().document(), use_content) - }; - match query_value { - Some(v) => prefers_color_scheme == v, - None => true, - } -} - -/// 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_content_prefers_color_scheme( - context: &Context, - 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, - } -} - -bitflags! { - /// https://drafts.csswg.org/mediaqueries-4/#mf-interaction - struct PointerCapabilities: u8 { - const COARSE = structs::PointerCapabilities_Coarse; - const FINE = structs::PointerCapabilities_Fine; - const HOVER = structs::PointerCapabilities_Hover; - } -} - -fn primary_pointer_capabilities(context: &Context) -> PointerCapabilities { - PointerCapabilities::from_bits_truncate(unsafe { - bindings::Gecko_MediaFeatures_PrimaryPointerCapabilities(context.device().document()) - }) -} - -fn all_pointer_capabilities(context: &Context) -> PointerCapabilities { - PointerCapabilities::from_bits_truncate(unsafe { - bindings::Gecko_MediaFeatures_AllPointerCapabilities(context.device().document()) - }) -} - -#[derive(Clone, Copy, Debug, FromPrimitive, Parse, ToCss)] -#[repr(u8)] -enum Pointer { - None, - Coarse, - Fine, -} - -fn eval_pointer_capabilities( - query_value: Option, - pointer_capabilities: PointerCapabilities, -) -> bool { - let query_value = match query_value { - Some(v) => v, - None => return !pointer_capabilities.is_empty(), - }; - - match query_value { - Pointer::None => pointer_capabilities.is_empty(), - Pointer::Coarse => pointer_capabilities.intersects(PointerCapabilities::COARSE), - Pointer::Fine => pointer_capabilities.intersects(PointerCapabilities::FINE), - } -} - -/// 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)) -} - -/// 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)) -} - -#[derive(Clone, Copy, Debug, FromPrimitive, Parse, ToCss)] -#[repr(u8)] -enum Hover { - None, - Hover, -} - -fn eval_hover_capabilities( - query_value: Option, - pointer_capabilities: PointerCapabilities, -) -> bool { - let can_hover = pointer_capabilities.intersects(PointerCapabilities::HOVER); - let query_value = match query_value { - Some(v) => v, - None => return can_hover, - }; - - match query_value { - Hover::None => !can_hover, - Hover::Hover => can_hover, - } -} - -/// 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)) -} - -/// 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_moz_is_glyph(context: &Context) -> bool { - context.device().document().mIsSVGGlyphsDocument() -} - -fn eval_moz_print_preview(context: &Context) -> bool { - let is_print_preview = context.device().is_print_preview(); - if is_print_preview { - debug_assert_eq!(context.device().media_type(), MediaType::print()); - } - is_print_preview -} - -fn eval_moz_non_native_content_theme(context: &Context) -> bool { - unsafe { bindings::Gecko_MediaFeatures_ShouldAvoidNativeTheme(context.device().document()) } -} - -fn eval_moz_is_resource_document(context: &Context) -> bool { - unsafe { bindings::Gecko_MediaFeatures_IsResourceDocument(context.device().document()) } -} - -/// Allows front-end CSS to discern platform via media queries. -#[derive(Clone, Copy, Debug, FromPrimitive, Parse, ToCss)] -#[repr(u8)] -pub enum Platform { - /// Matches any Android version. - Android, - /// For our purposes here, "linux" is just "gtk" (so unix-but-not-mac). - /// There's no need for our front-end code to differentiate between those - /// platforms and they already use the "linux" string elsewhere (e.g., - /// toolkit/themes/linux). - Linux, - /// Matches any macOS version. - Macos, - /// Matches any Windows version. - Windows, - /// Matches only Windows 7. - WindowsWin7, - /// Matches only Windows 8. - WindowsWin8, - /// Matches windows 10 and actually matches windows 11 too, as of right now. - WindowsWin10, -} - -fn eval_moz_platform(_: &Context, query_value: Option) -> bool { - let query_value = match query_value { - Some(v) => v, - None => return false, - }; - - unsafe { bindings::Gecko_MediaFeatures_MatchesPlatform(query_value) } -} - -/// Values for the scripting media feature. -/// https://drafts.csswg.org/mediaqueries-5/#scripting -#[derive(Clone, Copy, Debug, FromPrimitive, Parse, PartialEq, ToCss)] -#[repr(u8)] -pub enum Scripting { - /// Scripting is not supported or not enabled - None, - /// Scripting is supported and enabled, but only for initial page load - /// We will never match this value as it is intended for non-browser user agents, - /// but it is part of the spec so we should still parse it. - /// See: https://github.com/w3c/csswg-drafts/issues/8621 - InitialOnly, - /// Scripting is supported and enabled - Enabled, -} - -/// https://drafts.csswg.org/mediaqueries-5/#scripting -fn eval_scripting(context: &Context, query_value: Option) -> bool { - let scripting = unsafe { bindings::Gecko_MediaFeatures_Scripting(context.device().document()) }; - match query_value { - Some(v) => v == scripting, - None => scripting != Scripting::None, - } -} - -fn eval_moz_windows_non_native_menus(context: &Context) -> bool { - unsafe { bindings::Gecko_MediaFeatures_WindowsNonNativeMenus(context.device().document()) } -} - -fn eval_moz_overlay_scrollbars(context: &Context) -> bool { - unsafe { bindings::Gecko_MediaFeatures_UseOverlayScrollbars(context.device().document()) } -} - -fn get_lnf_int(int_id: i32) -> i32 { - unsafe { bindings::Gecko_GetLookAndFeelInt(int_id) } -} - -fn get_lnf_int_as_bool(int_id: i32) -> bool { - get_lnf_int(int_id) != 0 -} - -fn get_scrollbar_start_backward(int_id: i32) -> bool { - (get_lnf_int(int_id) & bindings::LookAndFeel_eScrollArrow_StartBackward as i32) != 0 -} - -fn get_scrollbar_start_forward(int_id: i32) -> bool { - (get_lnf_int(int_id) & bindings::LookAndFeel_eScrollArrow_StartForward as i32) != 0 -} - -fn get_scrollbar_end_backward(int_id: i32) -> bool { - (get_lnf_int(int_id) & bindings::LookAndFeel_eScrollArrow_EndBackward as i32) != 0 -} - -fn get_scrollbar_end_forward(int_id: i32) -> bool { - (get_lnf_int(int_id) & bindings::LookAndFeel_eScrollArrow_EndForward as i32) != 0 -} - -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) - } - - feature!( - $feature_name, - AllowsRanges::No, - Evaluator::BoolInteger(__eval), - FeatureFlags::CHROME_AND_UA_ONLY, - ) - }}; - ($feature_name:expr, $int_id:ident) => {{ - lnf_int_feature!($feature_name, $int_id, get_lnf_int_as_bool) - }}; -} - -/// bool pref-based features are an slightly less convenient to start using -/// version of @supports -moz-bool-pref, but with some benefits, mainly that -/// they can support dynamic changes, and don't require a pref lookup every time -/// they're used. -/// -/// In order to use them you need to make sure that the pref defined as a static -/// 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) - } - - feature!( - $feature_name, - AllowsRanges::No, - Evaluator::BoolInteger(__eval), - FeatureFlags::CHROME_AND_UA_ONLY, - ) - }}; -} - -/// Adding new media features requires (1) adding the new feature to this -/// array, with appropriate entries (and potentially any new code needed -/// 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; 67] = [ - feature!( - atom!("width"), - AllowsRanges::Yes, - Evaluator::Length(eval_width), - FeatureFlags::VIEWPORT_DEPENDENT, - ), - feature!( - atom!("height"), - AllowsRanges::Yes, - Evaluator::Length(eval_height), - FeatureFlags::VIEWPORT_DEPENDENT, - ), - feature!( - atom!("aspect-ratio"), - AllowsRanges::Yes, - Evaluator::NumberRatio(eval_aspect_ratio), - FeatureFlags::VIEWPORT_DEPENDENT, - ), - feature!( - atom!("orientation"), - AllowsRanges::No, - keyword_evaluator!(eval_orientation, Orientation), - FeatureFlags::VIEWPORT_DEPENDENT, - ), - feature!( - atom!("device-width"), - AllowsRanges::Yes, - Evaluator::Length(eval_device_width), - FeatureFlags::empty(), - ), - feature!( - atom!("device-height"), - AllowsRanges::Yes, - Evaluator::Length(eval_device_height), - FeatureFlags::empty(), - ), - feature!( - atom!("device-aspect-ratio"), - AllowsRanges::Yes, - Evaluator::NumberRatio(eval_device_aspect_ratio), - FeatureFlags::empty(), - ), - feature!( - atom!("-moz-device-orientation"), - AllowsRanges::No, - keyword_evaluator!(eval_device_orientation, Orientation), - FeatureFlags::empty(), - ), - // Webkit extensions that we support for de-facto web compatibility. - // -webkit-{min|max}-device-pixel-ratio (controlled with its own pref): - feature!( - atom!("device-pixel-ratio"), - AllowsRanges::Yes, - Evaluator::Float(eval_device_pixel_ratio), - FeatureFlags::WEBKIT_PREFIX, - ), - // -webkit-transform-3d. - feature!( - atom!("transform-3d"), - AllowsRanges::No, - Evaluator::BoolInteger(eval_transform_3d), - FeatureFlags::WEBKIT_PREFIX, - ), - feature!( - atom!("-moz-device-pixel-ratio"), - AllowsRanges::Yes, - Evaluator::Float(eval_device_pixel_ratio), - FeatureFlags::empty(), - ), - feature!( - atom!("resolution"), - AllowsRanges::Yes, - Evaluator::Resolution(eval_resolution), - FeatureFlags::empty(), - ), - feature!( - atom!("display-mode"), - AllowsRanges::No, - keyword_evaluator!(eval_display_mode, DisplayMode), - FeatureFlags::empty(), - ), - feature!( - atom!("grid"), - AllowsRanges::No, - Evaluator::BoolInteger(eval_grid), - FeatureFlags::empty(), - ), - feature!( - atom!("scan"), - AllowsRanges::No, - keyword_evaluator!(eval_scan, Scan), - FeatureFlags::empty(), - ), - feature!( - atom!("color"), - AllowsRanges::Yes, - Evaluator::Integer(eval_color), - FeatureFlags::empty(), - ), - feature!( - atom!("color-index"), - AllowsRanges::Yes, - Evaluator::Integer(eval_color_index), - FeatureFlags::empty(), - ), - feature!( - atom!("monochrome"), - AllowsRanges::Yes, - Evaluator::Integer(eval_monochrome), - FeatureFlags::empty(), - ), - feature!( - atom!("color-gamut"), - AllowsRanges::No, - keyword_evaluator!(eval_color_gamut, ColorGamut), - FeatureFlags::empty(), - ), - feature!( - atom!("prefers-reduced-motion"), - AllowsRanges::No, - keyword_evaluator!(eval_prefers_reduced_motion, PrefersReducedMotion), - FeatureFlags::empty(), - ), - feature!( - atom!("prefers-reduced-transparency"), - AllowsRanges::No, - keyword_evaluator!( - eval_prefers_reduced_transparency, - PrefersReducedTransparency - ), - FeatureFlags::empty(), - ), - feature!( - atom!("prefers-contrast"), - AllowsRanges::No, - keyword_evaluator!(eval_prefers_contrast, PrefersContrast), - // Note: by default this is only enabled in browser chrome and - // ua. It can be enabled on the web via the - // layout.css.prefers-contrast.enabled preference. See - // disabed_by_pref in media_feature_expression.rs for how that - // is done. - FeatureFlags::empty(), - ), - feature!( - atom!("forced-colors"), - AllowsRanges::No, - keyword_evaluator!(eval_forced_colors, ForcedColors), - FeatureFlags::empty(), - ), - feature!( - atom!("inverted-colors"), - AllowsRanges::No, - keyword_evaluator!(eval_inverted_colors, InvertedColors), - FeatureFlags::empty(), - ), - feature!( - atom!("overflow-block"), - AllowsRanges::No, - keyword_evaluator!(eval_overflow_block, OverflowBlock), - FeatureFlags::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(), - ), - 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(), - ), - feature!( - atom!("scripting"), - AllowsRanges::No, - keyword_evaluator!(eval_scripting, Scripting), - FeatureFlags::empty(), - ), - // Evaluates to the preferred color scheme for content. Only useful in - // chrome context, where the chrome color-scheme and the content - // color-scheme might differ. - feature!( - atom!("-moz-content-prefers-color-scheme"), - AllowsRanges::No, - keyword_evaluator!(eval_content_prefers_color_scheme, PrefersColorScheme), - FeatureFlags::CHROME_AND_UA_ONLY, - ), - feature!( - atom!("pointer"), - AllowsRanges::No, - keyword_evaluator!(eval_pointer, Pointer), - FeatureFlags::empty(), - ), - feature!( - atom!("any-pointer"), - AllowsRanges::No, - keyword_evaluator!(eval_any_pointer, Pointer), - FeatureFlags::empty(), - ), - feature!( - atom!("hover"), - AllowsRanges::No, - keyword_evaluator!(eval_hover, Hover), - FeatureFlags::empty(), - ), - feature!( - atom!("any-hover"), - AllowsRanges::No, - keyword_evaluator!(eval_any_hover, Hover), - FeatureFlags::empty(), - ), - // Internal -moz-is-glyph media feature: applies only inside SVG glyphs. - // Internal because it is really only useful in the user agent anyway - // and therefore not worth standardizing. - feature!( - atom!("-moz-is-glyph"), - AllowsRanges::No, - Evaluator::BoolInteger(eval_moz_is_glyph), - FeatureFlags::CHROME_AND_UA_ONLY, - ), - feature!( - atom!("-moz-is-resource-document"), - AllowsRanges::No, - Evaluator::BoolInteger(eval_moz_is_resource_document), - FeatureFlags::CHROME_AND_UA_ONLY, - ), - feature!( - atom!("-moz-platform"), - AllowsRanges::No, - keyword_evaluator!(eval_moz_platform, Platform), - FeatureFlags::CHROME_AND_UA_ONLY, - ), - feature!( - atom!("-moz-print-preview"), - AllowsRanges::No, - Evaluator::BoolInteger(eval_moz_print_preview), - FeatureFlags::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, - ), - feature!( - atom!("-moz-windows-non-native-menus"), - AllowsRanges::No, - Evaluator::BoolInteger(eval_moz_windows_non_native_menus), - FeatureFlags::CHROME_AND_UA_ONLY, - ), - feature!( - atom!("-moz-overlay-scrollbars"), - AllowsRanges::No, - Evaluator::BoolInteger(eval_moz_overlay_scrollbars), - FeatureFlags::CHROME_AND_UA_ONLY, - ), - lnf_int_feature!( - atom!("-moz-scrollbar-start-backward"), - ScrollArrowStyle, - get_scrollbar_start_backward - ), - lnf_int_feature!( - atom!("-moz-scrollbar-start-forward"), - ScrollArrowStyle, - get_scrollbar_start_forward - ), - lnf_int_feature!( - atom!("-moz-scrollbar-end-backward"), - ScrollArrowStyle, - get_scrollbar_end_backward - ), - lnf_int_feature!( - atom!("-moz-scrollbar-end-forward"), - ScrollArrowStyle, - get_scrollbar_end_forward - ), - lnf_int_feature!(atom!("-moz-menubar-drag"), MenuBarDrag), - lnf_int_feature!(atom!("-moz-windows-default-theme"), WindowsDefaultTheme), - lnf_int_feature!(atom!("-moz-mac-graphite-theme"), MacGraphiteTheme), - lnf_int_feature!(atom!("-moz-mac-big-sur-theme"), MacBigSurTheme), - lnf_int_feature!(atom!("-moz-mac-rtl"), MacRTL), - lnf_int_feature!( - atom!("-moz-windows-accent-color-in-titlebar"), - WindowsAccentColorInTitlebar - ), - lnf_int_feature!(atom!("-moz-windows-compositor"), DWMCompositor), - lnf_int_feature!(atom!("-moz-windows-classic"), WindowsClassic), - lnf_int_feature!(atom!("-moz-windows-glass"), WindowsGlass), - lnf_int_feature!(atom!("-moz-swipe-animation-enabled"), SwipeAnimationEnabled), - lnf_int_feature!(atom!("-moz-gtk-csd-available"), GTKCSDAvailable), - lnf_int_feature!(atom!("-moz-gtk-csd-minimize-button"), GTKCSDMinimizeButton), - lnf_int_feature!(atom!("-moz-gtk-csd-maximize-button"), GTKCSDMaximizeButton), - lnf_int_feature!(atom!("-moz-gtk-csd-close-button"), GTKCSDCloseButton), - lnf_int_feature!( - atom!("-moz-gtk-csd-reversed-placement"), - GTKCSDReversedPlacement - ), - lnf_int_feature!(atom!("-moz-system-dark-theme"), SystemUsesDarkTheme), - lnf_int_feature!(atom!("-moz-panel-animations"), PanelAnimations), - // media query for MathML Core's implementation of maction/semantics - bool_pref_feature!( - atom!("-moz-mathml-core-maction-and-semantics"), - "mathml.legacy_maction_and_semantics_implementations.disabled" - ), - // media query for MathML Core's implementation of ms - bool_pref_feature!( - atom!("-moz-mathml-core-ms"), - "mathml.ms_lquote_rquote_attributes.disabled" - ), - // media query for popover attribute - bool_pref_feature!(atom!("-moz-popover-enabled"), "dom.element.popover.enabled"), -]; diff --git a/components/style/gecko/media_queries.rs b/components/style/gecko/media_queries.rs deleted file mode 100644 index 2ea4229133a..00000000000 --- a/components/style/gecko/media_queries.rs +++ /dev/null @@ -1,560 +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/. */ - -//! Gecko's media-query device and expression representation. - -use crate::color::AbsoluteColor; -use crate::context::QuirksMode; -use crate::custom_properties::CssEnvironment; -use crate::font_metrics::FontMetrics; -use crate::gecko::values::{convert_absolute_color_to_nscolor, convert_nscolor_to_absolute_color}; -use crate::gecko_bindings::bindings; -use crate::gecko_bindings::structs; -use crate::media_queries::MediaType; -use crate::properties::ComputedValues; -use crate::string_cache::Atom; -use crate::values::computed::font::GenericFontFamily; -use crate::values::computed::{ColorScheme, Length, NonNegativeLength}; -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 euclid::default::Size2D; -use euclid::{Scale, SideOffsets2D}; -use servo_arc::Arc; -use std::sync::atomic::{AtomicBool, AtomicU32, AtomicUsize, Ordering}; -use std::{cmp, fmt}; -use style_traits::{CSSPixel, DevicePixel}; - -/// The `Device` in Gecko wraps a pres context, has a default values computed, -/// and contains all the viewport rule state. -pub struct Device { - /// NB: The document owns the styleset, who owns the stylist, and thus the - /// `Device`, so having a raw document pointer here is fine. - document: *const structs::Document, - default_values: Arc, - /// The font size of the root element. - /// - /// This is set when computing the style of the root element, and used for - /// rem units in other elements. - /// - /// When computing the style of the root element, there can't be any other - /// style being computed at the same time, given we need the style of the - /// parent to compute everything else. So it is correct to just use a - /// relaxed atomic here. - root_font_size: AtomicU32, - /// The body text color, stored as an `nscolor`, used for the "tables - /// inherit from body" quirk. - /// - /// - body_text_color: AtomicUsize, - /// Whether any styles computed in the document relied on the root font-size - /// by using rem units. - used_root_font_size: AtomicBool, - /// Whether any styles computed in the document relied on font metrics. - used_font_metrics: AtomicBool, - /// 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, -} - -impl fmt::Debug for Device { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - use nsstring::nsCString; - - let mut doc_uri = nsCString::new(); - unsafe { - bindings::Gecko_nsIURI_Debug((*self.document()).mDocumentURI.raw(), &mut doc_uri) - }; - - f.debug_struct("Device") - .field("document_url", &doc_uri) - .finish() - } -} - -unsafe impl Sync for Device {} -unsafe impl Send for Device {} - -impl Device { - /// Trivially constructs a new `Device`. - pub fn new(document: *const structs::Document) -> Self { - assert!(!document.is_null()); - let doc = unsafe { &*document }; - let prefs = unsafe { &*bindings::Gecko_GetPrefSheetPrefs(doc) }; - Device { - document, - default_values: ComputedValues::default_values(doc), - root_font_size: AtomicU32::new(FONT_MEDIUM_PX.to_bits()), - // This gets updated when we see the , so it doesn't really - // matter which color-scheme we look at here. - body_text_color: AtomicUsize::new(prefs.mLightColors.mDefault as usize), - used_root_font_size: AtomicBool::new(false), - used_font_metrics: AtomicBool::new(false), - used_viewport_size: AtomicBool::new(false), - used_dynamic_viewport_size: AtomicBool::new(false), - environment: CssEnvironment, - } - } - - /// Get the relevant environment to resolve `env()` functions. - #[inline] - pub fn environment(&self) -> &CssEnvironment { - &self.environment - } - - /// Returns the computed line-height for the font in a given computed values instance. - /// - /// If you pass down an element, then the used line-height is returned. - pub fn calc_line_height( - &self, - line_height: &crate::values::computed::LineHeight, - vertical: bool, - font: &crate::properties::style_structs::Font, - element: Option, - ) -> NonNegativeLength { - let pres_context = self.pres_context(); - let au = Au(unsafe { - bindings::Gecko_CalcLineHeight( - line_height, - pres_context.map_or(std::ptr::null(), |pc| pc), - vertical, - &**font, - element.map_or(std::ptr::null(), |e| e.0), - ) - }); - NonNegativeLength::new(au.to_f32_px()) - } - - /// Whether any animation name may be referenced from the style of any - /// element. - pub fn animation_name_may_be_referenced(&self, name: &KeyframesName) -> bool { - let pc = match self.pres_context() { - Some(pc) => pc, - None => return false, - }; - - unsafe { - bindings::Gecko_AnimationNameMayBeReferencedFromStyle(pc, name.as_atom().as_ptr()) - } - } - - /// Returns the default computed values as a reference, in order to match - /// Servo. - pub fn default_computed_values(&self) -> &ComputedValues { - &self.default_values - } - - /// Returns the default computed values as an `Arc`. - pub fn default_computed_values_arc(&self) -> &Arc { - &self.default_values - } - - /// Get the font size of the root element (for rem) - pub fn root_font_size(&self) -> Length { - self.used_root_font_size.store(true, Ordering::Relaxed); - Length::new(f32::from_bits(self.root_font_size.load(Ordering::Relaxed))) - } - - /// Set the font size of the root element (for rem) - pub fn set_root_font_size(&self, size: Length) { - self.root_font_size - .store(size.px().to_bits(), Ordering::Relaxed) - } - - /// The quirks mode of the document. - pub fn quirks_mode(&self) -> QuirksMode { - self.document().mCompatMode.into() - } - - /// Sets the body text color for the "inherit color from body" quirk. - /// - /// - pub fn set_body_text_color(&self, color: AbsoluteColor) { - self.body_text_color.store( - convert_absolute_color_to_nscolor(&color) as usize, - Ordering::Relaxed, - ) - } - - /// Gets the base size given a generic font family and a language. - pub fn base_size_for_generic(&self, language: &Atom, generic: GenericFontFamily) -> Length { - unsafe { bindings::Gecko_GetBaseSize(self.document(), language.as_ptr(), generic) } - } - - /// Gets the size of the scrollbar in CSS pixels. - pub fn scrollbar_inline_size(&self) -> Length { - let pc = match self.pres_context() { - Some(pc) => pc, - // XXX: we could have a more reasonable default perhaps. - None => return Length::new(0.0), - }; - Length::new(unsafe { bindings::Gecko_GetScrollbarInlineSize(pc) }) - } - - /// Queries font metrics - pub fn query_font_metrics( - &self, - vertical: bool, - font: &crate::properties::style_structs::Font, - base_size: Length, - in_media_query: bool, - retrieve_math_scales: bool, - ) -> FontMetrics { - self.used_font_metrics.store(true, Ordering::Relaxed); - let pc = match self.pres_context() { - Some(pc) => pc, - None => return Default::default(), - }; - let gecko_metrics = unsafe { - bindings::Gecko_GetFontMetrics( - pc, - vertical, - &**font, - base_size, - // we don't use the user font set in a media query - !in_media_query, - retrieve_math_scales, - ) - }; - FontMetrics { - x_height: Some(gecko_metrics.mXSize), - zero_advance_measure: if gecko_metrics.mChSize.px() >= 0. { - Some(gecko_metrics.mChSize) - } else { - None - }, - cap_height: if gecko_metrics.mCapHeight.px() >= 0. { - Some(gecko_metrics.mCapHeight) - } else { - None - }, - ic_width: if gecko_metrics.mIcWidth.px() >= 0. { - Some(gecko_metrics.mIcWidth) - } else { - None - }, - ascent: gecko_metrics.mAscent, - script_percent_scale_down: if gecko_metrics.mScriptPercentScaleDown > 0. { - Some(gecko_metrics.mScriptPercentScaleDown) - } else { - None - }, - script_script_percent_scale_down: if gecko_metrics.mScriptScriptPercentScaleDown > 0. { - Some(gecko_metrics.mScriptScriptPercentScaleDown) - } else { - None - }, - } - } - - /// Returns the body text color. - pub fn body_text_color(&self) -> AbsoluteColor { - convert_nscolor_to_absolute_color(self.body_text_color.load(Ordering::Relaxed) as u32) - } - - /// Gets the document pointer. - #[inline] - pub fn document(&self) -> &structs::Document { - unsafe { &*self.document } - } - - /// Gets the pres context associated with this document. - #[inline] - pub fn pres_context(&self) -> Option<&structs::nsPresContext> { - unsafe { - self.document() - .mPresShell - .as_ref()? - .mPresContext - .mRawPtr - .as_ref() - } - } - - /// Gets the preference stylesheet prefs for our document. - #[inline] - pub fn pref_sheet_prefs(&self) -> &structs::PreferenceSheet_Prefs { - unsafe { &*bindings::Gecko_GetPrefSheetPrefs(self.document()) } - } - - /// Recreates the default computed values. - pub fn reset_computed_values(&mut self) { - self.default_values = ComputedValues::default_values(self.document()); - } - - /// Rebuild all the cached data. - pub fn rebuild_cached_data(&mut self) { - self.reset_computed_values(); - 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. - pub fn used_root_font_size(&self) -> bool { - self.used_root_font_size.load(Ordering::Relaxed) - } - - /// Recreates all the temporary state that the `Device` stores. - /// - /// This includes the viewport override from `@viewport` rules, and also the - /// default computed values. - pub fn reset(&mut self) { - self.reset_computed_values(); - } - - /// Returns whether this document is in print preview. - pub fn is_print_preview(&self) -> bool { - let pc = match self.pres_context() { - Some(pc) => pc, - None => return false, - }; - pc.mType == structs::nsPresContext_nsPresContextType_eContext_PrintPreview - } - - /// Returns the current media type of the device. - pub fn media_type(&self) -> MediaType { - let pc = match self.pres_context() { - Some(pc) => pc, - None => return MediaType::screen(), - }; - - // Gecko allows emulating random media with mMediaEmulationData.mMedium. - let medium_to_use = if !pc.mMediaEmulationData.mMedium.mRawPtr.is_null() { - pc.mMediaEmulationData.mMedium.mRawPtr - } else { - pc.mMedium as *const structs::nsAtom as *mut _ - }; - - MediaType(CustomIdent(unsafe { Atom::from_raw(medium_to_use) })) - } - - // It may make sense to account for @page rule margins here somehow, however - // it's not clear how that'd work, see: - // https://github.com/w3c/csswg-drafts/issues/5437 - fn page_size_minus_default_margin(&self, pc: &structs::nsPresContext) -> Size2D { - debug_assert!(pc.mIsRootPaginatedDocument() != 0); - let area = &pc.mPageSize; - let margin = &pc.mDefaultPageMargin; - let width = area.width - margin.left - margin.right; - let height = area.height - margin.top - margin.bottom; - Size2D::new(Au(cmp::max(width, 0)), Au(cmp::max(height, 0))) - } - - /// Returns the current viewport size in app units. - pub fn au_viewport_size(&self) -> Size2D { - let pc = match self.pres_context() { - Some(pc) => pc, - None => return Size2D::new(Au(0), Au(0)), - }; - - if pc.mIsRootPaginatedDocument() != 0 { - return self.page_size_minus_default_margin(pc); - } - - let area = &pc.mVisibleArea; - Size2D::new(Au(area.width), Au(area.height)) - } - - /// 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 { - self.used_viewport_size.store(true, Ordering::Relaxed); - let pc = match self.pres_context() { - Some(pc) => pc, - None => return Size2D::new(Au(0), Au(0)), - }; - - if pc.mIsRootPaginatedDocument() != 0 { - 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.mDynamicToolbarMaxHeight - pc.mDynamicToolbarHeight) as i32 * - pc.mCurAppUnitsPerDevPixel), - ) - }, - } - } - - /// Returns whether we ever looked up the viewport size of the Device. - pub fn used_viewport_size(&self) -> bool { - 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 number of app units per device pixel we're using currently. - pub fn app_units_per_device_pixel(&self) -> i32 { - match self.pres_context() { - Some(pc) => pc.mCurAppUnitsPerDevPixel, - None => AU_PER_PX, - } - } - - /// Returns the device pixel ratio. - pub fn device_pixel_ratio(&self) -> Scale { - let pc = match self.pres_context() { - Some(pc) => pc, - None => return Scale::new(1.), - }; - - if pc.mMediaEmulationData.mDPPX > 0.0 { - return Scale::new(pc.mMediaEmulationData.mDPPX); - } - - let au_per_dpx = pc.mCurAppUnitsPerDevPixel as f32; - let au_per_px = AU_PER_PX as f32; - Scale::new(au_per_px / au_per_dpx) - } - - /// Returns whether document colors are enabled. - #[inline] - pub fn use_document_colors(&self) -> bool { - let doc = self.document(); - if doc.mIsBeingUsedAsImage() { - return true; - } - self.pref_sheet_prefs().mUseDocumentColors - } - - /// Computes a system color and returns it as an nscolor. - pub(crate) fn system_nscolor( - &self, - system_color: SystemColor, - color_scheme: &ColorScheme, - ) -> u32 { - unsafe { bindings::Gecko_ComputeSystemColor(system_color, self.document(), color_scheme) } - } - - /// Returns the default background color. - /// - /// This is only for forced-colors/high-contrast, so looking at light colors - /// is ok. - pub fn default_background_color(&self) -> AbsoluteColor { - let normal = ColorScheme::normal(); - convert_nscolor_to_absolute_color(self.system_nscolor(SystemColor::Canvas, &normal)) - } - - /// Returns the default foreground color. - /// - /// See above for looking at light colors only. - pub fn default_color(&self) -> AbsoluteColor { - let normal = ColorScheme::normal(); - convert_nscolor_to_absolute_color(self.system_nscolor(SystemColor::Canvastext, &normal)) - } - - /// Returns the current effective text zoom. - #[inline] - fn text_zoom(&self) -> f32 { - let pc = match self.pres_context() { - Some(pc) => pc, - None => return 1., - }; - pc.mTextZoom - } - - /// Applies text zoom to a font-size or line-height value (see nsStyleFont::ZoomText). - #[inline] - pub fn zoom_text(&self, size: Length) -> Length { - size.scale_by(self.text_zoom()) - } - - /// Un-apply text zoom. - #[inline] - pub fn unzoom_text(&self, size: Length) -> Length { - size.scale_by(1. / self.text_zoom()) - } - - /// Returns safe area insets - pub fn safe_area_insets(&self) -> SideOffsets2D { - let pc = match self.pres_context() { - Some(pc) => pc, - None => return SideOffsets2D::zero(), - }; - let mut top = 0.0; - let mut right = 0.0; - let mut bottom = 0.0; - let mut left = 0.0; - unsafe { - bindings::Gecko_GetSafeAreaInsets(pc, &mut top, &mut right, &mut bottom, &mut left) - }; - SideOffsets2D::new(top, right, bottom, left) - } - - /// Returns true if the given MIME type is supported - pub fn is_supported_mime_type(&self, mime_type: &str) -> bool { - unsafe { - bindings::Gecko_IsSupportedImageMimeType(mime_type.as_ptr(), mime_type.len() as u32) - } - } - - /// Return whether the document is a chrome document. - /// - /// This check is consistent with how we enable chrome rules for chrome:// and resource:// - /// stylesheets (and thus chrome:// documents). - #[inline] - pub fn chrome_rules_enabled_for_document(&self) -> bool { - self.document().mChromeRulesEnabled() - } -} diff --git a/components/style/gecko/mod.rs b/components/style/gecko/mod.rs deleted file mode 100644 index c32ded14f33..00000000000 --- a/components/style/gecko/mod.rs +++ /dev/null @@ -1,23 +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/. */ - -//! Gecko-specific style-system bits. - -#[macro_use] -mod non_ts_pseudo_class_list; - -pub mod arc_types; -pub mod conversions; -pub mod data; -pub mod media_features; -pub mod media_queries; -pub mod pseudo_element; -pub mod restyle_damage; -pub mod selector_parser; -pub mod snapshot; -pub mod snapshot_helpers; -pub mod traversal; -pub mod url; -pub mod values; -pub mod wrapper; diff --git a/components/style/gecko/non_ts_pseudo_class_list.rs b/components/style/gecko/non_ts_pseudo_class_list.rs deleted file mode 100644 index e494082047f..00000000000 --- a/components/style/gecko/non_ts_pseudo_class_list.rs +++ /dev/null @@ -1,100 +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/. */ - -/* - * This file contains a helper macro includes all supported non-tree-structural - * pseudo-classes. - * - * FIXME: Find a way to autogenerate this file. - * - * Expected usage is as follows: - * ``` - * macro_rules! pseudo_class_macro{ - * ([$(($css:expr, $name:ident, $gecko_type:tt, $state:tt, $flags:tt),)*]) => { - * // do stuff - * } - * } - * apply_non_ts_list!(pseudo_class_macro) - * ``` - * - * $gecko_type can be either "_" or an ident in Gecko's CSSPseudoClassType. - * $state can be either "_" or an expression of type ElementState. If present, - * the semantics are that the pseudo-class matches if any of the bits in - * $state are set on the element. - * $flags can be either "_" or an expression of type NonTSPseudoClassFlag, - * see selector_parser.rs for more details. - */ - -macro_rules! apply_non_ts_list { - ($apply_macro:ident) => { - $apply_macro! { - [ - ("-moz-table-border-nonzero", MozTableBorderNonzero, _, PSEUDO_CLASS_ENABLED_IN_UA_SHEETS), - ("-moz-browser-frame", MozBrowserFrame, _, PSEUDO_CLASS_ENABLED_IN_UA_SHEETS_AND_CHROME), - ("-moz-select-list-box", MozSelectListBox, _, PSEUDO_CLASS_ENABLED_IN_UA_SHEETS), - ("link", Link, UNVISITED, _), - ("any-link", AnyLink, VISITED_OR_UNVISITED, _), - ("visited", Visited, VISITED, _), - ("active", Active, ACTIVE, _), - ("autofill", Autofill, AUTOFILL, _), - ("checked", Checked, CHECKED, _), - ("defined", Defined, DEFINED, _), - ("disabled", Disabled, DISABLED, _), - ("enabled", Enabled, ENABLED, _), - ("focus", Focus, FOCUS, _), - ("focus-within", FocusWithin, FOCUS_WITHIN, _), - ("focus-visible", FocusVisible, FOCUSRING, _), - ("hover", Hover, HOVER, _), - ("-moz-drag-over", MozDragOver, DRAGOVER, _), - ("target", Target, URLTARGET, _), - ("indeterminate", Indeterminate, INDETERMINATE, _), - ("-moz-inert", MozInert, INERT, PSEUDO_CLASS_ENABLED_IN_UA_SHEETS), - ("-moz-devtools-highlighted", MozDevtoolsHighlighted, DEVTOOLS_HIGHLIGHTED, PSEUDO_CLASS_ENABLED_IN_UA_SHEETS), - ("-moz-styleeditor-transitioning", MozStyleeditorTransitioning, STYLEEDITOR_TRANSITIONING, PSEUDO_CLASS_ENABLED_IN_UA_SHEETS), - ("fullscreen", Fullscreen, FULLSCREEN, _), - ("modal", Modal, MODAL, _), - ("-moz-topmost-modal", MozTopmostModal, TOPMOST_MODAL, PSEUDO_CLASS_ENABLED_IN_UA_SHEETS), - ("-moz-broken", MozBroken, BROKEN, _), - ("-moz-loading", MozLoading, LOADING, _), - ("-moz-has-dir-attr", MozHasDirAttr, HAS_DIR_ATTR, PSEUDO_CLASS_ENABLED_IN_UA_SHEETS), - ("-moz-dir-attr-ltr", MozDirAttrLTR, HAS_DIR_ATTR_LTR, PSEUDO_CLASS_ENABLED_IN_UA_SHEETS), - ("-moz-dir-attr-rtl", MozDirAttrRTL, HAS_DIR_ATTR_RTL, PSEUDO_CLASS_ENABLED_IN_UA_SHEETS), - ("-moz-dir-attr-like-auto", MozDirAttrLikeAuto, HAS_DIR_ATTR_LIKE_AUTO, PSEUDO_CLASS_ENABLED_IN_UA_SHEETS), - - ("-moz-autofill-preview", MozAutofillPreview, AUTOFILL_PREVIEW, PSEUDO_CLASS_ENABLED_IN_UA_SHEETS_AND_CHROME), - ("-moz-value-empty", MozValueEmpty, VALUE_EMPTY, PSEUDO_CLASS_ENABLED_IN_UA_SHEETS), - ("-moz-revealed", MozRevealed, REVEALED, PSEUDO_CLASS_ENABLED_IN_UA_SHEETS), - - ("-moz-math-increment-script-level", MozMathIncrementScriptLevel, INCREMENT_SCRIPT_LEVEL, _), - - ("required", Required, REQUIRED, _), - ("popover-open", PopoverOpen, POPOVER_OPEN, PSEUDO_CLASS_ENABLED_IN_UA_SHEETS_AND_CHROME), - ("optional", Optional, OPTIONAL_, _), - ("valid", Valid, VALID, _), - ("invalid", Invalid, INVALID, _), - ("in-range", InRange, INRANGE, _), - ("out-of-range", OutOfRange, OUTOFRANGE, _), - ("default", Default, DEFAULT, _), - ("placeholder-shown", PlaceholderShown, PLACEHOLDER_SHOWN, _), - ("read-only", ReadOnly, READONLY, _), - ("read-write", ReadWrite, READWRITE, _), - ("user-valid", UserValid, USER_VALID, _), - ("user-invalid", UserInvalid, USER_INVALID, _), - ("-moz-meter-optimum", MozMeterOptimum, OPTIMUM, _), - ("-moz-meter-sub-optimum", MozMeterSubOptimum, SUB_OPTIMUM, _), - ("-moz-meter-sub-sub-optimum", MozMeterSubSubOptimum, SUB_SUB_OPTIMUM, _), - - ("-moz-first-node", MozFirstNode, _, _), - ("-moz-last-node", MozLastNode, _, _), - ("-moz-only-whitespace", MozOnlyWhitespace, _, _), - ("-moz-native-anonymous", MozNativeAnonymous, _, PSEUDO_CLASS_ENABLED_IN_UA_SHEETS), - ("-moz-use-shadow-tree-root", MozUseShadowTreeRoot, _, PSEUDO_CLASS_ENABLED_IN_UA_SHEETS), - ("-moz-is-html", MozIsHTML, _, _), - ("-moz-placeholder", MozPlaceholder, _, _), - ("-moz-lwtheme", MozLWTheme, _, PSEUDO_CLASS_ENABLED_IN_UA_SHEETS_AND_CHROME), - ("-moz-window-inactive", MozWindowInactive, _, _), - ] - } - } -} diff --git a/components/style/gecko/pseudo_element.rs b/components/style/gecko/pseudo_element.rs deleted file mode 100644 index d0c47d51a41..00000000000 --- a/components/style/gecko/pseudo_element.rs +++ /dev/null @@ -1,237 +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/. */ - -//! Gecko's definition of a pseudo-element. -//! -//! Note that a few autogenerated bits of this live in -//! `pseudo_element_definition.mako.rs`. If you touch that file, you probably -//! need to update the checked-in files for Servo. - -use crate::gecko_bindings::structs::{self, PseudoStyleType}; -use crate::properties::longhands::display::computed_value::T as Display; -use crate::properties::{ComputedValues, PropertyFlags}; -use crate::selector_parser::{PseudoElementCascadeType, SelectorImpl}; -use crate::str::{starts_with_ignore_ascii_case, string_as_ascii_lowercase}; -use crate::string_cache::Atom; -use crate::values::serialize_atom_identifier; -use crate::values::AtomIdent; -use cssparser::ToCss; -use static_prefs::pref; -use std::fmt; - -include!(concat!( - env!("OUT_DIR"), - "/gecko/pseudo_element_definition.rs" -)); - -impl ::selectors::parser::PseudoElement for PseudoElement { - type Impl = SelectorImpl; - - // ::slotted() should support all tree-abiding pseudo-elements, see - // https://drafts.csswg.org/css-scoping/#slotted-pseudo - // https://drafts.csswg.org/css-pseudo-4/#treelike - #[inline] - fn valid_after_slotted(&self) -> bool { - matches!( - *self, - PseudoElement::Before | - PseudoElement::After | - PseudoElement::Marker | - PseudoElement::Placeholder | - PseudoElement::FileSelectorButton - ) - } - - #[inline] - fn accepts_state_pseudo_classes(&self) -> bool { - self.supports_user_action_state() - } -} - -impl PseudoElement { - /// Returns the kind of cascade type that a given pseudo is going to use. - /// - /// In Gecko we only compute ::before and ::after eagerly. We save the rules - /// for anonymous boxes separately, so we resolve them as precomputed - /// pseudos. - /// - /// We resolve the others lazily, see `Servo_ResolvePseudoStyle`. - pub fn cascade_type(&self) -> PseudoElementCascadeType { - if self.is_eager() { - debug_assert!(!self.is_anon_box()); - return PseudoElementCascadeType::Eager; - } - - if self.is_precomputed() { - return PseudoElementCascadeType::Precomputed; - } - - PseudoElementCascadeType::Lazy - } - - /// Whether the pseudo-element should inherit from the default computed - /// values instead of from the parent element. - /// - /// This is not the common thing, but there are some pseudos (namely: - /// ::backdrop), that shouldn't inherit from the parent element. - pub fn inherits_from_default_values(&self) -> bool { - matches!(*self, PseudoElement::Backdrop) - } - - /// Gets the canonical index of this eagerly-cascaded pseudo-element. - #[inline] - pub fn eager_index(&self) -> usize { - EAGER_PSEUDOS - .iter() - .position(|p| p == self) - .expect("Not an eager pseudo") - } - - /// Creates a pseudo-element from an eager index. - #[inline] - pub fn from_eager_index(i: usize) -> Self { - EAGER_PSEUDOS[i].clone() - } - - /// Whether animations for the current pseudo element are stored in the - /// parent element. - #[inline] - pub fn animations_stored_in_parent(&self) -> bool { - matches!(*self, Self::Before | Self::After | Self::Marker) - } - - /// Whether the current pseudo element is ::before or ::after. - #[inline] - pub fn is_before_or_after(&self) -> bool { - self.is_before() || self.is_after() - } - - /// Whether this pseudo-element is the ::before pseudo. - #[inline] - pub fn is_before(&self) -> bool { - *self == PseudoElement::Before - } - - /// Whether this pseudo-element is the ::after pseudo. - #[inline] - pub fn is_after(&self) -> bool { - *self == PseudoElement::After - } - - /// Whether this pseudo-element is the ::marker pseudo. - #[inline] - pub fn is_marker(&self) -> bool { - *self == PseudoElement::Marker - } - - /// Whether this pseudo-element is the ::selection pseudo. - #[inline] - pub fn is_selection(&self) -> bool { - *self == PseudoElement::Selection - } - - /// Whether this pseudo-element is ::first-letter. - #[inline] - pub fn is_first_letter(&self) -> bool { - *self == PseudoElement::FirstLetter - } - - /// Whether this pseudo-element is ::first-line. - #[inline] - pub fn is_first_line(&self) -> bool { - *self == PseudoElement::FirstLine - } - - /// Whether this pseudo-element is the ::-moz-color-swatch pseudo. - #[inline] - pub fn is_color_swatch(&self) -> bool { - *self == PseudoElement::MozColorSwatch - } - - /// Whether this pseudo-element is lazily-cascaded. - #[inline] - pub fn is_lazy(&self) -> bool { - !self.is_eager() && !self.is_precomputed() - } - - /// The identifier of the highlight this pseudo-element represents. - pub fn highlight_name(&self) -> Option<&AtomIdent> { - match &*self { - PseudoElement::Highlight(name) => Some(&name), - _ => None, - } - } - - /// Whether this pseudo-element is the ::highlight pseudo. - pub fn is_highlight(&self) -> bool { - matches!(*self, PseudoElement::Highlight(_)) - } - - /// Whether this pseudo-element supports user action selectors. - pub fn supports_user_action_state(&self) -> bool { - (self.flags() & structs::CSS_PSEUDO_ELEMENT_SUPPORTS_USER_ACTION_STATE) != 0 - } - - /// Whether this pseudo-element is enabled for all content. - pub fn enabled_in_content(&self) -> bool { - if self.is_highlight() && !pref!("dom.customHighlightAPI.enabled") { - return false; - } - return self.flags() & structs::CSS_PSEUDO_ELEMENT_ENABLED_IN_UA_SHEETS_AND_CHROME == 0; - } - - /// Whether this pseudo is enabled explicitly in UA sheets. - pub fn enabled_in_ua_sheets(&self) -> bool { - (self.flags() & structs::CSS_PSEUDO_ELEMENT_ENABLED_IN_UA_SHEETS) != 0 - } - - /// Whether this pseudo is enabled explicitly in chrome sheets. - pub fn enabled_in_chrome(&self) -> bool { - (self.flags() & structs::CSS_PSEUDO_ELEMENT_ENABLED_IN_CHROME) != 0 - } - - /// Whether this pseudo-element skips flex/grid container display-based - /// fixup. - #[inline] - pub fn skip_item_display_fixup(&self) -> bool { - (self.flags() & structs::CSS_PSEUDO_ELEMENT_IS_FLEX_OR_GRID_ITEM) == 0 - } - - /// Whether this pseudo-element is precomputed. - #[inline] - pub fn is_precomputed(&self) -> bool { - self.is_anon_box() && !self.is_tree_pseudo_element() - } - - /// Property flag that properties must have to apply to this pseudo-element. - #[inline] - pub fn property_restriction(&self) -> Option { - Some(match *self { - PseudoElement::FirstLetter => PropertyFlags::APPLIES_TO_FIRST_LETTER, - PseudoElement::FirstLine => PropertyFlags::APPLIES_TO_FIRST_LINE, - PseudoElement::Placeholder => PropertyFlags::APPLIES_TO_PLACEHOLDER, - PseudoElement::Cue => PropertyFlags::APPLIES_TO_CUE, - PseudoElement::Marker if static_prefs::pref!("layout.css.marker.restricted") => { - PropertyFlags::APPLIES_TO_MARKER - }, - _ => return None, - }) - } - - /// Whether this pseudo-element should actually exist if it has - /// the given styles. - pub fn should_exist(&self, style: &ComputedValues) -> bool { - debug_assert!(self.is_eager()); - - if style.get_box().clone_display() == Display::None { - return false; - } - - if self.is_before_or_after() && style.ineffective_content_property() { - return false; - } - - true - } -} diff --git a/components/style/gecko/pseudo_element_definition.mako.rs b/components/style/gecko/pseudo_element_definition.mako.rs deleted file mode 100644 index 73e7893c998..00000000000 --- a/components/style/gecko/pseudo_element_definition.mako.rs +++ /dev/null @@ -1,276 +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/. */ - -/// Gecko's pseudo-element definition. -/// -/// We intentionally double-box legacy ::-moz-tree pseudo-elements to keep the -/// size of PseudoElement (and thus selector components) small. -#[derive(Clone, Debug, Eq, Hash, MallocSizeOf, PartialEq, ToShmem)] -pub enum PseudoElement { - % for pseudo in PSEUDOS: - /// ${pseudo.value} - % if pseudo.is_tree_pseudo_element(): - ${pseudo.capitalized_pseudo()}(thin_vec::ThinVec), - % elif pseudo.pseudo_ident == "highlight": - ${pseudo.capitalized_pseudo()}(AtomIdent), - % else: - ${pseudo.capitalized_pseudo()}, - % endif - % endfor - /// ::-webkit-* that we don't recognize - /// https://github.com/whatwg/compat/issues/103 - UnknownWebkit(Atom), -} - -/// Important: If you change this, you should also update Gecko's -/// nsCSSPseudoElements::IsEagerlyCascadedInServo. -<% EAGER_PSEUDOS = ["Before", "After", "FirstLine", "FirstLetter"] %> -<% TREE_PSEUDOS = [pseudo for pseudo in PSEUDOS if pseudo.is_tree_pseudo_element()] %> -<% SIMPLE_PSEUDOS = [pseudo for pseudo in PSEUDOS if pseudo.is_simple_pseudo_element()] %> - -/// The number of eager pseudo-elements. -pub const EAGER_PSEUDO_COUNT: usize = ${len(EAGER_PSEUDOS)}; - -/// The number of non-functional pseudo-elements. -pub const SIMPLE_PSEUDO_COUNT: usize = ${len(SIMPLE_PSEUDOS)}; - -/// The number of tree pseudo-elements. -pub const TREE_PSEUDO_COUNT: usize = ${len(TREE_PSEUDOS)}; - -/// The number of all pseudo-elements. -pub const PSEUDO_COUNT: usize = ${len(PSEUDOS)}; - -/// The list of eager pseudos. -pub const EAGER_PSEUDOS: [PseudoElement; EAGER_PSEUDO_COUNT] = [ - % for eager_pseudo_name in EAGER_PSEUDOS: - PseudoElement::${eager_pseudo_name}, - % endfor -]; - -<%def name="pseudo_element_variant(pseudo, tree_arg='..')">\ -PseudoElement::${pseudo.capitalized_pseudo()}${"({})".format(tree_arg) if not pseudo.is_simple_pseudo_element() else ""}\ - - -impl PseudoElement { - /// Returns an index of the pseudo-element. - #[inline] - pub fn index(&self) -> usize { - match *self { - % for i, pseudo in enumerate(PSEUDOS): - ${pseudo_element_variant(pseudo)} => ${i}, - % endfor - PseudoElement::UnknownWebkit(..) => unreachable!(), - } - } - - /// Returns an array of `None` values. - /// - /// FIXME(emilio): Integer generics can't come soon enough. - pub fn pseudo_none_array() -> [Option; PSEUDO_COUNT] { - [ - ${",\n ".join(["None" for pseudo in PSEUDOS])} - ] - } - - /// Whether this pseudo-element is an anonymous box. - #[inline] - pub fn is_anon_box(&self) -> bool { - match *self { - % for pseudo in PSEUDOS: - % if pseudo.is_anon_box(): - ${pseudo_element_variant(pseudo)} => true, - % endif - % endfor - _ => false, - } - } - - /// Whether this pseudo-element is eagerly-cascaded. - #[inline] - pub fn is_eager(&self) -> bool { - matches!(*self, - ${" | ".join(map(lambda name: "PseudoElement::{}".format(name), EAGER_PSEUDOS))}) - } - - /// Whether this pseudo-element is tree pseudo-element. - #[inline] - pub fn is_tree_pseudo_element(&self) -> bool { - match *self { - % for pseudo in TREE_PSEUDOS: - ${pseudo_element_variant(pseudo)} => true, - % endfor - _ => false, - } - } - - /// Whether this pseudo-element is an unknown Webkit-prefixed pseudo-element. - #[inline] - pub fn is_unknown_webkit_pseudo_element(&self) -> bool { - matches!(*self, PseudoElement::UnknownWebkit(..)) - } - - /// Gets the flags associated to this pseudo-element, or 0 if it's an - /// anonymous box. - pub fn flags(&self) -> u32 { - match *self { - % for pseudo in PSEUDOS: - ${pseudo_element_variant(pseudo)} => - % if pseudo.is_tree_pseudo_element(): - structs::CSS_PSEUDO_ELEMENT_ENABLED_IN_UA_SHEETS_AND_CHROME, - % elif pseudo.is_anon_box(): - structs::CSS_PSEUDO_ELEMENT_ENABLED_IN_UA_SHEETS, - % else: - structs::SERVO_CSS_PSEUDO_ELEMENT_FLAGS_${pseudo.pseudo_ident}, - % endif - % endfor - PseudoElement::UnknownWebkit(..) => 0, - } - } - - /// Construct a pseudo-element from a `PseudoStyleType`. - #[inline] - pub fn from_pseudo_type(type_: PseudoStyleType) -> Option { - match type_ { - % for pseudo in PSEUDOS: - % if pseudo.is_simple_pseudo_element(): - PseudoStyleType::${pseudo.pseudo_ident} => { - Some(${pseudo_element_variant(pseudo)}) - }, - % endif - % endfor - _ => None, - } - } - - /// Construct a `PseudoStyleType` from a pseudo-element - #[inline] - pub fn pseudo_type(&self) -> PseudoStyleType { - match *self { - % for pseudo in PSEUDOS: - % if pseudo.is_tree_pseudo_element(): - PseudoElement::${pseudo.capitalized_pseudo()}(..) => PseudoStyleType::XULTree, - % elif pseudo.pseudo_ident == "highlight": - PseudoElement::${pseudo.capitalized_pseudo()}(..) => PseudoStyleType::${pseudo.pseudo_ident}, - % else: - PseudoElement::${pseudo.capitalized_pseudo()} => PseudoStyleType::${pseudo.pseudo_ident}, - % endif - % endfor - PseudoElement::UnknownWebkit(..) => unreachable!(), - } - } - - /// Get the argument list of a tree pseudo-element. - #[inline] - pub fn tree_pseudo_args(&self) -> Option<<&[Atom]> { - match *self { - % for pseudo in TREE_PSEUDOS: - PseudoElement::${pseudo.capitalized_pseudo()}(ref args) => Some(args), - % endfor - _ => None, - } - } - - /// Construct a tree pseudo-element from atom and args. - #[inline] - pub fn from_tree_pseudo_atom(atom: &Atom, args: Box<[Atom]>) -> Option { - % for pseudo in PSEUDOS: - % if pseudo.is_tree_pseudo_element(): - if atom == &atom!("${pseudo.value}") { - return Some(PseudoElement::${pseudo.capitalized_pseudo()}(args.into())); - } - % endif - % endfor - None - } - - /// Constructs a pseudo-element from a string of text. - /// - /// Returns `None` if the pseudo-element is not recognised. - #[inline] - pub fn from_slice(name: &str, allow_unkown_webkit: bool) -> Option { - // We don't need to support tree pseudos because functional - // pseudo-elements needs arguments, and thus should be created - // via other methods. - match_ignore_ascii_case! { name, - % for pseudo in SIMPLE_PSEUDOS: - "${pseudo.value[1:]}" => { - return Some(${pseudo_element_variant(pseudo)}) - }, - % endfor - // Alias some legacy prefixed pseudos to their standardized name at parse time: - "-moz-selection" => { - return Some(PseudoElement::Selection); - }, - "-moz-placeholder" => { - return Some(PseudoElement::Placeholder); - }, - "-moz-list-bullet" | "-moz-list-number" => { - return Some(PseudoElement::Marker); - }, - _ => { - if starts_with_ignore_ascii_case(name, "-moz-tree-") { - return PseudoElement::tree_pseudo_element(name, Default::default()) - } - const WEBKIT_PREFIX: &str = "-webkit-"; - if allow_unkown_webkit && starts_with_ignore_ascii_case(name, WEBKIT_PREFIX) { - let part = string_as_ascii_lowercase(&name[WEBKIT_PREFIX.len()..]); - return Some(PseudoElement::UnknownWebkit(part.into())); - } - } - } - - None - } - - /// Constructs a tree pseudo-element from the given name and arguments. - /// "name" must start with "-moz-tree-". - /// - /// Returns `None` if the pseudo-element is not recognized. - #[inline] - pub fn tree_pseudo_element(name: &str, args: thin_vec::ThinVec) -> Option { - debug_assert!(starts_with_ignore_ascii_case(name, "-moz-tree-")); - let tree_part = &name[10..]; - % for pseudo in TREE_PSEUDOS: - if tree_part.eq_ignore_ascii_case("${pseudo.value[11:]}") { - return Some(${pseudo_element_variant(pseudo, "args")}); - } - % endfor - None - } -} - -impl ToCss for PseudoElement { - fn to_css(&self, dest: &mut W) -> fmt::Result where W: fmt::Write { - dest.write_char(':')?; - match *self { - % for pseudo in (p for p in PSEUDOS if p.pseudo_ident != "highlight"): - ${pseudo_element_variant(pseudo)} => dest.write_str("${pseudo.value}")?, - % endfor - PseudoElement::Highlight(ref name) => { - dest.write_str(":highlight(")?; - serialize_atom_identifier(name, dest)?; - dest.write_char(')')?; - } - PseudoElement::UnknownWebkit(ref atom) => { - dest.write_str(":-webkit-")?; - serialize_atom_identifier(atom, dest)?; - } - } - if let Some(args) = self.tree_pseudo_args() { - if !args.is_empty() { - dest.write_char('(')?; - let mut iter = args.iter(); - if let Some(first) = iter.next() { - serialize_atom_identifier(&first, dest)?; - for item in iter { - dest.write_str(", ")?; - serialize_atom_identifier(item, dest)?; - } - } - dest.write_char(')')?; - } - } - Ok(()) - } -} diff --git a/components/style/gecko/regen_atoms.py b/components/style/gecko/regen_atoms.py deleted file mode 100755 index 61f2fc4c635..00000000000 --- a/components/style/gecko/regen_atoms.py +++ /dev/null @@ -1,218 +0,0 @@ -#!/usr/bin/env python - -# 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/. - -import re -import os -import sys - -from io import BytesIO - -GECKO_DIR = os.path.dirname(__file__.replace("\\", "/")) -sys.path.insert(0, os.path.join(os.path.dirname(GECKO_DIR), "properties")) - -import build - - -# Matches lines like `GK_ATOM(foo, "foo", 0x12345678, true, nsStaticAtom, PseudoElementAtom)`. -PATTERN = re.compile( - '^GK_ATOM\(([^,]*),[^"]*"([^"]*)",\s*(0x[0-9a-f]+),\s*[^,]*,\s*([^,]*),\s*([^)]*)\)', - re.MULTILINE, -) -FILE = "include/nsGkAtomList.h" - - -def map_atom(ident): - if ident in { - "box", - "loop", - "match", - "mod", - "ref", - "self", - "type", - "use", - "where", - "in", - }: - return ident + "_" - return ident - - -class Atom: - def __init__(self, ident, value, hash, ty, atom_type): - self.ident = "nsGkAtoms_{}".format(ident) - self.original_ident = ident - self.value = value - self.hash = hash - # The Gecko type: "nsStaticAtom", "nsCSSPseudoElementStaticAtom", or - # "nsAnonBoxPseudoStaticAtom". - self.ty = ty - # The type of atom: "Atom", "PseudoElement", "NonInheritingAnonBox", - # or "InheritingAnonBox". - self.atom_type = atom_type - - if ( - self.is_pseudo_element() - or self.is_anon_box() - or self.is_tree_pseudo_element() - ): - self.pseudo_ident = (ident.split("_", 1))[1] - - if self.is_anon_box(): - assert self.is_inheriting_anon_box() or self.is_non_inheriting_anon_box() - - def type(self): - return self.ty - - def capitalized_pseudo(self): - return self.pseudo_ident[0].upper() + self.pseudo_ident[1:] - - def is_pseudo_element(self): - return self.atom_type == "PseudoElementAtom" - - def is_anon_box(self): - if self.is_tree_pseudo_element(): - return False - return self.is_non_inheriting_anon_box() or self.is_inheriting_anon_box() - - def is_non_inheriting_anon_box(self): - assert not self.is_tree_pseudo_element() - return self.atom_type == "NonInheritingAnonBoxAtom" - - def is_inheriting_anon_box(self): - if self.is_tree_pseudo_element(): - return False - return self.atom_type == "InheritingAnonBoxAtom" - - def is_tree_pseudo_element(self): - return self.value.startswith(":-moz-tree-") - - def is_simple_pseudo_element(self) -> bool: - return not (self.is_tree_pseudo_element() or self.pseudo_ident == "highlight") - - -def collect_atoms(objdir): - atoms = [] - path = os.path.abspath(os.path.join(objdir, FILE)) - print("cargo:rerun-if-changed={}".format(path)) - with open(path) as f: - content = f.read() - for result in PATTERN.finditer(content): - atoms.append( - Atom( - result.group(1), - result.group(2), - result.group(3), - result.group(4), - result.group(5), - ) - ) - return atoms - - -class FileAvoidWrite(BytesIO): - """File-like object that buffers output and only writes if content changed.""" - - def __init__(self, filename): - BytesIO.__init__(self) - self.name = filename - - def write(self, buf): - if isinstance(buf, str): - buf = buf.encode("utf-8") - BytesIO.write(self, buf) - - def close(self): - buf = self.getvalue() - BytesIO.close(self) - try: - with open(self.name, "rb") as f: - old_content = f.read() - if old_content == buf: - print("{} is not changed, skip".format(self.name)) - return - except IOError: - pass - with open(self.name, "wb") as f: - f.write(buf) - - def __enter__(self): - return self - - def __exit__(self, type, value, traceback): - if not self.closed: - self.close() - - -PRELUDE = """ -/* 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/. */ - -// Autogenerated file created by components/style/gecko/regen_atoms.py. -// DO NOT EDIT DIRECTLY -"""[ - 1: -] - -RULE_TEMPLATE = """ - ("{atom}") => {{{{ - #[allow(unsafe_code)] #[allow(unused_unsafe)] - unsafe {{ $crate::string_cache::Atom::from_index_unchecked({index}) }} - }}}}; -"""[ - 1: -] - -MACRO_TEMPLATE = """ -/// Returns a static atom by passing the literal string it represents. -#[macro_export] -macro_rules! atom {{ -{body}\ -}} -""" - - -def write_atom_macro(atoms, file_name): - with FileAvoidWrite(file_name) as f: - f.write(PRELUDE) - macro_rules = [ - RULE_TEMPLATE.format(atom=atom.value, name=atom.ident, index=i) - for (i, atom) in enumerate(atoms) - ] - f.write(MACRO_TEMPLATE.format(body="".join(macro_rules))) - - -def write_pseudo_elements(atoms, target_filename): - pseudos = [] - for atom in atoms: - if ( - atom.type() == "nsCSSPseudoElementStaticAtom" - or atom.type() == "nsCSSAnonBoxPseudoStaticAtom" - ): - pseudos.append(atom) - - pseudo_definition_template = os.path.join( - GECKO_DIR, "pseudo_element_definition.mako.rs" - ) - print("cargo:rerun-if-changed={}".format(pseudo_definition_template)) - contents = build.render(pseudo_definition_template, PSEUDOS=pseudos) - - with FileAvoidWrite(target_filename) as f: - f.write(contents) - - -def generate_atoms(dist, out): - atoms = collect_atoms(dist) - write_atom_macro(atoms, os.path.join(out, "atom_macro.rs")) - write_pseudo_elements(atoms, os.path.join(out, "pseudo_element_definition.rs")) - - -if __name__ == "__main__": - if len(sys.argv) != 3: - print("Usage: {} dist out".format(sys.argv[0])) - exit(2) - generate_atoms(sys.argv[1], sys.argv[2]) diff --git a/components/style/gecko/restyle_damage.rs b/components/style/gecko/restyle_damage.rs deleted file mode 100644 index 4749daea183..00000000000 --- a/components/style/gecko/restyle_damage.rs +++ /dev/null @@ -1,121 +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/. */ - -//! Gecko's restyle damage computation (aka change hints, aka `nsChangeHint`). - -use crate::gecko_bindings::bindings; -use crate::gecko_bindings::structs; -use crate::gecko_bindings::structs::nsChangeHint; -use crate::matching::{StyleChange, StyleDifference}; -use crate::properties::ComputedValues; -use std::ops::{BitAnd, BitOr, BitOrAssign, Not}; - -/// The representation of Gecko's restyle damage is just a wrapper over -/// `nsChangeHint`. -#[derive(Clone, Copy, Debug, PartialEq)] -pub struct GeckoRestyleDamage(nsChangeHint); - -impl GeckoRestyleDamage { - /// Trivially construct a new `GeckoRestyleDamage`. - #[inline] - pub fn new(raw: nsChangeHint) -> Self { - GeckoRestyleDamage(raw) - } - - /// Get the inner change hint for this damage. - #[inline] - pub fn as_change_hint(&self) -> nsChangeHint { - self.0 - } - - /// Get an empty change hint, that is (`nsChangeHint(0)`). - #[inline] - pub fn empty() -> Self { - GeckoRestyleDamage(nsChangeHint(0)) - } - - /// Returns whether this restyle damage represents the empty damage. - #[inline] - pub fn is_empty(&self) -> bool { - self.0 == nsChangeHint(0) - } - - /// Computes the `StyleDifference` (including the appropriate change hint) - /// given an old and a new style. - pub fn compute_style_difference( - old_style: &ComputedValues, - new_style: &ComputedValues, - ) -> StyleDifference { - let mut any_style_changed = false; - let mut reset_only = false; - let hint = unsafe { - bindings::Gecko_CalcStyleDifference( - old_style.as_gecko_computed_style(), - new_style.as_gecko_computed_style(), - &mut any_style_changed, - &mut reset_only, - ) - }; - if reset_only && !old_style.custom_properties_equal(new_style) { - // The Gecko_CalcStyleDifference call only checks the non-custom - // property structs, so we check the custom properties here. Since - // they generate no damage themselves, we can skip this check if we - // already know we had some inherited (regular) property - // differences. - any_style_changed = true; - reset_only = false; - } - let change = if any_style_changed { - StyleChange::Changed { reset_only } - } else { - StyleChange::Unchanged - }; - let damage = GeckoRestyleDamage(nsChangeHint(hint)); - StyleDifference { damage, change } - } - - /// Returns true if this restyle damage contains all the damage of |other|. - pub fn contains(self, other: Self) -> bool { - self & other == other - } - - /// Gets restyle damage to reconstruct the entire frame, subsuming all - /// other damage. - pub fn reconstruct() -> Self { - GeckoRestyleDamage(structs::nsChangeHint::nsChangeHint_ReconstructFrame) - } -} - -impl Default for GeckoRestyleDamage { - fn default() -> Self { - Self::empty() - } -} - -impl BitOr for GeckoRestyleDamage { - type Output = Self; - fn bitor(self, other: Self) -> Self { - GeckoRestyleDamage(self.0 | other.0) - } -} - -impl BitOrAssign for GeckoRestyleDamage { - fn bitor_assign(&mut self, other: Self) { - *self = *self | other; - } -} - -impl BitAnd for GeckoRestyleDamage { - type Output = Self; - fn bitand(self, other: Self) -> Self { - GeckoRestyleDamage(nsChangeHint((self.0).0 & (other.0).0)) - } -} - -impl Not for GeckoRestyleDamage { - type Output = Self; - fn not(self) -> Self { - GeckoRestyleDamage(nsChangeHint(!(self.0).0)) - } -} diff --git a/components/style/gecko/selector_parser.rs b/components/style/gecko/selector_parser.rs deleted file mode 100644 index 52549f9ecbe..00000000000 --- a/components/style/gecko/selector_parser.rs +++ /dev/null @@ -1,497 +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/. */ - -//! Gecko-specific bits for selector-parsing. - -use crate::computed_value_flags::ComputedValueFlags; -use crate::invalidation::element::document_state::InvalidationMatchingData; -use crate::properties::ComputedValues; -use crate::selector_parser::{Direction, HorizontalDirection, SelectorParser}; -use crate::str::starts_with_ignore_ascii_case; -use crate::string_cache::{Atom, Namespace, WeakAtom, WeakNamespace}; -use crate::values::{AtomIdent, AtomString}; -use cssparser::{BasicParseError, BasicParseErrorKind, Parser}; -use cssparser::{CowRcStr, SourceLocation, ToCss, Token}; -use dom::{DocumentState, ElementState}; -use selectors::parser::SelectorParseErrorKind; -use std::fmt; -use style_traits::{CssWriter, ParseError, StyleParseErrorKind, ToCss as ToCss_}; -use thin_vec::ThinVec; - -pub use crate::gecko::pseudo_element::{ - PseudoElement, EAGER_PSEUDOS, EAGER_PSEUDO_COUNT, PSEUDO_COUNT, -}; -pub use crate::gecko::snapshot::SnapshotMap; - -bitflags! { - // See NonTSPseudoClass::is_enabled_in() - struct NonTSPseudoClassFlag: u8 { - const PSEUDO_CLASS_ENABLED_IN_UA_SHEETS = 1 << 0; - const PSEUDO_CLASS_ENABLED_IN_CHROME = 1 << 1; - const PSEUDO_CLASS_ENABLED_IN_UA_SHEETS_AND_CHROME = - NonTSPseudoClassFlag::PSEUDO_CLASS_ENABLED_IN_UA_SHEETS.bits | - NonTSPseudoClassFlag::PSEUDO_CLASS_ENABLED_IN_CHROME.bits; - } -} - -/// The type used to store the language argument to the `:lang` pseudo-class. -#[derive(Clone, Debug, Eq, MallocSizeOf, PartialEq, ToCss, ToShmem)] -#[css(comma)] -pub struct Lang(#[css(iterable)] pub ThinVec); - -macro_rules! pseudo_class_name { - ([$(($css:expr, $name:ident, $state:tt, $flags:tt),)*]) => { - /// Our representation of a non tree-structural pseudo-class. - #[derive(Clone, Debug, Eq, MallocSizeOf, PartialEq, ToShmem)] - pub enum NonTSPseudoClass { - $( - #[doc = $css] - $name, - )* - /// The `:lang` pseudo-class. - Lang(Lang), - /// The `:dir` pseudo-class. - Dir(Direction), - /// The non-standard `:-moz-locale-dir` pseudo-class. - MozLocaleDir(Direction), - } - } -} -apply_non_ts_list!(pseudo_class_name); - -impl ToCss for NonTSPseudoClass { - fn to_css(&self, dest: &mut W) -> fmt::Result - where - W: fmt::Write, - { - macro_rules! pseudo_class_serialize { - ([$(($css:expr, $name:ident, $state:tt, $flags:tt),)*]) => { - match *self { - $(NonTSPseudoClass::$name => concat!(":", $css),)* - NonTSPseudoClass::Lang(ref lang) => { - dest.write_str(":lang(")?; - lang.to_css(&mut CssWriter::new(dest))?; - return dest.write_char(')'); - }, - NonTSPseudoClass::MozLocaleDir(ref dir) => { - dest.write_str(":-moz-locale-dir(")?; - dir.to_css(&mut CssWriter::new(dest))?; - return dest.write_char(')') - }, - NonTSPseudoClass::Dir(ref dir) => { - dest.write_str(":dir(")?; - dir.to_css(&mut CssWriter::new(dest))?; - return dest.write_char(')') - }, - } - } - } - let ser = apply_non_ts_list!(pseudo_class_serialize); - dest.write_str(ser) - } -} - -impl NonTSPseudoClass { - /// Parses the name and returns a non-ts-pseudo-class if succeeds. - /// None otherwise. It doesn't check whether the pseudo-class is enabled - /// in a particular state. - pub fn parse_non_functional(name: &str) -> Option { - macro_rules! pseudo_class_parse { - ([$(($css:expr, $name:ident, $state:tt, $flags:tt),)*]) => { - match_ignore_ascii_case! { &name, - $($css => Some(NonTSPseudoClass::$name),)* - "-moz-full-screen" => Some(NonTSPseudoClass::Fullscreen), - "-moz-read-only" => Some(NonTSPseudoClass::ReadOnly), - "-moz-read-write" => Some(NonTSPseudoClass::ReadWrite), - "-moz-focusring" => Some(NonTSPseudoClass::FocusVisible), - "-moz-ui-valid" => Some(NonTSPseudoClass::UserValid), - "-moz-ui-invalid" => Some(NonTSPseudoClass::UserInvalid), - "-webkit-autofill" => Some(NonTSPseudoClass::Autofill), - _ => None, - } - } - } - apply_non_ts_list!(pseudo_class_parse) - } - - /// Returns true if this pseudo-class has any of the given flags set. - fn has_any_flag(&self, flags: NonTSPseudoClassFlag) -> bool { - macro_rules! check_flag { - (_) => { - false - }; - ($flags:ident) => { - NonTSPseudoClassFlag::$flags.intersects(flags) - }; - } - macro_rules! pseudo_class_check_is_enabled_in { - ([$(($css:expr, $name:ident, $state:tt, $flags:tt),)*]) => { - match *self { - $(NonTSPseudoClass::$name => check_flag!($flags),)* - NonTSPseudoClass::MozLocaleDir(_) => check_flag!(PSEUDO_CLASS_ENABLED_IN_UA_SHEETS_AND_CHROME), - NonTSPseudoClass::Lang(_) | - NonTSPseudoClass::Dir(_) => false, - } - } - } - apply_non_ts_list!(pseudo_class_check_is_enabled_in) - } - - /// Returns whether the pseudo-class is enabled in content sheets. - #[inline] - fn is_enabled_in_content(&self) -> bool { - if matches!(*self, Self::PopoverOpen) { - return static_prefs::pref!("dom.element.popover.enabled"); - } - !self.has_any_flag(NonTSPseudoClassFlag::PSEUDO_CLASS_ENABLED_IN_UA_SHEETS_AND_CHROME) - } - - /// Get the state flag associated with a pseudo-class, if any. - pub fn state_flag(&self) -> ElementState { - macro_rules! flag { - (_) => { - ElementState::empty() - }; - ($state:ident) => { - ElementState::$state - }; - } - macro_rules! pseudo_class_state { - ([$(($css:expr, $name:ident, $state:tt, $flags:tt),)*]) => { - match *self { - $(NonTSPseudoClass::$name => flag!($state),)* - NonTSPseudoClass::Dir(ref dir) => dir.element_state(), - NonTSPseudoClass::MozLocaleDir(..) | - NonTSPseudoClass::Lang(..) => ElementState::empty(), - } - } - } - apply_non_ts_list!(pseudo_class_state) - } - - /// Get the document state flag associated with a pseudo-class, if any. - pub fn document_state_flag(&self) -> DocumentState { - match *self { - NonTSPseudoClass::MozLocaleDir(ref dir) => match dir.as_horizontal_direction() { - Some(HorizontalDirection::Ltr) => DocumentState::LTR_LOCALE, - Some(HorizontalDirection::Rtl) => DocumentState::RTL_LOCALE, - None => DocumentState::empty(), - }, - NonTSPseudoClass::MozWindowInactive => DocumentState::WINDOW_INACTIVE, - NonTSPseudoClass::MozLWTheme => DocumentState::LWTHEME, - _ => DocumentState::empty(), - } - } - - /// Returns true if the given pseudoclass should trigger style sharing cache - /// revalidation. - pub fn needs_cache_revalidation(&self) -> bool { - self.state_flag().is_empty() && - !matches!( - *self, - // :dir() depends on state only, but may have an empty - // state_flag for invalid arguments. - NonTSPseudoClass::Dir(_) | - // :-moz-is-html only depends on the state of the document and - // the namespace of the element; the former is invariant - // across all the elements involved and the latter is already - // checked for by our caching precondtions. - NonTSPseudoClass::MozIsHTML | - // We prevent style sharing for NAC. - 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 | - NonTSPseudoClass::MozLocaleDir(_) | - NonTSPseudoClass::MozWindowInactive - ) - } -} - -impl ::selectors::parser::NonTSPseudoClass for NonTSPseudoClass { - type Impl = SelectorImpl; - - #[inline] - fn is_active_or_hover(&self) -> bool { - matches!(*self, NonTSPseudoClass::Active | NonTSPseudoClass::Hover) - } - - /// We intentionally skip the link-related ones. - #[inline] - fn is_user_action_state(&self) -> bool { - matches!( - *self, - NonTSPseudoClass::Hover | NonTSPseudoClass::Active | NonTSPseudoClass::Focus - ) - } -} - -/// The dummy struct we use to implement our selector parsing. -#[derive(Clone, Debug, Eq, PartialEq)] -pub struct SelectorImpl; - -/// A set of extra data to carry along with the matching context, either for -/// selector-matching or invalidation. -#[derive(Default)] -pub struct ExtraMatchingData<'a> { - /// The invalidation data to invalidate doc-state pseudo-classes correctly. - pub invalidation_data: InvalidationMatchingData, - - /// The invalidation bits from matching container queries. These are here - /// just for convenience mostly. - pub cascade_input_flags: ComputedValueFlags, - - /// The style of the originating element in order to evaluate @container - /// size queries affecting pseudo-elements. - pub originating_element_style: Option<&'a ComputedValues>, -} - -impl ::selectors::SelectorImpl for SelectorImpl { - type ExtraMatchingData<'a> = ExtraMatchingData<'a>; - type AttrValue = AtomString; - type Identifier = AtomIdent; - type LocalName = AtomIdent; - type NamespacePrefix = AtomIdent; - type NamespaceUrl = Namespace; - type BorrowedNamespaceUrl = WeakNamespace; - type BorrowedLocalName = WeakAtom; - - type PseudoElement = PseudoElement; - type NonTSPseudoClass = NonTSPseudoClass; - - fn should_collect_attr_hash(name: &AtomIdent) -> bool { - !crate::bloom::is_attr_name_excluded_from_filter(name) - } -} - -impl<'a> SelectorParser<'a> { - fn is_pseudo_class_enabled(&self, pseudo_class: &NonTSPseudoClass) -> bool { - if pseudo_class.is_enabled_in_content() { - return true; - } - - if self.in_user_agent_stylesheet() && - pseudo_class.has_any_flag(NonTSPseudoClassFlag::PSEUDO_CLASS_ENABLED_IN_UA_SHEETS) - { - return true; - } - - if self.chrome_rules_enabled() && - pseudo_class.has_any_flag(NonTSPseudoClassFlag::PSEUDO_CLASS_ENABLED_IN_CHROME) - { - return true; - } - - return false; - } - - fn is_pseudo_element_enabled(&self, pseudo_element: &PseudoElement) -> bool { - if pseudo_element.enabled_in_content() { - return true; - } - - if self.in_user_agent_stylesheet() && pseudo_element.enabled_in_ua_sheets() { - return true; - } - - if self.chrome_rules_enabled() && pseudo_element.enabled_in_chrome() { - return true; - } - - return false; - } -} - -impl<'a, 'i> ::selectors::Parser<'i> for SelectorParser<'a> { - type Impl = SelectorImpl; - type Error = StyleParseErrorKind<'i>; - - fn parse_parent_selector(&self) -> bool { - static_prefs::pref!("layout.css.nesting.enabled") - } - - #[inline] - fn parse_slotted(&self) -> bool { - true - } - - #[inline] - fn parse_host(&self) -> bool { - true - } - - #[inline] - fn parse_nth_child_of(&self) -> bool { - true - } - - #[inline] - fn parse_is_and_where(&self) -> bool { - true - } - - #[inline] - fn parse_has(&self) -> bool { - static_prefs::pref!("layout.css.has-selector.enabled") - } - - #[inline] - fn parse_part(&self) -> bool { - true - } - - #[inline] - fn is_is_alias(&self, function: &str) -> bool { - function.eq_ignore_ascii_case("-moz-any") - } - - #[inline] - fn allow_forgiving_selectors(&self) -> bool { - !self.for_supports_rule - } - - fn parse_non_ts_pseudo_class( - &self, - location: SourceLocation, - name: CowRcStr<'i>, - ) -> Result> { - if let Some(pseudo_class) = NonTSPseudoClass::parse_non_functional(&name) { - if self.is_pseudo_class_enabled(&pseudo_class) { - return Ok(pseudo_class); - } - } - Err( - location.new_custom_error(SelectorParseErrorKind::UnsupportedPseudoClassOrElement( - name, - )), - ) - } - - fn parse_non_ts_functional_pseudo_class<'t>( - &self, - name: CowRcStr<'i>, - parser: &mut Parser<'i, 't>, - ) -> Result> { - let pseudo_class = match_ignore_ascii_case! { &name, - "lang" => { - let result = parser.parse_comma_separated(|input| { - Ok(AtomIdent::from(input.expect_ident_or_string()?.as_ref())) - })?; - if result.is_empty() { - return Err(parser.new_custom_error(StyleParseErrorKind::UnspecifiedError)); - } - NonTSPseudoClass::Lang(Lang(result.into())) - }, - "-moz-locale-dir" => { - NonTSPseudoClass::MozLocaleDir(Direction::parse(parser)?) - }, - "dir" => { - NonTSPseudoClass::Dir(Direction::parse(parser)?) - }, - _ => return Err(parser.new_custom_error( - SelectorParseErrorKind::UnsupportedPseudoClassOrElement(name.clone()) - )) - }; - if self.is_pseudo_class_enabled(&pseudo_class) { - Ok(pseudo_class) - } else { - Err( - parser.new_custom_error(SelectorParseErrorKind::UnsupportedPseudoClassOrElement( - name, - )), - ) - } - } - - fn parse_pseudo_element( - &self, - location: SourceLocation, - name: CowRcStr<'i>, - ) -> Result> { - let allow_unkown_webkit = !self.for_supports_rule; - if let Some(pseudo) = PseudoElement::from_slice(&name, allow_unkown_webkit) { - if self.is_pseudo_element_enabled(&pseudo) { - return Ok(pseudo); - } - } - - Err( - location.new_custom_error(SelectorParseErrorKind::UnsupportedPseudoClassOrElement( - name, - )), - ) - } - - fn parse_functional_pseudo_element<'t>( - &self, - name: CowRcStr<'i>, - parser: &mut Parser<'i, 't>, - ) -> Result> { - if starts_with_ignore_ascii_case(&name, "-moz-tree-") { - // Tree pseudo-elements can have zero or more arguments, separated - // by either comma or space. - let mut args = ThinVec::new(); - loop { - let location = parser.current_source_location(); - match parser.next() { - Ok(&Token::Ident(ref ident)) => args.push(Atom::from(ident.as_ref())), - Ok(&Token::Comma) => {}, - Ok(t) => return Err(location.new_unexpected_token_error(t.clone())), - Err(BasicParseError { - kind: BasicParseErrorKind::EndOfInput, - .. - }) => break, - _ => unreachable!("Parser::next() shouldn't return any other error"), - } - } - if let Some(pseudo) = PseudoElement::tree_pseudo_element(&name, args) { - if self.is_pseudo_element_enabled(&pseudo) { - return Ok(pseudo); - } - } - } else if name.eq_ignore_ascii_case("highlight") { - let pseudo = PseudoElement::Highlight(AtomIdent::from(parser.expect_ident()?.as_ref())); - if self.is_pseudo_element_enabled(&pseudo) { - return Ok(pseudo); - } - } - Err( - parser.new_custom_error(SelectorParseErrorKind::UnsupportedPseudoClassOrElement( - name, - )), - ) - } - - fn default_namespace(&self) -> Option { - self.namespaces.default.clone() - } - - fn namespace_for_prefix(&self, prefix: &AtomIdent) -> Option { - self.namespaces.prefixes.get(prefix).cloned() - } -} - -impl SelectorImpl { - /// A helper to traverse each eagerly cascaded pseudo-element, executing - /// `fun` on it. - #[inline] - pub fn each_eagerly_cascaded_pseudo_element(mut fun: F) - where - F: FnMut(PseudoElement), - { - for pseudo in &EAGER_PSEUDOS { - fun(pseudo.clone()) - } - } -} - -// 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.rs b/components/style/gecko/snapshot.rs deleted file mode 100644 index 372e7fdb7f5..00000000000 --- a/components/style/gecko/snapshot.rs +++ /dev/null @@ -1,174 +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 gecko snapshot, that stores the element attributes and state before they -//! change in order to properly calculate restyle hints. - -use crate::dom::TElement; -use crate::gecko::snapshot_helpers; -use crate::gecko::wrapper::GeckoElement; -use crate::gecko_bindings::bindings; -use crate::gecko_bindings::structs::ServoElementSnapshot; -use crate::gecko_bindings::structs::ServoElementSnapshotFlags as Flags; -use crate::gecko_bindings::structs::ServoElementSnapshotTable; -use crate::invalidation::element::element_wrapper::ElementSnapshot; -use crate::selector_parser::AttrValue; -use crate::string_cache::{Atom, Namespace}; -use crate::values::{AtomIdent, AtomString}; -use crate::LocalName; -use crate::WeakAtom; -use dom::ElementState; -use selectors::attr::{AttrSelectorOperation, CaseSensitivity, NamespaceConstraint}; - -/// A snapshot of a Gecko element. -pub type GeckoElementSnapshot = ServoElementSnapshot; - -/// A map from elements to snapshots for Gecko's style back-end. -pub type SnapshotMap = ServoElementSnapshotTable; - -impl SnapshotMap { - /// Gets the snapshot for this element, if any. - /// - /// FIXME(emilio): The transmute() business we do here is kind of nasty, but - /// it's a consequence of the map being a OpaqueNode -> Snapshot table in - /// Servo and an Element -> Snapshot table in Gecko. - /// - /// We should be able to make this a more type-safe with type annotations by - /// making SnapshotMap a trait and moving the implementations outside, but - /// that's a pain because it implies parameterizing SharedStyleContext. - pub fn get(&self, element: &E) -> Option<&GeckoElementSnapshot> { - debug_assert!(element.has_snapshot()); - - unsafe { - let element = ::std::mem::transmute::<&E, &GeckoElement>(element); - bindings::Gecko_GetElementSnapshot(self, element.0).as_ref() - } - } -} - -impl GeckoElementSnapshot { - #[inline] - fn has_any(&self, flags: Flags) -> bool { - (self.mContains as u8 & flags as u8) != 0 - } - - /// Returns true if the snapshot has stored state for pseudo-classes - /// that depend on things other than `ElementState`. - #[inline] - pub fn has_other_pseudo_class_state(&self) -> bool { - self.has_any(Flags::OtherPseudoClassState) - } - - /// Returns true if the snapshot recorded an id change. - #[inline] - pub fn id_changed(&self) -> bool { - self.mIdAttributeChanged() - } - - /// Returns true if the snapshot recorded a class attribute change. - #[inline] - pub fn class_changed(&self) -> bool { - self.mClassAttributeChanged() - } - - /// Executes the callback once for each attribute that changed. - #[inline] - pub fn each_attr_changed(&self, mut callback: F) - where - F: FnMut(&AtomIdent), - { - for attr in self.mChangedAttrNames.iter() { - unsafe { AtomIdent::with(attr.mRawPtr, &mut callback) } - } - } - - /// selectors::Element::attr_matches - pub fn attr_matches( - &self, - ns: &NamespaceConstraint<&Namespace>, - local_name: &LocalName, - operation: &AttrSelectorOperation<&AttrValue>, - ) -> bool { - snapshot_helpers::attr_matches(self.mAttrs.iter(), ns, local_name, operation) - } -} - -impl ElementSnapshot for GeckoElementSnapshot { - fn debug_list_attributes(&self) -> String { - use nsstring::nsCString; - let mut string = nsCString::new(); - unsafe { - bindings::Gecko_Snapshot_DebugListAttributes(self, &mut string); - } - String::from_utf8_lossy(&*string).into_owned() - } - - fn state(&self) -> Option { - if self.has_any(Flags::State) { - Some(ElementState::from_bits_truncate(self.mState)) - } else { - None - } - } - - #[inline] - fn has_attrs(&self) -> bool { - self.has_any(Flags::Attributes) - } - - #[inline] - fn id_attr(&self) -> Option<&WeakAtom> { - if !self.has_any(Flags::Id) { - return None; - } - - snapshot_helpers::get_id(&*self.mAttrs) - } - - #[inline] - fn is_part(&self, name: &AtomIdent) -> bool { - let attr = match snapshot_helpers::find_attr(&*self.mAttrs, &atom!("part")) { - Some(attr) => attr, - None => return false, - }; - - snapshot_helpers::has_class_or_part(name, CaseSensitivity::CaseSensitive, attr) - } - - #[inline] - fn imported_part(&self, name: &AtomIdent) -> Option { - snapshot_helpers::imported_part(&*self.mAttrs, name) - } - - #[inline] - fn has_class(&self, name: &AtomIdent, case_sensitivity: CaseSensitivity) -> bool { - if !self.has_any(Flags::MaybeClass) { - return false; - } - - snapshot_helpers::has_class_or_part(name, case_sensitivity, &self.mClass) - } - - #[inline] - fn each_class(&self, callback: F) - where - F: FnMut(&AtomIdent), - { - if !self.has_any(Flags::MaybeClass) { - return; - } - - snapshot_helpers::each_class_or_part(&self.mClass, callback) - } - - #[inline] - fn lang_attr(&self) -> Option { - let ptr = unsafe { bindings::Gecko_SnapshotLangValue(self) }; - if ptr.is_null() { - None - } else { - Some(AtomString(unsafe { Atom::from_addrefed(ptr) })) - } - } -} diff --git a/components/style/gecko/snapshot_helpers.rs b/components/style/gecko/snapshot_helpers.rs deleted file mode 100644 index 818e767e0e9..00000000000 --- a/components/style/gecko/snapshot_helpers.rs +++ /dev/null @@ -1,309 +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/. */ - -//! Element an snapshot common logic. - -use crate::dom::TElement; -use crate::gecko::wrapper::namespace_id_to_atom; -use crate::gecko_bindings::bindings; -use crate::gecko_bindings::structs::{self, nsAtom}; -use crate::invalidation::element::element_wrapper::ElementSnapshot; -use crate::selector_parser::{AttrValue, SnapshotMap}; -use crate::string_cache::WeakAtom; -use crate::values::AtomIdent; -use crate::{Atom, CaseSensitivityExt, LocalName, Namespace}; -use selectors::attr::{CaseSensitivity, NamespaceConstraint, AttrSelectorOperation, AttrSelectorOperator}; -use smallvec::SmallVec; - -/// A function that, given an element of type `T`, allows you to get a single -/// class or a class list. -enum Class<'a> { - None, - One(*const nsAtom), - More(&'a [structs::RefPtr]), -} - -#[inline(always)] -fn base_type(attr: &structs::nsAttrValue) -> structs::nsAttrValue_ValueBaseType { - (attr.mBits & structs::NS_ATTRVALUE_BASETYPE_MASK) as structs::nsAttrValue_ValueBaseType -} - -#[inline(always)] -unsafe fn ptr(attr: &structs::nsAttrValue) -> *const T { - (attr.mBits & !structs::NS_ATTRVALUE_BASETYPE_MASK) as *const T -} - -#[inline(always)] -unsafe fn get_class_or_part_from_attr(attr: &structs::nsAttrValue) -> Class { - debug_assert!(bindings::Gecko_AssertClassAttrValueIsSane(attr)); - let base_type = base_type(attr); - if base_type == structs::nsAttrValue_ValueBaseType_eAtomBase { - return Class::One(ptr::(attr)); - } - if base_type == structs::nsAttrValue_ValueBaseType_eOtherBase { - let container = ptr::(attr); - debug_assert_eq!( - (*container).mType, - structs::nsAttrValue_ValueType_eAtomArray - ); - // NOTE: Bindgen doesn't deal with AutoTArray, so cast it below. - let attr_array: *mut _ = *(*container) - .__bindgen_anon_1 - .mValue - .as_ref() - .__bindgen_anon_1 - .mAtomArray - .as_ref(); - let array = - (*attr_array).mArray.as_ptr() as *const structs::nsTArray>; - return Class::More(&**array); - } - debug_assert_eq!(base_type, structs::nsAttrValue_ValueBaseType_eStringBase); - Class::None -} - -#[inline(always)] -unsafe fn get_id_from_attr(attr: &structs::nsAttrValue) -> &WeakAtom { - debug_assert_eq!( - base_type(attr), - structs::nsAttrValue_ValueBaseType_eAtomBase - ); - WeakAtom::new(ptr::(attr)) -} - -impl structs::nsAttrName { - #[inline] - fn is_nodeinfo(&self) -> bool { - self.mBits & 1 != 0 - } - - #[inline] - unsafe fn as_nodeinfo(&self) -> &structs::NodeInfo { - debug_assert!(self.is_nodeinfo()); - &*((self.mBits & !1) as *const structs::NodeInfo) - } - - #[inline] - fn namespace_id(&self) -> i32 { - if !self.is_nodeinfo() { - return structs::kNameSpaceID_None; - } - unsafe { self.as_nodeinfo() }.mInner.mNamespaceID - } - - /// Returns the attribute name as an atom pointer. - #[inline] - pub fn name(&self) -> *const nsAtom { - if self.is_nodeinfo() { - unsafe { self.as_nodeinfo() }.mInner.mName - } else { - self.mBits as *const nsAtom - } - } -} - -/// Find an attribute value with a given name and no namespace. -#[inline(always)] -pub fn find_attr<'a>( - attrs: &'a [structs::AttrArray_InternalAttr], - name: &Atom, -) -> Option<&'a structs::nsAttrValue> { - attrs - .iter() - .find(|attr| attr.mName.mBits == name.as_ptr() as usize) - .map(|attr| &attr.mValue) -} - -/// Finds the id attribute from a list of attributes. -#[inline(always)] -pub fn get_id(attrs: &[structs::AttrArray_InternalAttr]) -> Option<&WeakAtom> { - Some(unsafe { get_id_from_attr(find_attr(attrs, &atom!("id"))?) }) -} - -#[inline(always)] -pub(super) fn each_exported_part( - attrs: &[structs::AttrArray_InternalAttr], - name: &AtomIdent, - mut callback: impl FnMut(&AtomIdent), -) { - let attr = match find_attr(attrs, &atom!("exportparts")) { - Some(attr) => attr, - None => return, - }; - let mut length = 0; - let atoms = unsafe { bindings::Gecko_Element_ExportedParts(attr, name.as_ptr(), &mut length) }; - if atoms.is_null() { - return; - } - - unsafe { - for atom in std::slice::from_raw_parts(atoms, length) { - AtomIdent::with(*atom, &mut callback) - } - } -} - -#[inline(always)] -pub(super) fn imported_part( - attrs: &[structs::AttrArray_InternalAttr], - name: &AtomIdent, -) -> Option { - let attr = find_attr(attrs, &atom!("exportparts"))?; - let atom = unsafe { bindings::Gecko_Element_ImportedPart(attr, name.as_ptr()) }; - if atom.is_null() { - return None; - } - Some(AtomIdent(unsafe { Atom::from_raw(atom) })) -} - -/// Given a class or part name, a case sensitivity, and an array of attributes, -/// returns whether the attribute has that name. -#[inline(always)] -pub fn has_class_or_part( - name: &AtomIdent, - case_sensitivity: CaseSensitivity, - attr: &structs::nsAttrValue, -) -> bool { - match unsafe { get_class_or_part_from_attr(attr) } { - Class::None => false, - Class::One(atom) => unsafe { case_sensitivity.eq_atom(name, WeakAtom::new(atom)) }, - Class::More(atoms) => match case_sensitivity { - CaseSensitivity::CaseSensitive => { - let name_ptr = name.as_ptr(); - atoms.iter().any(|atom| atom.mRawPtr == name_ptr) - }, - CaseSensitivity::AsciiCaseInsensitive => unsafe { - atoms - .iter() - .any(|atom| WeakAtom::new(atom.mRawPtr).eq_ignore_ascii_case(name)) - }, - }, - } -} - -/// Given an item, a callback, and a getter, execute `callback` for each class -/// or part name this `item` has. -#[inline(always)] -pub fn each_class_or_part(attr: &structs::nsAttrValue, mut callback: F) -where - F: FnMut(&AtomIdent), -{ - unsafe { - match get_class_or_part_from_attr(attr) { - Class::None => {}, - Class::One(atom) => AtomIdent::with(atom, callback), - Class::More(atoms) => { - for atom in atoms { - AtomIdent::with(atom.mRawPtr, &mut callback) - } - }, - } - } -} - -/// Returns a list of classes that were either added to or removed from the -/// element since the snapshot. -pub fn classes_changed(element: &E, snapshots: &SnapshotMap) -> SmallVec<[Atom; 8]> { - debug_assert!(element.has_snapshot(), "Why bothering?"); - let snapshot = snapshots.get(element).expect("has_snapshot lied"); - if !snapshot.class_changed() { - return SmallVec::new(); - } - - let mut classes_changed = SmallVec::<[Atom; 8]>::new(); - snapshot.each_class(|c| { - if !element.has_class(c, CaseSensitivity::CaseSensitive) { - classes_changed.push(c.0.clone()); - } - }); - element.each_class(|c| { - if !snapshot.has_class(c, CaseSensitivity::CaseSensitive) { - classes_changed.push(c.0.clone()); - } - }); - - classes_changed -} - -/// Returns whether a given attribute selector matches given the internal attrs. -pub(crate) fn attr_matches<'a>( - iter: impl Iterator, - ns: &NamespaceConstraint<&Namespace>, - local_name: &LocalName, - operation: &AttrSelectorOperation<&AttrValue>, -) -> bool { - let name_ptr = local_name.as_ptr(); - for attr in iter { - if attr.mName.name() != name_ptr { - continue; - } - - let ns_matches = match *ns { - NamespaceConstraint::Any => true, - NamespaceConstraint::Specific(ns) => { - if *ns == ns!() { - !attr.mName.is_nodeinfo() - } else { - ns.as_ptr() == unsafe { namespace_id_to_atom(attr.mName.namespace_id()) } - } - }, - }; - - if !ns_matches { - continue; - } - - let (operator, case_sensitivity, value) = match *operation { - AttrSelectorOperation::Exists => return true, - AttrSelectorOperation::WithValue { - operator, - case_sensitivity, - value, - } => (operator, case_sensitivity, value), - }; - let ignore_case = match case_sensitivity { - CaseSensitivity::CaseSensitive => false, - CaseSensitivity::AsciiCaseInsensitive => true, - }; - let value = value.as_ptr(); - let matches = unsafe { - match operator { - AttrSelectorOperator::Equal => bindings::Gecko_AttrEquals( - &attr.mValue, - value, - ignore_case, - ), - AttrSelectorOperator::Includes => bindings::Gecko_AttrIncludes( - &attr.mValue, - value, - ignore_case, - ), - AttrSelectorOperator::DashMatch => bindings::Gecko_AttrDashEquals( - &attr.mValue, - value, - ignore_case, - ), - AttrSelectorOperator::Prefix => bindings::Gecko_AttrHasPrefix( - &attr.mValue, - value, - ignore_case, - ), - AttrSelectorOperator::Suffix => bindings::Gecko_AttrHasSuffix( - &attr.mValue, - value, - ignore_case, - ), - AttrSelectorOperator::Substring => bindings::Gecko_AttrHasSubstring( - &attr.mValue, - value, - ignore_case, - ), - } - }; - if matches || *ns != NamespaceConstraint::Any { - return matches; - } - } - false -} diff --git a/components/style/gecko/traversal.rs b/components/style/gecko/traversal.rs deleted file mode 100644 index 71d1a2f949b..00000000000 --- a/components/style/gecko/traversal.rs +++ /dev/null @@ -1,53 +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/. */ - -//! Gecko-specific bits for the styling DOM traversal. - -use crate::context::{SharedStyleContext, StyleContext}; -use crate::dom::{TElement, TNode}; -use crate::gecko::wrapper::{GeckoElement, GeckoNode}; -use crate::traversal::{recalc_style_at, DomTraversal, PerLevelTraversalData}; - -/// This is the simple struct that Gecko uses to encapsulate a DOM traversal for -/// styling. -pub struct RecalcStyleOnly<'a> { - shared: SharedStyleContext<'a>, -} - -impl<'a> RecalcStyleOnly<'a> { - /// Create a `RecalcStyleOnly` traversal from a `SharedStyleContext`. - pub fn new(shared: SharedStyleContext<'a>) -> Self { - RecalcStyleOnly { shared: shared } - } -} - -impl<'recalc, 'le> DomTraversal> for RecalcStyleOnly<'recalc> { - fn process_preorder( - &self, - traversal_data: &PerLevelTraversalData, - context: &mut StyleContext>, - node: GeckoNode<'le>, - note_child: F, - ) where - F: FnMut(GeckoNode<'le>), - { - if let Some(el) = node.as_element() { - let mut data = unsafe { el.ensure_data() }; - recalc_style_at(self, traversal_data, context, el, &mut data, note_child); - } - } - - fn process_postorder(&self, _: &mut StyleContext>, _: GeckoNode<'le>) { - unreachable!(); - } - - /// We don't use the post-order traversal for anything. - fn needs_postorder_traversal() -> bool { - false - } - - fn shared_context(&self) -> &SharedStyleContext { - &self.shared - } -} diff --git a/components/style/gecko/url.rs b/components/style/gecko/url.rs deleted file mode 100644 index 8cf4aa51c0a..00000000000 --- a/components/style/gecko/url.rs +++ /dev/null @@ -1,383 +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 handling for the specified value CSS url() values. - -use crate::gecko_bindings::bindings; -use crate::gecko_bindings::structs; -use crate::parser::{Parse, ParserContext}; -use crate::stylesheets::{CorsMode, UrlExtraData}; -use crate::values::computed::{Context, ToComputedValue}; -use cssparser::Parser; -use malloc_size_of::{MallocSizeOf, MallocSizeOfOps}; -use nsstring::nsCString; -use servo_arc::Arc; -use std::collections::HashMap; -use std::fmt::{self, Write}; -use std::mem::ManuallyDrop; -use std::sync::RwLock; -use style_traits::{CssWriter, ParseError, ToCss}; -use to_shmem::{self, SharedMemoryBuilder, ToShmem}; - -/// A CSS url() value for gecko. -#[derive(Clone, Debug, PartialEq, SpecifiedValueInfo, ToCss, ToShmem)] -#[css(function = "url")] -#[repr(C)] -pub struct CssUrl(pub Arc); - -/// Data shared between CssUrls. -/// -/// cbindgen:derive-eq=false -/// cbindgen:derive-neq=false -#[derive(Debug, SpecifiedValueInfo, ToCss, ToShmem)] -#[repr(C)] -pub struct CssUrlData { - /// The URL in unresolved string form. - serialization: crate::OwnedStr, - - /// The URL extra data. - #[css(skip)] - pub extra_data: UrlExtraData, - - /// The CORS mode that will be used for the load. - #[css(skip)] - cors_mode: CorsMode, - - /// Data to trigger a load from Gecko. This is mutable in C++. - /// - /// TODO(emilio): Maybe we can eagerly resolve URLs and make this immutable? - #[css(skip)] - load_data: LoadDataSource, -} - -impl PartialEq for CssUrlData { - fn eq(&self, other: &Self) -> bool { - self.serialization == other.serialization && - self.extra_data == other.extra_data && - self.cors_mode == other.cors_mode - } -} - -impl CssUrl { - fn parse_with_cors_mode<'i, 't>( - context: &ParserContext, - input: &mut Parser<'i, 't>, - cors_mode: CorsMode, - ) -> Result> { - let url = input.expect_url()?; - Ok(Self::parse_from_string( - url.as_ref().to_owned(), - context, - cors_mode, - )) - } - - /// Parse a URL from a string value that is a valid CSS token for a URL. - pub fn parse_from_string(url: String, context: &ParserContext, cors_mode: CorsMode) -> Self { - CssUrl(Arc::new(CssUrlData { - serialization: url.into(), - extra_data: context.url_data.clone(), - cors_mode, - load_data: LoadDataSource::Owned(LoadData::default()), - })) - } - - /// Returns true if the URL is definitely invalid. We don't eagerly resolve - /// URLs in gecko, so we just return false here. - /// use its |resolved| status. - pub fn is_invalid(&self) -> bool { - false - } - - /// Returns true if this URL looks like a fragment. - /// See https://drafts.csswg.org/css-values/#local-urls - #[inline] - pub fn is_fragment(&self) -> bool { - self.0.is_fragment() - } - - /// Return the unresolved url as string, or the empty string if it's - /// invalid. - #[inline] - pub fn as_str(&self) -> &str { - self.0.as_str() - } -} - -impl CssUrlData { - /// Returns true if this URL looks like a fragment. - /// See https://drafts.csswg.org/css-values/#local-urls - pub fn is_fragment(&self) -> bool { - self.as_str() - .as_bytes() - .iter() - .next() - .map_or(false, |b| *b == b'#') - } - - /// Return the unresolved url as string, or the empty string if it's - /// invalid. - pub fn as_str(&self) -> &str { - &*self.serialization - } -} - -impl Parse for CssUrl { - fn parse<'i, 't>( - context: &ParserContext, - input: &mut Parser<'i, 't>, - ) -> Result> { - Self::parse_with_cors_mode(context, input, CorsMode::None) - } -} - -impl Eq for CssUrl {} - -impl MallocSizeOf for CssUrl { - fn size_of(&self, _ops: &mut MallocSizeOfOps) -> usize { - // XXX: measure `serialization` once bug 1397971 lands - - // We ignore `extra_data`, because RefPtr is tricky, and there aren't - // many of them in practise (sharing is common). - - 0 - } -} - -/// A key type for LOAD_DATA_TABLE. -#[derive(Eq, Hash, PartialEq)] -struct LoadDataKey(*const LoadDataSource); - -unsafe impl Sync for LoadDataKey {} -unsafe impl Send for LoadDataKey {} - -bitflags! { - /// Various bits of mutable state that are kept for image loads. - #[repr(C)] - pub struct LoadDataFlags: u8 { - /// Whether we tried to resolve the uri at least once. - const TRIED_TO_RESOLVE_URI = 1 << 0; - /// Whether we tried to resolve the image at least once. - const TRIED_TO_RESOLVE_IMAGE = 1 << 1; - } -} - -/// This is usable and movable from multiple threads just fine, as long as it's -/// not cloned (it is not clonable), and the methods that mutate it run only on -/// the main thread (when all the other threads we care about are paused). -unsafe impl Sync for LoadData {} -unsafe impl Send for LoadData {} - -/// The load data for a given URL. This is mutable from C++, and shouldn't be -/// accessed from rust for anything. -#[repr(C)] -#[derive(Debug)] -pub struct LoadData { - /// A strong reference to the imgRequestProxy, if any, that should be - /// released on drop. - /// - /// These are raw pointers because they are not safe to reference-count off - /// the main thread. - resolved_image: *mut structs::imgRequestProxy, - /// A strong reference to the resolved URI of this image. - resolved_uri: *mut structs::nsIURI, - /// A few flags that are set when resolving the image or such. - flags: LoadDataFlags, -} - -impl Drop for LoadData { - fn drop(&mut self) { - unsafe { bindings::Gecko_LoadData_Drop(self) } - } -} - -impl Default for LoadData { - fn default() -> Self { - Self { - resolved_image: std::ptr::null_mut(), - resolved_uri: std::ptr::null_mut(), - flags: LoadDataFlags::empty(), - } - } -} - -/// The data for a load, or a lazy-loaded, static member that will be stored in -/// LOAD_DATA_TABLE, keyed by the memory location of this object, which is -/// always in the heap because it's inside the CssUrlData object. -/// -/// This type is meant not to be used from C++ so we don't derive helper -/// methods. -/// -/// cbindgen:derive-helper-methods=false -#[derive(Debug)] -#[repr(u8, C)] -pub enum LoadDataSource { - /// An owned copy of the load data. - Owned(LoadData), - /// A lazily-resolved copy of it. - Lazy, -} - -impl LoadDataSource { - /// Gets the load data associated with the source. - /// - /// This relies on the source on being in a stable location if lazy. - #[inline] - pub unsafe fn get(&self) -> *const LoadData { - match *self { - LoadDataSource::Owned(ref d) => return d, - LoadDataSource::Lazy => {}, - } - - let key = LoadDataKey(self); - - { - let guard = LOAD_DATA_TABLE.read().unwrap(); - if let Some(r) = guard.get(&key) { - return &**r; - } - } - let mut guard = LOAD_DATA_TABLE.write().unwrap(); - let r = guard.entry(key).or_insert_with(Default::default); - &**r - } -} - -impl ToShmem for LoadDataSource { - fn to_shmem(&self, _builder: &mut SharedMemoryBuilder) -> to_shmem::Result { - Ok(ManuallyDrop::new(match self { - LoadDataSource::Owned(..) => LoadDataSource::Lazy, - LoadDataSource::Lazy => LoadDataSource::Lazy, - })) - } -} - -/// A specified non-image `url()` value. -pub type SpecifiedUrl = CssUrl; - -/// Clears LOAD_DATA_TABLE. Entries in this table, which are for specified URL -/// values that come from shared memory style sheets, would otherwise persist -/// until the end of the process and be reported as leaks. -pub fn shutdown() { - LOAD_DATA_TABLE.write().unwrap().clear(); -} - -impl ToComputedValue for SpecifiedUrl { - type ComputedValue = ComputedUrl; - - #[inline] - fn to_computed_value(&self, _: &Context) -> Self::ComputedValue { - ComputedUrl(self.clone()) - } - - #[inline] - fn from_computed_value(computed: &Self::ComputedValue) -> Self { - computed.0.clone() - } -} - -/// A specified image `url()` value. -#[derive(Clone, Debug, Eq, MallocSizeOf, PartialEq, SpecifiedValueInfo, ToCss, ToShmem)] -pub struct SpecifiedImageUrl(pub SpecifiedUrl); - -impl SpecifiedImageUrl { - /// Parse a URL from a string value that is a valid CSS token for a URL. - pub fn parse_from_string(url: String, context: &ParserContext, cors_mode: CorsMode) -> Self { - SpecifiedImageUrl(SpecifiedUrl::parse_from_string(url, context, cors_mode)) - } - - /// Provides an alternate method for parsing that associates the URL - /// with anonymous CORS headers. - pub fn parse_with_cors_mode<'i, 't>( - context: &ParserContext, - input: &mut Parser<'i, 't>, - cors_mode: CorsMode, - ) -> Result> { - Ok(SpecifiedImageUrl(SpecifiedUrl::parse_with_cors_mode( - context, input, cors_mode, - )?)) - } -} - -impl Parse for SpecifiedImageUrl { - fn parse<'i, 't>( - context: &ParserContext, - input: &mut Parser<'i, 't>, - ) -> Result> { - SpecifiedUrl::parse(context, input).map(SpecifiedImageUrl) - } -} - -impl ToComputedValue for SpecifiedImageUrl { - type ComputedValue = ComputedImageUrl; - - #[inline] - fn to_computed_value(&self, context: &Context) -> Self::ComputedValue { - ComputedImageUrl(self.0.to_computed_value(context)) - } - - #[inline] - fn from_computed_value(computed: &Self::ComputedValue) -> Self { - SpecifiedImageUrl(ToComputedValue::from_computed_value(&computed.0)) - } -} - -/// The computed value of a CSS non-image `url()`. -/// -/// The only difference between specified and computed URLs is the -/// serialization. -#[derive(Clone, Debug, Eq, MallocSizeOf, PartialEq)] -#[repr(C)] -pub struct ComputedUrl(pub SpecifiedUrl); - -impl ComputedUrl { - fn serialize_with( - &self, - function: unsafe extern "C" fn(*const Self, *mut nsCString), - dest: &mut CssWriter, - ) -> fmt::Result - where - W: Write, - { - dest.write_str("url(")?; - unsafe { - let mut string = nsCString::new(); - function(self, &mut string); - string.as_str_unchecked().to_css(dest)?; - } - dest.write_char(')') - } -} - -impl ToCss for ComputedUrl { - fn to_css(&self, dest: &mut CssWriter) -> fmt::Result - where - W: Write, - { - self.serialize_with(bindings::Gecko_GetComputedURLSpec, dest) - } -} - -/// The computed value of a CSS image `url()`. -#[derive(Clone, Debug, Eq, MallocSizeOf, PartialEq)] -#[repr(transparent)] -pub struct ComputedImageUrl(pub ComputedUrl); - -impl ToCss for ComputedImageUrl { - fn to_css(&self, dest: &mut CssWriter) -> fmt::Result - where - W: Write, - { - self.0 - .serialize_with(bindings::Gecko_GetComputedImageURLSpec, dest) - } -} - -lazy_static! { - /// A table mapping CssUrlData objects to their lazily created LoadData - /// objects. - static ref LOAD_DATA_TABLE: RwLock>> = { - Default::default() - }; -} diff --git a/components/style/gecko/values.rs b/components/style/gecko/values.rs deleted file mode 100644 index fbdb02c6bad..00000000000 --- a/components/style/gecko/values.rs +++ /dev/null @@ -1,72 +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/. */ - -#![allow(unsafe_code)] - -//! Different kind of helpers to interact with Gecko values. - -use crate::color::{AbsoluteColor, ColorSpace}; -use crate::counter_style::{Symbol, Symbols}; -use crate::gecko_bindings::bindings; -use crate::gecko_bindings::structs::CounterStylePtr; -use crate::values::generics::CounterStyle; -use crate::values::Either; -use crate::Atom; - -/// Convert a color value to `nscolor`. -pub fn convert_absolute_color_to_nscolor(color: &AbsoluteColor) -> u32 { - let srgb = color.to_color_space(ColorSpace::Srgb); - u32::from_le_bytes([ - (srgb.components.0 * 255.0).round() as u8, - (srgb.components.1 * 255.0).round() as u8, - (srgb.components.2 * 255.0).round() as u8, - (srgb.alpha * 255.0).round() as u8, - ]) -} - -/// Convert a given `nscolor` to a Servo AbsoluteColor value. -pub fn convert_nscolor_to_absolute_color(color: u32) -> AbsoluteColor { - let [r, g, b, a] = color.to_le_bytes(); - AbsoluteColor::srgb( - r as f32 / 255.0, - g as f32 / 255.0, - b as f32 / 255.0, - a as f32 / 255.0, - ) -} - -impl CounterStyle { - /// Convert this counter style to a Gecko CounterStylePtr. - #[inline] - pub fn to_gecko_value(&self, gecko_value: &mut CounterStylePtr) { - unsafe { bindings::Gecko_CounterStyle_ToPtr(self, gecko_value) } - } - - /// Convert Gecko CounterStylePtr to CounterStyle or String. - pub fn from_gecko_value(gecko_value: &CounterStylePtr) -> Either { - use crate::values::CustomIdent; - - let name = unsafe { bindings::Gecko_CounterStyle_GetName(gecko_value) }; - if !name.is_null() { - let name = unsafe { Atom::from_raw(name) }; - debug_assert_ne!(name, atom!("none")); - Either::First(CounterStyle::Name(CustomIdent(name))) - } else { - let anonymous = - unsafe { bindings::Gecko_CounterStyle_GetAnonymous(gecko_value).as_ref() }.unwrap(); - let symbols = &anonymous.mSymbols; - if anonymous.mSingleString { - debug_assert_eq!(symbols.len(), 1); - Either::Second(symbols[0].to_string()) - } else { - let symbol_type = anonymous.mSymbolsType; - let symbols = symbols - .iter() - .map(|gecko_symbol| Symbol::String(gecko_symbol.to_string().into())) - .collect(); - Either::First(CounterStyle::Symbols(symbol_type, Symbols(symbols))) - } - } - } -} diff --git a/components/style/gecko/wrapper.rs b/components/style/gecko/wrapper.rs deleted file mode 100644 index 490c063fbe7..00000000000 --- a/components/style/gecko/wrapper.rs +++ /dev/null @@ -1,2125 +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/. */ - -#![allow(unsafe_code)] - -//! Wrapper definitions on top of Gecko types in order to be used in the style -//! system. -//! -//! This really follows the Servo pattern in -//! `components/script/layout_wrapper.rs`. -//! -//! This theoretically should live in its own crate, but now it lives in the -//! style system it's kind of pointless in the Stylo case, and only Servo forces -//! the separation between the style system implementation and everything else. - -use crate::applicable_declarations::ApplicableDeclarationBlock; -use crate::context::{PostAnimationTasks, QuirksMode, SharedStyleContext, UpdateAnimationsTasks}; -use crate::data::ElementData; -use crate::dom::{LayoutIterator, NodeInfo, OpaqueNode, TDocument, TElement, TNode, TShadowRoot}; -use crate::gecko::selector_parser::{NonTSPseudoClass, PseudoElement, SelectorImpl}; -use crate::gecko::snapshot_helpers; -use crate::gecko_bindings::bindings; -use crate::gecko_bindings::bindings::Gecko_ElementHasAnimations; -use crate::gecko_bindings::bindings::Gecko_ElementHasCSSAnimations; -use crate::gecko_bindings::bindings::Gecko_ElementHasCSSTransitions; -use crate::gecko_bindings::bindings::Gecko_ElementState; -use crate::gecko_bindings::bindings::Gecko_GetActiveLinkAttrDeclarationBlock; -use crate::gecko_bindings::bindings::Gecko_GetAnimationEffectCount; -use crate::gecko_bindings::bindings::Gecko_GetAnimationRule; -use crate::gecko_bindings::bindings::Gecko_GetExtraContentStyleDeclarations; -use crate::gecko_bindings::bindings::Gecko_GetHTMLPresentationAttrDeclarationBlock; -use crate::gecko_bindings::bindings::Gecko_GetStyleAttrDeclarationBlock; -use crate::gecko_bindings::bindings::Gecko_GetUnvisitedLinkAttrDeclarationBlock; -use crate::gecko_bindings::bindings::Gecko_GetVisitedLinkAttrDeclarationBlock; -use crate::gecko_bindings::bindings::Gecko_IsSignificantChild; -use crate::gecko_bindings::bindings::Gecko_MatchLang; -use crate::gecko_bindings::bindings::Gecko_UnsetDirtyStyleAttr; -use crate::gecko_bindings::bindings::Gecko_UpdateAnimations; -use crate::gecko_bindings::structs; -use crate::gecko_bindings::structs::nsChangeHint; -use crate::gecko_bindings::structs::EffectCompositor_CascadeLevel as CascadeLevel; -use crate::gecko_bindings::structs::ELEMENT_HANDLED_SNAPSHOT; -use crate::gecko_bindings::structs::ELEMENT_HAS_ANIMATION_ONLY_DIRTY_DESCENDANTS_FOR_SERVO; -use crate::gecko_bindings::structs::ELEMENT_HAS_DIRTY_DESCENDANTS_FOR_SERVO; -use crate::gecko_bindings::structs::ELEMENT_HAS_SNAPSHOT; -use crate::gecko_bindings::structs::NODE_DESCENDANTS_NEED_FRAMES; -use crate::gecko_bindings::structs::NODE_NEEDS_FRAME; -use crate::gecko_bindings::structs::{nsAtom, nsIContent, nsINode_BooleanFlag}; -use crate::gecko_bindings::structs::{nsINode as RawGeckoNode, Element as RawGeckoElement}; -use crate::global_style_data::GLOBAL_STYLE_DATA; -use crate::invalidation::element::restyle_hints::RestyleHint; -use crate::media_queries::Device; -use crate::properties::animated_properties::{AnimationValue, AnimationValueMap}; -use crate::properties::{ComputedValues, LonghandId}; -use crate::properties::{Importance, PropertyDeclaration, PropertyDeclarationBlock}; -use crate::rule_tree::CascadeLevel as ServoCascadeLevel; -use crate::selector_parser::{AttrValue, Lang}; -use crate::shared_lock::{Locked, SharedRwLock}; -use crate::string_cache::{Atom, Namespace, WeakAtom, WeakNamespace}; -use crate::stylist::CascadeData; -use crate::values::computed::Display; -use crate::values::{AtomIdent, AtomString}; -use crate::CaseSensitivityExt; -use crate::LocalName; -use app_units::Au; -use atomic_refcell::{AtomicRef, AtomicRefCell, AtomicRefMut}; -use dom::{DocumentState, ElementState}; -use euclid::default::Size2D; -use fxhash::FxHashMap; -use selectors::attr::{AttrSelectorOperation, CaseSensitivity, NamespaceConstraint}; -use selectors::matching::VisitedHandlingMode; -use selectors::matching::{ElementSelectorFlags, MatchingContext}; -use selectors::sink::Push; -use selectors::{Element, OpaqueElement}; -use servo_arc::{Arc, ArcBorrow}; -use std::fmt; -use std::hash::{Hash, Hasher}; -use std::mem; -use std::ptr; -use std::sync::atomic::{AtomicU32, Ordering}; - -#[inline] -fn elements_with_id<'a, 'le>( - array: *const structs::nsTArray<*mut RawGeckoElement>, -) -> &'a [GeckoElement<'le>] { - unsafe { - if array.is_null() { - return &[]; - } - - let elements: &[*mut RawGeckoElement] = &**array; - - // NOTE(emilio): We rely on the in-memory representation of - // GeckoElement<'ld> and *mut RawGeckoElement being the same. - #[allow(dead_code)] - unsafe fn static_assert() { - mem::transmute::<*mut RawGeckoElement, GeckoElement<'static>>(0xbadc0de as *mut _); - } - - mem::transmute(elements) - } -} - -/// A simple wrapper over `Document`. -#[derive(Clone, Copy)] -pub struct GeckoDocument<'ld>(pub &'ld structs::Document); - -impl<'ld> TDocument for GeckoDocument<'ld> { - type ConcreteNode = GeckoNode<'ld>; - - #[inline] - fn as_node(&self) -> Self::ConcreteNode { - GeckoNode(&self.0._base) - } - - #[inline] - fn is_html_document(&self) -> bool { - self.0.mType == structs::Document_Type::eHTML - } - - #[inline] - fn quirks_mode(&self) -> QuirksMode { - self.0.mCompatMode.into() - } - - #[inline] - fn elements_with_id<'a>(&self, id: &AtomIdent) -> Result<&'a [GeckoElement<'ld>], ()> - where - Self: 'a, - { - Ok(elements_with_id(unsafe { - bindings::Gecko_Document_GetElementsWithId(self.0, id.as_ptr()) - })) - } - - fn shared_lock(&self) -> &SharedRwLock { - &GLOBAL_STYLE_DATA.shared_lock - } -} - -/// A simple wrapper over `ShadowRoot`. -#[derive(Clone, Copy)] -pub struct GeckoShadowRoot<'lr>(pub &'lr structs::ShadowRoot); - -impl<'ln> fmt::Debug for GeckoShadowRoot<'ln> { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - // TODO(emilio): Maybe print the host or something? - write!(f, " ({:#x})", self.as_node().opaque().0) - } -} - -impl<'lr> PartialEq for GeckoShadowRoot<'lr> { - #[inline] - fn eq(&self, other: &Self) -> bool { - self.0 as *const _ == other.0 as *const _ - } -} - -impl<'lr> TShadowRoot for GeckoShadowRoot<'lr> { - type ConcreteNode = GeckoNode<'lr>; - - #[inline] - fn as_node(&self) -> Self::ConcreteNode { - GeckoNode(&self.0._base._base._base._base) - } - - #[inline] - fn host(&self) -> GeckoElement<'lr> { - GeckoElement(unsafe { &*self.0._base.mHost.mRawPtr }) - } - - #[inline] - fn style_data<'a>(&self) -> Option<&'a CascadeData> - where - Self: 'a, - { - let author_styles = unsafe { self.0.mServoStyles.mPtr.as_ref()? }; - Some(&author_styles.data) - } - - #[inline] - fn elements_with_id<'a>(&self, id: &AtomIdent) -> Result<&'a [GeckoElement<'lr>], ()> - where - Self: 'a, - { - Ok(elements_with_id(unsafe { - bindings::Gecko_ShadowRoot_GetElementsWithId(self.0, id.as_ptr()) - })) - } - - #[inline] - fn parts<'a>(&self) -> &[::ConcreteElement] - where - Self: 'a, - { - let slice: &[*const RawGeckoElement] = &*self.0.mParts; - - #[allow(dead_code)] - unsafe fn static_assert() { - mem::transmute::<*const RawGeckoElement, GeckoElement<'static>>(0xbadc0de as *const _); - } - - unsafe { mem::transmute(slice) } - } -} - -/// A simple wrapper over a non-null Gecko node (`nsINode`) pointer. -/// -/// Important: We don't currently refcount the DOM, because the wrapper lifetime -/// magic guarantees that our LayoutFoo references won't outlive the root, and -/// we don't mutate any of the references on the Gecko side during restyle. -/// -/// We could implement refcounting if need be (at a potentially non-trivial -/// performance cost) by implementing Drop and making LayoutFoo non-Copy. -#[derive(Clone, Copy)] -pub struct GeckoNode<'ln>(pub &'ln RawGeckoNode); - -impl<'ln> PartialEq for GeckoNode<'ln> { - #[inline] - fn eq(&self, other: &Self) -> bool { - self.0 as *const _ == other.0 as *const _ - } -} - -impl<'ln> fmt::Debug for GeckoNode<'ln> { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - if let Some(el) = self.as_element() { - return el.fmt(f); - } - - if self.is_text_node() { - return write!(f, " ({:#x})", self.opaque().0); - } - - if self.is_document() { - return write!(f, " ({:#x})", self.opaque().0); - } - - if let Some(sr) = self.as_shadow_root() { - return sr.fmt(f); - } - - write!(f, " ({:#x})", self.opaque().0) - } -} - -impl<'ln> GeckoNode<'ln> { - #[inline] - fn is_document(&self) -> bool { - // This is a DOM constant that isn't going to change. - const DOCUMENT_NODE: u16 = 9; - self.node_info().mInner.mNodeType == DOCUMENT_NODE - } - - #[inline] - fn is_shadow_root(&self) -> bool { - self.is_in_shadow_tree() && self.parent_node().is_none() - } - - #[inline] - fn from_content(content: &'ln nsIContent) -> Self { - GeckoNode(&content._base) - } - - #[inline] - fn set_flags(&self, flags: u32) { - self.flags_atomic().fetch_or(flags, Ordering::Relaxed); - } - - #[inline] - fn flags_atomic(&self) -> &AtomicU32 { - use std::cell::Cell; - let flags: &Cell = &(self.0)._base._base_1.mFlags; - - #[allow(dead_code)] - fn static_assert() { - let _: [u8; std::mem::size_of::>()] = [0u8; std::mem::size_of::()]; - let _: [u8; std::mem::align_of::>()] = - [0u8; std::mem::align_of::()]; - } - - // Rust doesn't provide standalone atomic functions like GCC/clang do - // (via the atomic intrinsics) or via std::atomic_ref, but it guarantees - // that the memory representation of u32 and AtomicU32 matches: - // https://doc.rust-lang.org/std/sync/atomic/struct.AtomicU32.html - unsafe { std::mem::transmute::<&Cell, &AtomicU32>(flags) } - } - - #[inline] - fn flags(&self) -> u32 { - self.flags_atomic().load(Ordering::Relaxed) - } - - #[inline] - fn node_info(&self) -> &structs::NodeInfo { - debug_assert!(!self.0.mNodeInfo.mRawPtr.is_null()); - unsafe { &*self.0.mNodeInfo.mRawPtr } - } - - // These live in different locations depending on processor architecture. - #[cfg(target_pointer_width = "64")] - #[inline] - fn bool_flags(&self) -> u32 { - (self.0)._base._base_1.mBoolFlags - } - - #[cfg(target_pointer_width = "32")] - #[inline] - fn bool_flags(&self) -> u32 { - (self.0).mBoolFlags - } - - #[inline] - fn get_bool_flag(&self, flag: nsINode_BooleanFlag) -> bool { - self.bool_flags() & (1u32 << flag as u32) != 0 - } - - /// This logic is duplicate in Gecko's nsINode::IsInShadowTree(). - #[inline] - fn is_in_shadow_tree(&self) -> bool { - use crate::gecko_bindings::structs::NODE_IS_IN_SHADOW_TREE; - self.flags() & NODE_IS_IN_SHADOW_TREE != 0 - } - - /// Returns true if we know for sure that `flattened_tree_parent` and `parent_node` return the - /// same thing. - /// - /// TODO(emilio): Measure and consider not doing this fast-path, it's only a function call and - /// from profiles it seems that keeping this fast path makes the compiler not inline - /// `flattened_tree_parent` as a whole, so we're not gaining much either. - #[inline] - fn flattened_tree_parent_is_parent(&self) -> bool { - use crate::gecko_bindings::structs::*; - let flags = self.flags(); - - let parent = match self.parent_node() { - Some(p) => p, - None => return true, - }; - - if parent.is_shadow_root() { - return false; - } - - if let Some(parent) = parent.as_element() { - if flags & NODE_IS_NATIVE_ANONYMOUS_ROOT != 0 && parent.is_root() { - return false; - } - if parent.shadow_root().is_some() || parent.is_html_slot_element() { - return false; - } - } - - true - } - - #[inline] - fn flattened_tree_parent(&self) -> Option { - if self.flattened_tree_parent_is_parent() { - debug_assert_eq!( - unsafe { - bindings::Gecko_GetFlattenedTreeParentNode(self.0) - .as_ref() - .map(GeckoNode) - }, - self.parent_node(), - "Fast path stopped holding!" - ); - return self.parent_node(); - } - - // NOTE(emilio): If this call is too expensive, we could manually inline more aggressively. - unsafe { - bindings::Gecko_GetFlattenedTreeParentNode(self.0) - .as_ref() - .map(GeckoNode) - } - } - - #[inline] - fn contains_non_whitespace_content(&self) -> bool { - unsafe { Gecko_IsSignificantChild(self.0, false) } - } -} - -impl<'ln> NodeInfo for GeckoNode<'ln> { - #[inline] - fn is_element(&self) -> bool { - self.get_bool_flag(nsINode_BooleanFlag::NodeIsElement) - } - - fn is_text_node(&self) -> bool { - // This is a DOM constant that isn't going to change. - const TEXT_NODE: u16 = 3; - self.node_info().mInner.mNodeType == TEXT_NODE - } -} - -impl<'ln> TNode for GeckoNode<'ln> { - type ConcreteDocument = GeckoDocument<'ln>; - type ConcreteShadowRoot = GeckoShadowRoot<'ln>; - type ConcreteElement = GeckoElement<'ln>; - - #[inline] - fn parent_node(&self) -> Option { - unsafe { self.0.mParent.as_ref().map(GeckoNode) } - } - - #[inline] - fn first_child(&self) -> Option { - unsafe { - self.0 - .mFirstChild - .raw() - .as_ref() - .map(GeckoNode::from_content) - } - } - - #[inline] - fn last_child(&self) -> Option { - unsafe { bindings::Gecko_GetLastChild(self.0).as_ref().map(GeckoNode) } - } - - #[inline] - fn prev_sibling(&self) -> Option { - unsafe { - let prev_or_last = GeckoNode::from_content(self.0.mPreviousOrLastSibling.as_ref()?); - if prev_or_last.0.mNextSibling.raw().is_null() { - return None; - } - Some(prev_or_last) - } - } - - #[inline] - fn next_sibling(&self) -> Option { - unsafe { - self.0 - .mNextSibling - .raw() - .as_ref() - .map(GeckoNode::from_content) - } - } - - #[inline] - fn owner_doc(&self) -> Self::ConcreteDocument { - debug_assert!(!self.node_info().mDocument.is_null()); - GeckoDocument(unsafe { &*self.node_info().mDocument }) - } - - #[inline] - fn is_in_document(&self) -> bool { - self.get_bool_flag(nsINode_BooleanFlag::IsInDocument) - } - - fn traversal_parent(&self) -> Option> { - self.flattened_tree_parent().and_then(|n| n.as_element()) - } - - #[inline] - fn opaque(&self) -> OpaqueNode { - let ptr: usize = self.0 as *const _ as usize; - OpaqueNode(ptr) - } - - fn debug_id(self) -> usize { - unimplemented!() - } - - #[inline] - fn as_element(&self) -> Option> { - if !self.is_element() { - return None; - } - - Some(GeckoElement(unsafe { - &*(self.0 as *const _ as *const RawGeckoElement) - })) - } - - #[inline] - fn as_document(&self) -> Option { - if !self.is_document() { - return None; - } - - debug_assert_eq!(self.owner_doc().as_node(), *self, "How?"); - Some(self.owner_doc()) - } - - #[inline] - fn as_shadow_root(&self) -> Option { - if !self.is_shadow_root() { - return None; - } - - Some(GeckoShadowRoot(unsafe { - &*(self.0 as *const _ as *const structs::ShadowRoot) - })) - } -} - -/// A wrapper on top of two kind of iterators, depending on the parent being -/// iterated. -/// -/// We generally iterate children by traversing the light-tree siblings of the -/// first child like Servo does. -/// -/// However, for nodes with anonymous children, we use a custom (heavier-weight) -/// Gecko-implemented iterator. -/// -/// FIXME(emilio): If we take into account shadow DOM, we're going to need the -/// flat tree pretty much always. We can try to optimize the case where there's -/// no shadow root sibling, probably. -pub enum GeckoChildrenIterator<'a> { - /// A simple iterator that tracks the current node being iterated and - /// replaces it with the next sibling when requested. - Current(Option>), - /// A Gecko-implemented iterator we need to drop appropriately. - GeckoIterator(structs::StyleChildrenIterator), -} - -impl<'a> Drop for GeckoChildrenIterator<'a> { - fn drop(&mut self) { - if let GeckoChildrenIterator::GeckoIterator(ref mut it) = *self { - unsafe { - bindings::Gecko_DestroyStyleChildrenIterator(it); - } - } - } -} - -impl<'a> Iterator for GeckoChildrenIterator<'a> { - type Item = GeckoNode<'a>; - fn next(&mut self) -> Option> { - match *self { - GeckoChildrenIterator::Current(curr) => { - let next = curr.and_then(|node| node.next_sibling()); - *self = GeckoChildrenIterator::Current(next); - curr - }, - GeckoChildrenIterator::GeckoIterator(ref mut it) => unsafe { - // We do this unsafe lengthening of the lifetime here because - // structs::StyleChildrenIterator is actually StyleChildrenIterator<'a>, - // however we can't express this easily with bindgen, and it would - // introduce functions with two input lifetimes into bindgen, - // which would be out of scope for elision. - bindings::Gecko_GetNextStyleChild(&mut *(it as *mut _)) - .as_ref() - .map(GeckoNode) - }, - } - } -} - -/// A simple wrapper over a non-null Gecko `Element` pointer. -#[derive(Clone, Copy)] -pub struct GeckoElement<'le>(pub &'le RawGeckoElement); - -impl<'le> fmt::Debug for GeckoElement<'le> { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - use nsstring::nsCString; - - write!(f, "<{}", self.local_name())?; - - let mut attrs = nsCString::new(); - unsafe { - bindings::Gecko_Element_DebugListAttributes(self.0, &mut attrs); - } - write!(f, "{}", attrs)?; - write!(f, "> ({:#x})", self.as_node().opaque().0) - } -} - -impl<'le> GeckoElement<'le> { - /// Gets the raw `ElementData` refcell for the element. - #[inline(always)] - pub fn get_data(&self) -> Option<&AtomicRefCell> { - unsafe { self.0.mServoData.get().as_ref() } - } - - /// Returns whether any animation applies to this element. - #[inline] - pub fn has_any_animation(&self) -> bool { - self.may_have_animations() && unsafe { Gecko_ElementHasAnimations(self.0) } - } - - #[inline(always)] - fn attrs(&self) -> Option<&structs::AttrArray_Impl> { - unsafe { self.0.mAttrs.mImpl.mPtr.as_ref() } - } - - #[inline(always)] - fn non_mapped_attrs(&self) -> &[structs::AttrArray_InternalAttr] { - let attrs = match self.attrs() { - Some(attrs) => attrs, - None => return &[], - }; - unsafe { - attrs.mBuffer.as_slice(attrs.mAttrCount as usize) - } - } - - #[inline(always)] - fn mapped_attrs(&self) -> &[structs::AttrArray_InternalAttr] { - let attrs = match self.attrs() { - Some(attrs) => attrs, - None => return &[], - }; - unsafe { - let attrs = match attrs.mMappedAttrs.as_ref() { - Some(attrs) => attrs, - None => return &[], - }; - - attrs.mBuffer.as_slice(attrs.mAttrCount as usize) - } - } - - #[inline] - fn iter_attrs(&self) -> impl Iterator { - self.non_mapped_attrs().iter().chain(self.mapped_attrs().iter()) - } - - #[inline(always)] - fn get_part_attr(&self) -> Option<&structs::nsAttrValue> { - if !self.has_part_attr() { - return None; - } - snapshot_helpers::find_attr(self.non_mapped_attrs(), &atom!("part")) - } - - #[inline(always)] - fn get_class_attr(&self) -> Option<&structs::nsAttrValue> { - if !self.may_have_class() { - return None; - } - - if self.is_svg_element() { - let svg_class = unsafe { bindings::Gecko_GetSVGAnimatedClass(self.0).as_ref() }; - if let Some(c) = svg_class { - return Some(c); - } - } - - snapshot_helpers::find_attr(self.non_mapped_attrs(), &atom!("class")) - } - - #[inline] - fn may_have_anonymous_children(&self) -> bool { - self.as_node() - .get_bool_flag(nsINode_BooleanFlag::ElementMayHaveAnonymousChildren) - } - - #[inline] - fn flags(&self) -> u32 { - self.as_node().flags() - } - - #[inline] - fn set_flags(&self, flags: u32) { - self.as_node().set_flags(flags); - } - - #[inline] - unsafe fn unset_flags(&self, flags: u32) { - self.as_node() - .flags_atomic() - .fetch_and(!flags, Ordering::Relaxed); - } - - /// Returns true if this element has descendants for lazy frame construction. - #[inline] - pub fn descendants_need_frames(&self) -> bool { - self.flags() & NODE_DESCENDANTS_NEED_FRAMES != 0 - } - - /// Returns true if this element needs lazy frame construction. - #[inline] - pub fn needs_frame(&self) -> bool { - self.flags() & NODE_NEEDS_FRAME != 0 - } - - /// Returns a reference to the DOM slots for this Element, if they exist. - #[inline] - fn dom_slots(&self) -> Option<&structs::FragmentOrElement_nsDOMSlots> { - let slots = self.as_node().0.mSlots as *const structs::FragmentOrElement_nsDOMSlots; - unsafe { slots.as_ref() } - } - - /// Returns a reference to the extended DOM slots for this Element. - #[inline] - fn extended_slots(&self) -> Option<&structs::FragmentOrElement_nsExtendedDOMSlots> { - self.dom_slots().and_then(|s| unsafe { - // For the bit usage, see nsContentSlots::GetExtendedSlots. - let e_slots = s._base.mExtendedSlots & - !structs::nsIContent_nsContentSlots_sNonOwningExtendedSlotsFlag; - (e_slots as *const structs::FragmentOrElement_nsExtendedDOMSlots).as_ref() - }) - } - - #[inline] - fn namespace_id(&self) -> i32 { - self.as_node().node_info().mInner.mNamespaceID - } - - #[inline] - fn has_id(&self) -> bool { - self.as_node() - .get_bool_flag(nsINode_BooleanFlag::ElementHasID) - } - - #[inline] - fn state_internal(&self) -> u64 { - if !self - .as_node() - .get_bool_flag(nsINode_BooleanFlag::ElementHasLockedStyleStates) - { - return self.0.mState.bits; - } - unsafe { Gecko_ElementState(self.0) } - } - - #[inline] - fn document_state(&self) -> DocumentState { - DocumentState::from_bits_truncate(self.as_node().owner_doc().0.mDocumentState.bits) - } - - #[inline] - fn may_have_class(&self) -> bool { - self.as_node() - .get_bool_flag(nsINode_BooleanFlag::ElementMayHaveClass) - } - - #[inline] - fn has_properties(&self) -> bool { - use crate::gecko_bindings::structs::NODE_HAS_PROPERTIES; - - self.flags() & NODE_HAS_PROPERTIES != 0 - } - - #[inline] - fn before_or_after_pseudo(&self, is_before: bool) -> Option { - if !self.has_properties() { - return None; - } - - unsafe { - bindings::Gecko_GetBeforeOrAfterPseudo(self.0, is_before) - .as_ref() - .map(GeckoElement) - } - } - - #[inline] - fn may_have_style_attribute(&self) -> bool { - self.as_node() - .get_bool_flag(nsINode_BooleanFlag::ElementMayHaveStyle) - } - - /// Only safe to call on the main thread, with exclusive access to the - /// element and its ancestors. - /// - /// This function is also called after display property changed for SMIL - /// animation. - /// - /// Also this function schedules style flush. - pub unsafe fn note_explicit_hints(&self, restyle_hint: RestyleHint, change_hint: nsChangeHint) { - use crate::gecko::restyle_damage::GeckoRestyleDamage; - - let damage = GeckoRestyleDamage::new(change_hint); - debug!( - "note_explicit_hints: {:?}, restyle_hint={:?}, change_hint={:?}", - self, restyle_hint, change_hint - ); - - debug_assert!( - !(restyle_hint.has_animation_hint() && restyle_hint.has_non_animation_hint()), - "Animation restyle hints should not appear with non-animation restyle hints" - ); - - let mut data = match self.mutate_data() { - Some(d) => d, - None => { - debug!("(Element not styled, discarding hints)"); - return; - }, - }; - - debug_assert!(data.has_styles(), "how?"); - - // Propagate the bit up the chain. - if restyle_hint.has_animation_hint() { - bindings::Gecko_NoteAnimationOnlyDirtyElement(self.0); - } else { - bindings::Gecko_NoteDirtyElement(self.0); - } - - data.hint.insert(restyle_hint); - data.damage |= damage; - } - - /// This logic is duplicated in Gecko's nsIContent::IsRootOfNativeAnonymousSubtree. - #[inline] - fn is_root_of_native_anonymous_subtree(&self) -> bool { - use crate::gecko_bindings::structs::NODE_IS_NATIVE_ANONYMOUS_ROOT; - return self.flags() & NODE_IS_NATIVE_ANONYMOUS_ROOT != 0; - } - - /// Returns true if this node is the shadow root of an use-element shadow tree. - #[inline] - fn is_root_of_use_element_shadow_tree(&self) -> bool { - if !self.as_node().is_in_shadow_tree() { - return false; - } - if !self.parent_node_is_shadow_root() { - return false; - } - let host = self.containing_shadow_host().unwrap(); - host.is_svg_element() && host.local_name() == &**local_name!("use") - } - - fn css_transitions_info(&self) -> FxHashMap> { - use crate::gecko_bindings::bindings::Gecko_ElementTransitions_EndValueAt; - use crate::gecko_bindings::bindings::Gecko_ElementTransitions_Length; - - let collection_length = unsafe { Gecko_ElementTransitions_Length(self.0) } as usize; - let mut map = FxHashMap::with_capacity_and_hasher(collection_length, Default::default()); - - for i in 0..collection_length { - let end_value = - unsafe { Arc::from_raw_addrefed(Gecko_ElementTransitions_EndValueAt(self.0, i)) }; - let property = end_value.id(); - debug_assert!(!property.is_logical()); - map.insert(property, end_value); - } - map - } - - fn needs_transitions_update_per_property( - &self, - longhand_id: LonghandId, - combined_duration_seconds: f32, - before_change_style: &ComputedValues, - after_change_style: &ComputedValues, - existing_transitions: &FxHashMap>, - ) -> bool { - use crate::values::animated::{Animate, Procedure}; - debug_assert!(!longhand_id.is_logical()); - - // If there is an existing transition, update only if the end value - // differs. - // - // If the end value has not changed, we should leave the currently - // running transition as-is since we don't want to interrupt its timing - // function. - if let Some(ref existing) = existing_transitions.get(&longhand_id) { - let after_value = - AnimationValue::from_computed_values(longhand_id, after_change_style).unwrap(); - - return ***existing != after_value; - } - - let from = AnimationValue::from_computed_values(longhand_id, before_change_style); - let to = AnimationValue::from_computed_values(longhand_id, after_change_style); - - debug_assert_eq!(to.is_some(), from.is_some()); - - combined_duration_seconds > 0.0f32 && - from != to && - from.unwrap() - .animate( - to.as_ref().unwrap(), - Procedure::Interpolate { progress: 0.5 }, - ) - .is_ok() - } -} - -/// Converts flags from the layout used by rust-selectors to the layout used -/// by Gecko. We could align these and then do this without conditionals, but -/// it's probably not worth the trouble. -fn selector_flags_to_node_flags(flags: ElementSelectorFlags) -> u32 { - use crate::gecko_bindings::structs::*; - let mut gecko_flags = 0u32; - if flags.contains(ElementSelectorFlags::HAS_SLOW_SELECTOR) { - gecko_flags |= NODE_HAS_SLOW_SELECTOR; - } - if flags.contains(ElementSelectorFlags::HAS_SLOW_SELECTOR_LATER_SIBLINGS) { - gecko_flags |= NODE_HAS_SLOW_SELECTOR_LATER_SIBLINGS; - } - if flags.contains(ElementSelectorFlags::HAS_SLOW_SELECTOR_NTH_OF) { - gecko_flags |= NODE_HAS_SLOW_SELECTOR_NTH_OF; - } - if flags.contains(ElementSelectorFlags::HAS_EDGE_CHILD_SELECTOR) { - gecko_flags |= NODE_HAS_EDGE_CHILD_SELECTOR; - } - if flags.contains(ElementSelectorFlags::HAS_EMPTY_SELECTOR) { - gecko_flags |= NODE_HAS_EMPTY_SELECTOR; - } - - gecko_flags -} - -fn get_animation_rule( - element: &GeckoElement, - cascade_level: CascadeLevel, -) -> Option>> { - use crate::properties::longhands::ANIMATABLE_PROPERTY_COUNT; - - // There's a very rough correlation between the number of effects - // (animations) on an element and the number of properties it is likely to - // animate, so we use that as an initial guess for the size of the - // AnimationValueMap in order to reduce the number of re-allocations needed. - let effect_count = unsafe { Gecko_GetAnimationEffectCount(element.0) }; - // Also, we should try to reuse the PDB, to avoid creating extra rule nodes. - let mut animation_values = AnimationValueMap::with_capacity_and_hasher( - effect_count.min(ANIMATABLE_PROPERTY_COUNT), - Default::default(), - ); - if unsafe { Gecko_GetAnimationRule(element.0, cascade_level, &mut animation_values) } { - let shared_lock = &GLOBAL_STYLE_DATA.shared_lock; - Some(Arc::new(shared_lock.wrap( - PropertyDeclarationBlock::from_animation_value_map(&animation_values), - ))) - } else { - None - } -} - -/// Turns a gecko namespace id into an atom. Might panic if you pass any random thing that isn't a -/// namespace id. -#[inline(always)] -pub unsafe fn namespace_id_to_atom(id: i32) -> *mut nsAtom { - unsafe { - let namespace_manager = structs::nsNameSpaceManager_sInstance.mRawPtr; - (*namespace_manager).mURIArray[id as usize].mRawPtr - } -} - -impl<'le> TElement for GeckoElement<'le> { - type ConcreteNode = GeckoNode<'le>; - type TraversalChildrenIterator = GeckoChildrenIterator<'le>; - - fn inheritance_parent(&self) -> Option { - if self.is_pseudo_element() { - return self.pseudo_element_originating_element(); - } - - self.as_node() - .flattened_tree_parent() - .and_then(|n| n.as_element()) - } - - fn traversal_children(&self) -> LayoutIterator> { - // This condition is similar to the check that - // StyleChildrenIterator::IsNeeded does, except that it might return - // true if we used to (but no longer) have anonymous content from - // ::before/::after, or nsIAnonymousContentCreators. - if self.is_html_slot_element() || - self.shadow_root().is_some() || - self.may_have_anonymous_children() - { - unsafe { - let mut iter: structs::StyleChildrenIterator = ::std::mem::zeroed(); - bindings::Gecko_ConstructStyleChildrenIterator(self.0, &mut iter); - return LayoutIterator(GeckoChildrenIterator::GeckoIterator(iter)); - } - } - - LayoutIterator(GeckoChildrenIterator::Current(self.as_node().first_child())) - } - - fn before_pseudo_element(&self) -> Option { - self.before_or_after_pseudo(/* is_before = */ true) - } - - fn after_pseudo_element(&self) -> Option { - self.before_or_after_pseudo(/* is_before = */ false) - } - - fn marker_pseudo_element(&self) -> Option { - if !self.has_properties() { - return None; - } - - unsafe { - bindings::Gecko_GetMarkerPseudo(self.0) - .as_ref() - .map(GeckoElement) - } - } - - #[inline] - fn is_html_element(&self) -> bool { - self.namespace_id() == structs::kNameSpaceID_XHTML as i32 - } - - #[inline] - fn is_mathml_element(&self) -> bool { - self.namespace_id() == structs::kNameSpaceID_MathML as i32 - } - - #[inline] - fn is_svg_element(&self) -> bool { - self.namespace_id() == structs::kNameSpaceID_SVG as i32 - } - - #[inline] - fn is_xul_element(&self) -> bool { - self.namespace_id() == structs::root::kNameSpaceID_XUL as i32 - } - - #[inline] - fn local_name(&self) -> &WeakAtom { - unsafe { WeakAtom::new(self.as_node().node_info().mInner.mName) } - } - - #[inline] - fn namespace(&self) -> &WeakNamespace { - unsafe { WeakNamespace::new(namespace_id_to_atom(self.namespace_id())) } - } - - #[inline] - fn query_container_size(&self, display: &Display) -> Size2D> { - // If an element gets 'display: contents' and its nsIFrame has not been removed yet, - // Gecko_GetQueryContainerSize will not notice that it can't have size containment. - // Other cases like 'display: inline' will be handled once the new nsIFrame is created. - if display.is_contents() { - return Size2D::new(None, None); - } - - let mut width = -1; - let mut height = -1; - unsafe { - bindings::Gecko_GetQueryContainerSize(self.0, &mut width, &mut height); - } - Size2D::new( - if width >= 0 { Some(Au(width)) } else { None }, - if height >= 0 { Some(Au(height)) } else { None }, - ) - } - - /// Return the list of slotted nodes of this node. - #[inline] - fn slotted_nodes(&self) -> &[Self::ConcreteNode] { - if !self.is_html_slot_element() || !self.as_node().is_in_shadow_tree() { - return &[]; - } - - let slot: &structs::HTMLSlotElement = unsafe { mem::transmute(self.0) }; - - if cfg!(debug_assertions) { - let base: &RawGeckoElement = &slot._base._base._base._base; - assert_eq!(base as *const _, self.0 as *const _, "Bad cast"); - } - - // FIXME(emilio): Workaround a bindgen bug on Android that causes - // mAssignedNodes to be at the wrong offset. See bug 1466406. - // - // Bug 1466580 tracks running the Android layout tests on automation. - // - // The actual bindgen bug still needs reduction. - let assigned_nodes: &[structs::RefPtr] = if !cfg!(target_os = "android") { - debug_assert_eq!( - unsafe { bindings::Gecko_GetAssignedNodes(self.0) }, - &slot.mAssignedNodes as *const _, - ); - - &*slot.mAssignedNodes - } else { - unsafe { &**bindings::Gecko_GetAssignedNodes(self.0) } - }; - - debug_assert_eq!( - mem::size_of::>(), - mem::size_of::(), - "Bad cast!" - ); - - unsafe { mem::transmute(assigned_nodes) } - } - - #[inline] - fn shadow_root(&self) -> Option> { - let slots = self.extended_slots()?; - unsafe { slots.mShadowRoot.mRawPtr.as_ref().map(GeckoShadowRoot) } - } - - #[inline] - fn containing_shadow(&self) -> Option> { - let slots = self.extended_slots()?; - unsafe { - slots - ._base - .mContainingShadow - .mRawPtr - .as_ref() - .map(GeckoShadowRoot) - } - } - - fn each_anonymous_content_child(&self, mut f: F) - where - F: FnMut(Self), - { - if !self.may_have_anonymous_children() { - return; - } - - let array: *mut structs::nsTArray<*mut nsIContent> = - unsafe { bindings::Gecko_GetAnonymousContentForElement(self.0) }; - - if array.is_null() { - return; - } - - for content in unsafe { &**array } { - let node = GeckoNode::from_content(unsafe { &**content }); - let element = match node.as_element() { - Some(e) => e, - None => continue, - }; - - f(element); - } - - unsafe { bindings::Gecko_DestroyAnonymousContentList(array) }; - } - - #[inline] - fn as_node(&self) -> Self::ConcreteNode { - unsafe { GeckoNode(&*(self.0 as *const _ as *const RawGeckoNode)) } - } - - fn owner_doc_matches_for_testing(&self, device: &Device) -> bool { - self.as_node().owner_doc().0 as *const structs::Document == device.document() as *const _ - } - - fn style_attribute(&self) -> Option>> { - if !self.may_have_style_attribute() { - return None; - } - - unsafe { - let declarations = Gecko_GetStyleAttrDeclarationBlock(self.0).as_ref()?; - Some(ArcBorrow::from_ref(declarations)) - } - } - - fn unset_dirty_style_attribute(&self) { - if !self.may_have_style_attribute() { - return; - } - - unsafe { Gecko_UnsetDirtyStyleAttr(self.0) }; - } - - fn smil_override(&self) -> Option>> { - unsafe { - let slots = self.extended_slots()?; - - let declaration: &structs::DeclarationBlock = - slots.mSMILOverrideStyleDeclaration.mRawPtr.as_ref()?; - - let raw: &structs::StyleLockedDeclarationBlock = declaration.mRaw.mRawPtr.as_ref()?; - Some(ArcBorrow::from_ref(raw)) - } - } - - fn animation_rule( - &self, - _: &SharedStyleContext, - ) -> Option>> { - get_animation_rule(self, CascadeLevel::Animations) - } - - fn transition_rule( - &self, - _: &SharedStyleContext, - ) -> Option>> { - get_animation_rule(self, CascadeLevel::Transitions) - } - - #[inline] - fn state(&self) -> ElementState { - ElementState::from_bits_truncate(self.state_internal()) - } - - #[inline] - fn has_part_attr(&self) -> bool { - self.as_node() - .get_bool_flag(nsINode_BooleanFlag::ElementHasPart) - } - - #[inline] - fn exports_any_part(&self) -> bool { - snapshot_helpers::find_attr(self.non_mapped_attrs(), &atom!("exportparts")).is_some() - } - - // FIXME(emilio): we should probably just return a reference to the Atom. - #[inline] - fn id(&self) -> Option<&WeakAtom> { - if !self.has_id() { - return None; - } - - snapshot_helpers::get_id(self.non_mapped_attrs()) - } - - fn each_attr_name(&self, mut callback: F) - where - F: FnMut(&AtomIdent), - { - for attr in self.iter_attrs() { - unsafe { - AtomIdent::with(attr.mName.name(), |a| callback(a)) - } - } - } - - fn each_class(&self, callback: F) - where - F: FnMut(&AtomIdent), - { - let attr = match self.get_class_attr() { - Some(c) => c, - None => return, - }; - - snapshot_helpers::each_class_or_part(attr, callback) - } - - #[inline] - fn each_exported_part(&self, name: &AtomIdent, callback: F) - where - F: FnMut(&AtomIdent), - { - snapshot_helpers::each_exported_part(self.non_mapped_attrs(), name, callback) - } - - fn each_part(&self, callback: F) - where - F: FnMut(&AtomIdent), - { - let attr = match self.get_part_attr() { - Some(c) => c, - None => return, - }; - - snapshot_helpers::each_class_or_part(attr, callback) - } - - #[inline] - fn has_snapshot(&self) -> bool { - self.flags() & ELEMENT_HAS_SNAPSHOT != 0 - } - - #[inline] - fn handled_snapshot(&self) -> bool { - self.flags() & ELEMENT_HANDLED_SNAPSHOT != 0 - } - - unsafe fn set_handled_snapshot(&self) { - debug_assert!(self.has_data()); - self.set_flags(ELEMENT_HANDLED_SNAPSHOT) - } - - #[inline] - fn has_dirty_descendants(&self) -> bool { - self.flags() & ELEMENT_HAS_DIRTY_DESCENDANTS_FOR_SERVO != 0 - } - - unsafe fn set_dirty_descendants(&self) { - debug_assert!(self.has_data()); - debug!("Setting dirty descendants: {:?}", self); - self.set_flags(ELEMENT_HAS_DIRTY_DESCENDANTS_FOR_SERVO) - } - - unsafe fn unset_dirty_descendants(&self) { - self.unset_flags(ELEMENT_HAS_DIRTY_DESCENDANTS_FOR_SERVO) - } - - #[inline] - fn has_animation_only_dirty_descendants(&self) -> bool { - self.flags() & ELEMENT_HAS_ANIMATION_ONLY_DIRTY_DESCENDANTS_FOR_SERVO != 0 - } - - unsafe fn set_animation_only_dirty_descendants(&self) { - self.set_flags(ELEMENT_HAS_ANIMATION_ONLY_DIRTY_DESCENDANTS_FOR_SERVO) - } - - unsafe fn unset_animation_only_dirty_descendants(&self) { - self.unset_flags(ELEMENT_HAS_ANIMATION_ONLY_DIRTY_DESCENDANTS_FOR_SERVO) - } - - unsafe fn clear_descendant_bits(&self) { - self.unset_flags( - ELEMENT_HAS_DIRTY_DESCENDANTS_FOR_SERVO | - ELEMENT_HAS_ANIMATION_ONLY_DIRTY_DESCENDANTS_FOR_SERVO | - NODE_DESCENDANTS_NEED_FRAMES, - ) - } - - fn is_visited_link(&self) -> bool { - self.state().intersects(ElementState::VISITED) - } - - /// We want to match rules from the same tree in all cases, except for native anonymous content - /// that _isn't_ part directly of a UA widget (e.g., such generated by form controls, or - /// pseudo-elements). - #[inline] - fn matches_user_and_content_rules(&self) -> bool { - use crate::gecko_bindings::structs::{ - NODE_HAS_BEEN_IN_UA_WIDGET, NODE_IS_IN_NATIVE_ANONYMOUS_SUBTREE, - }; - let flags = self.flags(); - (flags & NODE_IS_IN_NATIVE_ANONYMOUS_SUBTREE) == 0 || - (flags & NODE_HAS_BEEN_IN_UA_WIDGET) != 0 - } - - #[inline] - fn implemented_pseudo_element(&self) -> Option { - if self.matches_user_and_content_rules() { - return None; - } - - if !self.has_properties() { - return None; - } - - PseudoElement::from_pseudo_type(unsafe { bindings::Gecko_GetImplementedPseudo(self.0) }) - } - - #[inline] - fn store_children_to_process(&self, _: isize) { - // This is only used for bottom-up traversal, and is thus a no-op for Gecko. - } - - fn did_process_child(&self) -> isize { - panic!("Atomic child count not implemented in Gecko"); - } - - unsafe fn ensure_data(&self) -> AtomicRefMut { - if !self.has_data() { - debug!("Creating ElementData for {:?}", self); - let ptr = Box::into_raw(Box::new(AtomicRefCell::new(ElementData::default()))); - self.0.mServoData.set(ptr); - } - self.mutate_data().unwrap() - } - - unsafe fn clear_data(&self) { - let ptr = self.0.mServoData.get(); - self.unset_flags( - ELEMENT_HAS_SNAPSHOT | - ELEMENT_HANDLED_SNAPSHOT | - structs::Element_kAllServoDescendantBits | - NODE_NEEDS_FRAME, - ); - if !ptr.is_null() { - debug!("Dropping ElementData for {:?}", self); - let data = Box::from_raw(self.0.mServoData.get()); - self.0.mServoData.set(ptr::null_mut()); - - // Perform a mutable borrow of the data in debug builds. This - // serves as an assertion that there are no outstanding borrows - // when we destroy the data. - debug_assert!({ - let _ = data.borrow_mut(); - true - }); - } - } - - #[inline] - fn skip_item_display_fixup(&self) -> bool { - debug_assert!( - !self.is_pseudo_element(), - "Just don't call me if I'm a pseudo, you should know the answer already" - ); - self.is_root_of_native_anonymous_subtree() - } - - #[inline] - fn may_have_animations(&self) -> bool { - if let Some(pseudo) = self.implemented_pseudo_element() { - if pseudo.animations_stored_in_parent() { - // FIXME(emilio): When would the parent of a ::before / ::after - // pseudo-element be null? - return self.parent_element().map_or(false, |p| { - p.as_node() - .get_bool_flag(nsINode_BooleanFlag::ElementHasAnimations) - }); - } - } - self.as_node() - .get_bool_flag(nsINode_BooleanFlag::ElementHasAnimations) - } - - /// Process various tasks that are a result of animation-only restyle. - fn process_post_animation(&self, tasks: PostAnimationTasks) { - debug_assert!(!tasks.is_empty(), "Should be involved a task"); - - // If display style was changed from none to other, we need to resolve - // the descendants in the display:none subtree. Instead of resolving - // those styles in animation-only restyle, we defer it to a subsequent - // normal restyle. - if tasks.intersects(PostAnimationTasks::DISPLAY_CHANGED_FROM_NONE_FOR_SMIL) { - debug_assert!( - self.implemented_pseudo_element() - .map_or(true, |p| !p.is_before_or_after()), - "display property animation shouldn't run on pseudo elements \ - since it's only for SMIL" - ); - unsafe { - self.note_explicit_hints( - RestyleHint::restyle_subtree(), - nsChangeHint::nsChangeHint_Empty, - ); - } - } - } - - /// Update various animation-related state on a given (pseudo-)element as - /// results of normal restyle. - fn update_animations( - &self, - before_change_style: Option>, - tasks: UpdateAnimationsTasks, - ) { - // We have to update animations even if the element has no computed - // style since it means the element is in a display:none subtree, we - // should destroy all CSS animations in display:none subtree. - let computed_data = self.borrow_data(); - let computed_values = computed_data.as_ref().map(|d| d.styles.primary()); - let before_change_values = before_change_style - .as_ref() - .map_or(ptr::null(), |x| x.as_gecko_computed_style()); - let computed_values_opt = computed_values - .as_ref() - .map_or(ptr::null(), |x| x.as_gecko_computed_style()); - unsafe { - Gecko_UpdateAnimations( - self.0, - before_change_values, - computed_values_opt, - tasks.bits(), - ); - } - } - - #[inline] - fn has_animations(&self, _: &SharedStyleContext) -> bool { - self.has_any_animation() - } - - fn has_css_animations(&self, _: &SharedStyleContext, _: Option) -> bool { - self.may_have_animations() && unsafe { Gecko_ElementHasCSSAnimations(self.0) } - } - - fn has_css_transitions(&self, _: &SharedStyleContext, _: Option) -> bool { - self.may_have_animations() && unsafe { Gecko_ElementHasCSSTransitions(self.0) } - } - - // Detect if there are any changes that require us to update transitions. - // - // This is used as a more thoroughgoing check than the cheaper - // might_need_transitions_update check. - // - // The following logic shadows the logic used on the Gecko side - // (nsTransitionManager::DoUpdateTransitions) where we actually perform the - // update. - // - // https://drafts.csswg.org/css-transitions/#starting - fn needs_transitions_update( - &self, - before_change_style: &ComputedValues, - after_change_style: &ComputedValues, - ) -> bool { - use crate::properties::LonghandIdSet; - - let after_change_ui_style = after_change_style.get_ui(); - let existing_transitions = self.css_transitions_info(); - - if after_change_style.get_box().clone_display().is_none() { - // We need to cancel existing transitions. - return !existing_transitions.is_empty(); - } - - let mut transitions_to_keep = LonghandIdSet::new(); - for transition_property in after_change_style.transition_properties() { - let physical_longhand = transition_property - .longhand_id - .to_physical(after_change_style.writing_mode); - 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) - .seconds(), - before_change_style, - after_change_style, - &existing_transitions, - ) { - return true; - } - } - - // Check if we have to cancel the running transition because this is not - // a matching transition-property value. - existing_transitions - .keys() - .any(|property| !transitions_to_keep.contains(*property)) - } - - /// Whether there is an ElementData container. - #[inline] - fn has_data(&self) -> bool { - self.get_data().is_some() - } - - /// Immutably borrows the ElementData. - fn borrow_data(&self) -> Option> { - self.get_data().map(|x| x.borrow()) - } - - /// Mutably borrows the ElementData. - fn mutate_data(&self) -> Option> { - self.get_data().map(|x| x.borrow_mut()) - } - - #[inline] - fn lang_attr(&self) -> Option { - let ptr = unsafe { bindings::Gecko_LangValue(self.0) }; - if ptr.is_null() { - None - } else { - Some(AtomString(unsafe { Atom::from_addrefed(ptr) })) - } - } - - fn match_element_lang(&self, override_lang: Option>, value: &Lang) -> bool { - // Gecko supports :lang() from CSS Selectors 4, which accepts a list - // of language tags, and does BCP47-style range matching. - let override_lang_ptr = match override_lang { - Some(Some(ref atom)) => atom.as_ptr(), - _ => ptr::null_mut(), - }; - value.0.iter().any(|lang| unsafe { - Gecko_MatchLang( - self.0, - override_lang_ptr, - override_lang.is_some(), - lang.as_slice().as_ptr(), - ) - }) - } - - fn is_html_document_body_element(&self) -> bool { - if self.local_name() != &**local_name!("body") { - return false; - } - - if !self.is_html_element() { - return false; - } - - unsafe { bindings::Gecko_IsDocumentBody(self.0) } - } - - fn synthesize_presentational_hints_for_legacy_attributes( - &self, - visited_handling: VisitedHandlingMode, - hints: &mut V, - ) where - V: Push, - { - use crate::properties::longhands::_x_lang::SpecifiedValue as SpecifiedLang; - use crate::properties::longhands::color::SpecifiedValue as SpecifiedColor; - use crate::stylesheets::layer_rule::LayerOrder; - use crate::values::specified::{color::Color, font::XTextScale}; - lazy_static! { - static ref TABLE_COLOR_RULE: ApplicableDeclarationBlock = { - let global_style_data = &*GLOBAL_STYLE_DATA; - let pdb = PropertyDeclarationBlock::with_one( - PropertyDeclaration::Color(SpecifiedColor(Color::InheritFromBodyQuirk.into())), - Importance::Normal, - ); - let arc = Arc::new_leaked(global_style_data.shared_lock.wrap(pdb)); - ApplicableDeclarationBlock::from_declarations( - arc, - ServoCascadeLevel::PresHints, - LayerOrder::root(), - ) - }; - static ref MATHML_LANG_RULE: ApplicableDeclarationBlock = { - let global_style_data = &*GLOBAL_STYLE_DATA; - let pdb = PropertyDeclarationBlock::with_one( - PropertyDeclaration::XLang(SpecifiedLang(atom!("x-math"))), - Importance::Normal, - ); - let arc = Arc::new_leaked(global_style_data.shared_lock.wrap(pdb)); - ApplicableDeclarationBlock::from_declarations( - arc, - ServoCascadeLevel::PresHints, - LayerOrder::root(), - ) - }; - static ref SVG_TEXT_DISABLE_SCALE_RULE: ApplicableDeclarationBlock = { - let global_style_data = &*GLOBAL_STYLE_DATA; - let pdb = PropertyDeclarationBlock::with_one( - PropertyDeclaration::XTextScale(XTextScale::None), - Importance::Normal, - ); - let arc = Arc::new_leaked(global_style_data.shared_lock.wrap(pdb)); - ApplicableDeclarationBlock::from_declarations( - arc, - ServoCascadeLevel::PresHints, - LayerOrder::root(), - ) - }; - }; - - 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() && - self.as_node().owner_doc().quirks_mode() == QuirksMode::Quirks - { - hints.push(TABLE_COLOR_RULE.clone()); - } - } - if ns == structs::kNameSpaceID_SVG as i32 { - if self.local_name().as_ptr() == atom!("text").as_ptr() { - hints.push(SVG_TEXT_DISABLE_SCALE_RULE.clone()); - } - } - let declarations = - unsafe { Gecko_GetHTMLPresentationAttrDeclarationBlock(self.0).as_ref() }; - if let Some(decl) = declarations { - hints.push(ApplicableDeclarationBlock::from_declarations( - unsafe { Arc::from_raw_addrefed(decl) }, - ServoCascadeLevel::PresHints, - LayerOrder::root(), - )); - } - let declarations = unsafe { Gecko_GetExtraContentStyleDeclarations(self.0).as_ref() }; - if let Some(decl) = declarations { - hints.push(ApplicableDeclarationBlock::from_declarations( - unsafe { Arc::from_raw_addrefed(decl) }, - ServoCascadeLevel::PresHints, - LayerOrder::root(), - )); - } - - // Support for link, vlink, and alink presentation hints on - if self.is_link() { - // Unvisited vs. visited styles are computed up-front based on the - // visited mode (not the element's actual state). - let declarations = match visited_handling { - VisitedHandlingMode::AllLinksVisitedAndUnvisited => { - unreachable!( - "We should never try to selector match with \ - AllLinksVisitedAndUnvisited" - ); - }, - VisitedHandlingMode::AllLinksUnvisited => unsafe { - Gecko_GetUnvisitedLinkAttrDeclarationBlock(self.0).as_ref() - }, - VisitedHandlingMode::RelevantLinkVisited => unsafe { - Gecko_GetVisitedLinkAttrDeclarationBlock(self.0).as_ref() - }, - }; - if let Some(decl) = declarations { - hints.push(ApplicableDeclarationBlock::from_declarations( - unsafe { Arc::from_raw_addrefed(decl) }, - ServoCascadeLevel::PresHints, - LayerOrder::root(), - )); - } - - let active = self - .state() - .intersects(NonTSPseudoClass::Active.state_flag()); - if active { - let declarations = - unsafe { Gecko_GetActiveLinkAttrDeclarationBlock(self.0).as_ref() }; - if let Some(decl) = declarations { - hints.push(ApplicableDeclarationBlock::from_declarations( - unsafe { Arc::from_raw_addrefed(decl) }, - ServoCascadeLevel::PresHints, - LayerOrder::root(), - )); - } - } - } - - // xml:lang has precedence over lang, which can be - // set by Gecko_GetHTMLPresentationAttrDeclarationBlock - // - // http://www.whatwg.org/specs/web-apps/current-work/multipage/elements.html#language - let ptr = unsafe { bindings::Gecko_GetXMLLangValue(self.0) }; - if !ptr.is_null() { - let global_style_data = &*GLOBAL_STYLE_DATA; - - let pdb = PropertyDeclarationBlock::with_one( - PropertyDeclaration::XLang(SpecifiedLang(unsafe { Atom::from_addrefed(ptr) })), - Importance::Normal, - ); - let arc = Arc::new(global_style_data.shared_lock.wrap(pdb)); - hints.push(ApplicableDeclarationBlock::from_declarations( - arc, - ServoCascadeLevel::PresHints, - LayerOrder::root(), - )) - } - // MathML's default lang has precedence over both `lang` and `xml:lang` - if ns == structs::kNameSpaceID_MathML as i32 { - if self.local_name().as_ptr() == atom!("math").as_ptr() { - hints.push(MATHML_LANG_RULE.clone()); - } - } - } -} - -impl<'le> PartialEq for GeckoElement<'le> { - #[inline] - fn eq(&self, other: &Self) -> bool { - self.0 as *const _ == other.0 as *const _ - } -} - -impl<'le> Eq for GeckoElement<'le> {} - -impl<'le> Hash for GeckoElement<'le> { - #[inline] - fn hash(&self, state: &mut H) { - (self.0 as *const RawGeckoElement).hash(state); - } -} - -impl<'le> ::selectors::Element for GeckoElement<'le> { - type Impl = SelectorImpl; - - #[inline] - fn opaque(&self) -> OpaqueElement { - OpaqueElement::new(self.0) - } - - #[inline] - fn parent_element(&self) -> Option { - let parent_node = self.as_node().parent_node(); - parent_node.and_then(|n| n.as_element()) - } - - #[inline] - fn parent_node_is_shadow_root(&self) -> bool { - self.as_node() - .parent_node() - .map_or(false, |p| p.is_shadow_root()) - } - - #[inline] - fn containing_shadow_host(&self) -> Option { - let shadow = self.containing_shadow()?; - Some(shadow.host()) - } - - #[inline] - fn is_pseudo_element(&self) -> bool { - self.implemented_pseudo_element().is_some() - } - - #[inline] - fn pseudo_element_originating_element(&self) -> Option { - debug_assert!(self.is_pseudo_element()); - debug_assert!(!self.matches_user_and_content_rules()); - let mut current = *self; - loop { - if current.is_root_of_native_anonymous_subtree() { - return current.traversal_parent(); - } - - current = current.traversal_parent()?; - } - } - - #[inline] - fn assigned_slot(&self) -> Option { - let slot = self.extended_slots()?._base.mAssignedSlot.mRawPtr; - - unsafe { Some(GeckoElement(&slot.as_ref()?._base._base._base._base)) } - } - - #[inline] - fn prev_sibling_element(&self) -> Option { - let mut sibling = self.as_node().prev_sibling(); - while let Some(sibling_node) = sibling { - if let Some(el) = sibling_node.as_element() { - return Some(el); - } - sibling = sibling_node.prev_sibling(); - } - None - } - - #[inline] - fn next_sibling_element(&self) -> Option { - let mut sibling = self.as_node().next_sibling(); - while let Some(sibling_node) = sibling { - if let Some(el) = sibling_node.as_element() { - return Some(el); - } - sibling = sibling_node.next_sibling(); - } - None - } - - #[inline] - fn first_element_child(&self) -> Option { - let mut child = self.as_node().first_child(); - while let Some(child_node) = child { - if let Some(el) = child_node.as_element() { - return Some(el); - } - child = child_node.next_sibling(); - } - None - } - - 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_flags(selector_flags_to_node_flags(flags)) - } - - // Handle flags that apply to the parent. - let parent_flags = flags.for_parent(); - if !parent_flags.is_empty() { - if let Some(p) = self.as_node().parent_node() { - if p.is_element() || p.is_shadow_root() { - p.set_flags(selector_flags_to_node_flags(parent_flags)); - } - } - } - } - - fn has_attr_in_no_namespace(&self, local_name: &LocalName) -> bool { - for attr in self.iter_attrs() { - if attr.mName.mBits == local_name.as_ptr() as usize { - return true; - } - } - false - } - - fn attr_matches( - &self, - ns: &NamespaceConstraint<&Namespace>, - local_name: &LocalName, - operation: &AttrSelectorOperation<&AttrValue>, - ) -> bool { - if self.attrs().is_none() { - return false; - } - snapshot_helpers::attr_matches(self.iter_attrs(), ns, local_name, operation) - } - - #[inline] - fn is_root(&self) -> bool { - if self - .as_node() - .get_bool_flag(nsINode_BooleanFlag::ParentIsContent) - { - return false; - } - - if !self.as_node().is_in_document() { - return false; - } - - debug_assert!(self - .as_node() - .parent_node() - .map_or(false, |p| p.is_document())); - // XXX this should always return true at this point, shouldn't it? - unsafe { bindings::Gecko_IsRootElement(self.0) } - } - - fn is_empty(&self) -> bool { - !self - .as_node() - .dom_children() - .any(|child| unsafe { Gecko_IsSignificantChild(child.0, true) }) - } - - #[inline] - fn has_local_name(&self, name: &WeakAtom) -> bool { - self.local_name() == name - } - - #[inline] - fn has_namespace(&self, ns: &WeakNamespace) -> bool { - self.namespace() == ns - } - - #[inline] - fn is_same_type(&self, other: &Self) -> bool { - self.local_name() == other.local_name() && self.namespace() == other.namespace() - } - - fn match_non_ts_pseudo_class( - &self, - pseudo_class: &NonTSPseudoClass, - context: &mut MatchingContext, - ) -> bool { - use selectors::matching::*; - match *pseudo_class { - NonTSPseudoClass::Autofill | - NonTSPseudoClass::Defined | - NonTSPseudoClass::Focus | - NonTSPseudoClass::Enabled | - NonTSPseudoClass::Disabled | - NonTSPseudoClass::Checked | - NonTSPseudoClass::Fullscreen | - NonTSPseudoClass::Indeterminate | - NonTSPseudoClass::MozInert | - NonTSPseudoClass::PopoverOpen | - NonTSPseudoClass::PlaceholderShown | - NonTSPseudoClass::Target | - NonTSPseudoClass::Valid | - NonTSPseudoClass::Invalid | - NonTSPseudoClass::MozBroken | - NonTSPseudoClass::MozLoading | - NonTSPseudoClass::Required | - NonTSPseudoClass::Optional | - NonTSPseudoClass::ReadOnly | - NonTSPseudoClass::ReadWrite | - NonTSPseudoClass::FocusWithin | - NonTSPseudoClass::FocusVisible | - NonTSPseudoClass::MozDragOver | - NonTSPseudoClass::MozDevtoolsHighlighted | - NonTSPseudoClass::MozStyleeditorTransitioning | - NonTSPseudoClass::MozMathIncrementScriptLevel | - NonTSPseudoClass::InRange | - NonTSPseudoClass::OutOfRange | - NonTSPseudoClass::Default | - NonTSPseudoClass::UserValid | - NonTSPseudoClass::UserInvalid | - NonTSPseudoClass::MozMeterOptimum | - NonTSPseudoClass::MozMeterSubOptimum | - NonTSPseudoClass::MozMeterSubSubOptimum | - NonTSPseudoClass::MozHasDirAttr | - NonTSPseudoClass::MozDirAttrLTR | - NonTSPseudoClass::MozDirAttrRTL | - NonTSPseudoClass::MozDirAttrLikeAuto | - NonTSPseudoClass::Modal | - NonTSPseudoClass::MozTopmostModal | - NonTSPseudoClass::Active | - NonTSPseudoClass::Hover | - NonTSPseudoClass::MozAutofillPreview | - NonTSPseudoClass::MozRevealed | - NonTSPseudoClass::MozValueEmpty | - NonTSPseudoClass::Dir(..) => self.state().intersects(pseudo_class.state_flag()), - NonTSPseudoClass::AnyLink => self.is_link(), - NonTSPseudoClass::Link => { - self.is_link() && context.visited_handling().matches_unvisited() - }, - NonTSPseudoClass::Visited => { - self.is_link() && context.visited_handling().matches_visited() - }, - NonTSPseudoClass::MozFirstNode => { - if context.needs_selector_flags() { - self.apply_selector_flags(ElementSelectorFlags::HAS_EDGE_CHILD_SELECTOR); - } - let mut elem = self.as_node(); - while let Some(prev) = elem.prev_sibling() { - if prev.contains_non_whitespace_content() { - return false; - } - elem = prev; - } - true - }, - NonTSPseudoClass::MozLastNode => { - if context.needs_selector_flags() { - self.apply_selector_flags(ElementSelectorFlags::HAS_EDGE_CHILD_SELECTOR); - } - let mut elem = self.as_node(); - while let Some(next) = elem.next_sibling() { - if next.contains_non_whitespace_content() { - return false; - } - elem = next; - } - true - }, - NonTSPseudoClass::MozOnlyWhitespace => { - if context.needs_selector_flags() { - self.apply_selector_flags(ElementSelectorFlags::HAS_EMPTY_SELECTOR); - } - if self - .as_node() - .dom_children() - .any(|c| c.contains_non_whitespace_content()) - { - return false; - } - true - }, - NonTSPseudoClass::MozNativeAnonymous => !self.matches_user_and_content_rules(), - NonTSPseudoClass::MozUseShadowTreeRoot => self.is_root_of_use_element_shadow_tree(), - NonTSPseudoClass::MozTableBorderNonzero => unsafe { - bindings::Gecko_IsTableBorderNonzero(self.0) - }, - NonTSPseudoClass::MozBrowserFrame => unsafe { bindings::Gecko_IsBrowserFrame(self.0) }, - NonTSPseudoClass::MozSelectListBox => unsafe { - bindings::Gecko_IsSelectListBox(self.0) - }, - NonTSPseudoClass::MozIsHTML => self.is_html_element_in_html_document(), - - NonTSPseudoClass::MozLWTheme | - NonTSPseudoClass::MozLocaleDir(..) | - NonTSPseudoClass::MozWindowInactive => { - let state_bit = pseudo_class.document_state_flag(); - if state_bit.is_empty() { - debug_assert!( - matches!(pseudo_class, NonTSPseudoClass::MozLocaleDir(..)), - "Only moz-locale-dir should ever return an empty state" - ); - return false; - } - if context - .extra_data - .invalidation_data - .document_state - .intersects(state_bit) - { - return !context.in_negation(); - } - self.document_state().contains(state_bit) - }, - NonTSPseudoClass::MozPlaceholder => false, - NonTSPseudoClass::Lang(ref lang_arg) => self.match_element_lang(None, lang_arg), - } - } - - fn match_pseudo_element( - &self, - pseudo_element: &PseudoElement, - _context: &mut MatchingContext, - ) -> bool { - // TODO(emilio): I believe we could assert we are a pseudo-element and - // match the proper pseudo-element, given how we rulehash the stuff - // based on the pseudo. - match self.implemented_pseudo_element() { - Some(ref pseudo) => *pseudo == *pseudo_element, - None => false, - } - } - - #[inline] - fn is_link(&self) -> bool { - self.state().intersects(ElementState::VISITED_OR_UNVISITED) - } - - #[inline] - fn has_id(&self, id: &AtomIdent, case_sensitivity: CaseSensitivity) -> bool { - if !self.has_id() { - return false; - } - - let element_id = match snapshot_helpers::get_id(self.non_mapped_attrs()) { - Some(id) => id, - None => return false, - }; - - case_sensitivity.eq_atom(element_id, id) - } - - #[inline] - fn is_part(&self, name: &AtomIdent) -> bool { - let attr = match self.get_part_attr() { - Some(c) => c, - None => return false, - }; - - snapshot_helpers::has_class_or_part(name, CaseSensitivity::CaseSensitive, attr) - } - - #[inline] - fn imported_part(&self, name: &AtomIdent) -> Option { - snapshot_helpers::imported_part(self.non_mapped_attrs(), name) - } - - #[inline(always)] - fn has_class(&self, name: &AtomIdent, case_sensitivity: CaseSensitivity) -> bool { - let attr = match self.get_class_attr() { - Some(c) => c, - None => return false, - }; - - snapshot_helpers::has_class_or_part(name, case_sensitivity, attr) - } - - #[inline] - fn is_html_element_in_html_document(&self) -> bool { - self.is_html_element() && self.as_node().owner_doc().is_html_document() - } - - #[inline] - fn is_html_slot_element(&self) -> bool { - self.is_html_element() && self.local_name().as_ptr() == local_name!("slot").as_ptr() - } - - #[inline] - fn ignores_nth_child_selectors(&self) -> bool { - self.is_root_of_native_anonymous_subtree() - } -} diff --git a/components/style/gecko_bindings/mod.rs b/components/style/gecko_bindings/mod.rs deleted file mode 100644 index f0b0adc7ecb..00000000000 --- a/components/style/gecko_bindings/mod.rs +++ /dev/null @@ -1,28 +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/. */ - -//! Gecko's C++ bindings, along with some rust helpers to ease its use. - -// FIXME: We allow `improper_ctypes` (for now), because the lint doesn't allow -// foreign structs to have `PhantomData`. We should remove this once the lint -// ignores this case. - -#[allow( - dead_code, - improper_ctypes, - non_camel_case_types, - non_snake_case, - non_upper_case_globals, - missing_docs -)] -// TODO: Remove this when updating bindgen, see -// https://github.com/rust-lang/rust-bindgen/issues/1651 -#[cfg_attr(test, allow(deref_nullptr))] -pub mod structs { - include!(concat!(env!("OUT_DIR"), "/gecko/structs.rs")); -} - -pub use self::structs as bindings; - -pub mod sugar; diff --git a/components/style/gecko_bindings/sugar/mod.rs b/components/style/gecko_bindings/sugar/mod.rs deleted file mode 100644 index 00faf63ba66..00000000000 --- a/components/style/gecko_bindings/sugar/mod.rs +++ /dev/null @@ -1,13 +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/. */ - -//! Rust sugar and convenience methods for Gecko types. - -mod ns_com_ptr; -mod ns_compatibility; -mod ns_style_auto_array; -mod ns_t_array; -pub mod origin_flags; -pub mod ownership; -pub mod refptr; diff --git a/components/style/gecko_bindings/sugar/ns_com_ptr.rs b/components/style/gecko_bindings/sugar/ns_com_ptr.rs deleted file mode 100644 index 1c54541bd80..00000000000 --- a/components/style/gecko_bindings/sugar/ns_com_ptr.rs +++ /dev/null @@ -1,15 +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/. */ - -//! Little helpers for `nsCOMPtr`. - -use crate::gecko_bindings::structs::nsCOMPtr; - -impl nsCOMPtr { - /// Get this pointer as a raw pointer. - #[inline] - pub fn raw(&self) -> *mut T { - self.mRawPtr - } -} diff --git a/components/style/gecko_bindings/sugar/ns_compatibility.rs b/components/style/gecko_bindings/sugar/ns_compatibility.rs deleted file mode 100644 index f4b81e9f790..00000000000 --- a/components/style/gecko_bindings/sugar/ns_compatibility.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/. */ - -//! Little helper for `nsCompatibility`. - -use crate::context::QuirksMode; -use crate::gecko_bindings::structs::nsCompatibility; - -impl From for QuirksMode { - #[inline] - fn from(mode: nsCompatibility) -> QuirksMode { - match mode { - nsCompatibility::eCompatibility_FullStandards => QuirksMode::NoQuirks, - nsCompatibility::eCompatibility_AlmostStandards => QuirksMode::LimitedQuirks, - nsCompatibility::eCompatibility_NavQuirks => QuirksMode::Quirks, - } - } -} diff --git a/components/style/gecko_bindings/sugar/ns_style_auto_array.rs b/components/style/gecko_bindings/sugar/ns_style_auto_array.rs deleted file mode 100644 index b5772a6c77a..00000000000 --- a/components/style/gecko_bindings/sugar/ns_style_auto_array.rs +++ /dev/null @@ -1,111 +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/. */ - -//! Rust helpers for Gecko's `nsStyleAutoArray`. - -use crate::gecko_bindings::bindings::Gecko_EnsureStyleAnimationArrayLength; -use crate::gecko_bindings::bindings::Gecko_EnsureStyleScrollTimelineArrayLength; -use crate::gecko_bindings::bindings::Gecko_EnsureStyleTransitionArrayLength; -use crate::gecko_bindings::bindings::Gecko_EnsureStyleViewTimelineArrayLength; -use crate::gecko_bindings::structs::nsStyleAutoArray; -use crate::gecko_bindings::structs::{StyleAnimation, StyleTransition}; -use crate::gecko_bindings::structs::{StyleScrollTimeline, StyleViewTimeline}; -use std::iter::{once, Chain, IntoIterator, Once}; -use std::ops::{Index, IndexMut}; -use std::slice::{Iter, IterMut}; - -impl Index for nsStyleAutoArray { - type Output = T; - fn index(&self, index: usize) -> &T { - match index { - 0 => &self.mFirstElement, - _ => &self.mOtherElements[index - 1], - } - } -} - -impl IndexMut for nsStyleAutoArray { - fn index_mut(&mut self, index: usize) -> &mut T { - match index { - 0 => &mut self.mFirstElement, - _ => &mut self.mOtherElements[index - 1], - } - } -} - -impl nsStyleAutoArray { - /// Mutably iterate over the array elements. - pub fn iter_mut(&mut self) -> Chain, IterMut> { - once(&mut self.mFirstElement).chain(self.mOtherElements.iter_mut()) - } - - /// Iterate over the array elements. - pub fn iter(&self) -> Chain, Iter> { - once(&self.mFirstElement).chain(self.mOtherElements.iter()) - } - - /// Returns the length of the array. - /// - /// Note that often structs containing autoarrays will have additional - /// member fields that contain the length, which must be kept in sync. - pub fn len(&self) -> usize { - 1 + self.mOtherElements.len() - } -} - -impl nsStyleAutoArray { - /// Ensures that the array has length at least the given length. - pub fn ensure_len(&mut self, len: usize) { - unsafe { - Gecko_EnsureStyleAnimationArrayLength( - self as *mut nsStyleAutoArray as *mut _, - len, - ); - } - } -} - -impl nsStyleAutoArray { - /// Ensures that the array has length at least the given length. - pub fn ensure_len(&mut self, len: usize) { - unsafe { - Gecko_EnsureStyleTransitionArrayLength( - self as *mut nsStyleAutoArray as *mut _, - len, - ); - } - } -} - -impl nsStyleAutoArray { - /// Ensures that the array has length at least the given length. - pub fn ensure_len(&mut self, len: usize) { - unsafe { - Gecko_EnsureStyleViewTimelineArrayLength( - self as *mut nsStyleAutoArray as *mut _, - len, - ); - } - } -} - -impl nsStyleAutoArray { - /// Ensures that the array has length at least the given length. - pub fn ensure_len(&mut self, len: usize) { - unsafe { - Gecko_EnsureStyleScrollTimelineArrayLength( - self as *mut nsStyleAutoArray as *mut _, - len, - ); - } - } -} - -impl<'a, T> IntoIterator for &'a mut nsStyleAutoArray { - type Item = &'a mut T; - type IntoIter = Chain, IterMut<'a, T>>; - fn into_iter(self) -> Self::IntoIter { - self.iter_mut() - } -} diff --git a/components/style/gecko_bindings/sugar/ns_t_array.rs b/components/style/gecko_bindings/sugar/ns_t_array.rs deleted file mode 100644 index d10ed420dd8..00000000000 --- a/components/style/gecko_bindings/sugar/ns_t_array.rs +++ /dev/null @@ -1,144 +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/. */ - -//! Rust helpers for Gecko's nsTArray. - -use crate::gecko_bindings::bindings; -use crate::gecko_bindings::structs::{nsTArray, nsTArrayHeader, CopyableTArray}; -use std::mem; -use std::ops::{Deref, DerefMut}; -use std::slice; - -impl Deref for nsTArray { - type Target = [T]; - - #[inline] - fn deref<'a>(&'a self) -> &'a [T] { - unsafe { slice::from_raw_parts(self.slice_begin(), self.header().mLength as usize) } - } -} - -impl DerefMut for nsTArray { - fn deref_mut<'a>(&'a mut self) -> &'a mut [T] { - unsafe { slice::from_raw_parts_mut(self.slice_begin(), self.header().mLength as usize) } - } -} - -impl nsTArray { - #[inline] - fn header<'a>(&'a self) -> &'a nsTArrayHeader { - debug_assert!(!self.mBuffer.is_null()); - unsafe { mem::transmute(self.mBuffer) } - } - - // unsafe, since header may be in shared static or something - unsafe fn header_mut<'a>(&'a mut self) -> &'a mut nsTArrayHeader { - debug_assert!(!self.mBuffer.is_null()); - - mem::transmute(self.mBuffer) - } - - #[inline] - unsafe fn slice_begin(&self) -> *mut T { - debug_assert!(!self.mBuffer.is_null()); - (self.mBuffer as *const nsTArrayHeader).offset(1) as *mut _ - } - - /// Ensures the array has enough capacity at least to hold `cap` elements. - /// - /// NOTE: This doesn't call the constructor on the values! - pub fn ensure_capacity(&mut self, cap: usize) { - if cap >= self.len() { - unsafe { - bindings::Gecko_EnsureTArrayCapacity( - self as *mut nsTArray as *mut _, - cap, - mem::size_of::(), - ) - } - } - } - - /// Clears the array storage without calling the destructor on the values. - #[inline] - pub unsafe fn clear(&mut self) { - if self.len() != 0 { - bindings::Gecko_ClearPODTArray( - self as *mut nsTArray as *mut _, - mem::size_of::(), - mem::align_of::(), - ); - } - } - - /// Clears a POD array. This is safe since copy types are memcopyable. - #[inline] - pub fn clear_pod(&mut self) - where - T: Copy, - { - unsafe { self.clear() } - } - - /// Resize and set the length of the array to `len`. - /// - /// unsafe because this may leave the array with uninitialized elements. - /// - /// This will not call constructors. If you need that, either manually add - /// bindings or run the typed `EnsureCapacity` call on the gecko side. - pub unsafe fn set_len(&mut self, len: u32) { - // this can leak - debug_assert!(len >= self.len() as u32); - if self.len() == len as usize { - return; - } - self.ensure_capacity(len as usize); - self.header_mut().mLength = len; - } - - /// Resizes an array containing only POD elements - /// - /// unsafe because this may leave the array with uninitialized elements. - /// - /// This will not leak since it only works on POD types (and thus doesn't assert) - pub unsafe fn set_len_pod(&mut self, len: u32) - where - T: Copy, - { - if self.len() == len as usize { - return; - } - self.ensure_capacity(len as usize); - let header = self.header_mut(); - header.mLength = len; - } - - /// Collects the given iterator into this array. - /// - /// Not unsafe because we won't leave uninitialized elements in the array. - pub fn assign_from_iter_pod(&mut self, iter: I) - where - T: Copy, - I: ExactSizeIterator + Iterator, - { - debug_assert!(iter.len() <= 0xFFFFFFFF); - unsafe { - self.set_len_pod(iter.len() as u32); - } - self.iter_mut().zip(iter).for_each(|(r, v)| *r = v); - } -} - -impl Deref for CopyableTArray { - type Target = nsTArray; - fn deref(&self) -> &Self::Target { - &self._base - } -} - -impl DerefMut for CopyableTArray { - fn deref_mut(&mut self) -> &mut nsTArray { - &mut self._base - } -} diff --git a/components/style/gecko_bindings/sugar/origin_flags.rs b/components/style/gecko_bindings/sugar/origin_flags.rs deleted file mode 100644 index e0f0981c5d8..00000000000 --- a/components/style/gecko_bindings/sugar/origin_flags.rs +++ /dev/null @@ -1,31 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ - -//! Helper to iterate over `OriginFlags` bits. - -use crate::gecko_bindings::structs::OriginFlags; -use crate::stylesheets::OriginSet; - -/// Checks that the values for OriginFlags are the ones we expect. -pub fn assert_flags_match() { - use crate::stylesheets::origin::*; - debug_assert_eq!( - OriginFlags::UserAgent.0, - OriginSet::ORIGIN_USER_AGENT.bits() - ); - debug_assert_eq!(OriginFlags::Author.0, OriginSet::ORIGIN_AUTHOR.bits()); - debug_assert_eq!(OriginFlags::User.0, OriginSet::ORIGIN_USER.bits()); -} - -impl From for OriginSet { - fn from(flags: OriginFlags) -> Self { - Self::from_bits_truncate(flags.0) - } -} - -impl From for OriginFlags { - fn from(set: OriginSet) -> Self { - OriginFlags(set.bits()) - } -} diff --git a/components/style/gecko_bindings/sugar/ownership.rs b/components/style/gecko_bindings/sugar/ownership.rs deleted file mode 100644 index 31b512cf1e8..00000000000 --- a/components/style/gecko_bindings/sugar/ownership.rs +++ /dev/null @@ -1,61 +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/. */ - -//! Helpers for different FFI pointer kinds that Gecko's FFI layer uses. - -use crate::gecko_bindings::structs::root::mozilla::detail::CopyablePtr; -use servo_arc::Arc; -use std::marker::PhantomData; -use std::ops::{Deref, DerefMut}; -use std::ptr; - -/// Gecko-FFI-safe Arc (T is an ArcInner). -/// -/// This can be null. -/// -/// Leaks on drop. Please don't drop this. -#[repr(C)] -pub struct Strong { - ptr: *const GeckoType, - _marker: PhantomData, -} - -impl From> for Strong { - fn from(arc: Arc) -> Self { - Self { - ptr: Arc::into_raw(arc), - _marker: PhantomData, - } - } -} - -impl Strong { - #[inline] - /// Returns whether this reference is null. - pub fn is_null(&self) -> bool { - self.ptr.is_null() - } - - #[inline] - /// Returns a null pointer - pub fn null() -> Self { - Self { - ptr: ptr::null(), - _marker: PhantomData, - } - } -} - -impl Deref for CopyablePtr { - type Target = T; - fn deref(&self) -> &Self::Target { - &self.mPtr - } -} - -impl DerefMut for CopyablePtr { - fn deref_mut<'a>(&'a mut self) -> &'a mut T { - &mut self.mPtr - } -} diff --git a/components/style/gecko_bindings/sugar/refptr.rs b/components/style/gecko_bindings/sugar/refptr.rs deleted file mode 100644 index c4a0479a077..00000000000 --- a/components/style/gecko_bindings/sugar/refptr.rs +++ /dev/null @@ -1,289 +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 rust helper to ease the use of Gecko's refcounted types. - -use crate::gecko_bindings::{bindings, structs}; -use crate::Atom; -use servo_arc::Arc; -use std::fmt::Write; -use std::marker::PhantomData; -use std::ops::Deref; -use std::{fmt, mem, ptr}; - -/// Trait for all objects that have Addref() and Release -/// methods and can be placed inside RefPtr -pub unsafe trait RefCounted { - /// Bump the reference count. - fn addref(&self); - /// Decrease the reference count. - unsafe fn release(&self); -} - -/// Trait for types which can be shared across threads in RefPtr. -pub unsafe trait ThreadSafeRefCounted: RefCounted {} - -/// A custom RefPtr implementation to take into account Drop semantics and -/// a bit less-painful memory management. -pub struct RefPtr { - ptr: *mut T, - _marker: PhantomData, -} - -impl fmt::Debug for RefPtr { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - f.write_str("RefPtr { ")?; - self.ptr.fmt(f)?; - f.write_char('}') - } -} - -impl RefPtr { - /// Create a new RefPtr from an already addrefed pointer obtained from FFI. - /// - /// The pointer must be valid, non-null and have been addrefed. - pub unsafe fn from_addrefed(ptr: *mut T) -> Self { - debug_assert!(!ptr.is_null()); - RefPtr { - ptr, - _marker: PhantomData, - } - } - - /// Returns whether the current pointer is null. - pub fn is_null(&self) -> bool { - self.ptr.is_null() - } - - /// Returns a null pointer. - pub fn null() -> Self { - Self { - ptr: ptr::null_mut(), - _marker: PhantomData, - } - } - - /// Create a new RefPtr from a pointer obtained from FFI. - /// - /// This method calls addref() internally - pub unsafe fn new(ptr: *mut T) -> Self { - let ret = RefPtr { - ptr, - _marker: PhantomData, - }; - ret.addref(); - ret - } - - /// Produces an FFI-compatible RefPtr that can be stored in style structs. - /// - /// structs::RefPtr does not have a destructor, so this may leak - pub fn forget(self) -> structs::RefPtr { - let ret = structs::RefPtr { - mRawPtr: self.ptr, - _phantom_0: PhantomData, - }; - mem::forget(self); - ret - } - - /// Returns the raw inner pointer to be fed back into FFI. - pub fn get(&self) -> *mut T { - self.ptr - } - - /// Addref the inner data, obviously leaky on its own. - pub fn addref(&self) { - if !self.ptr.is_null() { - unsafe { - (*self.ptr).addref(); - } - } - } - - /// Release the inner data. - /// - /// Call only when the data actually needs releasing. - pub unsafe fn release(&self) { - if !self.ptr.is_null() { - (*self.ptr).release(); - } - } -} - -impl Deref for RefPtr { - type Target = T; - fn deref(&self) -> &T { - debug_assert!(!self.ptr.is_null()); - unsafe { &*self.ptr } - } -} - -impl structs::RefPtr { - /// Produces a Rust-side RefPtr from an FFI RefPtr, bumping the refcount. - /// - /// Must be called on a valid, non-null structs::RefPtr. - pub unsafe fn to_safe(&self) -> RefPtr { - let r = RefPtr { - ptr: self.mRawPtr, - _marker: PhantomData, - }; - r.addref(); - r - } - /// Produces a Rust-side RefPtr, consuming the existing one (and not bumping - /// the refcount). - pub unsafe fn into_safe(self) -> RefPtr { - debug_assert!(!self.mRawPtr.is_null()); - RefPtr { - ptr: self.mRawPtr, - _marker: PhantomData, - } - } - - /// Replace a structs::RefPtr with a different one, appropriately - /// addref/releasing. - /// - /// Both `self` and `other` must be valid, but can be null. - /// - /// Safe when called on an aliased pointer because the refcount in that case - /// needs to be at least two. - pub unsafe fn set(&mut self, other: &Self) { - self.clear(); - if !other.mRawPtr.is_null() { - *self = other.to_safe().forget(); - } - } - - /// Clear an instance of the structs::RefPtr, by releasing - /// it and setting its contents to null. - /// - /// `self` must be valid, but can be null. - pub unsafe fn clear(&mut self) { - if !self.mRawPtr.is_null() { - (*self.mRawPtr).release(); - self.mRawPtr = ptr::null_mut(); - } - } - - /// Replace a `structs::RefPtr` with a `RefPtr`, - /// consuming the `RefPtr`, and releasing the old - /// value in `self` if necessary. - /// - /// `self` must be valid, possibly null. - pub fn set_move(&mut self, other: RefPtr) { - if !self.mRawPtr.is_null() { - unsafe { - (*self.mRawPtr).release(); - } - } - *self = other.forget(); - } -} - -impl structs::RefPtr { - /// Returns a new, null refptr. - pub fn null() -> Self { - Self { - mRawPtr: ptr::null_mut(), - _phantom_0: PhantomData, - } - } - - /// Create a new RefPtr from an arc. - pub fn from_arc(s: Arc) -> Self { - Self { - mRawPtr: Arc::into_raw(s) as *mut _, - _phantom_0: PhantomData, - } - } - - /// Sets the contents to an Arc. - pub fn set_arc(&mut self, other: Arc) { - unsafe { - if !self.mRawPtr.is_null() { - let _ = Arc::from_raw(self.mRawPtr); - } - self.mRawPtr = Arc::into_raw(other) as *mut _; - } - } -} - -impl Drop for RefPtr { - fn drop(&mut self) { - unsafe { self.release() } - } -} - -impl Clone for RefPtr { - fn clone(&self) -> Self { - self.addref(); - RefPtr { - ptr: self.ptr, - _marker: PhantomData, - } - } -} - -impl PartialEq for RefPtr { - fn eq(&self, other: &Self) -> bool { - self.ptr == other.ptr - } -} - -unsafe impl Send for RefPtr {} -unsafe impl Sync for RefPtr {} - -macro_rules! impl_refcount { - ($t:ty, $addref:path, $release:path) => { - unsafe impl RefCounted for $t { - #[inline] - fn addref(&self) { - unsafe { $addref(self as *const _ as *mut _) } - } - - #[inline] - unsafe fn release(&self) { - $release(self as *const _ as *mut _) - } - } - }; -} - -// Companion of NS_DECL_THREADSAFE_FFI_REFCOUNTING. -// -// Gets you a free RefCounted impl implemented via FFI. -macro_rules! impl_threadsafe_refcount { - ($t:ty, $addref:path, $release:path) => { - impl_refcount!($t, $addref, $release); - unsafe impl ThreadSafeRefCounted for $t {} - }; -} - -impl_threadsafe_refcount!( - structs::mozilla::URLExtraData, - bindings::Gecko_AddRefURLExtraDataArbitraryThread, - bindings::Gecko_ReleaseURLExtraDataArbitraryThread -); -impl_threadsafe_refcount!( - structs::nsIURI, - bindings::Gecko_AddRefnsIURIArbitraryThread, - bindings::Gecko_ReleasensIURIArbitraryThread -); -impl_threadsafe_refcount!( - structs::SheetLoadDataHolder, - bindings::Gecko_AddRefSheetLoadDataHolderArbitraryThread, - bindings::Gecko_ReleaseSheetLoadDataHolderArbitraryThread -); - -#[inline] -unsafe fn addref_atom(atom: *mut structs::nsAtom) { - mem::forget(Atom::from_raw(atom)); -} - -#[inline] -unsafe fn release_atom(atom: *mut structs::nsAtom) { - let _ = Atom::from_addrefed(atom); -} -impl_threadsafe_refcount!(structs::nsAtom, addref_atom, release_atom); diff --git a/components/style/gecko_string_cache/mod.rs b/components/style/gecko_string_cache/mod.rs deleted file mode 100644 index cb040390cfe..00000000000 --- a/components/style/gecko_string_cache/mod.rs +++ /dev/null @@ -1,532 +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/. */ - -#![allow(unsafe_code)] -// This is needed for the constants in atom_macro.rs, because we have some -// atoms whose names differ only by case, e.g. datetime and dateTime. -#![allow(non_upper_case_globals)] - -//! A drop-in replacement for string_cache, but backed by Gecko `nsAtom`s. - -use crate::gecko_bindings::bindings::Gecko_AddRefAtom; -use crate::gecko_bindings::bindings::Gecko_Atomize; -use crate::gecko_bindings::bindings::Gecko_Atomize16; -use crate::gecko_bindings::bindings::Gecko_ReleaseAtom; -use crate::gecko_bindings::structs::root::mozilla::detail::gGkAtoms; -use crate::gecko_bindings::structs::root::mozilla::detail::kGkAtomsArrayOffset; -use crate::gecko_bindings::structs::root::mozilla::detail::GkAtoms_Atoms_AtomsCount; -use crate::gecko_bindings::structs::{nsAtom, nsDynamicAtom, nsStaticAtom}; -use nsstring::{nsAString, nsStr}; -use precomputed_hash::PrecomputedHash; -use std::borrow::{Borrow, Cow}; -use std::char::{self, DecodeUtf16}; -use std::fmt::{self, Write}; -use std::hash::{Hash, Hasher}; -use std::iter::Cloned; -use std::mem::{self, ManuallyDrop}; -use std::num::NonZeroUsize; -use std::ops::Deref; -use std::{slice, str}; -use style_traits::SpecifiedValueInfo; -use to_shmem::{self, SharedMemoryBuilder, ToShmem}; - -#[macro_use] -#[allow(improper_ctypes, non_camel_case_types, missing_docs)] -pub mod atom_macro { - include!(concat!(env!("OUT_DIR"), "/gecko/atom_macro.rs")); -} - -#[macro_use] -pub mod namespace; - -pub use self::namespace::{Namespace, WeakNamespace}; - -/// A handle to a Gecko atom. This is a type that can represent either: -/// -/// * A strong reference to a dynamic atom (an `nsAtom` pointer), in which case -/// the `usize` just holds the pointer value. -/// -/// * A byte offset from `gGkAtoms` to the `nsStaticAtom` object (shifted to -/// the left one bit, and with the lower bit set to `1` to differentiate it -/// from the above), so `(offset << 1 | 1)`. -/// -#[derive(Eq, PartialEq)] -#[repr(C)] -pub struct Atom(NonZeroUsize); - -/// An atom *without* a strong reference. -/// -/// Only usable as `&'a WeakAtom`, -/// where `'a` is the lifetime of something that holds a strong reference to that atom. -pub struct WeakAtom(nsAtom); - -/// The number of static atoms we have. -const STATIC_ATOM_COUNT: usize = GkAtoms_Atoms_AtomsCount as usize; - -/// Returns the Gecko static atom array. -/// -/// We have this rather than use rust-bindgen to generate -/// mozilla::detail::gGkAtoms and then just reference gGkAtoms.mAtoms, so we -/// avoid a problem with lld-link.exe on Windows. -/// -/// https://bugzilla.mozilla.org/show_bug.cgi?id=1517685 -#[inline] -fn static_atoms() -> &'static [nsStaticAtom; STATIC_ATOM_COUNT] { - unsafe { - let addr = &gGkAtoms as *const _ as usize + kGkAtomsArrayOffset as usize; - &*(addr as *const _) - } -} - -/// Returns whether the specified address points to one of the nsStaticAtom -/// objects in the Gecko static atom array. -#[inline] -fn valid_static_atom_addr(addr: usize) -> bool { - unsafe { - let atoms = static_atoms(); - let start = atoms.as_ptr(); - let end = atoms.get_unchecked(STATIC_ATOM_COUNT) as *const _; - let in_range = addr >= start as usize && addr < end as usize; - let aligned = addr % mem::align_of::() == 0; - in_range && aligned - } -} - -impl Deref for Atom { - type Target = WeakAtom; - - #[inline] - fn deref(&self) -> &WeakAtom { - unsafe { - let addr = if self.is_static() { - (&gGkAtoms as *const _ as usize) + (self.0.get() >> 1) - } else { - self.0.get() - }; - debug_assert!(!self.is_static() || valid_static_atom_addr(addr)); - WeakAtom::new(addr as *const nsAtom) - } - } -} - -impl PrecomputedHash for Atom { - #[inline] - fn precomputed_hash(&self) -> u32 { - self.get_hash() - } -} - -impl Borrow for Atom { - #[inline] - fn borrow(&self) -> &WeakAtom { - self - } -} - -impl ToShmem for Atom { - fn to_shmem(&self, _builder: &mut SharedMemoryBuilder) -> to_shmem::Result { - if !self.is_static() { - return Err(format!( - "ToShmem failed for Atom: must be a static atom: {}", - self - )); - } - - Ok(ManuallyDrop::new(Atom(self.0))) - } -} - -impl Eq for WeakAtom {} -impl PartialEq for WeakAtom { - #[inline] - fn eq(&self, other: &Self) -> bool { - let weak: *const WeakAtom = self; - let other: *const WeakAtom = other; - weak == other - } -} - -impl PartialEq for WeakAtom { - #[inline] - fn eq(&self, other: &Atom) -> bool { - self == &**other - } -} - -unsafe impl Send for Atom {} -unsafe impl Sync for Atom {} -unsafe impl Sync for WeakAtom {} - -impl WeakAtom { - /// Construct a `WeakAtom` from a raw `nsAtom`. - #[inline] - pub unsafe fn new<'a>(atom: *const nsAtom) -> &'a mut Self { - &mut *(atom as *mut WeakAtom) - } - - /// Clone this atom, bumping the refcount if the atom is not static. - #[inline] - pub fn clone(&self) -> Atom { - unsafe { Atom::from_raw(self.as_ptr()) } - } - - /// Get the atom hash. - #[inline] - pub fn get_hash(&self) -> u32 { - self.0.mHash - } - - /// Get the atom as a slice of utf-16 chars. - #[inline] - pub fn as_slice(&self) -> &[u16] { - let string = if self.is_static() { - let atom_ptr = self.as_ptr() as *const nsStaticAtom; - let string_offset = unsafe { (*atom_ptr).mStringOffset }; - let string_offset = -(string_offset as isize); - let u8_ptr = atom_ptr as *const u8; - // It is safe to use offset() here because both addresses are within - // the same struct, e.g. mozilla::detail::gGkAtoms. - unsafe { u8_ptr.offset(string_offset) as *const u16 } - } else { - let atom_ptr = self.as_ptr() as *const nsDynamicAtom; - // Dynamic atom chars are stored at the end of the object. - unsafe { atom_ptr.offset(1) as *const u16 } - }; - unsafe { slice::from_raw_parts(string, self.len() as usize) } - } - - // NOTE: don't expose this, since it's slow, and easy to be misused. - fn chars(&self) -> DecodeUtf16>> { - char::decode_utf16(self.as_slice().iter().cloned()) - } - - /// Execute `cb` with the string that this atom represents. - /// - /// Find alternatives to this function when possible, please, since it's - /// pretty slow. - pub fn with_str(&self, cb: F) -> Output - where - F: FnOnce(&str) -> Output, - { - let mut buffer = mem::MaybeUninit::<[u8; 64]>::uninit(); - let buffer = unsafe { &mut *buffer.as_mut_ptr() }; - - // The total string length in utf16 is going to be less than or equal - // the slice length (each utf16 character is going to take at least one - // and at most 2 items in the utf16 slice). - // - // Each of those characters will take at most four bytes in the utf8 - // one. Thus if the slice is less than 64 / 4 (16) we can guarantee that - // we'll decode it in place. - let owned_string; - let len = self.len(); - let utf8_slice = if len <= 16 { - let mut total_len = 0; - - for c in self.chars() { - let c = c.unwrap_or(char::REPLACEMENT_CHARACTER); - let utf8_len = c.encode_utf8(&mut buffer[total_len..]).len(); - total_len += utf8_len; - } - - let slice = unsafe { str::from_utf8_unchecked(&buffer[..total_len]) }; - debug_assert_eq!(slice, String::from_utf16_lossy(self.as_slice())); - slice - } else { - owned_string = String::from_utf16_lossy(self.as_slice()); - &*owned_string - }; - - cb(utf8_slice) - } - - /// Returns whether this atom is static. - #[inline] - pub fn is_static(&self) -> bool { - self.0.mIsStatic() != 0 - } - - /// Returns whether this atom is ascii lowercase. - #[inline] - fn is_ascii_lowercase(&self) -> bool { - self.0.mIsAsciiLowercase() != 0 - } - - /// Returns the length of the atom string. - #[inline] - pub fn len(&self) -> u32 { - self.0.mLength() - } - - /// Returns whether this atom is the empty string. - #[inline] - pub fn is_empty(&self) -> bool { - self.len() == 0 - } - - /// Returns the atom as a mutable pointer. - #[inline] - pub fn as_ptr(&self) -> *mut nsAtom { - let const_ptr: *const nsAtom = &self.0; - const_ptr as *mut nsAtom - } - - /// Convert this atom to ASCII lower-case - pub fn to_ascii_lowercase(&self) -> Atom { - if self.is_ascii_lowercase() { - return self.clone(); - } - - let slice = self.as_slice(); - let mut buffer = mem::MaybeUninit::<[u16; 64]>::uninit(); - let buffer = unsafe { &mut *buffer.as_mut_ptr() }; - let mut vec; - let mutable_slice = if let Some(buffer_prefix) = buffer.get_mut(..slice.len()) { - buffer_prefix.copy_from_slice(slice); - buffer_prefix - } else { - vec = slice.to_vec(); - &mut vec - }; - for char16 in &mut *mutable_slice { - if *char16 <= 0x7F { - *char16 = (*char16 as u8).to_ascii_lowercase() as u16 - } - } - Atom::from(&*mutable_slice) - } - - /// Return whether two atoms are ASCII-case-insensitive matches - #[inline] - pub fn eq_ignore_ascii_case(&self, other: &Self) -> bool { - if self == other { - return true; - } - - // If we know both atoms are ascii-lowercase, then we can stick with - // pointer equality. - if self.is_ascii_lowercase() && other.is_ascii_lowercase() { - debug_assert!(!self.eq_ignore_ascii_case_slow(other)); - return false; - } - - self.eq_ignore_ascii_case_slow(other) - } - - fn eq_ignore_ascii_case_slow(&self, other: &Self) -> bool { - let a = self.as_slice(); - let b = other.as_slice(); - - if a.len() != b.len() { - return false; - } - - a.iter().zip(b).all(|(&a16, &b16)| { - if a16 <= 0x7F && b16 <= 0x7F { - (a16 as u8).eq_ignore_ascii_case(&(b16 as u8)) - } else { - a16 == b16 - } - }) - } -} - -impl fmt::Debug for WeakAtom { - fn fmt(&self, w: &mut fmt::Formatter) -> fmt::Result { - write!(w, "Gecko WeakAtom({:p}, {})", self, self) - } -} - -impl fmt::Display for WeakAtom { - fn fmt(&self, w: &mut fmt::Formatter) -> fmt::Result { - for c in self.chars() { - w.write_char(c.unwrap_or(char::REPLACEMENT_CHARACTER))? - } - Ok(()) - } -} - -#[inline] -unsafe fn make_handle(ptr: *const nsAtom) -> NonZeroUsize { - debug_assert!(!ptr.is_null()); - if !WeakAtom::new(ptr).is_static() { - NonZeroUsize::new_unchecked(ptr as usize) - } else { - make_static_handle(ptr as *mut nsStaticAtom) - } -} - -#[inline] -unsafe fn make_static_handle(ptr: *const nsStaticAtom) -> NonZeroUsize { - // FIXME(heycam): Use offset_from once it's stabilized. - // https://github.com/rust-lang/rust/issues/41079 - debug_assert!(valid_static_atom_addr(ptr as usize)); - let base = &gGkAtoms as *const _; - let offset = ptr as usize - base as usize; - NonZeroUsize::new_unchecked((offset << 1) | 1) -} - -impl Atom { - #[inline] - fn is_static(&self) -> bool { - self.0.get() & 1 == 1 - } - - /// Execute a callback with the atom represented by `ptr`. - pub unsafe fn with(ptr: *const nsAtom, callback: F) -> R - where - F: FnOnce(&Atom) -> R, - { - let atom = Atom(make_handle(ptr as *mut nsAtom)); - let ret = callback(&atom); - mem::forget(atom); - ret - } - - /// Creates a static atom from its index in the static atom table, without - /// checking. - #[inline] - pub const unsafe fn from_index_unchecked(index: u16) -> Self { - // FIXME(emilio): No support for debug_assert! in const fn for now. Note - // that violating this invariant will debug-assert in the `Deref` impl - // though. - // - // debug_assert!((index as usize) < STATIC_ATOM_COUNT); - let offset = - (index as usize) * std::mem::size_of::() + kGkAtomsArrayOffset as usize; - Atom(NonZeroUsize::new_unchecked((offset << 1) | 1)) - } - - /// Creates an atom from an atom pointer. - #[inline(always)] - pub unsafe fn from_raw(ptr: *mut nsAtom) -> Self { - let atom = Atom(make_handle(ptr)); - if !atom.is_static() { - Gecko_AddRefAtom(ptr); - } - atom - } - - /// Creates an atom from an atom pointer that has already had AddRef - /// called on it. This may be a static or dynamic atom. - #[inline] - pub unsafe fn from_addrefed(ptr: *mut nsAtom) -> Self { - assert!(!ptr.is_null()); - Atom(make_handle(ptr)) - } - - /// Convert this atom into an addrefed nsAtom pointer. - #[inline] - pub fn into_addrefed(self) -> *mut nsAtom { - let ptr = self.as_ptr(); - mem::forget(self); - ptr - } -} - -impl Hash for Atom { - fn hash(&self, state: &mut H) - where - H: Hasher, - { - state.write_u32(self.get_hash()); - } -} - -impl Hash for WeakAtom { - fn hash(&self, state: &mut H) - where - H: Hasher, - { - state.write_u32(self.get_hash()); - } -} - -impl Clone for Atom { - #[inline(always)] - fn clone(&self) -> Atom { - unsafe { - let atom = Atom(self.0); - if !atom.is_static() { - Gecko_AddRefAtom(atom.as_ptr()); - } - atom - } - } -} - -impl Drop for Atom { - #[inline] - fn drop(&mut self) { - if !self.is_static() { - unsafe { - Gecko_ReleaseAtom(self.as_ptr()); - } - } - } -} - -impl Default for Atom { - #[inline] - fn default() -> Self { - atom!("") - } -} - -impl fmt::Debug for Atom { - fn fmt(&self, w: &mut fmt::Formatter) -> fmt::Result { - write!(w, "Atom(0x{:08x}, {})", self.0, self) - } -} - -impl fmt::Display for Atom { - fn fmt(&self, w: &mut fmt::Formatter) -> fmt::Result { - self.deref().fmt(w) - } -} - -impl<'a> From<&'a str> for Atom { - #[inline] - fn from(string: &str) -> Atom { - debug_assert!(string.len() <= u32::max_value() as usize); - unsafe { - Atom::from_addrefed(Gecko_Atomize( - string.as_ptr() as *const _, - string.len() as u32, - )) - } - } -} - -impl<'a> From<&'a [u16]> for Atom { - #[inline] - fn from(slice: &[u16]) -> Atom { - Atom::from(&*nsStr::from(slice)) - } -} - -impl<'a> From<&'a nsAString> for Atom { - #[inline] - fn from(string: &nsAString) -> Atom { - unsafe { Atom::from_addrefed(Gecko_Atomize16(string)) } - } -} - -impl<'a> From> for Atom { - #[inline] - fn from(string: Cow<'a, str>) -> Atom { - Atom::from(&*string) - } -} - -impl From for Atom { - #[inline] - fn from(string: String) -> Atom { - Atom::from(&*string) - } -} - -malloc_size_of_is_0!(Atom); - -impl SpecifiedValueInfo for Atom {} diff --git a/components/style/gecko_string_cache/namespace.rs b/components/style/gecko_string_cache/namespace.rs deleted file mode 100644 index d9745b9e21f..00000000000 --- a/components/style/gecko_string_cache/namespace.rs +++ /dev/null @@ -1,105 +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 type to represent a namespace. - -use crate::gecko_bindings::structs::nsAtom; -use crate::string_cache::{Atom, WeakAtom}; -use precomputed_hash::PrecomputedHash; -use std::borrow::Borrow; -use std::fmt; -use std::ops::Deref; - -/// In Gecko namespaces are just regular atoms, so this is a simple macro to -/// forward one macro to the other. -#[macro_export] -macro_rules! ns { - () => { - $crate::string_cache::Namespace(atom!("")) - }; - ($s:tt) => { - $crate::string_cache::Namespace(atom!($s)) - }; -} - -/// A Gecko namespace is just a wrapped atom. -#[derive( - Clone, - Debug, - Default, - Eq, - Hash, - MallocSizeOf, - PartialEq, - ToComputedValue, - ToResolvedValue, - ToShmem, -)] -#[repr(transparent)] -pub struct Namespace(pub Atom); - -impl PrecomputedHash for Namespace { - #[inline] - fn precomputed_hash(&self) -> u32 { - self.0.precomputed_hash() - } -} - -/// A Gecko WeakNamespace is a wrapped WeakAtom. -#[derive(Deref, Hash)] -pub struct WeakNamespace(WeakAtom); - -impl Deref for Namespace { - type Target = WeakNamespace; - - #[inline] - fn deref(&self) -> &WeakNamespace { - let weak: *const WeakAtom = &*self.0; - unsafe { &*(weak as *const WeakNamespace) } - } -} - -impl<'a> From<&'a str> for Namespace { - fn from(s: &'a str) -> Self { - Namespace(Atom::from(s)) - } -} - -impl fmt::Display for Namespace { - fn fmt(&self, w: &mut fmt::Formatter) -> fmt::Result { - self.0.fmt(w) - } -} - -impl Borrow for Namespace { - #[inline] - fn borrow(&self) -> &WeakNamespace { - self - } -} - -impl WeakNamespace { - /// Trivially construct a WeakNamespace. - #[inline] - pub unsafe fn new<'a>(atom: *mut nsAtom) -> &'a Self { - &*(atom as *const WeakNamespace) - } - - /// Clone this WeakNamespace to obtain a strong reference to the same - /// underlying namespace. - #[inline] - pub fn clone(&self) -> Namespace { - Namespace(self.0.clone()) - } -} - -impl Eq for WeakNamespace {} -impl PartialEq for WeakNamespace { - #[inline] - fn eq(&self, other: &Self) -> bool { - let weak: *const WeakNamespace = self; - let other: *const WeakNamespace = other; - weak == other - } -} diff --git a/components/style/global_style_data.rs b/components/style/global_style_data.rs deleted file mode 100644 index 8b5f3c89024..00000000000 --- a/components/style/global_style_data.rs +++ /dev/null @@ -1,173 +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/. */ - -//! Global style data - -use crate::context::StyleSystemOptions; -#[cfg(feature = "gecko")] -use crate::gecko_bindings::bindings; -use crate::parallel::STYLE_THREAD_STACK_SIZE_KB; -use crate::shared_lock::SharedRwLock; -use crate::thread_state; -#[cfg(feature = "gecko")] -use gecko_profiler; -use parking_lot::{Mutex, RwLock, RwLockReadGuard}; -use rayon; -use std::{io, thread}; - -/// Global style data -pub struct GlobalStyleData { - /// Shared RWLock for CSSOM objects - pub shared_lock: SharedRwLock, - - /// Global style system options determined by env vars. - pub options: StyleSystemOptions, -} - -/// Global thread pool. -pub struct StyleThreadPool { - /// How many threads parallel styling can use. If not using a thread pool, this is set to `None`. - pub num_threads: Option, - - /// The parallel styling thread pool. - /// - /// For leak-checking purposes, we want to terminate the thread-pool, which - /// waits for all the async jobs to complete. Thus the RwLock. - style_thread_pool: RwLock>, -} - -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(()) -} - -fn thread_startup(_index: usize) { - thread_state::initialize_layout_worker_thread(); - #[cfg(feature = "gecko")] - unsafe { - bindings::Gecko_SetJemallocThreadLocalArena(true); - let name = thread_name(_index); - gecko_profiler::register_thread(&name); - } -} - -fn thread_shutdown(_: usize) { - #[cfg(feature = "gecko")] - unsafe { - gecko_profiler::unregister_thread(); - bindings::Gecko_SetJemallocThreadLocalArena(false); - } -} - -impl StyleThreadPool { - /// Shuts down the thread pool, waiting for all work to complete. - pub fn shutdown() { - if STYLE_THREAD_JOIN_HANDLES.lock().is_empty() { - 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(); - } - } - - /// Returns a reference to the thread pool. - /// - /// We only really want to give read-only access to the pool, except - /// for shutdown(). - pub fn pool(&self) -> RwLockReadGuard> { - self.style_thread_pool.read() - } -} - -#[cfg(feature = "servo")] -fn stylo_threads_pref() -> i32 { - style_config::get_i32("layout.threads") -} - -#[cfg(feature = "gecko")] -fn stylo_threads_pref() -> i32 { - static_prefs::pref!("layout.css.stylo-threads") -} - -/// The performance benefit of additional threads seems to level off at around six, so we cap it -/// there on many-core machines (see bug 1431285 comment 14). -pub(crate) const STYLO_MAX_THREADS: usize = 6; - -lazy_static! { - /// Global thread pool - pub static ref STYLE_THREAD_POOL: std::sync::Mutex = { - use std::cmp; - // We always set this pref on startup, before layout or script have had a chance of - // accessing (and thus creating) the thread-pool. - let threads_pref: i32 = stylo_threads_pref(); - let num_threads = if threads_pref >= 0 { - threads_pref as usize - } else { - use num_cpus; - // The default heuristic is num_virtual_cores * .75. This gives us three threads on a - // hyper-threaded dual core, and six threads on a hyper-threaded quad core. - let threads = cmp::max(num_cpus::get() * 3 / 4, 1); - // There's no point in creating a thread pool if there's one thread. - if threads == 1 { 0 } else { threads } - }; - - let num_threads = cmp::min(num_threads, STYLO_MAX_THREADS); - let (pool, num_threads) = if num_threads < 1 { - (None, None) - } else { - let workers = rayon::ThreadPoolBuilder::new() - .spawn_handler(thread_spawn) - .num_threads(num_threads) - .thread_name(thread_name) - .start_handler(thread_startup) - .exit_handler(thread_shutdown) - .stack_size(STYLE_THREAD_STACK_SIZE_KB * 1024) - .build(); - (workers.ok(), Some(num_threads)) - }; - - std::sync::Mutex::new(StyleThreadPool { - num_threads, - style_thread_pool: RwLock::new(pool), - }) - }; - - /// Global style data - pub static ref GLOBAL_STYLE_DATA: GlobalStyleData = GlobalStyleData { - shared_lock: SharedRwLock::new_leaked(), - options: StyleSystemOptions::default(), - }; -} diff --git a/components/style/invalidation/element/document_state.rs b/components/style/invalidation/element/document_state.rs deleted file mode 100644 index b0bbce3b855..00000000000 --- a/components/style/invalidation/element/document_state.rs +++ /dev/null @@ -1,142 +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/. */ - -//! An invalidation processor for style changes due to document state changes. - -use crate::dom::TElement; -use crate::invalidation::element::invalidation_map::Dependency; -use crate::invalidation::element::invalidator::{DescendantInvalidationLists, InvalidationVector}; -use crate::invalidation::element::invalidator::{Invalidation, InvalidationProcessor}; -use crate::invalidation::element::state_and_attributes; -use crate::stylist::CascadeData; -use selectors::matching::{ - MatchingContext, MatchingMode, NeedsSelectorFlags, QuirksMode, VisitedHandlingMode, -}; -use selectors::NthIndexCache; -use style_traits::dom::DocumentState; - -/// A struct holding the members necessary to invalidate document state -/// selectors. -#[derive(Debug)] -pub struct InvalidationMatchingData { - /// The document state that has changed, which makes it always match. - pub document_state: DocumentState, -} - -impl Default for InvalidationMatchingData { - #[inline(always)] - fn default() -> Self { - Self { - document_state: DocumentState::empty(), - } - } -} - -/// An invalidation processor for style changes due to state and attribute -/// changes. -pub struct DocumentStateInvalidationProcessor<'a, E: TElement, I> { - rules: I, - matching_context: MatchingContext<'a, E::Impl>, - document_states_changed: DocumentState, -} - -impl<'a, E: TElement, I> DocumentStateInvalidationProcessor<'a, E, I> { - /// Creates a new DocumentStateInvalidationProcessor. - #[inline] - pub fn new( - rules: I, - document_states_changed: DocumentState, - nth_index_cache: &'a mut NthIndexCache, - quirks_mode: QuirksMode, - ) -> Self { - let mut matching_context = MatchingContext::<'a, E::Impl>::new_for_visited( - MatchingMode::Normal, - None, - nth_index_cache, - VisitedHandlingMode::AllLinksVisitedAndUnvisited, - quirks_mode, - NeedsSelectorFlags::No, - ); - - matching_context.extra_data.invalidation_data.document_state = document_states_changed; - - Self { - rules, - document_states_changed, - matching_context, - } - } -} - -impl<'a, E, I> InvalidationProcessor<'a, E> for DocumentStateInvalidationProcessor<'a, E, I> -where - E: TElement, - I: Iterator, -{ - fn check_outer_dependency(&mut self, _: &Dependency, _: E) -> bool { - debug_assert!( - false, - "how, we should only have parent-less dependencies here!" - ); - true - } - - fn collect_invalidations( - &mut self, - _element: E, - self_invalidations: &mut InvalidationVector<'a>, - _descendant_invalidations: &mut DescendantInvalidationLists<'a>, - _sibling_invalidations: &mut InvalidationVector<'a>, - ) -> bool { - for cascade_data in &mut self.rules { - let map = cascade_data.invalidation_map(); - for dependency in &map.document_state_selectors { - if !dependency.state.intersects(self.document_states_changed) { - continue; - } - - // We pass `None` as a scope, as document state selectors aren't - // affected by the current scope. - // - // FIXME(emilio): We should really pass the relevant host for - // self.rules, so that we invalidate correctly if the selector - // happens to have something like :host(:-moz-window-inactive) - // for example. - self_invalidations.push(Invalidation::new( - &dependency.dependency, - /* scope = */ None, - )); - } - } - - false - } - - fn matching_context(&mut self) -> &mut MatchingContext<'a, E::Impl> { - &mut self.matching_context - } - - fn recursion_limit_exceeded(&mut self, _: E) { - unreachable!("We don't run document state invalidation with stack limits") - } - - fn should_process_descendants(&mut self, element: E) -> bool { - match element.borrow_data() { - Some(d) => state_and_attributes::should_process_descendants(&d), - None => false, - } - } - - fn invalidated_descendants(&mut self, element: E, child: E) { - state_and_attributes::invalidated_descendants(element, child) - } - - fn invalidated_self(&mut self, element: E) { - state_and_attributes::invalidated_self(element); - } - - fn invalidated_sibling(&mut self, sibling: E, of: E) { - state_and_attributes::invalidated_sibling(sibling, of); - } -} diff --git a/components/style/invalidation/element/element_wrapper.rs b/components/style/invalidation/element/element_wrapper.rs deleted file mode 100644 index 8c25153d1a8..00000000000 --- a/components/style/invalidation/element/element_wrapper.rs +++ /dev/null @@ -1,391 +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 wrapper over an element and a snapshot, that allows us to selector-match -//! against a past state of the element. - -use crate::dom::TElement; -use crate::selector_parser::{AttrValue, NonTSPseudoClass, PseudoElement, SelectorImpl}; -use crate::selector_parser::{Snapshot, SnapshotMap}; -use crate::values::AtomIdent; -use crate::{CaseSensitivityExt, LocalName, Namespace, WeakAtom}; -use selectors::attr::{AttrSelectorOperation, CaseSensitivity, NamespaceConstraint}; -use selectors::matching::{ElementSelectorFlags, MatchingContext}; -use selectors::{Element, OpaqueElement}; -use std::cell::Cell; -use std::fmt; -use style_traits::dom::ElementState; - -/// In order to compute restyle hints, we perform a selector match against a -/// list of partial selectors whose rightmost simple selector may be sensitive -/// to the thing being changed. We do this matching twice, once for the element -/// as it exists now and once for the element as it existed at the time of the -/// last restyle. If the results of the selector match differ, that means that -/// the given partial selector is sensitive to the change, and we compute a -/// restyle hint based on its combinator. -/// -/// In order to run selector matching against the old element state, we generate -/// a wrapper for the element which claims to have the old state. This is the -/// ElementWrapper logic below. -/// -/// Gecko does this differently for element states, and passes a mask called -/// mStateMask, which indicates the states that need to be ignored during -/// selector matching. This saves an ElementWrapper allocation and an additional -/// selector match call at the expense of additional complexity inside the -/// selector matching logic. This only works for boolean states though, so we -/// still need to take the ElementWrapper approach for attribute-dependent -/// style. So we do it the same both ways for now to reduce complexity, but it's -/// worth measuring the performance impact (if any) of the mStateMask approach. -pub trait ElementSnapshot: Sized { - /// The state of the snapshot, if any. - fn state(&self) -> Option; - - /// If this snapshot contains attribute information. - fn has_attrs(&self) -> bool; - - /// Gets the attribute information of the snapshot as a string. - /// - /// Only for debugging purposes. - fn debug_list_attributes(&self) -> String { - String::new() - } - - /// The ID attribute per this snapshot. Should only be called if - /// `has_attrs()` returns true. - fn id_attr(&self) -> Option<&WeakAtom>; - - /// Whether this snapshot contains the class `name`. Should only be called - /// if `has_attrs()` returns true. - fn has_class(&self, name: &AtomIdent, case_sensitivity: CaseSensitivity) -> bool; - - /// Whether this snapshot represents the part named `name`. Should only be - /// called if `has_attrs()` returns true. - fn is_part(&self, name: &AtomIdent) -> bool; - - /// See Element::imported_part. - fn imported_part(&self, name: &AtomIdent) -> Option; - - /// A callback that should be called for each class of the snapshot. Should - /// only be called if `has_attrs()` returns true. - fn each_class(&self, _: F) - where - F: FnMut(&AtomIdent); - - /// The `xml:lang=""` or `lang=""` attribute value per this snapshot. - fn lang_attr(&self) -> Option; -} - -/// A simple wrapper over an element and a snapshot, that allows us to -/// selector-match against a past state of the element. -#[derive(Clone)] -pub struct ElementWrapper<'a, E> -where - E: TElement, -{ - element: E, - cached_snapshot: Cell>, - snapshot_map: &'a SnapshotMap, -} - -impl<'a, E> ElementWrapper<'a, E> -where - E: TElement, -{ - /// Trivially constructs an `ElementWrapper`. - pub fn new(el: E, snapshot_map: &'a SnapshotMap) -> Self { - ElementWrapper { - element: el, - cached_snapshot: Cell::new(None), - snapshot_map: snapshot_map, - } - } - - /// Gets the snapshot associated with this element, if any. - pub fn snapshot(&self) -> Option<&'a Snapshot> { - if !self.element.has_snapshot() { - return None; - } - - if let Some(s) = self.cached_snapshot.get() { - return Some(s); - } - - let snapshot = self.snapshot_map.get(&self.element); - debug_assert!(snapshot.is_some(), "has_snapshot lied!"); - - self.cached_snapshot.set(snapshot); - - snapshot - } - - /// Returns the states that have changed since the element was snapshotted. - pub fn state_changes(&self) -> ElementState { - let snapshot = match self.snapshot() { - Some(s) => s, - None => return ElementState::empty(), - }; - - match snapshot.state() { - Some(state) => state ^ self.element.state(), - None => ElementState::empty(), - } - } - - /// Returns the value of the `xml:lang=""` (or, if appropriate, `lang=""`) - /// attribute from this element's snapshot or the closest ancestor - /// element snapshot with the attribute specified. - fn get_lang(&self) -> Option { - let mut current = self.clone(); - loop { - let lang = match self.snapshot() { - Some(snapshot) if snapshot.has_attrs() => snapshot.lang_attr(), - _ => current.element.lang_attr(), - }; - if lang.is_some() { - return lang; - } - current = current.parent_element()?; - } - } -} - -impl<'a, E> fmt::Debug for ElementWrapper<'a, E> -where - E: TElement, -{ - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - // Ignore other fields for now, can change later if needed. - self.element.fmt(f) - } -} - -impl<'a, E> Element for ElementWrapper<'a, E> -where - E: TElement, -{ - type Impl = SelectorImpl; - - fn match_non_ts_pseudo_class( - &self, - pseudo_class: &NonTSPseudoClass, - context: &mut MatchingContext, - ) -> bool { - // Some pseudo-classes need special handling to evaluate them against - // the snapshot. - match *pseudo_class { - // For :link and :visited, we don't actually want to test the - // element state directly. - // - // Instead, we use the `visited_handling` to determine if they - // match. - NonTSPseudoClass::Link => { - return self.is_link() && context.visited_handling().matches_unvisited(); - }, - NonTSPseudoClass::Visited => { - return self.is_link() && context.visited_handling().matches_visited(); - }, - - #[cfg(feature = "gecko")] - NonTSPseudoClass::MozTableBorderNonzero => { - if let Some(snapshot) = self.snapshot() { - if snapshot.has_other_pseudo_class_state() { - return snapshot.mIsTableBorderNonzero(); - } - } - }, - - #[cfg(feature = "gecko")] - NonTSPseudoClass::MozBrowserFrame => { - if let Some(snapshot) = self.snapshot() { - if snapshot.has_other_pseudo_class_state() { - return snapshot.mIsMozBrowserFrame(); - } - } - }, - - #[cfg(feature = "gecko")] - NonTSPseudoClass::MozSelectListBox => { - if let Some(snapshot) = self.snapshot() { - if snapshot.has_other_pseudo_class_state() { - return snapshot.mIsSelectListBox(); - } - } - }, - - // :lang() needs to match using the closest ancestor xml:lang="" or - // lang="" attribtue from snapshots. - NonTSPseudoClass::Lang(ref lang_arg) => { - return self - .element - .match_element_lang(Some(self.get_lang()), lang_arg); - }, - - _ => {}, - } - - let flag = pseudo_class.state_flag(); - if flag.is_empty() { - return self - .element - .match_non_ts_pseudo_class(pseudo_class, context); - } - match self.snapshot().and_then(|s| s.state()) { - Some(snapshot_state) => snapshot_state.intersects(flag), - None => self - .element - .match_non_ts_pseudo_class(pseudo_class, context), - } - } - - fn apply_selector_flags(&self, _flags: ElementSelectorFlags) { - debug_assert!(false, "Shouldn't need selector flags for invalidation"); - } - - fn match_pseudo_element( - &self, - pseudo_element: &PseudoElement, - context: &mut MatchingContext, - ) -> bool { - self.element.match_pseudo_element(pseudo_element, context) - } - - fn is_link(&self) -> bool { - match self.snapshot().and_then(|s| s.state()) { - Some(state) => state.intersects(ElementState::VISITED_OR_UNVISITED), - None => self.element.is_link(), - } - } - - fn opaque(&self) -> OpaqueElement { - self.element.opaque() - } - - fn parent_element(&self) -> Option { - let parent = self.element.parent_element()?; - Some(Self::new(parent, self.snapshot_map)) - } - - fn parent_node_is_shadow_root(&self) -> bool { - self.element.parent_node_is_shadow_root() - } - - fn containing_shadow_host(&self) -> Option { - let host = self.element.containing_shadow_host()?; - Some(Self::new(host, self.snapshot_map)) - } - - fn prev_sibling_element(&self) -> Option { - let sibling = self.element.prev_sibling_element()?; - Some(Self::new(sibling, self.snapshot_map)) - } - - fn next_sibling_element(&self) -> Option { - let sibling = self.element.next_sibling_element()?; - Some(Self::new(sibling, self.snapshot_map)) - } - - fn first_element_child(&self) -> Option { - let child = self.element.first_element_child()?; - Some(Self::new(child, self.snapshot_map)) - } - - #[inline] - fn is_html_element_in_html_document(&self) -> bool { - self.element.is_html_element_in_html_document() - } - - #[inline] - fn is_html_slot_element(&self) -> bool { - self.element.is_html_slot_element() - } - - #[inline] - fn has_local_name( - &self, - local_name: &::BorrowedLocalName, - ) -> bool { - self.element.has_local_name(local_name) - } - - #[inline] - fn has_namespace( - &self, - ns: &::BorrowedNamespaceUrl, - ) -> bool { - self.element.has_namespace(ns) - } - - #[inline] - fn is_same_type(&self, other: &Self) -> bool { - self.element.is_same_type(&other.element) - } - - fn attr_matches( - &self, - ns: &NamespaceConstraint<&Namespace>, - local_name: &LocalName, - operation: &AttrSelectorOperation<&AttrValue>, - ) -> bool { - match self.snapshot() { - Some(snapshot) if snapshot.has_attrs() => { - snapshot.attr_matches(ns, local_name, operation) - }, - _ => self.element.attr_matches(ns, local_name, operation), - } - } - - fn has_id(&self, id: &AtomIdent, case_sensitivity: CaseSensitivity) -> bool { - match self.snapshot() { - Some(snapshot) if snapshot.has_attrs() => snapshot - .id_attr() - .map_or(false, |atom| case_sensitivity.eq_atom(&atom, id)), - _ => self.element.has_id(id, case_sensitivity), - } - } - - fn is_part(&self, name: &AtomIdent) -> bool { - match self.snapshot() { - Some(snapshot) if snapshot.has_attrs() => snapshot.is_part(name), - _ => self.element.is_part(name), - } - } - - fn imported_part(&self, name: &AtomIdent) -> Option { - match self.snapshot() { - Some(snapshot) if snapshot.has_attrs() => snapshot.imported_part(name), - _ => self.element.imported_part(name), - } - } - - fn has_class(&self, name: &AtomIdent, case_sensitivity: CaseSensitivity) -> bool { - match self.snapshot() { - Some(snapshot) if snapshot.has_attrs() => snapshot.has_class(name, case_sensitivity), - _ => self.element.has_class(name, case_sensitivity), - } - } - - fn is_empty(&self) -> bool { - self.element.is_empty() - } - - fn is_root(&self) -> bool { - self.element.is_root() - } - - fn is_pseudo_element(&self) -> bool { - self.element.is_pseudo_element() - } - - fn pseudo_element_originating_element(&self) -> Option { - self.element - .pseudo_element_originating_element() - .map(|e| ElementWrapper::new(e, self.snapshot_map)) - } - - fn assigned_slot(&self) -> Option { - self.element - .assigned_slot() - .map(|e| ElementWrapper::new(e, self.snapshot_map)) - } -} diff --git a/components/style/invalidation/element/invalidation_map.rs b/components/style/invalidation/element/invalidation_map.rs deleted file mode 100644 index 1cfc2fa7c05..00000000000 --- a/components/style/invalidation/element/invalidation_map.rs +++ /dev/null @@ -1,547 +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 for invalidations due to state or attribute changes. - -use crate::context::QuirksMode; -use crate::selector_map::{ - MaybeCaseInsensitiveHashMap, PrecomputedHashMap, SelectorMap, SelectorMapEntry, -}; -use crate::selector_parser::SelectorImpl; -use crate::AllocErr; -use crate::{Atom, LocalName, Namespace, ShrinkIfNeeded}; -use selectors::attr::NamespaceConstraint; -use selectors::parser::{Combinator, Component}; -use selectors::parser::{Selector, SelectorIter}; -use selectors::visitor::{SelectorListKind, SelectorVisitor}; -use smallvec::SmallVec; -use style_traits::dom::{DocumentState, ElementState}; - -/// Mapping between (partial) CompoundSelectors (and the combinator to their -/// right) and the states and attributes they depend on. -/// -/// In general, for all selectors in all applicable stylesheets of the form: -/// -/// |a _ b _ c _ d _ e| -/// -/// Where: -/// * |b| and |d| are simple selectors that depend on state (like :hover) or -/// attributes (like [attr...], .foo, or #foo). -/// * |a|, |c|, and |e| are arbitrary simple selectors that do not depend on -/// state or attributes. -/// -/// We generate a Dependency for both |a _ b:X _| and |a _ b:X _ c _ d:Y _|, -/// even though those selectors may not appear on their own in any stylesheet. -/// This allows us to quickly scan through the dependency sites of all style -/// rules and determine the maximum effect that a given state or attribute -/// change may have on the style of elements in the document. -#[derive(Clone, Debug, MallocSizeOf)] -pub struct Dependency { - /// The dependency selector. - #[cfg_attr( - feature = "gecko", - ignore_malloc_size_of = "CssRules have primary refs, we measure there" - )] - #[cfg_attr(feature = "servo", ignore_malloc_size_of = "Arc")] - pub selector: Selector, - - /// The offset into the selector that we should match on. - pub selector_offset: usize, - - /// The parent dependency for an ancestor selector. For example, consider - /// the following: - /// - /// .foo .bar:where(.baz span) .qux - /// ^ ^ ^ - /// A B C - /// - /// We'd generate: - /// - /// * One dependency for .qux (offset: 0, parent: None) - /// * One dependency for .baz pointing to B with parent being a - /// dependency pointing to C. - /// * One dependency from .bar pointing to C (parent: None) - /// * One dependency from .foo pointing to A (parent: None) - /// - 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 { - /// This dependency may affect the element that changed itself. - Element, - /// This dependency affects the style of the element itself, and also the - /// style of its descendants. - /// - /// TODO(emilio): Each time this feels more of a hack for eager pseudos... - ElementAndDescendants, - /// This dependency may affect descendants down the tree. - Descendants, - /// This dependency may affect siblings to the right of the element that - /// changed. - Siblings, - /// This dependency may affect slotted elements of the element that changed. - SlottedElements, - /// This dependency may affect parts of the element that changed. - Parts, -} - -impl Dependency { - /// Creates a dummy dependency to invalidate the whole selector. - /// - /// This is necessary because document state invalidation wants to - /// invalidate all elements in the document. - /// - /// The offset is such as that Invalidation::new(self) returns a zero - /// offset. That is, it points to a virtual "combinator" outside of the - /// selector, so calling combinator() on such a dependency will panic. - pub fn for_full_selector_invalidation(selector: Selector) -> Self { - Self { - selector_offset: selector.len() + 1, - selector, - parent: None, - } - } - - /// Returns the combinator to the right of the partial selector this - /// dependency represents. - /// - /// TODO(emilio): Consider storing inline if it helps cache locality? - pub fn combinator(&self) -> Option { - if self.selector_offset == 0 { - return None; - } - - Some( - self.selector - .combinator_at_match_order(self.selector_offset - 1), - ) - } - - /// The kind of invalidation that this would generate. - pub fn invalidation_kind(&self) -> DependencyInvalidationKind { - match self.combinator() { - None => DependencyInvalidationKind::Element, - Some(Combinator::Child) | Some(Combinator::Descendant) => { - DependencyInvalidationKind::Descendants - }, - Some(Combinator::LaterSibling) | Some(Combinator::NextSibling) => { - DependencyInvalidationKind::Siblings - }, - // TODO(emilio): We could look at the selector itself to see if it's - // an eager pseudo, and return only Descendants here if not. - Some(Combinator::PseudoElement) => DependencyInvalidationKind::ElementAndDescendants, - Some(Combinator::SlotAssignment) => DependencyInvalidationKind::SlottedElements, - Some(Combinator::Part) => DependencyInvalidationKind::Parts, - } - } -} - -impl SelectorMapEntry for Dependency { - fn selector(&self) -> SelectorIter { - self.selector.iter_from(self.selector_offset) - } -} - -/// The same, but for state selectors, which can track more exactly what state -/// do they track. -#[derive(Clone, Debug, MallocSizeOf)] -pub struct StateDependency { - /// The other dependency fields. - pub dep: Dependency, - /// The state this dependency is affected by. - pub state: ElementState, -} - -impl SelectorMapEntry for StateDependency { - fn selector(&self) -> SelectorIter { - self.dep.selector() - } -} - -/// The same, but for document state selectors. -#[derive(Clone, Debug, MallocSizeOf)] -pub struct DocumentStateDependency { - /// We track `Dependency` even though we don't need to track an offset, - /// since when it changes it changes for the whole document anyway. - #[cfg_attr( - feature = "gecko", - ignore_malloc_size_of = "CssRules have primary refs, we measure there" - )] - #[cfg_attr(feature = "servo", ignore_malloc_size_of = "Arc")] - pub dependency: Dependency, - /// The state this dependency is affected by. - pub state: DocumentState, -} - -/// A map where we store invalidations. -/// -/// This is slightly different to a SelectorMap, in the sense of that the same -/// selector may appear multiple times. -/// -/// In particular, we want to lookup as few things as possible to get the fewer -/// selectors the better, so this looks up by id, class, or looks at the list of -/// state/other attribute affecting selectors. -#[derive(Clone, Debug, MallocSizeOf)] -pub struct InvalidationMap { - /// A map from a given class name to all the selectors with that class - /// selector. - pub class_to_selector: MaybeCaseInsensitiveHashMap>, - /// A map from a given id to all the selectors with that ID in the - /// stylesheets currently applying to the document. - pub id_to_selector: MaybeCaseInsensitiveHashMap>, - /// A map of all the state dependencies. - pub state_affecting_selectors: SelectorMap, - /// A list of document state dependencies in the rules we represent. - pub document_state_selectors: Vec, - /// A map of other attribute affecting selectors. - pub other_attribute_affecting_selectors: - PrecomputedHashMap>, -} - -impl InvalidationMap { - /// Creates an empty `InvalidationMap`. - pub fn new() -> Self { - Self { - class_to_selector: MaybeCaseInsensitiveHashMap::new(), - id_to_selector: MaybeCaseInsensitiveHashMap::new(), - state_affecting_selectors: SelectorMap::new(), - document_state_selectors: Vec::new(), - other_attribute_affecting_selectors: PrecomputedHashMap::default(), - } - } - - /// Returns the number of dependencies stored in the invalidation map. - pub fn len(&self) -> usize { - self.state_affecting_selectors.len() + - self.document_state_selectors.len() + - self.other_attribute_affecting_selectors - .iter() - .fold(0, |accum, (_, ref v)| accum + v.len()) + - self.id_to_selector - .iter() - .fold(0, |accum, (_, ref v)| accum + v.len()) + - self.class_to_selector - .iter() - .fold(0, |accum, (_, ref v)| accum + v.len()) - } - - /// Clears this map, leaving it empty. - pub fn clear(&mut self) { - self.class_to_selector.clear(); - self.id_to_selector.clear(); - self.state_affecting_selectors.clear(); - self.document_state_selectors.clear(); - self.other_attribute_affecting_selectors.clear(); - } - - /// Shrink the capacity of hash maps if needed. - pub fn shrink_if_needed(&mut self) { - self.class_to_selector.shrink_if_needed(); - self.id_to_selector.shrink_if_needed(); - self.state_affecting_selectors.shrink_if_needed(); - self.other_attribute_affecting_selectors.shrink_if_needed(); - } - - /// Adds a selector to this `InvalidationMap`. Returns Err(..) to - /// signify OOM. - pub fn note_selector( - &mut self, - selector: &Selector, - quirks_mode: QuirksMode, - ) -> Result<(), AllocErr> { - debug!("InvalidationMap::note_selector({:?})", selector); - - let mut document_state = DocumentState::empty(); - - { - let mut parent_stack = SmallVec::new(); - let mut alloc_error = None; - let mut collector = SelectorDependencyCollector { - map: self, - document_state: &mut document_state, - selector, - parent_selectors: &mut parent_stack, - quirks_mode, - compound_state: PerCompoundState::new(0), - alloc_error: &mut alloc_error, - }; - - let visit_result = collector.visit_whole_selector(); - debug_assert_eq!(!visit_result, alloc_error.is_some()); - if let Some(alloc_error) = alloc_error { - return Err(alloc_error); - } - } - - if !document_state.is_empty() { - let dep = DocumentStateDependency { - state: document_state, - dependency: Dependency::for_full_selector_invalidation(selector.clone()), - }; - self.document_state_selectors.try_reserve(1)?; - self.document_state_selectors.push(dep); - } - - Ok(()) - } -} - -struct PerCompoundState { - /// The offset at which our compound starts. - offset: usize, - - /// The state this compound selector is affected by. - element_state: ElementState, -} - -impl PerCompoundState { - fn new(offset: usize) -> Self { - Self { - offset, - element_state: ElementState::empty(), - } - } -} - -/// A struct that collects invalidations for a given compound selector. -struct SelectorDependencyCollector<'a> { - map: &'a mut InvalidationMap, - - /// The document this _complex_ selector is affected by. - /// - /// We don't need to track state per compound selector, since it's global - /// state and it changes for everything. - document_state: &'a mut DocumentState, - - /// The current selector and offset we're iterating. - selector: &'a Selector, - - /// The stack of parent selectors that we have, and at which offset of the - /// sequence. - /// - /// This starts empty. It grows when we find nested :is and :where selector - /// lists. - parent_selectors: &'a mut SmallVec<[(Selector, usize); 5]>, - - /// The quirks mode of the document where we're inserting dependencies. - quirks_mode: QuirksMode, - - /// State relevant to a given compound selector. - compound_state: PerCompoundState, - - /// The allocation error, if we OOM. - alloc_error: &'a mut Option, -} - -impl<'a> SelectorDependencyCollector<'a> { - fn visit_whole_selector(&mut self) -> bool { - let iter = self.selector.iter(); - self.visit_whole_selector_from(iter, 0) - } - - fn visit_whole_selector_from( - &mut self, - mut iter: SelectorIter, - mut index: usize, - ) -> bool { - loop { - // Reset the compound state. - self.compound_state = PerCompoundState::new(index); - - // Visit all the simple selectors in this sequence. - for ss in &mut iter { - if !ss.visit(self) { - return false; - } - index += 1; // Account for the simple selector. - } - - if !self.compound_state.element_state.is_empty() { - let dependency = self.dependency(); - let result = self.map.state_affecting_selectors.insert( - StateDependency { - dep: dependency, - state: self.compound_state.element_state, - }, - self.quirks_mode, - ); - if let Err(alloc_error) = result { - *self.alloc_error = Some(alloc_error.into()); - return false; - } - } - - let combinator = iter.next_sequence(); - if combinator.is_none() { - return true; - } - index += 1; // account for the combinator - } - } - - fn add_attr_dependency(&mut self, name: LocalName) -> bool { - let dependency = self.dependency(); - - let map = &mut self.map.other_attribute_affecting_selectors; - if let Err(err) = map.try_reserve(1) { - *self.alloc_error = Some(err.into()); - return false; - } - let vec = map.entry(name).or_default(); - if let Err(err) = vec.try_reserve(1) { - *self.alloc_error = Some(err.into()); - return false; - } - vec.push(dependency); - true - } - - fn dependency(&self) -> Dependency { - let mut parent = None; - - // TODO(emilio): Maybe we should refcount the parent dependencies, or - // cache them or something. - for &(ref selector, ref selector_offset) in self.parent_selectors.iter() { - debug_assert_ne!( - self.compound_state.offset, 0, - "Shouldn't bother creating nested dependencies for the rightmost compound", - ); - let new_parent = Dependency { - selector: selector.clone(), - selector_offset: *selector_offset, - parent, - }; - parent = Some(Box::new(new_parent)); - } - - Dependency { - selector: self.selector.clone(), - selector_offset: self.compound_state.offset, - parent, - } - } -} - -impl<'a> SelectorVisitor for SelectorDependencyCollector<'a> { - type Impl = SelectorImpl; - - fn visit_selector_list( - &mut self, - _list_kind: SelectorListKind, - list: &[Selector], - ) -> bool { - for selector in list { - // Here we cheat a bit: We can visit the rightmost compound with - // the "outer" visitor, and it'd be fine. This reduces the amount of - // state and attribute invalidations, and we need to check the outer - // selector to the left anyway to avoid over-invalidation, so it - // avoids matching it twice uselessly. - let mut iter = selector.iter(); - let mut index = 0; - - for ss in &mut iter { - if !ss.visit(self) { - return false; - } - index += 1; - } - - let combinator = iter.next_sequence(); - if combinator.is_none() { - continue; - } - - index += 1; // account for the combinator. - - self.parent_selectors - .push((self.selector.clone(), self.compound_state.offset)); - let mut nested = SelectorDependencyCollector { - map: &mut *self.map, - document_state: &mut *self.document_state, - selector, - parent_selectors: &mut *self.parent_selectors, - quirks_mode: self.quirks_mode, - compound_state: PerCompoundState::new(index), - alloc_error: &mut *self.alloc_error, - }; - if !nested.visit_whole_selector_from(iter, index) { - return false; - } - self.parent_selectors.pop(); - } - true - } - - fn visit_simple_selector(&mut self, s: &Component) -> bool { - use crate::selector_parser::NonTSPseudoClass; - - match *s { - Component::ID(ref atom) | Component::Class(ref atom) => { - let dependency = self.dependency(); - let map = match *s { - Component::ID(..) => &mut self.map.id_to_selector, - Component::Class(..) => &mut self.map.class_to_selector, - _ => unreachable!(), - }; - let entry = match map.try_entry(atom.0.clone(), self.quirks_mode) { - Ok(entry) => entry, - Err(err) => { - *self.alloc_error = Some(err.into()); - return false; - }, - }; - let vec = entry.or_insert_with(SmallVec::new); - if let Err(err) = vec.try_reserve(1) { - *self.alloc_error = Some(err.into()); - return false; - } - vec.push(dependency); - true - }, - Component::NonTSPseudoClass(ref pc) => { - self.compound_state.element_state |= pc.state_flag(); - *self.document_state |= pc.document_state_flag(); - - let attr_name = match *pc { - #[cfg(feature = "gecko")] - NonTSPseudoClass::MozTableBorderNonzero => local_name!("border"), - #[cfg(feature = "gecko")] - NonTSPseudoClass::MozBrowserFrame => local_name!("mozbrowser"), - #[cfg(feature = "gecko")] - NonTSPseudoClass::MozSelectListBox => { - // This depends on two attributes. - return self.add_attr_dependency(local_name!("multiple")) && - self.add_attr_dependency(local_name!("size")); - }, - NonTSPseudoClass::Lang(..) => local_name!("lang"), - _ => return true, - }; - - self.add_attr_dependency(attr_name) - }, - _ => true, - } - } - - fn visit_attribute_selector( - &mut self, - _: &NamespaceConstraint<&Namespace>, - local_name: &LocalName, - local_name_lower: &LocalName, - ) -> bool { - if !self.add_attr_dependency(local_name.clone()) { - return false; - } - - if local_name != local_name_lower && !self.add_attr_dependency(local_name_lower.clone()) { - return false; - } - - true - } -} diff --git a/components/style/invalidation/element/invalidator.rs b/components/style/invalidation/element/invalidator.rs deleted file mode 100644 index 00f714c5b15..00000000000 --- a/components/style/invalidation/element/invalidator.rs +++ /dev/null @@ -1,1017 +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/. */ - -//! The struct that takes care of encapsulating all the logic on where and how -//! element styles need to be invalidated. - -use crate::context::StackLimitChecker; -use crate::dom::{TElement, TNode, TShadowRoot}; -use crate::invalidation::element::invalidation_map::{Dependency, DependencyInvalidationKind}; -use selectors::matching::matches_compound_selector_from; -use selectors::matching::{CompoundSelectorMatchingResult, MatchingContext}; -use selectors::parser::{Combinator, Component}; -use selectors::OpaqueElement; -use smallvec::SmallVec; -use std::fmt; -use std::fmt::Write; - -/// A trait to abstract the collection of invalidations for a given pass. -pub trait InvalidationProcessor<'a, E> -where - E: TElement, -{ - /// Whether an invalidation that contains only a pseudo-element selector - /// like ::before or ::after triggers invalidation of the element that would - /// originate it. - fn invalidates_on_pseudo_element(&self) -> bool { - false - } - - /// Whether the invalidation processor only cares about light-tree - /// descendants of a given element, that is, doesn't invalidate - /// pseudo-elements, NAC, shadow dom... - fn light_tree_only(&self) -> bool { - false - } - - /// When a dependency from a :where or :is selector matches, it may still be - /// the case that we don't need to invalidate the full style. Consider the - /// case of: - /// - /// div .foo:where(.bar *, .baz) .qux - /// - /// We can get to the `*` part after a .bar class change, but you only need - /// to restyle the element if it also matches .foo. - /// - /// Similarly, you only need to restyle .baz if the whole result of matching - /// the selector changes. - /// - /// This function is called to check the result of matching the "outer" - /// dependency that we generate for the parent of the `:where` selector, - /// that is, in the case above it should match - /// `div .foo:where(.bar *, .baz)`. - /// - /// Returning true unconditionally here is over-optimistic and may - /// over-invalidate. - fn check_outer_dependency(&mut self, dependency: &Dependency, element: E) -> bool; - - /// The matching context that should be used to process invalidations. - fn matching_context(&mut self) -> &mut MatchingContext<'a, E::Impl>; - - /// Collect invalidations for a given element's descendants and siblings. - /// - /// Returns whether the element itself was invalidated. - fn collect_invalidations( - &mut self, - element: E, - self_invalidations: &mut InvalidationVector<'a>, - descendant_invalidations: &mut DescendantInvalidationLists<'a>, - sibling_invalidations: &mut InvalidationVector<'a>, - ) -> bool; - - /// Returns whether the invalidation process should process the descendants - /// of the given element. - fn should_process_descendants(&mut self, element: E) -> bool; - - /// Executes an arbitrary action when the recursion limit is exceded (if - /// any). - fn recursion_limit_exceeded(&mut self, element: E); - - /// Executes an action when `Self` is invalidated. - fn invalidated_self(&mut self, element: E); - - /// Executes an action when `sibling` is invalidated as a sibling of - /// `of`. - fn invalidated_sibling(&mut self, sibling: E, of: E); - - /// Executes an action when any descendant of `Self` is invalidated. - fn invalidated_descendants(&mut self, element: E, child: E); -} - -/// Different invalidation lists for descendants. -#[derive(Debug, Default)] -pub struct DescendantInvalidationLists<'a> { - /// Invalidations for normal DOM children and pseudo-elements. - /// - /// TODO(emilio): Having a list of invalidations just for pseudo-elements - /// may save some work here and there. - pub dom_descendants: InvalidationVector<'a>, - /// Invalidations for slotted children of an element. - pub slotted_descendants: InvalidationVector<'a>, - /// Invalidations for ::part()s of an element. - pub parts: InvalidationVector<'a>, -} - -impl<'a> DescendantInvalidationLists<'a> { - fn is_empty(&self) -> bool { - self.dom_descendants.is_empty() && - self.slotted_descendants.is_empty() && - self.parts.is_empty() - } -} - -/// The struct that takes care of encapsulating all the logic on where and how -/// element styles need to be invalidated. -pub struct TreeStyleInvalidator<'a, 'b, E, P: 'a> -where - 'b: 'a, - E: TElement, - P: InvalidationProcessor<'b, E>, -{ - element: E, - stack_limit_checker: Option<&'a StackLimitChecker>, - processor: &'a mut P, - _marker: ::std::marker::PhantomData<&'b ()>, -} - -/// A vector of invalidations, optimized for small invalidation sets. -pub type InvalidationVector<'a> = SmallVec<[Invalidation<'a>; 10]>; - -/// The kind of descendant invalidation we're processing. -#[derive(Clone, Copy, Debug, Eq, PartialEq)] -enum DescendantInvalidationKind { - /// A DOM descendant invalidation. - Dom, - /// A ::slotted() descendant invalidation. - Slotted, - /// A ::part() descendant invalidation. - Part, -} - -/// The kind of invalidation we're processing. -/// -/// We can use this to avoid pushing invalidations of the same kind to our -/// descendants or siblings. -#[derive(Clone, Copy, Debug, Eq, PartialEq)] -enum InvalidationKind { - Descendant(DescendantInvalidationKind), - Sibling, -} - -/// An `Invalidation` is a complex selector that describes which elements, -/// relative to a current element we are processing, must be restyled. -#[derive(Clone)] -pub struct Invalidation<'a> { - /// The dependency that generated this invalidation. - /// - /// Note that the offset inside the dependency is not really useful after - /// construction. - dependency: &'a Dependency, - /// The right shadow host from where the rule came from, if any. - /// - /// This is needed to ensure that we match the selector with the right - /// state, as whether some selectors like :host and ::part() match depends - /// on it. - scope: Option, - /// The offset of the selector pointing to a compound selector. - /// - /// This order is a "parse order" offset, that is, zero is the leftmost part - /// of the selector written as parsed / serialized. - /// - /// It is initialized from the offset from `dependency`. - offset: usize, - /// Whether the invalidation was already matched by any previous sibling or - /// ancestor. - /// - /// If this is the case, we can avoid pushing invalidations generated by - /// this one if the generated invalidation is effective for all the siblings - /// or descendants after us. - matched_by_any_previous: bool, -} - -impl<'a> Invalidation<'a> { - /// Create a new invalidation for matching a dependency. - pub fn new(dependency: &'a Dependency, scope: Option) -> Self { - debug_assert!( - dependency.selector_offset == dependency.selector.len() + 1 || - dependency.invalidation_kind() != DependencyInvalidationKind::Element, - "No point to this, if the dependency matched the element we should just invalidate it" - ); - Self { - dependency, - scope, - // + 1 to go past the combinator. - offset: dependency.selector.len() + 1 - dependency.selector_offset, - matched_by_any_previous: false, - } - } - - /// Whether this invalidation is effective for the next sibling or - /// descendant after us. - fn effective_for_next(&self) -> bool { - if self.offset == 0 { - return true; - } - - // TODO(emilio): For pseudo-elements this should be mostly false, except - // for the weird pseudos in . - // - // We should be able to do better here! - match self - .dependency - .selector - .combinator_at_parse_order(self.offset - 1) - { - Combinator::Descendant | Combinator::LaterSibling | Combinator::PseudoElement => true, - Combinator::Part | - Combinator::SlotAssignment | - Combinator::NextSibling | - Combinator::Child => false, - } - } - - fn kind(&self) -> InvalidationKind { - if self.offset == 0 { - return InvalidationKind::Descendant(DescendantInvalidationKind::Dom); - } - - match self - .dependency - .selector - .combinator_at_parse_order(self.offset - 1) - { - Combinator::Child | Combinator::Descendant | Combinator::PseudoElement => { - InvalidationKind::Descendant(DescendantInvalidationKind::Dom) - }, - Combinator::Part => InvalidationKind::Descendant(DescendantInvalidationKind::Part), - Combinator::SlotAssignment => { - InvalidationKind::Descendant(DescendantInvalidationKind::Slotted) - }, - Combinator::NextSibling | Combinator::LaterSibling => InvalidationKind::Sibling, - } - } -} - -impl<'a> fmt::Debug for Invalidation<'a> { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - use cssparser::ToCss; - - f.write_str("Invalidation(")?; - for component in self - .dependency - .selector - .iter_raw_parse_order_from(self.offset) - { - if matches!(*component, Component::Combinator(..)) { - break; - } - component.to_css(f)?; - } - f.write_char(')') - } -} - -/// The result of processing a single invalidation for a given element. -struct SingleInvalidationResult { - /// Whether the element itself was invalidated. - invalidated_self: bool, - /// Whether the invalidation matched, either invalidating the element or - /// generating another invalidation. - matched: bool, -} - -/// The result of a whole invalidation process for a given element. -pub struct InvalidationResult { - /// Whether the element itself was invalidated. - invalidated_self: bool, - /// Whether the element's descendants were invalidated. - invalidated_descendants: bool, - /// Whether the element's siblings were invalidated. - invalidated_siblings: bool, -} - -impl InvalidationResult { - /// Create an emtpy result. - pub fn empty() -> Self { - Self { - invalidated_self: false, - invalidated_descendants: false, - invalidated_siblings: false, - } - } - - /// Whether the invalidation has invalidate the element itself. - pub fn has_invalidated_self(&self) -> bool { - self.invalidated_self - } - - /// Whether the invalidation has invalidate desendants. - pub fn has_invalidated_descendants(&self) -> bool { - self.invalidated_descendants - } - - /// Whether the invalidation has invalidate siblings. - pub fn has_invalidated_siblings(&self) -> bool { - self.invalidated_siblings - } -} - -impl<'a, 'b, E, P: 'a> TreeStyleInvalidator<'a, 'b, E, P> -where - 'b: 'a, - E: TElement, - P: InvalidationProcessor<'b, E>, -{ - /// Trivially constructs a new `TreeStyleInvalidator`. - pub fn new( - element: E, - stack_limit_checker: Option<&'a StackLimitChecker>, - processor: &'a mut P, - ) -> Self { - Self { - element, - stack_limit_checker, - processor, - _marker: ::std::marker::PhantomData, - } - } - - /// Perform the invalidation pass. - pub fn invalidate(mut self) -> InvalidationResult { - debug!("StyleTreeInvalidator::invalidate({:?})", self.element); - - let mut self_invalidations = InvalidationVector::new(); - let mut descendant_invalidations = DescendantInvalidationLists::default(); - let mut sibling_invalidations = InvalidationVector::new(); - - let mut invalidated_self = self.processor.collect_invalidations( - self.element, - &mut self_invalidations, - &mut descendant_invalidations, - &mut sibling_invalidations, - ); - - debug!("Collected invalidations (self: {}): ", invalidated_self); - debug!( - " > self: {}, {:?}", - self_invalidations.len(), - self_invalidations - ); - debug!(" > descendants: {:?}", descendant_invalidations); - debug!( - " > siblings: {}, {:?}", - sibling_invalidations.len(), - sibling_invalidations - ); - - let invalidated_self_from_collection = invalidated_self; - - invalidated_self |= self.process_descendant_invalidations( - &self_invalidations, - &mut descendant_invalidations, - &mut sibling_invalidations, - DescendantInvalidationKind::Dom, - ); - - if invalidated_self && !invalidated_self_from_collection { - self.processor.invalidated_self(self.element); - } - - let invalidated_descendants = self.invalidate_descendants(&descendant_invalidations); - let invalidated_siblings = self.invalidate_siblings(&mut sibling_invalidations); - - InvalidationResult { - invalidated_self, - invalidated_descendants, - invalidated_siblings, - } - } - - /// Go through later DOM siblings, invalidating style as needed using the - /// `sibling_invalidations` list. - /// - /// Returns whether any sibling's style or any sibling descendant's style - /// was invalidated. - fn invalidate_siblings(&mut self, sibling_invalidations: &mut InvalidationVector<'b>) -> bool { - if sibling_invalidations.is_empty() { - return false; - } - - let mut current = self.element.next_sibling_element(); - let mut any_invalidated = false; - - while let Some(sibling) = current { - let mut sibling_invalidator = - TreeStyleInvalidator::new(sibling, self.stack_limit_checker, self.processor); - - let mut invalidations_for_descendants = DescendantInvalidationLists::default(); - let invalidated_sibling = sibling_invalidator.process_sibling_invalidations( - &mut invalidations_for_descendants, - sibling_invalidations, - ); - - if invalidated_sibling { - sibling_invalidator - .processor - .invalidated_sibling(sibling, self.element); - } - - any_invalidated |= invalidated_sibling; - - any_invalidated |= - sibling_invalidator.invalidate_descendants(&invalidations_for_descendants); - - if sibling_invalidations.is_empty() { - break; - } - - current = sibling.next_sibling_element(); - } - - any_invalidated - } - - fn invalidate_pseudo_element_or_nac( - &mut self, - child: E, - invalidations: &[Invalidation<'b>], - ) -> bool { - let mut sibling_invalidations = InvalidationVector::new(); - - let result = self.invalidate_child( - child, - invalidations, - &mut sibling_invalidations, - DescendantInvalidationKind::Dom, - ); - - // Roots of NAC subtrees can indeed generate sibling invalidations, but - // they can be just ignored, since they have no siblings. - // - // Note that we can end up testing selectors that wouldn't end up - // matching due to this being NAC, like those coming from document - // rules, but we overinvalidate instead of checking this. - - result - } - - /// Invalidate a child and recurse down invalidating its descendants if - /// needed. - fn invalidate_child( - &mut self, - child: E, - invalidations: &[Invalidation<'b>], - sibling_invalidations: &mut InvalidationVector<'b>, - descendant_invalidation_kind: DescendantInvalidationKind, - ) -> bool { - let mut invalidations_for_descendants = DescendantInvalidationLists::default(); - - let mut invalidated_child = false; - let invalidated_descendants = { - let mut child_invalidator = - TreeStyleInvalidator::new(child, self.stack_limit_checker, self.processor); - - invalidated_child |= child_invalidator.process_sibling_invalidations( - &mut invalidations_for_descendants, - sibling_invalidations, - ); - - invalidated_child |= child_invalidator.process_descendant_invalidations( - invalidations, - &mut invalidations_for_descendants, - sibling_invalidations, - descendant_invalidation_kind, - ); - - if invalidated_child { - child_invalidator.processor.invalidated_self(child); - } - - child_invalidator.invalidate_descendants(&invalidations_for_descendants) - }; - - // The child may not be a flattened tree child of the current element, - // but may be arbitrarily deep. - // - // Since we keep the traversal flags in terms of the flattened tree, - // we need to propagate it as appropriate. - if invalidated_child || invalidated_descendants { - self.processor.invalidated_descendants(self.element, child); - } - - invalidated_child || invalidated_descendants - } - - fn invalidate_nac(&mut self, invalidations: &[Invalidation<'b>]) -> bool { - let mut any_nac_root = false; - - let element = self.element; - element.each_anonymous_content_child(|nac| { - any_nac_root |= self.invalidate_pseudo_element_or_nac(nac, invalidations); - }); - - any_nac_root - } - - // NB: It's important that this operates on DOM children, which is what - // selector-matching operates on. - fn invalidate_dom_descendants_of( - &mut self, - parent: E::ConcreteNode, - invalidations: &[Invalidation<'b>], - ) -> bool { - let mut any_descendant = false; - - let mut sibling_invalidations = InvalidationVector::new(); - for child in parent.dom_children() { - let child = match child.as_element() { - Some(e) => e, - None => continue, - }; - - any_descendant |= self.invalidate_child( - child, - invalidations, - &mut sibling_invalidations, - DescendantInvalidationKind::Dom, - ); - } - - any_descendant - } - - fn invalidate_parts_in_shadow_tree( - &mut self, - shadow: ::ConcreteShadowRoot, - invalidations: &[Invalidation<'b>], - ) -> bool { - debug_assert!(!invalidations.is_empty()); - - let mut any = false; - let mut sibling_invalidations = InvalidationVector::new(); - - for node in shadow.as_node().dom_descendants() { - let element = match node.as_element() { - Some(e) => e, - None => continue, - }; - - if element.has_part_attr() { - any |= self.invalidate_child( - element, - invalidations, - &mut sibling_invalidations, - DescendantInvalidationKind::Part, - ); - debug_assert!( - sibling_invalidations.is_empty(), - "::part() shouldn't have sibling combinators to the right, \ - this makes no sense! {:?}", - sibling_invalidations - ); - } - - if let Some(shadow) = element.shadow_root() { - if element.exports_any_part() { - any |= self.invalidate_parts_in_shadow_tree(shadow, invalidations) - } - } - } - - any - } - - fn invalidate_parts(&mut self, invalidations: &[Invalidation<'b>]) -> bool { - if invalidations.is_empty() { - return false; - } - - let shadow = match self.element.shadow_root() { - Some(s) => s, - None => return false, - }; - - self.invalidate_parts_in_shadow_tree(shadow, invalidations) - } - - fn invalidate_slotted_elements(&mut self, invalidations: &[Invalidation<'b>]) -> bool { - if invalidations.is_empty() { - return false; - } - - let slot = self.element; - self.invalidate_slotted_elements_in_slot(slot, invalidations) - } - - fn invalidate_slotted_elements_in_slot( - &mut self, - slot: E, - invalidations: &[Invalidation<'b>], - ) -> bool { - let mut any = false; - - let mut sibling_invalidations = InvalidationVector::new(); - for node in slot.slotted_nodes() { - let element = match node.as_element() { - Some(e) => e, - None => continue, - }; - - if element.is_html_slot_element() { - any |= self.invalidate_slotted_elements_in_slot(element, invalidations); - } else { - any |= self.invalidate_child( - element, - invalidations, - &mut sibling_invalidations, - DescendantInvalidationKind::Slotted, - ); - } - - debug_assert!( - sibling_invalidations.is_empty(), - "::slotted() shouldn't have sibling combinators to the right, \ - this makes no sense! {:?}", - sibling_invalidations - ); - } - - any - } - - fn invalidate_non_slotted_descendants(&mut self, invalidations: &[Invalidation<'b>]) -> bool { - if invalidations.is_empty() { - return false; - } - - if self.processor.light_tree_only() { - let node = self.element.as_node(); - return self.invalidate_dom_descendants_of(node, invalidations); - } - - let mut any_descendant = false; - - // NOTE(emilio): This is only needed for Shadow DOM to invalidate - // correctly on :host(..) changes. Instead of doing this, we could add - // a third kind of invalidation list that walks shadow root children, - // but it's not clear it's worth it. - // - // Also, it's needed as of right now for document state invalidation, - // where we rely on iterating every element that ends up in the composed - // doc, but we could fix that invalidating per subtree. - if let Some(root) = self.element.shadow_root() { - any_descendant |= self.invalidate_dom_descendants_of(root.as_node(), invalidations); - } - - if let Some(marker) = self.element.marker_pseudo_element() { - any_descendant |= self.invalidate_pseudo_element_or_nac(marker, invalidations); - } - - if let Some(before) = self.element.before_pseudo_element() { - any_descendant |= self.invalidate_pseudo_element_or_nac(before, invalidations); - } - - let node = self.element.as_node(); - any_descendant |= self.invalidate_dom_descendants_of(node, invalidations); - - if let Some(after) = self.element.after_pseudo_element() { - any_descendant |= self.invalidate_pseudo_element_or_nac(after, invalidations); - } - - any_descendant |= self.invalidate_nac(invalidations); - - any_descendant - } - - /// Given the descendant invalidation lists, go through the current - /// element's descendants, and invalidate style on them. - fn invalidate_descendants(&mut self, invalidations: &DescendantInvalidationLists<'b>) -> bool { - if invalidations.is_empty() { - return false; - } - - debug!( - "StyleTreeInvalidator::invalidate_descendants({:?})", - self.element - ); - debug!(" > {:?}", invalidations); - - let should_process = self.processor.should_process_descendants(self.element); - - if !should_process { - return false; - } - - if let Some(checker) = self.stack_limit_checker { - if checker.limit_exceeded() { - self.processor.recursion_limit_exceeded(self.element); - return true; - } - } - - let mut any_descendant = false; - - any_descendant |= self.invalidate_non_slotted_descendants(&invalidations.dom_descendants); - any_descendant |= self.invalidate_slotted_elements(&invalidations.slotted_descendants); - any_descendant |= self.invalidate_parts(&invalidations.parts); - - any_descendant - } - - /// Process the given sibling invalidations coming from our previous - /// sibling. - /// - /// The sibling invalidations are somewhat special because they can be - /// modified on the fly. New invalidations may be added and removed. - /// - /// In particular, all descendants get the same set of invalidations from - /// the parent, but the invalidations from a given sibling depend on the - /// ones we got from the previous one. - /// - /// Returns whether invalidated the current element's style. - fn process_sibling_invalidations( - &mut self, - descendant_invalidations: &mut DescendantInvalidationLists<'b>, - sibling_invalidations: &mut InvalidationVector<'b>, - ) -> bool { - let mut i = 0; - let mut new_sibling_invalidations = InvalidationVector::new(); - let mut invalidated_self = false; - - while i < sibling_invalidations.len() { - let result = self.process_invalidation( - &sibling_invalidations[i], - descendant_invalidations, - &mut new_sibling_invalidations, - InvalidationKind::Sibling, - ); - - invalidated_self |= result.invalidated_self; - sibling_invalidations[i].matched_by_any_previous |= result.matched; - if sibling_invalidations[i].effective_for_next() { - i += 1; - } else { - sibling_invalidations.remove(i); - } - } - - sibling_invalidations.extend(new_sibling_invalidations.drain(..)); - invalidated_self - } - - /// Process a given invalidation list coming from our parent, - /// adding to `descendant_invalidations` and `sibling_invalidations` as - /// needed. - /// - /// Returns whether our style was invalidated as a result. - fn process_descendant_invalidations( - &mut self, - invalidations: &[Invalidation<'b>], - descendant_invalidations: &mut DescendantInvalidationLists<'b>, - sibling_invalidations: &mut InvalidationVector<'b>, - descendant_invalidation_kind: DescendantInvalidationKind, - ) -> bool { - let mut invalidated = false; - - for invalidation in invalidations { - let result = self.process_invalidation( - invalidation, - descendant_invalidations, - sibling_invalidations, - InvalidationKind::Descendant(descendant_invalidation_kind), - ); - - invalidated |= result.invalidated_self; - if invalidation.effective_for_next() { - let mut invalidation = invalidation.clone(); - invalidation.matched_by_any_previous |= result.matched; - debug_assert_eq!( - descendant_invalidation_kind, - DescendantInvalidationKind::Dom, - "Slotted or part invalidations don't propagate." - ); - descendant_invalidations.dom_descendants.push(invalidation); - } - } - - invalidated - } - - /// Processes a given invalidation, potentially invalidating the style of - /// the current element. - /// - /// Returns whether invalidated the style of the element, and whether the - /// invalidation should be effective to subsequent siblings or descendants - /// down in the tree. - fn process_invalidation( - &mut self, - invalidation: &Invalidation<'b>, - descendant_invalidations: &mut DescendantInvalidationLists<'b>, - sibling_invalidations: &mut InvalidationVector<'b>, - invalidation_kind: InvalidationKind, - ) -> SingleInvalidationResult { - debug!( - "TreeStyleInvalidator::process_invalidation({:?}, {:?}, {:?})", - self.element, invalidation, invalidation_kind - ); - - let matching_result = { - let context = self.processor.matching_context(); - context.current_host = invalidation.scope; - - matches_compound_selector_from( - &invalidation.dependency.selector, - invalidation.offset, - context, - &self.element, - ) - }; - - let next_invalidation = match matching_result { - CompoundSelectorMatchingResult::NotMatched => { - return SingleInvalidationResult { - invalidated_self: false, - matched: false, - } - }, - CompoundSelectorMatchingResult::FullyMatched => { - debug!(" > Invalidation matched completely"); - // We matched completely. If we're an inner selector now we need - // to go outside our selector and carry on invalidating. - let mut cur_dependency = invalidation.dependency; - loop { - cur_dependency = match cur_dependency.parent { - None => { - return SingleInvalidationResult { - invalidated_self: true, - matched: true, - } - }, - Some(ref p) => &**p, - }; - - debug!(" > Checking outer dependency {:?}", cur_dependency); - - // The inner selector changed, now check if the full - // previous part of the selector did, before keeping - // checking for descendants. - if !self - .processor - .check_outer_dependency(cur_dependency, self.element) - { - return SingleInvalidationResult { - invalidated_self: false, - matched: false, - }; - } - - if cur_dependency.invalidation_kind() == DependencyInvalidationKind::Element { - continue; - } - - debug!(" > Generating invalidation"); - break Invalidation::new(cur_dependency, invalidation.scope); - } - }, - CompoundSelectorMatchingResult::Matched { - next_combinator_offset, - } => Invalidation { - dependency: invalidation.dependency, - scope: invalidation.scope, - offset: next_combinator_offset + 1, - matched_by_any_previous: false, - }, - }; - - debug_assert_ne!( - next_invalidation.offset, 0, - "Rightmost selectors shouldn't generate more invalidations", - ); - - let mut invalidated_self = false; - let next_combinator = next_invalidation - .dependency - .selector - .combinator_at_parse_order(next_invalidation.offset - 1); - - if matches!(next_combinator, Combinator::PseudoElement) && - self.processor.invalidates_on_pseudo_element() - { - // We need to invalidate the element whenever pseudos change, for - // two reasons: - // - // * Eager pseudo styles are stored as part of the originating - // element's computed style. - // - // * Lazy pseudo-styles might be cached on the originating - // element's pseudo-style cache. - // - // This could be more fine-grained (perhaps with a RESTYLE_PSEUDOS - // hint?). - // - // Note that we'll also restyle the pseudo-element because it would - // match this invalidation. - // - // FIXME: For non-element-backed pseudos this is still not quite - // correct. For example for ::selection even though we invalidate - // the style properly there's nothing that triggers a repaint - // necessarily. Though this matches old Gecko behavior, and the - // ::selection implementation needs to change significantly anyway - // to implement https://github.com/w3c/csswg-drafts/issues/2474 for - // example. - invalidated_self = true; - } - - debug!( - " > Invalidation matched, next: {:?}, ({:?})", - next_invalidation, next_combinator - ); - - let next_invalidation_kind = next_invalidation.kind(); - - // We can skip pushing under some circumstances, and we should - // because otherwise the invalidation list could grow - // exponentially. - // - // * First of all, both invalidations need to be of the same - // kind. This is because of how we propagate them going to - // the right of the tree for sibling invalidations and going - // down the tree for children invalidations. A sibling - // invalidation that ends up generating a children - // invalidation ends up (correctly) in five different lists, - // not in the same list five different times. - // - // * Then, the invalidation needs to be matched by a previous - // ancestor/sibling, in order to know that this invalidation - // has been generated already. - // - // * Finally, the new invalidation needs to be - // `effective_for_next()`, in order for us to know that it is - // still in the list, since we remove the dependencies that - // aren't from the lists for our children / siblings. - // - // To go through an example, let's imagine we are processing a - // dom subtree like: - // - //
- // - // And an invalidation list with a single invalidation like: - // - // [div div div] - // - // When we process the invalidation list for the outer div, we - // match it, and generate a `div div` invalidation, so for the - //
child we have: - // - // [div div div, div div] - // - // With the first of them marked as `matched`. - // - // When we process the
child, we don't match any of - // them, so both invalidations go untouched to our children. - // - // When we process the second
, we match _both_ - // invalidations. - // - // However, when matching the first, we can tell it's been - // matched, and not push the corresponding `div div` - // invalidation, since we know it's necessarily already on the - // list. - // - // Thus, without skipping the push, we'll arrive to the - // innermost
with: - // - // [div div div, div div, div div, div] - // - // While skipping it, we won't arrive here with duplicating - // dependencies: - // - // [div div div, div div, div] - // - let can_skip_pushing = next_invalidation_kind == invalidation_kind && - invalidation.matched_by_any_previous && - next_invalidation.effective_for_next(); - - if can_skip_pushing { - debug!( - " > Can avoid push, since the invalidation had \ - already been matched before" - ); - } else { - match next_invalidation_kind { - InvalidationKind::Descendant(DescendantInvalidationKind::Dom) => { - descendant_invalidations - .dom_descendants - .push(next_invalidation); - }, - InvalidationKind::Descendant(DescendantInvalidationKind::Part) => { - descendant_invalidations.parts.push(next_invalidation); - }, - InvalidationKind::Descendant(DescendantInvalidationKind::Slotted) => { - descendant_invalidations - .slotted_descendants - .push(next_invalidation); - }, - InvalidationKind::Sibling => { - sibling_invalidations.push(next_invalidation); - }, - } - } - - SingleInvalidationResult { - invalidated_self, - matched: true, - } - } -} diff --git a/components/style/invalidation/element/mod.rs b/components/style/invalidation/element/mod.rs deleted file mode 100644 index 1f19cc54f59..00000000000 --- a/components/style/invalidation/element/mod.rs +++ /dev/null @@ -1,12 +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/. */ - -//! Invalidation of element styles due to attribute or style changes. - -pub mod document_state; -pub mod element_wrapper; -pub mod invalidation_map; -pub mod invalidator; -pub mod restyle_hints; -pub mod state_and_attributes; diff --git a/components/style/invalidation/element/restyle_hints.rs b/components/style/invalidation/element/restyle_hints.rs deleted file mode 100644 index ffadecd7aa2..00000000000 --- a/components/style/invalidation/element/restyle_hints.rs +++ /dev/null @@ -1,190 +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/. */ - -//! Restyle hints: an optimization to avoid unnecessarily matching selectors. - -use crate::traversal_flags::TraversalFlags; - -bitflags! { - /// The kind of restyle we need to do for a given element. - #[repr(C)] - pub struct RestyleHint: u16 { - /// Do a selector match of the element. - const RESTYLE_SELF = 1 << 0; - - /// Do a selector match of the element's pseudo-elements. Always to be combined with - /// RESTYLE_SELF. - const RESTYLE_PSEUDOS = 1 << 1; - - /// Do a selector match if the element is a pseudo-element. - const RESTYLE_SELF_IF_PSEUDO = 1 << 2; - - /// Do a selector match of the element's descendants. - const RESTYLE_DESCENDANTS = 1 << 3; - - /// Recascade the current element. - const RECASCADE_SELF = 1 << 4; - - /// Recascade the current element if it inherits any reset style. - const RECASCADE_SELF_IF_INHERIT_RESET_STYLE = 1 << 5; - - /// Recascade all descendant elements. - const RECASCADE_DESCENDANTS = 1 << 6; - - /// Replace the style data coming from CSS transitions without updating - /// any other style data. This hint is only processed in animation-only - /// traversal which is prior to normal traversal. - const RESTYLE_CSS_TRANSITIONS = 1 << 7; - - /// Replace the style data coming from CSS animations without updating - /// any other style data. This hint is only processed in animation-only - /// traversal which is prior to normal traversal. - const RESTYLE_CSS_ANIMATIONS = 1 << 8; - - /// Don't re-run selector-matching on the element, only the style - /// attribute has changed, and this change didn't have any other - /// dependencies. - const RESTYLE_STYLE_ATTRIBUTE = 1 << 9; - - /// Replace the style data coming from SMIL animations without updating - /// any other style data. This hint is only processed in animation-only - /// traversal which is prior to normal traversal. - const RESTYLE_SMIL = 1 << 10; - } -} - -impl RestyleHint { - /// Creates a new `RestyleHint` indicating that the current element and all - /// its descendants must be fully restyled. - pub fn restyle_subtree() -> Self { - RestyleHint::RESTYLE_SELF | RestyleHint::RESTYLE_DESCENDANTS - } - - /// Creates a new `RestyleHint` indicating that the current element and all - /// its descendants must be recascaded. - pub fn recascade_subtree() -> Self { - RestyleHint::RECASCADE_SELF | RestyleHint::RECASCADE_DESCENDANTS - } - - /// Returns whether this hint invalidates the element and all its - /// descendants. - pub fn contains_subtree(&self) -> bool { - self.contains(Self::restyle_subtree()) - } - - /// Returns whether we'll recascade all of the descendants. - pub fn will_recascade_subtree(&self) -> bool { - self.contains_subtree() || self.contains(Self::recascade_subtree()) - } - - /// Returns whether we need to restyle this element. - pub fn has_non_animation_invalidations(&self) -> bool { - !(*self & !Self::for_animations()).is_empty() - } - - /// Propagates this restyle hint to a child element. - pub fn propagate(&mut self, traversal_flags: &TraversalFlags) -> Self { - use std::mem; - - // In the middle of an animation only restyle, we don't need to - // propagate any restyle hints, and we need to remove ourselves. - if traversal_flags.for_animation_only() { - self.remove_animation_hints(); - return Self::empty(); - } - - debug_assert!( - !self.has_animation_hint(), - "There should not be any animation restyle hints \ - during normal traversal" - ); - - // Else we should clear ourselves, and return the propagated hint. - mem::replace(self, Self::empty()).propagate_for_non_animation_restyle() - } - - /// Returns a new `RestyleHint` appropriate for children of the current element. - fn propagate_for_non_animation_restyle(&self) -> Self { - if self.contains(RestyleHint::RESTYLE_DESCENDANTS) { - return Self::restyle_subtree(); - } - let mut result = Self::empty(); - if self.contains(RestyleHint::RESTYLE_PSEUDOS) { - result |= Self::RESTYLE_SELF_IF_PSEUDO; - } - if self.contains(RestyleHint::RECASCADE_DESCENDANTS) { - result |= Self::recascade_subtree(); - } - result - } - - /// Returns a hint that contains all the replacement hints. - pub fn replacements() -> Self { - RestyleHint::RESTYLE_STYLE_ATTRIBUTE | Self::for_animations() - } - - /// The replacements for the animation cascade levels. - #[inline] - pub fn for_animations() -> Self { - RestyleHint::RESTYLE_SMIL | - RestyleHint::RESTYLE_CSS_ANIMATIONS | - RestyleHint::RESTYLE_CSS_TRANSITIONS - } - - /// Returns whether the hint specifies that an animation cascade level must - /// be replaced. - #[inline] - pub fn has_animation_hint(&self) -> bool { - self.intersects(Self::for_animations()) - } - - /// Returns whether the hint specifies that an animation cascade level must - /// be replaced. - #[inline] - pub fn has_animation_hint_or_recascade(&self) -> bool { - self.intersects( - Self::for_animations() | - Self::RECASCADE_SELF | - Self::RECASCADE_SELF_IF_INHERIT_RESET_STYLE, - ) - } - - /// Returns whether the hint specifies some restyle work other than an - /// animation cascade level replacement. - #[inline] - pub fn has_non_animation_hint(&self) -> bool { - !(*self & !Self::for_animations()).is_empty() - } - - /// Returns whether the hint specifies that some cascade levels must be - /// replaced. - #[inline] - pub fn has_replacements(&self) -> bool { - self.intersects(Self::replacements()) - } - - /// Removes all of the animation-related hints. - #[inline] - pub fn remove_animation_hints(&mut self) { - self.remove(Self::for_animations()); - - // While RECASCADE_SELF is not animation-specific, we only ever add and process it during - // traversal. If we are here, removing animation hints, then we are in an animation-only - // traversal, and we know that any RECASCADE_SELF flag must have been set due to changes in - // inherited values after restyling for animations, and thus we want to remove it so that - // we don't later try to restyle the element during a normal restyle. - // (We could have separate RECASCADE_SELF_NORMAL and RECASCADE_SELF_ANIMATIONS flags to - // make it clear, but this isn't currently necessary.) - self.remove(Self::RECASCADE_SELF | Self::RECASCADE_SELF_IF_INHERIT_RESET_STYLE); - } -} - -impl Default for RestyleHint { - fn default() -> Self { - Self::empty() - } -} - -#[cfg(feature = "servo")] -malloc_size_of_is_0!(RestyleHint); diff --git a/components/style/invalidation/element/state_and_attributes.rs b/components/style/invalidation/element/state_and_attributes.rs deleted file mode 100644 index bd69f35c66c..00000000000 --- a/components/style/invalidation/element/state_and_attributes.rs +++ /dev/null @@ -1,552 +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/. */ - -//! An invalidation processor for style changes due to state and attribute -//! changes. - -use crate::context::SharedStyleContext; -use crate::data::ElementData; -use crate::dom::{TElement, TNode}; -use crate::invalidation::element::element_wrapper::{ElementSnapshot, ElementWrapper}; -use crate::invalidation::element::invalidation_map::*; -use crate::invalidation::element::invalidator::{DescendantInvalidationLists, InvalidationVector}; -use crate::invalidation::element::invalidator::{Invalidation, InvalidationProcessor}; -use crate::invalidation::element::restyle_hints::RestyleHint; -use crate::selector_map::SelectorMap; -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, NeedsSelectorFlags, VisitedHandlingMode, -}; -use selectors::NthIndexCache; -use smallvec::SmallVec; -use style_traits::dom::ElementState; - -/// The collector implementation. -struct Collector<'a, 'b: 'a, 'selectors: 'a, E> -where - E: TElement, -{ - element: E, - wrapper: ElementWrapper<'b, E>, - snapshot: &'a Snapshot, - matching_context: &'a mut MatchingContext<'b, E::Impl>, - lookup_element: E, - removed_id: Option<&'a WeakAtom>, - added_id: Option<&'a WeakAtom>, - classes_removed: &'a SmallVec<[Atom; 8]>, - classes_added: &'a SmallVec<[Atom; 8]>, - state_changes: ElementState, - descendant_invalidations: &'a mut DescendantInvalidationLists<'selectors>, - sibling_invalidations: &'a mut InvalidationVector<'selectors>, - invalidates_self: bool, -} - -/// An invalidation processor for style changes due to state and attribute -/// changes. -pub struct StateAndAttrInvalidationProcessor<'a, 'b: 'a, E: TElement> { - shared_context: &'a SharedStyleContext<'b>, - element: E, - data: &'a mut ElementData, - matching_context: MatchingContext<'a, E::Impl>, -} - -impl<'a, 'b: 'a, E: TElement + 'b> StateAndAttrInvalidationProcessor<'a, 'b, E> { - /// Creates a new StateAndAttrInvalidationProcessor. - pub fn new( - shared_context: &'a SharedStyleContext<'b>, - element: E, - data: &'a mut ElementData, - nth_index_cache: &'a mut NthIndexCache, - ) -> Self { - let matching_context = MatchingContext::new_for_visited( - MatchingMode::Normal, - None, - nth_index_cache, - VisitedHandlingMode::AllLinksVisitedAndUnvisited, - shared_context.quirks_mode(), - NeedsSelectorFlags::No, - ); - - Self { - shared_context, - element, - data, - matching_context, - } - } -} - -/// Checks a dependency against a given element and wrapper, to see if something -/// changed. -pub fn check_dependency( - dependency: &Dependency, - element: &E, - wrapper: &W, - context: &mut MatchingContext<'_, E::Impl>, -) -> bool -where - E: TElement, - W: selectors::Element, -{ - let matches_now = matches_selector( - &dependency.selector, - dependency.selector_offset, - None, - element, - context, - ); - - let matched_then = matches_selector( - &dependency.selector, - dependency.selector_offset, - None, - wrapper, - context, - ); - - matched_then != matches_now -} - -/// Whether we should process the descendants of a given element for style -/// invalidation. -pub fn should_process_descendants(data: &ElementData) -> bool { - !data.styles.is_display_none() && !data.hint.contains(RestyleHint::RESTYLE_DESCENDANTS) -} - -/// Propagates the bits after invalidating a descendant child. -pub fn propagate_dirty_bit_up_to(ancestor: E, child: E) -where - E: TElement, -{ - // The child may not be a flattened tree child of the current element, - // but may be arbitrarily deep. - // - // Since we keep the traversal flags in terms of the flattened tree, - // we need to propagate it as appropriate. - let mut current = child.traversal_parent(); - while let Some(parent) = current.take() { - unsafe { parent.set_dirty_descendants() }; - current = parent.traversal_parent(); - - if parent == ancestor { - return; - } - } - debug_assert!( - false, - "Should've found {:?} as an ancestor of {:?}", - ancestor, child - ); -} - -/// Propagates the bits after invalidating a descendant child, if needed. -pub fn invalidated_descendants(element: E, child: E) -where - E: TElement, -{ - if !child.has_data() { - return; - } - propagate_dirty_bit_up_to(element, child) -} - -/// Sets the appropriate restyle hint after invalidating the style of a given -/// element. -pub fn invalidated_self(element: E) -> bool -where - E: TElement, -{ - let mut data = match element.mutate_data() { - Some(data) => data, - None => return false, - }; - data.hint.insert(RestyleHint::RESTYLE_SELF); - true -} - -/// Sets the appropriate hint after invalidating the style of a sibling. -pub fn invalidated_sibling(element: E, of: E) -where - E: TElement, -{ - debug_assert_eq!( - element.as_node().parent_node(), - of.as_node().parent_node(), - "Should be siblings" - ); - if !invalidated_self(element) { - return; - } - if element.traversal_parent() != of.traversal_parent() { - let parent = element.as_node().parent_element_or_host(); - debug_assert!( - parent.is_some(), - "How can we have siblings without parent nodes?" - ); - if let Some(e) = parent { - propagate_dirty_bit_up_to(e, element) - } - } -} - -impl<'a, 'b: 'a, E: 'a> InvalidationProcessor<'a, E> - for StateAndAttrInvalidationProcessor<'a, 'b, E> -where - E: TElement, -{ - /// We need to invalidate style on pseudo-elements, in order to process - /// changes that could otherwise end up in ::before or ::after content being - /// generated, and invalidate lazy pseudo caches. - fn invalidates_on_pseudo_element(&self) -> bool { - true - } - - fn check_outer_dependency(&mut self, dependency: &Dependency, element: E) -> bool { - // We cannot assert about `element` having a snapshot here (in fact it - // most likely won't), because it may be an arbitrary descendant or - // later-sibling of the element we started invalidating with. - let wrapper = ElementWrapper::new(element, &*self.shared_context.snapshot_map); - check_dependency(dependency, &element, &wrapper, &mut self.matching_context) - } - - fn matching_context(&mut self) -> &mut MatchingContext<'a, E::Impl> { - &mut self.matching_context - } - - fn collect_invalidations( - &mut self, - element: E, - _self_invalidations: &mut InvalidationVector<'a>, - descendant_invalidations: &mut DescendantInvalidationLists<'a>, - sibling_invalidations: &mut InvalidationVector<'a>, - ) -> bool { - debug_assert_eq!(element, self.element); - debug_assert!(element.has_snapshot(), "Why bothering?"); - - let wrapper = ElementWrapper::new(element, &*self.shared_context.snapshot_map); - - let state_changes = wrapper.state_changes(); - let snapshot = wrapper.snapshot().expect("has_snapshot lied"); - - if !snapshot.has_attrs() && state_changes.is_empty() { - return false; - } - - let mut classes_removed = SmallVec::<[Atom; 8]>::new(); - let mut classes_added = SmallVec::<[Atom; 8]>::new(); - if snapshot.class_changed() { - // TODO(emilio): Do this more efficiently! - snapshot.each_class(|c| { - if !element.has_class(c, CaseSensitivity::CaseSensitive) { - classes_removed.push(c.0.clone()) - } - }); - - element.each_class(|c| { - if !snapshot.has_class(c, CaseSensitivity::CaseSensitive) { - classes_added.push(c.0.clone()) - } - }) - } - - let mut id_removed = None; - let mut id_added = None; - if snapshot.id_changed() { - let old_id = snapshot.id_attr(); - let current_id = element.id(); - - if old_id != current_id { - id_removed = old_id; - id_added = current_id; - } - } - - if log_enabled!(::log::Level::Debug) { - debug!("Collecting changes for: {:?}", element); - if !state_changes.is_empty() { - debug!(" > state: {:?}", state_changes); - } - if snapshot.id_changed() { - debug!(" > id changed: +{:?} -{:?}", id_added, id_removed); - } - if snapshot.class_changed() { - debug!( - " > class changed: +{:?} -{:?}", - classes_added, classes_removed - ); - } - let mut attributes_changed = false; - snapshot.each_attr_changed(|_| { - attributes_changed = true; - }); - if attributes_changed { - debug!( - " > attributes changed, old: {}", - snapshot.debug_list_attributes() - ) - } - } - - let lookup_element = if element.implemented_pseudo_element().is_some() { - element.pseudo_element_originating_element().unwrap() - } else { - element - }; - - let mut shadow_rule_datas = SmallVec::<[_; 3]>::new(); - let matches_document_author_rules = - element.each_applicable_non_document_style_rule_data(|data, host| { - shadow_rule_datas.push((data, host.opaque())) - }); - - let invalidated_self = { - let mut collector = Collector { - wrapper, - lookup_element, - state_changes, - element, - snapshot: &snapshot, - matching_context: &mut self.matching_context, - removed_id: id_removed, - added_id: id_added, - classes_removed: &classes_removed, - classes_added: &classes_added, - descendant_invalidations, - sibling_invalidations, - invalidates_self: false, - }; - - let document_origins = if !matches_document_author_rules { - OriginSet::ORIGIN_USER_AGENT | OriginSet::ORIGIN_USER - } else { - OriginSet::all() - }; - - for (cascade_data, origin) in self.shared_context.stylist.iter_origins() { - if document_origins.contains(origin.into()) { - collector - .collect_dependencies_in_invalidation_map(cascade_data.invalidation_map()); - } - } - - for &(ref data, ref host) in &shadow_rule_datas { - collector.matching_context.current_host = Some(host.clone()); - collector.collect_dependencies_in_invalidation_map(data.invalidation_map()); - } - - collector.invalidates_self - }; - - // If we generated a ton of descendant invalidations, it's probably not - // worth to go ahead and try to process them. - // - // Just restyle the descendants directly. - // - // This number is completely made-up, but the page that made us add this - // code generated 1960+ invalidations (bug 1420741). - // - // We don't look at slotted_descendants because those don't propagate - // down more than one level anyway. - if descendant_invalidations.dom_descendants.len() > 150 { - self.data.hint.insert(RestyleHint::RESTYLE_DESCENDANTS); - } - - if invalidated_self { - self.data.hint.insert(RestyleHint::RESTYLE_SELF); - } - - invalidated_self - } - - fn should_process_descendants(&mut self, element: E) -> bool { - if element == self.element { - return should_process_descendants(&self.data); - } - - match element.borrow_data() { - Some(d) => should_process_descendants(&d), - None => return false, - } - } - - fn recursion_limit_exceeded(&mut self, element: E) { - if element == self.element { - self.data.hint.insert(RestyleHint::RESTYLE_DESCENDANTS); - return; - } - - if let Some(mut data) = element.mutate_data() { - data.hint.insert(RestyleHint::RESTYLE_DESCENDANTS); - } - } - - fn invalidated_descendants(&mut self, element: E, child: E) { - invalidated_descendants(element, child) - } - - fn invalidated_self(&mut self, element: E) { - debug_assert_ne!(element, self.element); - invalidated_self(element); - } - - fn invalidated_sibling(&mut self, element: E, of: E) { - debug_assert_ne!(element, self.element); - invalidated_sibling(element, of); - } -} - -impl<'a, 'b, 'selectors, E> Collector<'a, 'b, 'selectors, E> -where - E: TElement, - 'selectors: 'a, -{ - fn collect_dependencies_in_invalidation_map(&mut self, map: &'selectors InvalidationMap) { - let quirks_mode = self.matching_context.quirks_mode(); - let removed_id = self.removed_id; - if let Some(ref id) = removed_id { - if let Some(deps) = map.id_to_selector.get(id, quirks_mode) { - for dep in deps { - self.scan_dependency(dep); - } - } - } - - let added_id = self.added_id; - if let Some(ref id) = added_id { - if let Some(deps) = map.id_to_selector.get(id, quirks_mode) { - for dep in deps { - self.scan_dependency(dep); - } - } - } - - for class in self.classes_added.iter().chain(self.classes_removed.iter()) { - if let Some(deps) = map.class_to_selector.get(class, quirks_mode) { - for dep in deps { - self.scan_dependency(dep); - } - } - } - - self.snapshot.each_attr_changed(|attribute| { - if let Some(deps) = map.other_attribute_affecting_selectors.get(attribute) { - for dep in deps { - self.scan_dependency(dep); - } - } - }); - - self.collect_state_dependencies(&map.state_affecting_selectors) - } - - fn collect_state_dependencies(&mut self, map: &'selectors SelectorMap) { - if self.state_changes.is_empty() { - return; - } - map.lookup_with_additional( - self.lookup_element, - self.matching_context.quirks_mode(), - self.removed_id, - self.classes_removed, - self.state_changes, - |dependency| { - if !dependency.state.intersects(self.state_changes) { - return true; - } - self.scan_dependency(&dependency.dep); - true - }, - ); - } - - /// Check whether a dependency should be taken into account. - #[inline] - fn check_dependency(&mut self, dependency: &Dependency) -> bool { - check_dependency( - dependency, - &self.element, - &self.wrapper, - &mut self.matching_context, - ) - } - - fn scan_dependency(&mut self, dependency: &'selectors Dependency) { - debug!( - "TreeStyleInvalidator::scan_dependency({:?}, {:?})", - self.element, dependency - ); - - if !self.dependency_may_be_relevant(dependency) { - return; - } - - if self.check_dependency(dependency) { - return self.note_dependency(dependency); - } - } - - fn note_dependency(&mut self, dependency: &'selectors Dependency) { - debug_assert!(self.dependency_may_be_relevant(dependency)); - - let invalidation_kind = dependency.invalidation_kind(); - if matches!(invalidation_kind, DependencyInvalidationKind::Element) { - if let Some(ref parent) = dependency.parent { - // We know something changed in the inner selector, go outwards - // now. - self.scan_dependency(parent); - } else { - self.invalidates_self = true; - } - return; - } - - debug_assert_ne!(dependency.selector_offset, 0); - debug_assert_ne!(dependency.selector_offset, dependency.selector.len()); - - let invalidation = - Invalidation::new(&dependency, self.matching_context.current_host.clone()); - - match invalidation_kind { - DependencyInvalidationKind::Element => unreachable!(), - DependencyInvalidationKind::ElementAndDescendants => { - self.invalidates_self = true; - self.descendant_invalidations - .dom_descendants - .push(invalidation); - }, - DependencyInvalidationKind::Descendants => { - self.descendant_invalidations - .dom_descendants - .push(invalidation); - }, - DependencyInvalidationKind::Siblings => { - self.sibling_invalidations.push(invalidation); - }, - DependencyInvalidationKind::Parts => { - self.descendant_invalidations.parts.push(invalidation); - }, - DependencyInvalidationKind::SlottedElements => { - self.descendant_invalidations - .slotted_descendants - .push(invalidation); - }, - } - } - - /// Returns whether `dependency` may cause us to invalidate the style of - /// more elements than what we've already invalidated. - fn dependency_may_be_relevant(&self, dependency: &Dependency) -> bool { - match dependency.invalidation_kind() { - DependencyInvalidationKind::Element => !self.invalidates_self, - DependencyInvalidationKind::SlottedElements => self.element.is_html_slot_element(), - DependencyInvalidationKind::Parts => self.element.shadow_root().is_some(), - DependencyInvalidationKind::ElementAndDescendants | - DependencyInvalidationKind::Siblings | - DependencyInvalidationKind::Descendants => true, - } - } -} diff --git a/components/style/invalidation/media_queries.rs b/components/style/invalidation/media_queries.rs deleted file mode 100644 index 6928b29d3d9..00000000000 --- a/components/style/invalidation/media_queries.rs +++ /dev/null @@ -1,130 +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 related to the invalidation of media-query-affected rules. - -use crate::context::QuirksMode; -use crate::media_queries::Device; -use crate::shared_lock::SharedRwLockReadGuard; -use crate::stylesheets::{DocumentRule, ImportRule, MediaRule}; -use crate::stylesheets::{NestedRuleIterationCondition, StylesheetContents, SupportsRule}; -use fxhash::FxHashSet; - -/// A key for a given media query result. -/// -/// NOTE: It happens to be the case that all the media lists we care about -/// happen to have a stable address, so we can just use an opaque pointer to -/// represent them. -/// -/// Also, note that right now when a rule or stylesheet is removed, we do a full -/// style flush, so there's no need to worry about other item created with the -/// same pointer address. -/// -/// If this changes, though, we may need to remove the item from the cache if -/// present before it goes away. -#[derive(Clone, Copy, Debug, Eq, Hash, MallocSizeOf, PartialEq)] -pub struct MediaListKey(usize); - -impl MediaListKey { - /// Create a MediaListKey from a raw usize. - pub fn from_raw(k: usize) -> Self { - MediaListKey(k) - } -} - -/// A trait to get a given `MediaListKey` for a given item that can hold a -/// `MediaList`. -pub trait ToMediaListKey: Sized { - /// Get a `MediaListKey` for this item. This key needs to uniquely identify - /// the item. - fn to_media_list_key(&self) -> MediaListKey { - MediaListKey(self as *const Self as usize) - } -} - -impl ToMediaListKey for StylesheetContents {} -impl ToMediaListKey for ImportRule {} -impl ToMediaListKey for MediaRule {} - -/// A struct that holds the result of a media query evaluation pass for the -/// media queries that evaluated successfully. -#[derive(Clone, Debug, MallocSizeOf, PartialEq)] -pub struct EffectiveMediaQueryResults { - /// The set of media lists that matched last time. - set: FxHashSet, -} - -impl EffectiveMediaQueryResults { - /// Trivially constructs an empty `EffectiveMediaQueryResults`. - pub fn new() -> Self { - Self { - set: FxHashSet::default(), - } - } - - /// Resets the results, using an empty key. - pub fn clear(&mut self) { - self.set.clear() - } - - /// Returns whether a given item was known to be effective when the results - /// were cached. - pub fn was_effective(&self, item: &T) -> bool - where - T: ToMediaListKey, - { - self.set.contains(&item.to_media_list_key()) - } - - /// Notices that an effective item has been seen, and caches it as matching. - pub fn saw_effective(&mut self, item: &T) - where - T: ToMediaListKey, - { - // NOTE(emilio): We can't assert that we don't cache the same item twice - // because of stylesheet reusing... shrug. - self.set.insert(item.to_media_list_key()); - } -} - -/// A filter that filters over effective rules, but allowing all potentially -/// effective `@media` rules. -pub struct PotentiallyEffectiveMediaRules; - -impl NestedRuleIterationCondition for PotentiallyEffectiveMediaRules { - fn process_import( - _: &SharedRwLockReadGuard, - _: &Device, - _: QuirksMode, - _: &ImportRule, - ) -> bool { - true - } - - fn process_media(_: &SharedRwLockReadGuard, _: &Device, _: QuirksMode, _: &MediaRule) -> bool { - true - } - - /// Whether we should process the nested rules in a given `@-moz-document` rule. - fn process_document( - guard: &SharedRwLockReadGuard, - device: &Device, - quirks_mode: QuirksMode, - rule: &DocumentRule, - ) -> bool { - use crate::stylesheets::EffectiveRules; - EffectiveRules::process_document(guard, device, quirks_mode, rule) - } - - /// Whether we should process the nested rules in a given `@supports` rule. - fn process_supports( - guard: &SharedRwLockReadGuard, - device: &Device, - quirks_mode: QuirksMode, - rule: &SupportsRule, - ) -> bool { - use crate::stylesheets::EffectiveRules; - EffectiveRules::process_supports(guard, device, quirks_mode, rule) - } -} diff --git a/components/style/invalidation/mod.rs b/components/style/invalidation/mod.rs deleted file mode 100644 index 12b0d06853b..00000000000 --- a/components/style/invalidation/mod.rs +++ /dev/null @@ -1,10 +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/. */ - -//! Different bits of code related to invalidating style. - -pub mod element; -pub mod media_queries; -pub mod stylesheets; -pub mod viewport_units; diff --git a/components/style/invalidation/stylesheets.rs b/components/style/invalidation/stylesheets.rs deleted file mode 100644 index 6ae67452dbd..00000000000 --- a/components/style/invalidation/stylesheets.rs +++ /dev/null @@ -1,655 +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 collection of invalidations due to changes in which stylesheets affect a -//! document. - -#![deny(unsafe_code)] - -use crate::context::QuirksMode; -use crate::dom::{TDocument, TElement, TNode}; -use crate::invalidation::element::element_wrapper::{ElementSnapshot, ElementWrapper}; -use crate::invalidation::element::restyle_hints::RestyleHint; -use crate::media_queries::Device; -use crate::selector_map::{MaybeCaseInsensitiveHashMap, PrecomputedHashMap}; -use crate::selector_parser::{SelectorImpl, Snapshot, SnapshotMap}; -use crate::shared_lock::SharedRwLockReadGuard; -use crate::stylesheets::{CssRule, StylesheetInDocument}; -use crate::stylesheets::{EffectiveRules, EffectiveRulesIterator}; -use crate::values::AtomIdent; -use crate::LocalName as SelectorLocalName; -use crate::{Atom, ShrinkIfNeeded}; -use selectors::parser::{Component, LocalName, Selector}; - -/// The kind of change that happened for a given rule. -#[repr(u32)] -#[derive(Clone, Copy, Debug, Eq, Hash, MallocSizeOf, PartialEq)] -pub enum RuleChangeKind { - /// The rule was inserted. - Insertion, - /// The rule was removed. - Removal, - /// Some change in the rule which we don't know about, and could have made - /// the rule change in any way. - Generic, - /// A change in the declarations of a style rule. - StyleRuleDeclarations, -} - -/// A style sheet invalidation represents a kind of element or subtree that may -/// need to be restyled. Whether it represents a whole subtree or just a single -/// element is determined by the given InvalidationKind in -/// StylesheetInvalidationSet's maps. -#[derive(Debug, Eq, Hash, MallocSizeOf, PartialEq)] -enum Invalidation { - /// An element with a given id. - ID(AtomIdent), - /// An element with a given class name. - Class(AtomIdent), - /// An element with a given local name. - LocalName { - name: SelectorLocalName, - lower_name: SelectorLocalName, - }, -} - -impl Invalidation { - fn is_id(&self) -> bool { - matches!(*self, Invalidation::ID(..)) - } - - fn is_id_or_class(&self) -> bool { - matches!(*self, Invalidation::ID(..) | Invalidation::Class(..)) - } -} - -/// Whether we should invalidate just the element, or the whole subtree within -/// it. -#[derive(Clone, Copy, Debug, Eq, MallocSizeOf, Ord, PartialEq, PartialOrd)] -enum InvalidationKind { - None = 0, - Element, - Scope, -} - -impl std::ops::BitOrAssign for InvalidationKind { - #[inline] - fn bitor_assign(&mut self, other: Self) { - *self = std::cmp::max(*self, other); - } -} - -impl InvalidationKind { - #[inline] - fn is_scope(self) -> bool { - matches!(self, Self::Scope) - } - - #[inline] - fn add(&mut self, other: Option<&InvalidationKind>) { - if let Some(other) = other { - *self |= *other; - } - } -} - -/// A set of invalidations due to stylesheet additions. -/// -/// TODO(emilio): We might be able to do the same analysis for media query -/// changes too (or even selector changes?). -#[derive(Debug, Default, MallocSizeOf)] -pub struct StylesheetInvalidationSet { - classes: MaybeCaseInsensitiveHashMap, - ids: MaybeCaseInsensitiveHashMap, - local_names: PrecomputedHashMap, - fully_invalid: bool, -} - -impl StylesheetInvalidationSet { - /// Create an empty `StylesheetInvalidationSet`. - pub fn new() -> Self { - Default::default() - } - - /// Mark the DOM tree styles' as fully invalid. - pub fn invalidate_fully(&mut self) { - debug!("StylesheetInvalidationSet::invalidate_fully"); - self.clear(); - self.fully_invalid = true; - } - - fn shrink_if_needed(&mut self) { - if self.fully_invalid { - return; - } - self.classes.shrink_if_needed(); - self.ids.shrink_if_needed(); - self.local_names.shrink_if_needed(); - } - - /// Analyze the given stylesheet, and collect invalidations from their - /// rules, in order to avoid doing a full restyle when we style the document - /// next time. - pub fn collect_invalidations_for( - &mut self, - device: &Device, - stylesheet: &S, - guard: &SharedRwLockReadGuard, - ) where - S: StylesheetInDocument, - { - debug!("StylesheetInvalidationSet::collect_invalidations_for"); - if self.fully_invalid { - debug!(" > Fully invalid already"); - return; - } - - if !stylesheet.enabled() || !stylesheet.is_effective_for_device(device, guard) { - debug!(" > Stylesheet was not effective"); - return; // Nothing to do here. - } - - let quirks_mode = device.quirks_mode(); - for rule in stylesheet.effective_rules(device, guard) { - self.collect_invalidations_for_rule(rule, guard, device, quirks_mode); - if self.fully_invalid { - break; - } - } - - self.shrink_if_needed(); - - debug!(" > resulting class invalidations: {:?}", self.classes); - debug!(" > resulting id invalidations: {:?}", self.ids); - debug!( - " > resulting local name invalidations: {:?}", - self.local_names - ); - debug!(" > fully_invalid: {}", self.fully_invalid); - } - - /// Clears the invalidation set, invalidating elements as needed if - /// `document_element` is provided. - /// - /// Returns true if any invalidations ocurred. - pub fn flush(&mut self, document_element: Option, snapshots: Option<&SnapshotMap>) -> bool - where - E: TElement, - { - debug!( - "Stylist::flush({:?}, snapshots: {})", - document_element, - snapshots.is_some() - ); - let have_invalidations = match document_element { - Some(e) => self.process_invalidations(e, snapshots), - None => false, - }; - self.clear(); - have_invalidations - } - - /// Returns whether there's no invalidation to process. - pub fn is_empty(&self) -> bool { - !self.fully_invalid && - self.classes.is_empty() && - self.ids.is_empty() && - self.local_names.is_empty() - } - - fn invalidation_kind_for( - &self, - element: E, - snapshot: Option<&Snapshot>, - quirks_mode: QuirksMode, - ) -> InvalidationKind - where - E: TElement, - { - debug_assert!(!self.fully_invalid); - - let mut kind = InvalidationKind::None; - - if !self.classes.is_empty() { - element.each_class(|c| { - kind.add(self.classes.get(c, quirks_mode)); - }); - - if kind.is_scope() { - return kind; - } - - if let Some(snapshot) = snapshot { - snapshot.each_class(|c| { - kind.add(self.classes.get(c, quirks_mode)); - }); - - if kind.is_scope() { - return kind; - } - } - } - - if !self.ids.is_empty() { - if let Some(ref id) = element.id() { - kind.add(self.ids.get(id, quirks_mode)); - if kind.is_scope() { - return kind; - } - } - - if let Some(ref old_id) = snapshot.and_then(|s| s.id_attr()) { - kind.add(self.ids.get(old_id, quirks_mode)); - if kind.is_scope() { - return kind; - } - } - } - - if !self.local_names.is_empty() { - kind.add(self.local_names.get(element.local_name())); - } - - kind - } - - /// Clears the invalidation set without processing. - pub fn clear(&mut self) { - self.classes.clear(); - self.ids.clear(); - self.local_names.clear(); - self.fully_invalid = false; - debug_assert!(self.is_empty()); - } - - fn process_invalidations(&self, element: E, snapshots: Option<&SnapshotMap>) -> bool - where - E: TElement, - { - debug!("Stylist::process_invalidations({:?}, {:?})", element, self); - - { - let mut data = match element.mutate_data() { - Some(data) => data, - None => return false, - }; - - if self.fully_invalid { - debug!("process_invalidations: fully_invalid({:?})", element); - data.hint.insert(RestyleHint::restyle_subtree()); - return true; - } - } - - if self.is_empty() { - debug!("process_invalidations: empty invalidation set"); - return false; - } - - let quirks_mode = element.as_node().owner_doc().quirks_mode(); - self.process_invalidations_in_subtree(element, snapshots, quirks_mode) - } - - /// Process style invalidations in a given subtree. This traverses the - /// subtree looking for elements that match the invalidations in our hash - /// map members. - /// - /// Returns whether it invalidated at least one element's style. - #[allow(unsafe_code)] - fn process_invalidations_in_subtree( - &self, - element: E, - snapshots: Option<&SnapshotMap>, - quirks_mode: QuirksMode, - ) -> bool - where - E: TElement, - { - debug!("process_invalidations_in_subtree({:?})", element); - let mut data = match element.mutate_data() { - Some(data) => data, - None => return false, - }; - - if !data.has_styles() { - return false; - } - - if data.hint.contains_subtree() { - debug!( - "process_invalidations_in_subtree: {:?} was already invalid", - element - ); - return false; - } - - let element_wrapper = snapshots.map(|s| ElementWrapper::new(element, s)); - let snapshot = element_wrapper.as_ref().and_then(|e| e.snapshot()); - - match self.invalidation_kind_for(element, snapshot, quirks_mode) { - InvalidationKind::None => {}, - InvalidationKind::Element => { - debug!( - "process_invalidations_in_subtree: {:?} matched self", - element - ); - data.hint.insert(RestyleHint::RESTYLE_SELF); - }, - InvalidationKind::Scope => { - debug!( - "process_invalidations_in_subtree: {:?} matched subtree", - element - ); - data.hint.insert(RestyleHint::restyle_subtree()); - return true; - }, - } - - let mut any_children_invalid = false; - - for child in element.traversal_children() { - let child = match child.as_element() { - Some(e) => e, - None => continue, - }; - - any_children_invalid |= - self.process_invalidations_in_subtree(child, snapshots, quirks_mode); - } - - if any_children_invalid { - debug!( - "Children of {:?} changed, setting dirty descendants", - element - ); - unsafe { element.set_dirty_descendants() } - } - - data.hint.contains(RestyleHint::RESTYLE_SELF) || any_children_invalid - } - - /// TODO(emilio): Reuse the bucket stuff from selectormap? That handles - /// :is() / :where() etc. - fn scan_component( - component: &Component, - invalidation: &mut Option, - ) { - match *component { - Component::LocalName(LocalName { - ref name, - ref lower_name, - }) => { - if invalidation.is_none() { - *invalidation = Some(Invalidation::LocalName { - name: name.clone(), - lower_name: lower_name.clone(), - }); - } - }, - Component::Class(ref class) => { - if invalidation.as_ref().map_or(true, |s| !s.is_id_or_class()) { - *invalidation = Some(Invalidation::Class(class.clone())); - } - }, - Component::ID(ref id) => { - if invalidation.as_ref().map_or(true, |s| !s.is_id()) { - *invalidation = Some(Invalidation::ID(id.clone())); - } - }, - _ => { - // Ignore everything else, at least for now. - }, - } - } - - /// Collect invalidations for a given selector. - /// - /// We look at the outermost local name, class, or ID selector to the left - /// of an ancestor combinator, in order to restyle only a given subtree. - /// - /// If the selector has no ancestor combinator, then we do the same for - /// the only sequence it has, but record it as an element invalidation - /// instead of a subtree invalidation. - /// - /// We prefer IDs to classs, and classes to local names, on the basis - /// that the former should be more specific than the latter. We also - /// prefer to generate subtree invalidations for the outermost part - /// of the selector, to reduce the amount of traversal we need to do - /// when flushing invalidations. - fn collect_invalidations( - &mut self, - selector: &Selector, - quirks_mode: QuirksMode, - ) { - debug!( - "StylesheetInvalidationSet::collect_invalidations({:?})", - selector - ); - - let mut element_invalidation: Option = None; - let mut subtree_invalidation: Option = None; - - let mut scan_for_element_invalidation = true; - let mut scan_for_subtree_invalidation = false; - - let mut iter = selector.iter(); - - loop { - for component in &mut iter { - if scan_for_element_invalidation { - Self::scan_component(component, &mut element_invalidation); - } else if scan_for_subtree_invalidation { - Self::scan_component(component, &mut subtree_invalidation); - } - } - match iter.next_sequence() { - None => break, - Some(combinator) => { - scan_for_subtree_invalidation = combinator.is_ancestor(); - }, - } - scan_for_element_invalidation = false; - } - - if let Some(s) = subtree_invalidation { - debug!(" > Found subtree invalidation: {:?}", s); - if self.insert_invalidation(s, InvalidationKind::Scope, quirks_mode) { - return; - } - } - - if let Some(s) = element_invalidation { - debug!(" > Found element invalidation: {:?}", s); - if self.insert_invalidation(s, InvalidationKind::Element, quirks_mode) { - return; - } - } - - // The selector was of a form that we can't handle. Any element could - // match it, so let's just bail out. - debug!(" > Can't handle selector or OOMd, marking fully invalid"); - self.invalidate_fully() - } - - fn insert_invalidation( - &mut self, - invalidation: Invalidation, - kind: InvalidationKind, - quirks_mode: QuirksMode, - ) -> bool { - match invalidation { - Invalidation::Class(c) => { - let entry = match self.classes.try_entry(c.0, quirks_mode) { - Ok(e) => e, - Err(..) => return false, - }; - *entry.or_insert(InvalidationKind::None) |= kind; - }, - Invalidation::ID(i) => { - let entry = match self.ids.try_entry(i.0, quirks_mode) { - Ok(e) => e, - Err(..) => return false, - }; - *entry.or_insert(InvalidationKind::None) |= kind; - }, - Invalidation::LocalName { name, lower_name } => { - let insert_lower = name != lower_name; - if self.local_names.try_reserve(1).is_err() { - return false; - } - let entry = self.local_names.entry(name); - *entry.or_insert(InvalidationKind::None) |= kind; - if insert_lower { - if self.local_names.try_reserve(1).is_err() { - return false; - } - let entry = self.local_names.entry(lower_name); - *entry.or_insert(InvalidationKind::None) |= kind; - } - }, - } - - true - } - - /// Collects invalidations for a given CSS rule, if not fully invalid - /// already. - /// - /// TODO(emilio): we can't check whether the rule is inside a non-effective - /// subtree, we potentially could do that. - pub fn rule_changed( - &mut self, - stylesheet: &S, - rule: &CssRule, - guard: &SharedRwLockReadGuard, - device: &Device, - quirks_mode: QuirksMode, - change_kind: RuleChangeKind, - ) where - S: StylesheetInDocument, - { - use crate::stylesheets::CssRule::*; - - debug!("StylesheetInvalidationSet::rule_changed"); - if self.fully_invalid { - return; - } - - if !stylesheet.enabled() || !stylesheet.is_effective_for_device(device, guard) { - debug!(" > Stylesheet was not effective"); - return; // Nothing to do here. - } - - let is_generic_change = change_kind == RuleChangeKind::Generic; - - match *rule { - Namespace(..) => { - // It's not clear what handling changes for this correctly would - // look like. - }, - LayerStatement(..) => { - // Layer statement insertions might alter styling order, so we need to always - // invalidate fully. - return self.invalidate_fully(); - }, - CounterStyle(..) | - Page(..) | - Property(..) | - FontFeatureValues(..) | - FontPaletteValues(..) | - FontFace(..) | - Keyframes(..) | - Container(..) | - Style(..) => { - if is_generic_change { - // TODO(emilio): We need to do this for selector / keyframe - // name / font-face changes, because we don't have the old - // selector / name. If we distinguish those changes - // specially, then we can at least use this invalidation for - // style declaration changes. - return self.invalidate_fully(); - } - - self.collect_invalidations_for_rule(rule, guard, device, quirks_mode) - }, - Document(..) | Import(..) | Media(..) | Supports(..) | LayerBlock(..) => { - if !is_generic_change && - !EffectiveRules::is_effective(guard, device, quirks_mode, rule) - { - return; - } - - let rules = - EffectiveRulesIterator::effective_children(device, quirks_mode, guard, rule); - for rule in rules { - self.collect_invalidations_for_rule(rule, guard, device, quirks_mode); - if self.fully_invalid { - break; - } - } - }, - } - } - - /// Collects invalidations for a given CSS rule. - fn collect_invalidations_for_rule( - &mut self, - rule: &CssRule, - guard: &SharedRwLockReadGuard, - device: &Device, - quirks_mode: QuirksMode, - ) { - use crate::stylesheets::CssRule::*; - debug!("StylesheetInvalidationSet::collect_invalidations_for_rule"); - debug_assert!(!self.fully_invalid, "Not worth to be here!"); - - match *rule { - Style(ref lock) => { - let style_rule = lock.read_with(guard); - for selector in &style_rule.selectors.0 { - self.collect_invalidations(selector, quirks_mode); - if self.fully_invalid { - return; - } - } - }, - Document(..) | Namespace(..) | Import(..) | Media(..) | Supports(..) | - Container(..) | LayerStatement(..) | LayerBlock(..) => { - // Do nothing, relevant nested rules are visited as part of the - // iteration. - }, - FontFace(..) => { - // Do nothing, @font-face doesn't affect computed style - // information. We'll restyle when the font face loads, if - // needed. - }, - Keyframes(ref lock) => { - let keyframes_rule = lock.read_with(guard); - if device.animation_name_may_be_referenced(&keyframes_rule.name) { - debug!( - " > Found @keyframes rule potentially referenced \ - from the page, marking the whole tree invalid." - ); - self.fully_invalid = true; - } else { - // Do nothing, this animation can't affect the style of - // existing elements. - } - }, - CounterStyle(..) | - Page(..) | - Property(..) | - FontFeatureValues(..) | - FontPaletteValues(..) => { - debug!(" > Found unsupported rule, marking the whole subtree invalid."); - - // TODO(emilio): Can we do better here? - // - // At least in `@page`, we could check the relevant media, I - // guess. - self.fully_invalid = true; - }, - } - } -} diff --git a/components/style/invalidation/viewport_units.rs b/components/style/invalidation/viewport_units.rs deleted file mode 100644 index 06faeb14c46..00000000000 --- a/components/style/invalidation/viewport_units.rs +++ /dev/null @@ -1,71 +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/. */ - -//! Invalidates style of all elements that depend on viewport units. - -use crate::data::ViewportUnitUsage; -use crate::dom::{TElement, TNode}; -use crate::invalidation::element::restyle_hints::RestyleHint; - -/// Invalidates style of all elements that depend on viewport units. -/// -/// Returns whether any element was invalidated. -pub fn invalidate(root: E) -> bool -where - E: TElement, -{ - debug!("invalidation::viewport_units::invalidate({:?})", root); - invalidate_recursively(root) -} - -fn invalidate_recursively(element: E) -> bool -where - E: TElement, -{ - let mut data = match element.mutate_data() { - Some(data) => data, - None => return false, - }; - - if data.hint.will_recascade_subtree() { - debug!("invalidate_recursively: {:?} was already invalid", element); - return false; - } - - let usage = data.styles.viewport_unit_usage(); - let uses_viewport_units = usage != ViewportUnitUsage::None; - if uses_viewport_units { - debug!( - "invalidate_recursively: {:?} uses viewport units {:?}", - element, usage - ); - } - - match usage { - ViewportUnitUsage::None => {}, - ViewportUnitUsage::FromQuery => { - data.hint.insert(RestyleHint::RESTYLE_SELF); - }, - ViewportUnitUsage::FromDeclaration => { - data.hint.insert(RestyleHint::RECASCADE_SELF); - }, - } - - let mut any_children_invalid = false; - for child in element.traversal_children() { - if let Some(child) = child.as_element() { - any_children_invalid |= invalidate_recursively(child); - } - } - - if any_children_invalid { - debug!( - "invalidate_recursively: Children of {:?} changed, setting dirty descendants", - element - ); - unsafe { element.set_dirty_descendants() } - } - - uses_viewport_units || any_children_invalid -} diff --git a/components/style/lib.rs b/components/style/lib.rs deleted file mode 100644 index 90a52e567b1..00000000000 --- a/components/style/lib.rs +++ /dev/null @@ -1,329 +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/. */ - -//! Calculate [specified][specified] and [computed values][computed] from a -//! tree of DOM nodes and a set of stylesheets. -//! -//! [computed]: https://drafts.csswg.org/css-cascade/#computed -//! [specified]: https://drafts.csswg.org/css-cascade/#specified -//! -//! In particular, this crate contains the definitions of supported properties, -//! the code to parse them into specified values and calculate the computed -//! values based on the specified values, as well as the code to serialize both -//! specified and computed values. -//! -//! The main entry point is [`recalc_style_at`][recalc_style_at]. -//! -//! [recalc_style_at]: traversal/fn.recalc_style_at.html -//! -//! Major dependencies are the [cssparser][cssparser] and [selectors][selectors] -//! crates. -//! -//! [cssparser]: ../cssparser/index.html -//! [selectors]: ../selectors/index.html - -#![deny(missing_docs)] - -#[macro_use] -extern crate bitflags; -#[macro_use] -extern crate cssparser; -#[macro_use] -extern crate debug_unreachable; -#[macro_use] -extern crate derive_more; -#[macro_use] -#[cfg(feature = "gecko")] -extern crate gecko_profiler; -#[cfg(feature = "gecko")] -#[macro_use] -pub mod gecko_string_cache; -#[cfg(feature = "servo")] -#[macro_use] -extern crate html5ever; -#[macro_use] -extern crate lazy_static; -#[macro_use] -extern crate log; -#[macro_use] -extern crate malloc_size_of; -#[macro_use] -extern crate malloc_size_of_derive; -#[cfg(feature = "gecko")] -pub use nsstring; -#[cfg(feature = "gecko")] -extern crate num_cpus; -#[macro_use] -extern crate num_derive; -#[macro_use] -extern crate serde; -pub use servo_arc; -#[cfg(feature = "servo")] -#[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; - -#[macro_use] -mod macros; - -pub mod animation; -pub mod applicable_declarations; -#[allow(missing_docs)] // TODO. -#[cfg(feature = "servo")] -pub mod attr; -pub mod author_styles; -pub mod bezier; -pub mod bloom; -pub mod color; -#[path = "properties/computed_value_flags.rs"] -pub mod computed_value_flags; -pub mod context; -pub mod counter_style; -pub mod custom_properties; -pub mod data; -pub mod dom; -pub mod dom_apis; -pub mod driver; -#[cfg(feature = "servo")] -mod encoding_support; -pub mod error_reporting; -pub mod font_face; -pub mod font_metrics; -#[cfg(feature = "gecko")] -#[allow(unsafe_code)] -pub mod gecko_bindings; -pub mod global_style_data; -pub mod invalidation; -#[allow(missing_docs)] // TODO. -pub mod logical_geometry; -pub mod matching; -pub mod media_queries; -pub mod parallel; -pub mod parser; -pub mod piecewise_linear; -pub mod properties_and_values; -#[macro_use] -pub mod queries; -pub mod rule_cache; -pub mod rule_collector; -pub mod rule_tree; -pub mod scoped_tls; -pub mod selector_map; -pub mod selector_parser; -pub mod shared_lock; -pub mod sharing; -pub mod str; -pub mod style_adjuster; -pub mod style_resolver; -pub mod stylesheet_set; -pub mod stylesheets; -pub mod stylist; -pub mod thread_state; -pub mod traversal; -pub mod traversal_flags; -pub mod use_counters; -#[macro_use] -#[allow(non_camel_case_types)] -pub mod values; - -#[cfg(feature = "gecko")] -pub use crate::gecko_string_cache as string_cache; -#[cfg(feature = "gecko")] -pub use crate::gecko_string_cache::Atom; -/// The namespace prefix type for Gecko, which is just an atom. -#[cfg(feature = "gecko")] -pub type Prefix = crate::values::AtomIdent; -/// The local name of an element for Gecko, which is just an atom. -#[cfg(feature = "gecko")] -pub type LocalName = crate::values::AtomIdent; -#[cfg(feature = "gecko")] -pub use crate::gecko_string_cache::Namespace; - -#[cfg(feature = "servo")] -pub use servo_atoms::Atom; - -#[cfg(feature = "servo")] -#[allow(missing_docs)] -pub type LocalName = crate::values::GenericAtomIdent; -#[cfg(feature = "servo")] -#[allow(missing_docs)] -pub type Namespace = crate::values::GenericAtomIdent; -#[cfg(feature = "servo")] -#[allow(missing_docs)] -pub type Prefix = crate::values::GenericAtomIdent; - -pub use style_traits::arc_slice::ArcSlice; -pub use style_traits::owned_slice::OwnedSlice; -pub use style_traits::owned_str::OwnedStr; - -use std::hash::{BuildHasher, Hash}; - -#[macro_use] -pub mod properties; - -#[cfg(feature = "gecko")] -#[allow(unsafe_code)] -pub mod gecko; - -// uses a macro from properties -#[cfg(feature = "servo")] -#[allow(unsafe_code)] -pub mod servo; - -macro_rules! reexport_computed_values { - ( $( { $name: ident } )+ ) => { - /// Types for [computed values][computed]. - /// - /// [computed]: https://drafts.csswg.org/css-cascade/#computed - pub mod computed_values { - $( - pub use crate::properties::longhands::$name::computed_value as $name; - )+ - // Don't use a side-specific name needlessly: - pub use crate::properties::longhands::border_top_style::computed_value as border_style; - } - } -} -longhand_properties_idents!(reexport_computed_values); -#[cfg(feature = "gecko")] -use crate::gecko_string_cache::WeakAtom; -#[cfg(feature = "servo")] -use servo_atoms::Atom as WeakAtom; - -/// Extension methods for selectors::attr::CaseSensitivity -pub trait CaseSensitivityExt { - /// Return whether two atoms compare equal according to this case sensitivity. - fn eq_atom(self, a: &WeakAtom, b: &WeakAtom) -> bool; -} - -impl CaseSensitivityExt for selectors::attr::CaseSensitivity { - fn eq_atom(self, a: &WeakAtom, b: &WeakAtom) -> bool { - match self { - selectors::attr::CaseSensitivity::CaseSensitive => a == b, - selectors::attr::CaseSensitivity::AsciiCaseInsensitive => a.eq_ignore_ascii_case(b), - } - } -} - -/// A trait pretty much similar to num_traits::Zero, but without the need of -/// implementing `Add`. -pub trait Zero { - /// Returns the zero value. - fn zero() -> Self; - - /// Returns whether this value is zero. - fn is_zero(&self) -> bool; -} - -impl Zero for T -where - T: num_traits::Zero, -{ - fn zero() -> Self { - ::zero() - } - - fn is_zero(&self) -> bool { - ::is_zero(self) - } -} - -/// A trait implementing a function to tell if the number is zero without a percent -pub trait ZeroNoPercent { - /// So, `0px` should return `true`, but `0%` or `1px` should return `false` - fn is_zero_no_percent(&self) -> bool; -} - -/// A trait pretty much similar to num_traits::One, but without the need of -/// implementing `Mul`. -pub trait One { - /// Reutrns the one value. - fn one() -> Self; - - /// Returns whether this value is one. - fn is_one(&self) -> bool; -} - -impl One for T -where - T: num_traits::One + PartialEq, -{ - fn one() -> Self { - ::one() - } - - fn is_one(&self) -> bool { - *self == One::one() - } -} - -/// An allocation error. -/// -/// TODO(emilio): Would be nice to have more information here, or for SmallVec -/// to return the standard error type (and then we can just return that). -/// -/// But given we use these mostly to bail out and ignore them, it's not a big -/// deal. -#[derive(Debug)] -pub struct AllocErr; - -impl From for AllocErr { - #[inline] - fn from(_: smallvec::CollectionAllocErr) -> Self { - Self - } -} - -impl From for AllocErr { - #[inline] - fn from(_: std::collections::TryReserveError) -> Self { - Self - } -} - -/// Shrink the capacity of the collection if needed. -pub(crate) trait ShrinkIfNeeded { - fn shrink_if_needed(&mut self); -} - -/// We shrink the capacity of a collection if we're wasting more than a 25% of -/// its capacity, and if the collection is arbitrarily big enough -/// (>= CAPACITY_THRESHOLD entries). -#[inline] -fn should_shrink(len: usize, capacity: usize) -> bool { - const CAPACITY_THRESHOLD: usize = 64; - capacity >= CAPACITY_THRESHOLD && len + capacity / 4 < capacity -} - -impl ShrinkIfNeeded for std::collections::HashMap -where - K: Eq + Hash, - H: BuildHasher, -{ - fn shrink_if_needed(&mut self) { - if should_shrink(self.len(), self.capacity()) { - self.shrink_to_fit(); - } - } -} - -impl ShrinkIfNeeded for std::collections::HashSet -where - T: Eq + Hash, - H: BuildHasher, -{ - fn shrink_if_needed(&mut self) { - if should_shrink(self.len(), self.capacity()) { - self.shrink_to_fit(); - } - } -} - -// TODO(emilio): Measure and see if we're wasting a lot of memory on Vec / -// SmallVec, and if so consider shrinking those as well. diff --git a/components/style/logical_geometry.rs b/components/style/logical_geometry.rs deleted file mode 100644 index 9a823a5a24a..00000000000 --- a/components/style/logical_geometry.rs +++ /dev/null @@ -1,1522 +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/. */ - -//! Geometry in flow-relative space. - -use crate::properties::style_structs; -use euclid::default::{Point2D, Rect, SideOffsets2D, Size2D}; -use euclid::num::Zero; -use std::cmp::{max, min}; -use std::fmt::{self, Debug, Error, Formatter}; -use std::ops::{Add, Sub}; -use unicode_bidi as bidi; - -pub enum BlockFlowDirection { - TopToBottom, - RightToLeft, - LeftToRight, -} - -pub enum InlineBaseDirection { - LeftToRight, - RightToLeft, -} - -// TODO: improve the readability of the WritingMode serialization, refer to the Debug:fmt() -bitflags!( - #[cfg_attr(feature = "servo", derive(MallocSizeOf, Serialize))] - #[repr(C)] - pub struct WritingMode: u8 { - /// A vertical writing mode; writing-mode is vertical-rl, - /// vertical-lr, sideways-lr, or sideways-rl. - const VERTICAL = 1 << 0; - /// The inline flow direction is reversed against the physical - /// direction (i.e. right-to-left or bottom-to-top); writing-mode is - /// sideways-lr or direction is rtl (but not both). - /// - /// (This bit can be derived from the others, but we store it for - /// convenience.) - const INLINE_REVERSED = 1 << 1; - /// A vertical writing mode whose block progression direction is left- - /// to-right; writing-mode is vertical-lr or sideways-lr. - /// - /// Never set without VERTICAL. - const VERTICAL_LR = 1 << 2; - /// The line-over/line-under sides are inverted with respect to the - /// block-start/block-end edge; writing-mode is vertical-lr. - /// - /// Never set without VERTICAL and VERTICAL_LR. - const LINE_INVERTED = 1 << 3; - /// direction is rtl. - const RTL = 1 << 4; - /// All text within a vertical writing mode is displayed sideways - /// and runs top-to-bottom or bottom-to-top; set in these cases: - /// - /// * writing-mode: sideways-rl; - /// * writing-mode: sideways-lr; - /// - /// Never set without VERTICAL. - const VERTICAL_SIDEWAYS = 1 << 5; - /// Similar to VERTICAL_SIDEWAYS, but is set via text-orientation; - /// set in these cases: - /// - /// * writing-mode: vertical-rl; text-orientation: sideways; - /// * writing-mode: vertical-lr; text-orientation: sideways; - /// - /// Never set without VERTICAL. - const TEXT_SIDEWAYS = 1 << 6; - /// Horizontal text within a vertical writing mode is displayed with each - /// glyph upright; set in these cases: - /// - /// * writing-mode: vertical-rl; text-orientation: upright; - /// * writing-mode: vertical-lr: text-orientation: upright; - /// - /// Never set without VERTICAL. - const UPRIGHT = 1 << 7; - } -); - -impl WritingMode { - /// Return a WritingMode bitflags from the relevant CSS properties. - pub fn new(inheritedbox_style: &style_structs::InheritedBox) -> Self { - use crate::properties::longhands::direction::computed_value::T as Direction; - use crate::properties::longhands::writing_mode::computed_value::T as SpecifiedWritingMode; - - let mut flags = WritingMode::empty(); - - let direction = inheritedbox_style.clone_direction(); - let writing_mode = inheritedbox_style.clone_writing_mode(); - - match direction { - Direction::Ltr => {}, - Direction::Rtl => { - flags.insert(WritingMode::RTL); - }, - } - - match writing_mode { - SpecifiedWritingMode::HorizontalTb => { - if direction == Direction::Rtl { - flags.insert(WritingMode::INLINE_REVERSED); - } - }, - SpecifiedWritingMode::VerticalRl => { - flags.insert(WritingMode::VERTICAL); - if direction == Direction::Rtl { - flags.insert(WritingMode::INLINE_REVERSED); - } - }, - SpecifiedWritingMode::VerticalLr => { - flags.insert(WritingMode::VERTICAL); - flags.insert(WritingMode::VERTICAL_LR); - flags.insert(WritingMode::LINE_INVERTED); - if direction == Direction::Rtl { - flags.insert(WritingMode::INLINE_REVERSED); - } - }, - #[cfg(feature = "gecko")] - SpecifiedWritingMode::SidewaysRl => { - flags.insert(WritingMode::VERTICAL); - flags.insert(WritingMode::VERTICAL_SIDEWAYS); - if direction == Direction::Rtl { - flags.insert(WritingMode::INLINE_REVERSED); - } - }, - #[cfg(feature = "gecko")] - SpecifiedWritingMode::SidewaysLr => { - flags.insert(WritingMode::VERTICAL); - flags.insert(WritingMode::VERTICAL_LR); - flags.insert(WritingMode::VERTICAL_SIDEWAYS); - if direction == Direction::Ltr { - flags.insert(WritingMode::INLINE_REVERSED); - } - }, - } - - #[cfg(feature = "gecko")] - { - use crate::properties::longhands::text_orientation::computed_value::T as TextOrientation; - - // text-orientation only has an effect for vertical-rl and - // vertical-lr values of writing-mode. - match writing_mode { - SpecifiedWritingMode::VerticalRl | SpecifiedWritingMode::VerticalLr => { - match inheritedbox_style.clone_text_orientation() { - TextOrientation::Mixed => {}, - TextOrientation::Upright => { - flags.insert(WritingMode::UPRIGHT); - - // https://drafts.csswg.org/css-writing-modes-3/#valdef-text-orientation-upright: - // - // > This value causes the used value of direction - // > to be ltr, and for the purposes of bidi - // > reordering, causes all characters to be treated - // > as strong LTR. - flags.remove(WritingMode::RTL); - flags.remove(WritingMode::INLINE_REVERSED); - }, - TextOrientation::Sideways => { - flags.insert(WritingMode::TEXT_SIDEWAYS); - }, - } - }, - _ => {}, - } - } - - 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) - } - - #[inline] - pub fn is_horizontal(&self) -> bool { - !self.is_vertical() - } - - /// Assuming .is_vertical(), does the block direction go left to right? - #[inline] - pub fn is_vertical_lr(&self) -> bool { - self.intersects(WritingMode::VERTICAL_LR) - } - - /// Assuming .is_vertical(), does the inline direction go top to bottom? - #[inline] - pub fn is_inline_tb(&self) -> bool { - // https://drafts.csswg.org/css-writing-modes-3/#logical-to-physical - !self.intersects(WritingMode::INLINE_REVERSED) - } - - #[inline] - pub fn is_bidi_ltr(&self) -> bool { - !self.intersects(WritingMode::RTL) - } - - #[inline] - pub fn is_sideways(&self) -> bool { - self.intersects(WritingMode::VERTICAL_SIDEWAYS | WritingMode::TEXT_SIDEWAYS) - } - - #[inline] - pub fn is_upright(&self) -> bool { - self.intersects(WritingMode::UPRIGHT) - } - - /// https://drafts.csswg.org/css-writing-modes/#logical-to-physical - /// - /// | Return | line-left is… | line-right is… | - /// |---------|---------------|----------------| - /// | `true` | inline-start | inline-end | - /// | `false` | inline-end | inline-start | - #[inline] - pub fn line_left_is_inline_start(&self) -> bool { - // https://drafts.csswg.org/css-writing-modes/#inline-start - // “For boxes with a used direction value of ltr, this means the line-left side. - // For boxes with a used direction value of rtl, this means the line-right side.” - self.is_bidi_ltr() - } - - #[inline] - pub fn inline_start_physical_side(&self) -> PhysicalSide { - match (self.is_vertical(), self.is_inline_tb(), self.is_bidi_ltr()) { - (false, _, true) => PhysicalSide::Left, - (false, _, false) => PhysicalSide::Right, - (true, true, _) => PhysicalSide::Top, - (true, false, _) => PhysicalSide::Bottom, - } - } - - #[inline] - pub fn inline_end_physical_side(&self) -> PhysicalSide { - match (self.is_vertical(), self.is_inline_tb(), self.is_bidi_ltr()) { - (false, _, true) => PhysicalSide::Right, - (false, _, false) => PhysicalSide::Left, - (true, true, _) => PhysicalSide::Bottom, - (true, false, _) => PhysicalSide::Top, - } - } - - #[inline] - pub fn block_start_physical_side(&self) -> PhysicalSide { - match (self.is_vertical(), self.is_vertical_lr()) { - (false, _) => PhysicalSide::Top, - (true, true) => PhysicalSide::Left, - (true, false) => PhysicalSide::Right, - } - } - - #[inline] - pub fn block_end_physical_side(&self) -> PhysicalSide { - match (self.is_vertical(), self.is_vertical_lr()) { - (false, _) => PhysicalSide::Bottom, - (true, true) => PhysicalSide::Right, - (true, false) => PhysicalSide::Left, - } - } - - #[inline] - fn physical_sides_to_corner( - block_side: PhysicalSide, - inline_side: PhysicalSide, - ) -> PhysicalCorner { - match (block_side, inline_side) { - (PhysicalSide::Top, PhysicalSide::Left) | (PhysicalSide::Left, PhysicalSide::Top) => { - PhysicalCorner::TopLeft - }, - (PhysicalSide::Top, PhysicalSide::Right) | (PhysicalSide::Right, PhysicalSide::Top) => { - PhysicalCorner::TopRight - }, - (PhysicalSide::Bottom, PhysicalSide::Right) | - (PhysicalSide::Right, PhysicalSide::Bottom) => PhysicalCorner::BottomRight, - (PhysicalSide::Bottom, PhysicalSide::Left) | - (PhysicalSide::Left, PhysicalSide::Bottom) => PhysicalCorner::BottomLeft, - _ => unreachable!("block and inline sides must be orthogonal"), - } - } - - #[inline] - pub fn start_start_physical_corner(&self) -> PhysicalCorner { - WritingMode::physical_sides_to_corner( - self.block_start_physical_side(), - self.inline_start_physical_side(), - ) - } - - #[inline] - pub fn start_end_physical_corner(&self) -> PhysicalCorner { - WritingMode::physical_sides_to_corner( - self.block_start_physical_side(), - self.inline_end_physical_side(), - ) - } - - #[inline] - pub fn end_start_physical_corner(&self) -> PhysicalCorner { - WritingMode::physical_sides_to_corner( - self.block_end_physical_side(), - self.inline_start_physical_side(), - ) - } - - #[inline] - pub fn end_end_physical_corner(&self) -> PhysicalCorner { - WritingMode::physical_sides_to_corner( - self.block_end_physical_side(), - self.inline_end_physical_side(), - ) - } - - #[inline] - pub fn block_flow_direction(&self) -> BlockFlowDirection { - match (self.is_vertical(), self.is_vertical_lr()) { - (false, _) => BlockFlowDirection::TopToBottom, - (true, true) => BlockFlowDirection::LeftToRight, - (true, false) => BlockFlowDirection::RightToLeft, - } - } - - #[inline] - pub fn inline_base_direction(&self) -> InlineBaseDirection { - if self.intersects(WritingMode::RTL) { - InlineBaseDirection::RightToLeft - } else { - InlineBaseDirection::LeftToRight - } - } - - #[inline] - /// The default bidirectional embedding level for this writing mode. - /// - /// Returns bidi level 0 if the mode is LTR, or 1 otherwise. - pub fn to_bidi_level(&self) -> bidi::Level { - if self.is_bidi_ltr() { - bidi::Level::ltr() - } else { - bidi::Level::rtl() - } - } -} - -impl fmt::Display for WritingMode { - fn fmt(&self, formatter: &mut Formatter) -> Result<(), Error> { - if self.is_vertical() { - write!(formatter, "V")?; - if self.is_vertical_lr() { - write!(formatter, " LR")?; - } else { - write!(formatter, " RL")?; - } - if self.is_sideways() { - write!(formatter, " Sideways")?; - } - if self.intersects(WritingMode::LINE_INVERTED) { - write!(formatter, " Inverted")?; - } - } else { - write!(formatter, "H")?; - } - if self.is_bidi_ltr() { - write!(formatter, " LTR") - } else { - write!(formatter, " RTL") - } - } -} - -/// Wherever logical geometry is used, the writing mode is known based on context: -/// every method takes a `mode` parameter. -/// However, this context is easy to get wrong. -/// In debug builds only, logical geometry objects store their writing mode -/// (in addition to taking it as a parameter to methods) and check it. -/// In non-debug builds, make this storage zero-size and the checks no-ops. -#[cfg(not(debug_assertions))] -#[derive(Clone, Copy, Eq, PartialEq)] -#[cfg_attr(feature = "servo", derive(Serialize))] -struct DebugWritingMode; - -#[cfg(debug_assertions)] -#[derive(Clone, Copy, Eq, PartialEq)] -#[cfg_attr(feature = "servo", derive(Serialize))] -struct DebugWritingMode { - mode: WritingMode, -} - -#[cfg(not(debug_assertions))] -impl DebugWritingMode { - #[inline] - fn check(&self, _other: WritingMode) {} - - #[inline] - fn check_debug(&self, _other: DebugWritingMode) {} - - #[inline] - fn new(_mode: WritingMode) -> DebugWritingMode { - DebugWritingMode - } -} - -#[cfg(debug_assertions)] -impl DebugWritingMode { - #[inline] - fn check(&self, other: WritingMode) { - assert_eq!(self.mode, other) - } - - #[inline] - fn check_debug(&self, other: DebugWritingMode) { - assert_eq!(self.mode, other.mode) - } - - #[inline] - fn new(mode: WritingMode) -> DebugWritingMode { - DebugWritingMode { mode: mode } - } -} - -impl Debug for DebugWritingMode { - #[cfg(not(debug_assertions))] - fn fmt(&self, formatter: &mut Formatter) -> Result<(), Error> { - write!(formatter, "?") - } - - #[cfg(debug_assertions)] - fn fmt(&self, formatter: &mut Formatter) -> Result<(), Error> { - write!(formatter, "{}", self.mode) - } -} - -// Used to specify the logical direction. -#[derive(Clone, Copy, Debug, PartialEq)] -#[cfg_attr(feature = "servo", derive(Serialize))] -pub enum Direction { - Inline, - Block, -} - -/// A 2D size in flow-relative dimensions -#[derive(Clone, Copy, Eq, PartialEq)] -#[cfg_attr(feature = "servo", derive(Serialize))] -pub struct LogicalSize { - pub inline: T, // inline-size, a.k.a. logical width, a.k.a. measure - pub block: T, // block-size, a.k.a. logical height, a.k.a. extent - debug_writing_mode: DebugWritingMode, -} - -impl Debug for LogicalSize { - fn fmt(&self, formatter: &mut Formatter) -> Result<(), Error> { - write!( - formatter, - "LogicalSize({:?}, i{:?}×b{:?})", - self.debug_writing_mode, self.inline, self.block - ) - } -} - -// Can not implement the Zero trait: its zero() method does not have the `mode` parameter. -impl LogicalSize { - #[inline] - pub fn zero(mode: WritingMode) -> LogicalSize { - LogicalSize { - inline: Zero::zero(), - block: Zero::zero(), - debug_writing_mode: DebugWritingMode::new(mode), - } - } -} - -impl LogicalSize { - #[inline] - pub fn new(mode: WritingMode, inline: T, block: T) -> LogicalSize { - LogicalSize { - inline: inline, - block: block, - debug_writing_mode: DebugWritingMode::new(mode), - } - } - - #[inline] - pub fn from_physical(mode: WritingMode, size: Size2D) -> LogicalSize { - if mode.is_vertical() { - LogicalSize::new(mode, size.height, size.width) - } else { - LogicalSize::new(mode, size.width, size.height) - } - } -} - -impl LogicalSize { - #[inline] - pub fn width(&self, mode: WritingMode) -> T { - self.debug_writing_mode.check(mode); - if mode.is_vertical() { - self.block - } else { - self.inline - } - } - - #[inline] - pub fn set_width(&mut self, mode: WritingMode, width: T) { - self.debug_writing_mode.check(mode); - if mode.is_vertical() { - self.block = width - } else { - self.inline = width - } - } - - #[inline] - pub fn height(&self, mode: WritingMode) -> T { - self.debug_writing_mode.check(mode); - if mode.is_vertical() { - self.inline - } else { - self.block - } - } - - #[inline] - pub fn set_height(&mut self, mode: WritingMode, height: T) { - self.debug_writing_mode.check(mode); - if mode.is_vertical() { - self.inline = height - } else { - self.block = height - } - } - - #[inline] - pub fn to_physical(&self, mode: WritingMode) -> Size2D { - self.debug_writing_mode.check(mode); - if mode.is_vertical() { - Size2D::new(self.block, self.inline) - } else { - Size2D::new(self.inline, self.block) - } - } - - #[inline] - pub fn convert(&self, mode_from: WritingMode, mode_to: WritingMode) -> LogicalSize { - if mode_from == mode_to { - self.debug_writing_mode.check(mode_from); - *self - } else { - LogicalSize::from_physical(mode_to, self.to_physical(mode_from)) - } - } -} - -impl> Add for LogicalSize { - type Output = LogicalSize; - - #[inline] - fn add(self, other: LogicalSize) -> LogicalSize { - self.debug_writing_mode - .check_debug(other.debug_writing_mode); - LogicalSize { - debug_writing_mode: self.debug_writing_mode, - inline: self.inline + other.inline, - block: self.block + other.block, - } - } -} - -// TODO(servo#30577) remove this once underlying bugs are fixed -impl> LogicalSize { - #[inline] - pub fn add_or_warn(self, other: LogicalSize) -> LogicalSize { - #[cfg(debug_assertions)] - if !(self.debug_writing_mode.mode == other.debug_writing_mode.mode) { - log::warn!("debug assertion failed! self.debug_writing_mode.mode == other.debug_writing_mode.mode"); - } - LogicalSize { - debug_writing_mode: self.debug_writing_mode, - inline: self.inline + other.inline, - block: self.block + other.block, - } - } -} - -impl> Sub for LogicalSize { - type Output = LogicalSize; - - #[inline] - fn sub(self, other: LogicalSize) -> LogicalSize { - self.debug_writing_mode - .check_debug(other.debug_writing_mode); - LogicalSize { - debug_writing_mode: self.debug_writing_mode, - inline: self.inline - other.inline, - block: self.block - other.block, - } - } -} - -/// A 2D point in flow-relative dimensions -#[derive(Clone, Copy, Eq, PartialEq)] -#[cfg_attr(feature = "servo", derive(Serialize))] -pub struct LogicalPoint { - /// inline-axis coordinate - pub i: T, - /// block-axis coordinate - pub b: T, - debug_writing_mode: DebugWritingMode, -} - -impl Debug for LogicalPoint { - fn fmt(&self, formatter: &mut Formatter) -> Result<(), Error> { - write!( - formatter, - "LogicalPoint({:?} (i{:?}, b{:?}))", - self.debug_writing_mode, self.i, self.b - ) - } -} - -// Can not implement the Zero trait: its zero() method does not have the `mode` parameter. -impl LogicalPoint { - #[inline] - pub fn zero(mode: WritingMode) -> LogicalPoint { - LogicalPoint { - i: Zero::zero(), - b: Zero::zero(), - debug_writing_mode: DebugWritingMode::new(mode), - } - } -} - -impl LogicalPoint { - #[inline] - pub fn new(mode: WritingMode, i: T, b: T) -> LogicalPoint { - LogicalPoint { - i: i, - b: b, - debug_writing_mode: DebugWritingMode::new(mode), - } - } -} - -impl> LogicalPoint { - #[inline] - pub fn from_physical( - mode: WritingMode, - point: Point2D, - container_size: Size2D, - ) -> LogicalPoint { - if mode.is_vertical() { - LogicalPoint { - i: if mode.is_inline_tb() { - point.y - } else { - container_size.height - point.y - }, - b: if mode.is_vertical_lr() { - point.x - } else { - container_size.width - point.x - }, - debug_writing_mode: DebugWritingMode::new(mode), - } - } else { - LogicalPoint { - i: if mode.is_bidi_ltr() { - point.x - } else { - container_size.width - point.x - }, - b: point.y, - debug_writing_mode: DebugWritingMode::new(mode), - } - } - } - - #[inline] - pub fn x(&self, mode: WritingMode, container_size: Size2D) -> T { - self.debug_writing_mode.check(mode); - if mode.is_vertical() { - if mode.is_vertical_lr() { - self.b - } else { - container_size.width - self.b - } - } else { - if mode.is_bidi_ltr() { - self.i - } else { - container_size.width - self.i - } - } - } - - #[inline] - pub fn set_x(&mut self, mode: WritingMode, x: T, container_size: Size2D) { - self.debug_writing_mode.check(mode); - if mode.is_vertical() { - self.b = if mode.is_vertical_lr() { - x - } else { - container_size.width - x - } - } else { - self.i = if mode.is_bidi_ltr() { - x - } else { - container_size.width - x - } - } - } - - #[inline] - pub fn y(&self, mode: WritingMode, container_size: Size2D) -> T { - self.debug_writing_mode.check(mode); - if mode.is_vertical() { - if mode.is_inline_tb() { - self.i - } else { - container_size.height - self.i - } - } else { - self.b - } - } - - #[inline] - pub fn set_y(&mut self, mode: WritingMode, y: T, container_size: Size2D) { - self.debug_writing_mode.check(mode); - if mode.is_vertical() { - self.i = if mode.is_inline_tb() { - y - } else { - container_size.height - y - } - } else { - self.b = y - } - } - - #[inline] - pub fn to_physical(&self, mode: WritingMode, container_size: Size2D) -> Point2D { - self.debug_writing_mode.check(mode); - if mode.is_vertical() { - Point2D::new( - if mode.is_vertical_lr() { - self.b - } else { - container_size.width - self.b - }, - if mode.is_inline_tb() { - self.i - } else { - container_size.height - self.i - }, - ) - } else { - Point2D::new( - if mode.is_bidi_ltr() { - self.i - } else { - container_size.width - self.i - }, - self.b, - ) - } - } - - // TODO(servo#30577) remove this once underlying bugs are fixed - #[inline] - pub fn to_physical_or_warn(&self, mode: WritingMode, container_size: Size2D) -> Point2D { - #[cfg(debug_assertions)] - if !(self.debug_writing_mode.mode == mode) { - log::warn!("debug assertion failed! self.debug_writing_mode.mode == mode"); - } - if mode.is_vertical() { - Point2D::new( - if mode.is_vertical_lr() { - self.b - } else { - container_size.width - self.b - }, - if mode.is_inline_tb() { - self.i - } else { - container_size.height - self.i - }, - ) - } else { - Point2D::new( - if mode.is_bidi_ltr() { - self.i - } else { - container_size.width - self.i - }, - self.b, - ) - } - } - - #[inline] - pub fn convert( - &self, - mode_from: WritingMode, - mode_to: WritingMode, - container_size: Size2D, - ) -> LogicalPoint { - if mode_from == mode_to { - self.debug_writing_mode.check(mode_from); - *self - } else { - LogicalPoint::from_physical( - mode_to, - self.to_physical(mode_from, container_size), - container_size, - ) - } - } -} - -impl> LogicalPoint { - /// This doesn’t really makes sense, - /// but happens when dealing with multiple origins. - #[inline] - pub fn add_point(&self, other: &LogicalPoint) -> LogicalPoint { - self.debug_writing_mode - .check_debug(other.debug_writing_mode); - LogicalPoint { - debug_writing_mode: self.debug_writing_mode, - i: self.i + other.i, - b: self.b + other.b, - } - } -} - -impl> Add> for LogicalPoint { - type Output = LogicalPoint; - - #[inline] - fn add(self, other: LogicalSize) -> LogicalPoint { - self.debug_writing_mode - .check_debug(other.debug_writing_mode); - LogicalPoint { - debug_writing_mode: self.debug_writing_mode, - i: self.i + other.inline, - b: self.b + other.block, - } - } -} - -impl> Sub> for LogicalPoint { - type Output = LogicalPoint; - - #[inline] - fn sub(self, other: LogicalSize) -> LogicalPoint { - self.debug_writing_mode - .check_debug(other.debug_writing_mode); - LogicalPoint { - debug_writing_mode: self.debug_writing_mode, - i: self.i - other.inline, - b: self.b - other.block, - } - } -} - -/// A "margin" in flow-relative dimensions -/// Represents the four sides of the margins, borders, or padding of a CSS box, -/// or a combination of those. -/// A positive "margin" can be added to a rectangle to obtain a bigger rectangle. -#[derive(Clone, Copy, Eq, PartialEq)] -#[cfg_attr(feature = "servo", derive(Serialize))] -pub struct LogicalMargin { - pub block_start: T, - pub inline_end: T, - pub block_end: T, - pub inline_start: T, - debug_writing_mode: DebugWritingMode, -} - -impl Debug for LogicalMargin { - fn fmt(&self, formatter: &mut Formatter) -> Result<(), Error> { - let writing_mode_string = if cfg!(debug_assertions) { - format!("{:?}, ", self.debug_writing_mode) - } else { - "".to_owned() - }; - - write!( - formatter, - "LogicalMargin({}i:{:?}..{:?} b:{:?}..{:?})", - writing_mode_string, - self.inline_start, - self.inline_end, - self.block_start, - self.block_end - ) - } -} - -impl LogicalMargin { - #[inline] - pub fn zero(mode: WritingMode) -> LogicalMargin { - LogicalMargin { - block_start: Zero::zero(), - inline_end: Zero::zero(), - block_end: Zero::zero(), - inline_start: Zero::zero(), - debug_writing_mode: DebugWritingMode::new(mode), - } - } -} - -impl LogicalMargin { - #[inline] - pub fn new( - mode: WritingMode, - block_start: T, - inline_end: T, - block_end: T, - inline_start: T, - ) -> LogicalMargin { - LogicalMargin { - block_start, - inline_end, - block_end, - inline_start, - debug_writing_mode: DebugWritingMode::new(mode), - } - } - - #[inline] - pub fn from_physical(mode: WritingMode, offsets: SideOffsets2D) -> LogicalMargin { - let block_start; - let inline_end; - let block_end; - let inline_start; - if mode.is_vertical() { - if mode.is_vertical_lr() { - block_start = offsets.left; - block_end = offsets.right; - } else { - block_start = offsets.right; - block_end = offsets.left; - } - if mode.is_inline_tb() { - inline_start = offsets.top; - inline_end = offsets.bottom; - } else { - inline_start = offsets.bottom; - inline_end = offsets.top; - } - } else { - block_start = offsets.top; - block_end = offsets.bottom; - if mode.is_bidi_ltr() { - inline_start = offsets.left; - inline_end = offsets.right; - } else { - inline_start = offsets.right; - inline_end = offsets.left; - } - } - LogicalMargin::new(mode, block_start, inline_end, block_end, inline_start) - } -} - -impl LogicalMargin { - #[inline] - pub fn new_all_same(mode: WritingMode, value: T) -> LogicalMargin { - LogicalMargin::new(mode, value, value, value, value) - } - - #[inline] - pub fn top(&self, mode: WritingMode) -> T { - self.debug_writing_mode.check(mode); - if mode.is_vertical() { - if mode.is_inline_tb() { - self.inline_start - } else { - self.inline_end - } - } else { - self.block_start - } - } - - #[inline] - pub fn set_top(&mut self, mode: WritingMode, top: T) { - self.debug_writing_mode.check(mode); - if mode.is_vertical() { - if mode.is_inline_tb() { - self.inline_start = top - } else { - self.inline_end = top - } - } else { - self.block_start = top - } - } - - #[inline] - pub fn right(&self, mode: WritingMode) -> T { - self.debug_writing_mode.check(mode); - if mode.is_vertical() { - if mode.is_vertical_lr() { - self.block_end - } else { - self.block_start - } - } else { - if mode.is_bidi_ltr() { - self.inline_end - } else { - self.inline_start - } - } - } - - #[inline] - pub fn set_right(&mut self, mode: WritingMode, right: T) { - self.debug_writing_mode.check(mode); - if mode.is_vertical() { - if mode.is_vertical_lr() { - self.block_end = right - } else { - self.block_start = right - } - } else { - if mode.is_bidi_ltr() { - self.inline_end = right - } else { - self.inline_start = right - } - } - } - - #[inline] - pub fn bottom(&self, mode: WritingMode) -> T { - self.debug_writing_mode.check(mode); - if mode.is_vertical() { - if mode.is_inline_tb() { - self.inline_end - } else { - self.inline_start - } - } else { - self.block_end - } - } - - #[inline] - pub fn set_bottom(&mut self, mode: WritingMode, bottom: T) { - self.debug_writing_mode.check(mode); - if mode.is_vertical() { - if mode.is_inline_tb() { - self.inline_end = bottom - } else { - self.inline_start = bottom - } - } else { - self.block_end = bottom - } - } - - #[inline] - pub fn left(&self, mode: WritingMode) -> T { - self.debug_writing_mode.check(mode); - if mode.is_vertical() { - if mode.is_vertical_lr() { - self.block_start - } else { - self.block_end - } - } else { - if mode.is_bidi_ltr() { - self.inline_start - } else { - self.inline_end - } - } - } - - #[inline] - pub fn set_left(&mut self, mode: WritingMode, left: T) { - self.debug_writing_mode.check(mode); - if mode.is_vertical() { - if mode.is_vertical_lr() { - self.block_start = left - } else { - self.block_end = left - } - } else { - if mode.is_bidi_ltr() { - self.inline_start = left - } else { - self.inline_end = 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 { - #[inline] - pub fn to_physical(&self, mode: WritingMode) -> SideOffsets2D { - self.debug_writing_mode.check(mode); - let top; - let right; - let bottom; - let left; - if mode.is_vertical() { - if mode.is_vertical_lr() { - left = self.block_start.clone(); - right = self.block_end.clone(); - } else { - right = self.block_start.clone(); - left = self.block_end.clone(); - } - if mode.is_inline_tb() { - top = self.inline_start.clone(); - bottom = self.inline_end.clone(); - } else { - bottom = self.inline_start.clone(); - top = self.inline_end.clone(); - } - } else { - top = self.block_start.clone(); - bottom = self.block_end.clone(); - if mode.is_bidi_ltr() { - left = self.inline_start.clone(); - right = self.inline_end.clone(); - } else { - right = self.inline_start.clone(); - left = self.inline_end.clone(); - } - } - SideOffsets2D::new(top, right, bottom, left) - } -} - -impl LogicalMargin { - #[inline] - pub fn is_zero(&self) -> bool { - self.block_start == Zero::zero() && - self.inline_end == Zero::zero() && - self.block_end == Zero::zero() && - self.inline_start == Zero::zero() - } -} - -impl> LogicalMargin { - #[inline] - pub fn inline_start_end(&self) -> T { - self.inline_start + self.inline_end - } - - #[inline] - pub fn block_start_end(&self) -> T { - self.block_start + self.block_end - } - - #[inline] - pub fn start_end(&self, direction: Direction) -> T { - match direction { - Direction::Inline => self.inline_start + self.inline_end, - Direction::Block => self.block_start + self.block_end, - } - } - - #[inline] - pub fn top_bottom(&self, mode: WritingMode) -> T { - self.debug_writing_mode.check(mode); - if mode.is_vertical() { - self.inline_start_end() - } else { - self.block_start_end() - } - } - - #[inline] - pub fn left_right(&self, mode: WritingMode) -> T { - self.debug_writing_mode.check(mode); - if mode.is_vertical() { - self.block_start_end() - } else { - self.inline_start_end() - } - } -} - -impl> Add for LogicalMargin { - type Output = LogicalMargin; - - #[inline] - fn add(self, other: LogicalMargin) -> LogicalMargin { - self.debug_writing_mode - .check_debug(other.debug_writing_mode); - LogicalMargin { - debug_writing_mode: self.debug_writing_mode, - block_start: self.block_start + other.block_start, - inline_end: self.inline_end + other.inline_end, - block_end: self.block_end + other.block_end, - inline_start: self.inline_start + other.inline_start, - } - } -} - -impl> Sub for LogicalMargin { - type Output = LogicalMargin; - - #[inline] - fn sub(self, other: LogicalMargin) -> LogicalMargin { - self.debug_writing_mode - .check_debug(other.debug_writing_mode); - LogicalMargin { - debug_writing_mode: self.debug_writing_mode, - block_start: self.block_start - other.block_start, - inline_end: self.inline_end - other.inline_end, - block_end: self.block_end - other.block_end, - inline_start: self.inline_start - other.inline_start, - } - } -} - -/// A rectangle in flow-relative dimensions -#[derive(Clone, Copy, Eq, PartialEq)] -#[cfg_attr(feature = "servo", derive(Serialize))] -pub struct LogicalRect { - pub start: LogicalPoint, - pub size: LogicalSize, - debug_writing_mode: DebugWritingMode, -} - -impl Debug for LogicalRect { - fn fmt(&self, formatter: &mut Formatter) -> Result<(), Error> { - let writing_mode_string = if cfg!(debug_assertions) { - format!("{:?}, ", self.debug_writing_mode) - } else { - "".to_owned() - }; - - write!( - formatter, - "LogicalRect({}i{:?}×b{:?}, @ (i{:?},b{:?}))", - writing_mode_string, self.size.inline, self.size.block, self.start.i, self.start.b - ) - } -} - -impl LogicalRect { - #[inline] - pub fn zero(mode: WritingMode) -> LogicalRect { - LogicalRect { - start: LogicalPoint::zero(mode), - size: LogicalSize::zero(mode), - debug_writing_mode: DebugWritingMode::new(mode), - } - } -} - -impl LogicalRect { - #[inline] - pub fn new( - mode: WritingMode, - inline_start: T, - block_start: T, - inline: T, - block: T, - ) -> LogicalRect { - LogicalRect { - start: LogicalPoint::new(mode, inline_start, block_start), - size: LogicalSize::new(mode, inline, block), - debug_writing_mode: DebugWritingMode::new(mode), - } - } - - #[inline] - pub fn from_point_size( - mode: WritingMode, - start: LogicalPoint, - size: LogicalSize, - ) -> LogicalRect { - start.debug_writing_mode.check(mode); - size.debug_writing_mode.check(mode); - LogicalRect { - start: start, - size: size, - debug_writing_mode: DebugWritingMode::new(mode), - } - } -} - -impl + Sub> LogicalRect { - #[inline] - pub fn from_physical( - mode: WritingMode, - rect: Rect, - container_size: Size2D, - ) -> LogicalRect { - let inline_start; - let block_start; - let inline; - let block; - if mode.is_vertical() { - inline = rect.size.height; - block = rect.size.width; - if mode.is_vertical_lr() { - block_start = rect.origin.x; - } else { - block_start = container_size.width - (rect.origin.x + rect.size.width); - } - if mode.is_inline_tb() { - inline_start = rect.origin.y; - } else { - inline_start = container_size.height - (rect.origin.y + rect.size.height); - } - } else { - inline = rect.size.width; - block = rect.size.height; - block_start = rect.origin.y; - if mode.is_bidi_ltr() { - inline_start = rect.origin.x; - } else { - inline_start = container_size.width - (rect.origin.x + rect.size.width); - } - } - LogicalRect { - start: LogicalPoint::new(mode, inline_start, block_start), - size: LogicalSize::new(mode, inline, block), - debug_writing_mode: DebugWritingMode::new(mode), - } - } - - #[inline] - pub fn inline_end(&self) -> T { - self.start.i + self.size.inline - } - - #[inline] - pub fn block_end(&self) -> T { - self.start.b + self.size.block - } - - #[inline] - pub fn to_physical(&self, mode: WritingMode, container_size: Size2D) -> Rect { - self.debug_writing_mode.check(mode); - let x; - let y; - let width; - let height; - if mode.is_vertical() { - width = self.size.block; - height = self.size.inline; - if mode.is_vertical_lr() { - x = self.start.b; - } else { - x = container_size.width - self.block_end(); - } - if mode.is_inline_tb() { - y = self.start.i; - } else { - y = container_size.height - self.inline_end(); - } - } else { - width = self.size.inline; - height = self.size.block; - y = self.start.b; - if mode.is_bidi_ltr() { - x = self.start.i; - } else { - x = container_size.width - self.inline_end(); - } - } - Rect { - origin: Point2D::new(x, y), - size: Size2D::new(width, height), - } - } - - #[inline] - pub fn convert( - &self, - mode_from: WritingMode, - mode_to: WritingMode, - container_size: Size2D, - ) -> LogicalRect { - if mode_from == mode_to { - self.debug_writing_mode.check(mode_from); - *self - } else { - LogicalRect::from_physical( - mode_to, - self.to_physical(mode_from, container_size), - container_size, - ) - } - } - - pub fn translate_by_size(&self, offset: LogicalSize) -> LogicalRect { - LogicalRect { - start: self.start + offset, - ..*self - } - } - - pub fn translate(&self, offset: &LogicalPoint) -> LogicalRect { - LogicalRect { - start: self.start + - LogicalSize { - inline: offset.i, - block: offset.b, - debug_writing_mode: offset.debug_writing_mode, - }, - size: self.size, - debug_writing_mode: self.debug_writing_mode, - } - } -} - -impl + Sub> LogicalRect { - #[inline] - pub fn union(&self, other: &LogicalRect) -> LogicalRect { - self.debug_writing_mode - .check_debug(other.debug_writing_mode); - - let inline_start = min(self.start.i, other.start.i); - let block_start = min(self.start.b, other.start.b); - LogicalRect { - start: LogicalPoint { - i: inline_start, - b: block_start, - debug_writing_mode: self.debug_writing_mode, - }, - size: LogicalSize { - inline: max(self.inline_end(), other.inline_end()) - inline_start, - block: max(self.block_end(), other.block_end()) - block_start, - debug_writing_mode: self.debug_writing_mode, - }, - debug_writing_mode: self.debug_writing_mode, - } - } -} - -impl + Sub> Add> for LogicalRect { - type Output = LogicalRect; - - #[inline] - fn add(self, other: LogicalMargin) -> LogicalRect { - self.debug_writing_mode - .check_debug(other.debug_writing_mode); - LogicalRect { - start: LogicalPoint { - // Growing a rectangle on the start side means pushing its - // start point on the negative direction. - i: self.start.i - other.inline_start, - b: self.start.b - other.block_start, - debug_writing_mode: self.debug_writing_mode, - }, - size: LogicalSize { - inline: self.size.inline + other.inline_start_end(), - block: self.size.block + other.block_start_end(), - debug_writing_mode: self.debug_writing_mode, - }, - debug_writing_mode: self.debug_writing_mode, - } - } -} - -impl + Sub> Sub> for LogicalRect { - type Output = LogicalRect; - - #[inline] - fn sub(self, other: LogicalMargin) -> LogicalRect { - self.debug_writing_mode - .check_debug(other.debug_writing_mode); - LogicalRect { - start: LogicalPoint { - // Shrinking a rectangle on the start side means pushing its - // start point on the positive direction. - i: self.start.i + other.inline_start, - b: self.start.b + other.block_start, - debug_writing_mode: self.debug_writing_mode, - }, - size: LogicalSize { - inline: self.size.inline - other.inline_start_end(), - block: self.size.block - other.block_start_end(), - debug_writing_mode: self.debug_writing_mode, - }, - debug_writing_mode: self.debug_writing_mode, - } - } -} - -#[derive(Clone, Copy, Debug, PartialEq)] -pub enum PhysicalSide { - Top, - Right, - Bottom, - Left, -} - -#[derive(Clone, Copy, Debug, PartialEq)] -pub enum PhysicalCorner { - TopLeft, - TopRight, - BottomRight, - BottomLeft, -} diff --git a/components/style/macros.rs b/components/style/macros.rs deleted file mode 100644 index 5f3a1ea463b..00000000000 --- a/components/style/macros.rs +++ /dev/null @@ -1,98 +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/. */ - -//! Various macro helpers. - -macro_rules! exclusive_value { - (($value:ident, $set:expr) => $ident:path) => { - if $value.intersects($set) { - return Err(()); - } else { - $ident - } - }; -} - -#[cfg(feature = "gecko")] -macro_rules! impl_gecko_keyword_conversions { - ($name:ident, $utype:ty) => { - impl From<$utype> for $name { - fn from(bits: $utype) -> $name { - $name::from_gecko_keyword(bits) - } - } - - impl From<$name> for $utype { - fn from(v: $name) -> $utype { - v.to_gecko_keyword() - } - } - }; -} - -macro_rules! trivial_to_computed_value { - ($name:ty) => { - impl $crate::values::computed::ToComputedValue for $name { - type ComputedValue = $name; - - fn to_computed_value(&self, _: &$crate::values::computed::Context) -> Self { - self.clone() - } - - fn from_computed_value(other: &Self) -> Self { - other.clone() - } - } - }; -} - -/// A macro to parse an identifier, or return an `UnexpectedIdent` error -/// otherwise. -/// -/// FIXME(emilio): The fact that `UnexpectedIdent` is a `SelectorParseError` -/// doesn't make a lot of sense to me. -macro_rules! try_match_ident_ignore_ascii_case { - ($input:expr, $( $match_body:tt )*) => {{ - let location = $input.current_source_location(); - let ident = $input.expect_ident_cloned()?; - match_ignore_ascii_case! { &ident, - $( $match_body )* - _ => return Err(location.new_custom_error( - ::selectors::parser::SelectorParseErrorKind::UnexpectedIdent(ident.clone()) - )) - } - }} -} - -#[cfg(feature = "servo")] -macro_rules! local_name { - ($s:tt) => { - $crate::values::GenericAtomIdent(html5ever::local_name!($s)) - }; -} - -#[cfg(feature = "servo")] -macro_rules! ns { - () => { - $crate::values::GenericAtomIdent(html5ever::ns!()) - }; - ($s:tt) => { - $crate::values::GenericAtomIdent(html5ever::ns!($s)) - }; -} - -#[cfg(feature = "gecko")] -macro_rules! local_name { - ($s:tt) => { - $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 deleted file mode 100644 index bc9c797a5d2..00000000000 --- a/components/style/matching.rs +++ /dev/null @@ -1,1096 +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/. */ - -//! High-level interface to CSS selector matching. - -#![allow(unsafe_code)] -#![deny(missing_docs)] - -use crate::computed_value_flags::ComputedValueFlags; -use crate::context::{CascadeInputs, ElementCascadeInputs, QuirksMode}; -use crate::context::{SharedStyleContext, StyleContext}; -use crate::data::{ElementData, ElementStyles}; -use crate::dom::TElement; -#[cfg(feature = "servo")] -use crate::dom::TNode; -use crate::invalidation::element::restyle_hints::RestyleHint; -use crate::properties::longhands::display::computed_value::T as Display; -use crate::properties::ComputedValues; -use crate::properties::PropertyDeclarationBlock; -use crate::rule_tree::{CascadeLevel, StrongRuleNode}; -use crate::selector_parser::{PseudoElement, RestyleDamage}; -use crate::shared_lock::Locked; -use crate::style_resolver::ResolvedElementStyles; -use crate::style_resolver::{PseudoElementResolution, StyleResolverForElement}; -use crate::stylesheets::layer_rule::LayerOrder; -use crate::stylist::RuleInclusion; -use crate::traversal_flags::TraversalFlags; -use servo_arc::{Arc, ArcBorrow}; - -/// Represents the result of comparing an element's old and new style. -#[derive(Debug)] -pub struct StyleDifference { - /// The resulting damage. - pub damage: RestyleDamage, - - /// Whether any styles changed. - pub change: StyleChange, -} - -/// Represents whether or not the style of an element has changed. -#[derive(Clone, Copy, Debug)] -pub enum StyleChange { - /// The style hasn't changed. - Unchanged, - /// The style has changed. - Changed { - /// Whether only reset structs changed. - reset_only: bool, - }, -} - -/// Whether or not newly computed values for an element need to be cascaded to -/// children (or children might need to be re-matched, e.g., for container -/// queries). -#[derive(Clone, Copy, Debug, Eq, Ord, PartialEq, PartialOrd)] -pub enum ChildRestyleRequirement { - /// Old and new computed values were the same, or we otherwise know that - /// we won't bother recomputing style for children, so we can skip cascading - /// the new values into child elements. - CanSkipCascade = 0, - /// The same as `MustCascadeChildren`, but we only need to actually - /// recascade if the child inherits any explicit reset style. - MustCascadeChildrenIfInheritResetStyle = 1, - /// Old and new computed values were different, so we must cascade the - /// new values to children. - MustCascadeChildren = 2, - /// The same as `MustCascadeChildren`, but for the entire subtree. This is - /// used to handle root font-size updates needing to recascade the whole - /// document. - MustCascadeDescendants = 3, - /// We need to re-match the whole subttree. This is used to handle container - /// query relative unit changes for example. Container query size changes - /// also trigger re-match, but after layout. - MustMatchDescendants = 4, -} - -/// Determines which styles are being cascaded currently. -#[derive(Clone, Copy, Debug, Eq, PartialEq)] -enum CascadeVisitedMode { - /// Cascade the regular, unvisited styles. - Unvisited, - /// Cascade the styles used when an element's relevant link is visited. A - /// "relevant link" is the element being matched if it is a link or the - /// nearest ancestor link. - Visited, -} - -trait PrivateMatchMethods: TElement { - fn replace_single_rule_node( - context: &SharedStyleContext, - level: CascadeLevel, - layer_order: LayerOrder, - pdb: Option>>, - path: &mut StrongRuleNode, - ) -> bool { - let stylist = &context.stylist; - let guards = &context.guards; - - let mut important_rules_changed = false; - let new_node = stylist.rule_tree().update_rule_at_level( - level, - layer_order, - pdb, - path, - guards, - &mut important_rules_changed, - ); - if let Some(n) = new_node { - *path = n; - } - important_rules_changed - } - - /// Updates the rule nodes without re-running selector matching, using just - /// the rule tree, for a specific visited mode. - /// - /// Returns true if an !important rule was replaced. - fn replace_rules_internal( - &self, - replacements: RestyleHint, - context: &mut StyleContext, - cascade_visited: CascadeVisitedMode, - cascade_inputs: &mut ElementCascadeInputs, - ) -> bool { - debug_assert!( - replacements.intersects(RestyleHint::replacements()) && - (replacements & !RestyleHint::replacements()).is_empty() - ); - - let primary_rules = match cascade_visited { - CascadeVisitedMode::Unvisited => cascade_inputs.primary.rules.as_mut(), - CascadeVisitedMode::Visited => cascade_inputs.primary.visited_rules.as_mut(), - }; - - let primary_rules = match primary_rules { - Some(r) => r, - None => return false, - }; - - if !context.shared.traversal_flags.for_animation_only() { - let mut result = false; - if replacements.contains(RestyleHint::RESTYLE_STYLE_ATTRIBUTE) { - let style_attribute = self.style_attribute(); - result |= Self::replace_single_rule_node( - context.shared, - CascadeLevel::same_tree_author_normal(), - LayerOrder::root(), - style_attribute, - primary_rules, - ); - result |= Self::replace_single_rule_node( - context.shared, - CascadeLevel::same_tree_author_important(), - LayerOrder::root(), - style_attribute, - primary_rules, - ); - // FIXME(emilio): Still a hack! - self.unset_dirty_style_attribute(); - } - return result; - } - - // Animation restyle hints are processed prior to other restyle - // hints in the animation-only traversal. - // - // Non-animation restyle hints will be processed in a subsequent - // normal traversal. - if replacements.intersects(RestyleHint::for_animations()) { - debug_assert!(context.shared.traversal_flags.for_animation_only()); - - if replacements.contains(RestyleHint::RESTYLE_SMIL) { - Self::replace_single_rule_node( - context.shared, - CascadeLevel::SMILOverride, - LayerOrder::root(), - self.smil_override(), - primary_rules, - ); - } - - if replacements.contains(RestyleHint::RESTYLE_CSS_TRANSITIONS) { - Self::replace_single_rule_node( - context.shared, - CascadeLevel::Transitions, - LayerOrder::root(), - self.transition_rule(&context.shared) - .as_ref() - .map(|a| a.borrow_arc()), - primary_rules, - ); - } - - if replacements.contains(RestyleHint::RESTYLE_CSS_ANIMATIONS) { - Self::replace_single_rule_node( - context.shared, - CascadeLevel::Animations, - LayerOrder::root(), - self.animation_rule(&context.shared) - .as_ref() - .map(|a| a.borrow_arc()), - primary_rules, - ); - } - } - - false - } - - /// If there is no transition rule in the ComputedValues, it returns None. - fn after_change_style( - &self, - context: &mut StyleContext, - primary_style: &Arc, - ) -> Option> { - let rule_node = primary_style.rules(); - let without_transition_rules = context - .shared - .stylist - .rule_tree() - .remove_transition_rule_if_applicable(rule_node); - if without_transition_rules == *rule_node { - // We don't have transition rule in this case, so return None to let - // the caller use the original ComputedValues. - return None; - } - - // FIXME(bug 868975): We probably need to transition visited style as - // well. - let inputs = CascadeInputs { - rules: Some(without_transition_rules), - visited_rules: primary_style.visited_rules().cloned(), - flags: primary_style.flags.for_cascade_inputs(), - }; - - // Actually `PseudoElementResolution` doesn't really matter. - let style = StyleResolverForElement::new( - *self, - context, - RuleInclusion::All, - PseudoElementResolution::IfApplicable, - ) - .cascade_style_and_visited_with_default_parents(inputs); - - Some(style.0) - } - - fn needs_animations_update( - &self, - context: &mut StyleContext, - old_style: Option<&ComputedValues>, - 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 has_animations = self.has_css_animations(&context.shared, pseudo_element); - if !new_style_specifies_animations && !has_animations { - return false; - } - - let old_style = match old_style { - Some(old) => old, - // If we have no old style but have animations, we may be a - // pseudo-element which was re-created without style changes. - // - // This can happen when we reframe the pseudo-element without - // restyling it (due to content insertion on a flex container or - // such, for example). See bug 1564366. - // - // FIXME(emilio): The really right fix for this is keeping the - // pseudo-element itself around on reframes, but that's a bit - // harder. If we do that we can probably remove quite a lot of the - // EffectSet complexity though, since right now it's stored on the - // parent element for pseudo-elements given we need to keep it - // around... - None => { - return new_style_specifies_animations || new_style.is_pseudo_style(); - }, - }; - - let old_ui_style = old_style.get_ui(); - - let keyframes_could_have_changed = context - .shared - .traversal_flags - .contains(TraversalFlags::ForCSSRuleChanges); - - // If the traversal is triggered due to changes in CSS rules changes, we - // need to try to update all CSS animations on the element if the - // element has or will have CSS animation style regardless of whether - // the animation is running or not. - // - // TODO: We should check which @keyframes were added/changed/deleted and - // update only animations corresponding to those @keyframes. - if keyframes_could_have_changed { - return true; - } - - // If the animations changed, well... - if !old_ui_style.animations_equals(new_ui_style) { - return true; - } - - let old_display = old_style.clone_display(); - let new_display = new_style.clone_display(); - - // If we were display: none, we may need to trigger animations. - if old_display == Display::None && new_display != Display::None { - return new_style_specifies_animations; - } - - // If we are becoming display: none, we may need to stop animations. - if old_display != Display::None && new_display == Display::None { - return has_animations; - } - - // We might need to update animations if writing-mode or direction - // changed, and any of the animations contained logical properties. - // - // We may want to be more granular, but it's probably not worth it. - if new_style.writing_mode != old_style.writing_mode { - return has_animations; - } - - false - } - - fn might_need_transitions_update( - &self, - context: &StyleContext, - old_style: Option<&ComputedValues>, - new_style: &ComputedValues, - pseudo_element: Option, - ) -> bool { - let old_style = match old_style { - Some(v) => v, - None => return false, - }; - - if !self.has_css_transitions(context.shared, pseudo_element) && - !new_style.get_ui().specifies_transitions() - { - return false; - } - - if old_style.clone_display().is_none() { - return false; - } - - return true; - } - - /// Create a SequentialTask for resolving descendants in a SMIL display - /// property animation if the display property changed from none. - #[cfg(feature = "gecko")] - fn handle_display_change_for_smil_if_needed( - &self, - context: &mut StyleContext, - old_values: Option<&ComputedValues>, - new_values: &ComputedValues, - restyle_hints: RestyleHint, - ) { - use crate::context::PostAnimationTasks; - - if !restyle_hints.intersects(RestyleHint::RESTYLE_SMIL) { - return; - } - - if new_values.is_display_property_changed_from_none(old_values) { - // When display value is changed from none to other, we need to - // traverse descendant elements in a subsequent normal - // traversal (we can't traverse them in this animation-only restyle - // since we have no way to know whether the decendants - // need to be traversed at the beginning of the animation-only - // restyle). - let task = crate::context::SequentialTask::process_post_animation( - *self, - PostAnimationTasks::DISPLAY_CHANGED_FROM_NONE_FOR_SMIL, - ); - context.thread_local.tasks.push(task); - } - } - - #[cfg(feature = "gecko")] - fn process_animations( - &self, - context: &mut StyleContext, - old_styles: &mut ElementStyles, - new_styles: &mut ResolvedElementStyles, - restyle_hint: RestyleHint, - important_rules_changed: bool, - ) { - use crate::context::UpdateAnimationsTasks; - - let new_values = new_styles.primary_style_mut(); - let old_values = &old_styles.primary; - if context.shared.traversal_flags.for_animation_only() { - self.handle_display_change_for_smil_if_needed( - context, - old_values.as_deref(), - new_values, - restyle_hint, - ); - return; - } - - // Bug 868975: These steps should examine and update the visited styles - // in addition to the unvisited styles. - - let mut tasks = UpdateAnimationsTasks::empty(); - - if old_values.as_deref().map_or_else( - || new_values.get_ui().specifies_scroll_timelines(), - |old| !old.get_ui().scroll_timelines_equals(new_values.get_ui()), - ) { - tasks.insert(UpdateAnimationsTasks::SCROLL_TIMELINES); - } - - if old_values.as_deref().map_or_else( - || new_values.get_ui().specifies_view_timelines(), - |old| !old.get_ui().view_timelines_equals(new_values.get_ui()), - ) { - tasks.insert(UpdateAnimationsTasks::VIEW_TIMELINES); - } - - if self.needs_animations_update( - context, - old_values.as_deref(), - new_values, - /* pseudo_element = */ None, - ) { - tasks.insert(UpdateAnimationsTasks::CSS_ANIMATIONS); - } - - let before_change_style = if self.might_need_transitions_update( - context, - old_values.as_deref(), - new_values, - /* pseudo_element = */ None, - ) { - let after_change_style = - if self.has_css_transitions(context.shared, /* pseudo_element = */ None) { - self.after_change_style(context, new_values) - } else { - None - }; - - // In order to avoid creating a SequentialTask for transitions which - // may not be updated, we check it per property to make sure Gecko - // side will really update transition. - let needs_transitions_update = { - // We borrow new_values here, so need to add a scope to make - // sure we release it before assigning a new value to it. - let after_change_style_ref = after_change_style.as_ref().unwrap_or(&new_values); - - self.needs_transitions_update(old_values.as_ref().unwrap(), after_change_style_ref) - }; - - if needs_transitions_update { - if let Some(values_without_transitions) = after_change_style { - *new_values = values_without_transitions; - } - tasks.insert(UpdateAnimationsTasks::CSS_TRANSITIONS); - - // We need to clone old_values into SequentialTask, so we can - // use it later. - old_values.clone() - } else { - None - } - } else { - None - }; - - if self.has_animations(&context.shared) { - tasks.insert(UpdateAnimationsTasks::EFFECT_PROPERTIES); - if important_rules_changed { - tasks.insert(UpdateAnimationsTasks::CASCADE_RESULTS); - } - if new_values.is_display_property_changed_from_none(old_values.as_deref()) { - tasks.insert(UpdateAnimationsTasks::DISPLAY_CHANGED_FROM_NONE); - } - } - - if !tasks.is_empty() { - let task = crate::context::SequentialTask::update_animations( - *self, - before_change_style, - tasks, - ); - context.thread_local.tasks.push(task); - } - } - - #[cfg(feature = "servo")] - fn process_animations( - &self, - context: &mut StyleContext, - old_styles: &mut ElementStyles, - new_resolved_styles: &mut ResolvedElementStyles, - _restyle_hint: RestyleHint, - _important_rules_changed: bool, - ) { - use crate::animation::AnimationSetKey; - use crate::dom::TDocument; - - let style_changed = self.process_animations_for_style( - context, - &mut old_styles.primary, - new_resolved_styles.primary_style_mut(), - /* pseudo_element = */ None, - ); - - // If we have modified animation or transitions, we recascade style for this node. - if style_changed { - let primary_style = new_resolved_styles.primary_style(); - let mut rule_node = primary_style.rules().clone(); - let declarations = context.shared.animations.get_all_declarations( - &AnimationSetKey::new_for_non_pseudo(self.as_node().opaque()), - context.shared.current_time_for_animations, - self.as_node().owner_doc().shared_lock(), - ); - Self::replace_single_rule_node( - &context.shared, - CascadeLevel::Transitions, - LayerOrder::root(), - declarations.transitions.as_ref().map(|a| a.borrow_arc()), - &mut rule_node, - ); - Self::replace_single_rule_node( - &context.shared, - CascadeLevel::Animations, - LayerOrder::root(), - declarations.animations.as_ref().map(|a| a.borrow_arc()), - &mut rule_node, - ); - - if rule_node != *primary_style.rules() { - let inputs = CascadeInputs { - rules: Some(rule_node), - visited_rules: primary_style.visited_rules().cloned(), - flags: primary_style.flags.for_cascade_inputs(), - }; - - new_resolved_styles.primary.style = StyleResolverForElement::new( - *self, - context, - RuleInclusion::All, - PseudoElementResolution::IfApplicable, - ) - .cascade_style_and_visited_with_default_parents(inputs); - } - } - - self.process_animations_for_pseudo( - context, - old_styles, - new_resolved_styles, - PseudoElement::Before, - ); - self.process_animations_for_pseudo( - context, - old_styles, - new_resolved_styles, - PseudoElement::After, - ); - } - - #[cfg(feature = "servo")] - fn process_animations_for_pseudo( - &self, - context: &mut StyleContext, - old_styles: &mut ElementStyles, - new_resolved_styles: &mut ResolvedElementStyles, - pseudo_element: PseudoElement, - ) { - use crate::animation::AnimationSetKey; - use crate::dom::TDocument; - - let key = AnimationSetKey::new_for_pseudo(self.as_node().opaque(), pseudo_element.clone()); - let mut style = match new_resolved_styles.pseudos.get(&pseudo_element) { - Some(style) => Arc::clone(style), - None => { - context - .shared - .animations - .cancel_all_animations_for_key(&key); - return; - }, - }; - - let mut old_style = old_styles.pseudos.get(&pseudo_element).cloned(); - self.process_animations_for_style( - context, - &mut old_style, - &mut style, - Some(pseudo_element.clone()), - ); - - let declarations = context.shared.animations.get_all_declarations( - &key, - context.shared.current_time_for_animations, - self.as_node().owner_doc().shared_lock(), - ); - if declarations.is_empty() { - return; - } - - let mut rule_node = style.rules().clone(); - Self::replace_single_rule_node( - &context.shared, - CascadeLevel::Transitions, - LayerOrder::root(), - declarations.transitions.as_ref().map(|a| a.borrow_arc()), - &mut rule_node, - ); - Self::replace_single_rule_node( - &context.shared, - CascadeLevel::Animations, - LayerOrder::root(), - declarations.animations.as_ref().map(|a| a.borrow_arc()), - &mut rule_node, - ); - if rule_node == *style.rules() { - return; - } - - let inputs = CascadeInputs { - rules: Some(rule_node), - visited_rules: style.visited_rules().cloned(), - flags: style.flags.for_cascade_inputs(), - }; - - let new_style = StyleResolverForElement::new( - *self, - context, - RuleInclusion::All, - PseudoElementResolution::IfApplicable, - ) - .cascade_style_and_visited_for_pseudo_with_default_parents( - inputs, - &pseudo_element, - &new_resolved_styles.primary, - ); - - new_resolved_styles - .pseudos - .set(&pseudo_element, new_style.0); - } - - #[cfg(feature = "servo")] - fn process_animations_for_style( - &self, - context: &mut StyleContext, - old_values: &mut Option>, - new_values: &mut Arc, - pseudo_element: Option, - ) -> bool { - use crate::animation::{AnimationSetKey, AnimationState}; - - // We need to call this before accessing the `ElementAnimationSet` from the - // map because this call will do a RwLock::read(). - let needs_animations_update = self.needs_animations_update( - context, - old_values.as_deref(), - new_values, - pseudo_element, - ); - - let might_need_transitions_update = self.might_need_transitions_update( - context, - old_values.as_deref(), - new_values, - pseudo_element, - ); - - let mut after_change_style = None; - if might_need_transitions_update { - after_change_style = self.after_change_style(context, new_values); - } - - let key = AnimationSetKey::new(self.as_node().opaque(), pseudo_element); - let shared_context = context.shared; - let mut animation_set = shared_context - .animations - .sets - .write() - .remove(&key) - .unwrap_or_default(); - - // Starting animations is expensive, because we have to recalculate the style - // for all the keyframes. We only want to do this if we think that there's a - // chance that the animations really changed. - if needs_animations_update { - let mut resolver = StyleResolverForElement::new( - *self, - context, - RuleInclusion::All, - PseudoElementResolution::IfApplicable, - ); - - animation_set.update_animations_for_new_style::( - *self, - &shared_context, - &new_values, - &mut resolver, - ); - } - - animation_set.update_transitions_for_new_style( - might_need_transitions_update, - &shared_context, - old_values.as_ref(), - after_change_style.as_ref().unwrap_or(new_values), - ); - - // We clear away any finished transitions, but retain animations, because they - // might still be used for proper calculation of `animation-fill-mode`. This - // should change the computed values in the style, so we don't need to mark - // this set as dirty. - animation_set - .transitions - .retain(|transition| transition.state != AnimationState::Finished); - - // If the ElementAnimationSet is empty, and don't store it in order to - // save memory and to avoid extra processing later. - let changed_animations = animation_set.dirty; - if !animation_set.is_empty() { - animation_set.dirty = false; - shared_context - .animations - .sets - .write() - .insert(key, animation_set); - } - - changed_animations - } - - /// Computes and applies non-redundant damage. - fn accumulate_damage_for( - &self, - shared_context: &SharedStyleContext, - damage: &mut RestyleDamage, - old_values: &ComputedValues, - new_values: &ComputedValues, - pseudo: Option<&PseudoElement>, - ) -> ChildRestyleRequirement { - debug!("accumulate_damage_for: {:?}", self); - debug_assert!(!shared_context - .traversal_flags - .contains(TraversalFlags::FinalAnimationTraversal)); - - let difference = self.compute_style_difference(old_values, new_values, pseudo); - - *damage |= difference.damage; - - debug!(" > style difference: {:?}", difference); - - // We need to cascade the children in order to ensure the correct - // propagation of inherited computed value flags. - if old_values.flags.maybe_inherited() != new_values.flags.maybe_inherited() { - debug!( - " > flags changed: {:?} != {:?}", - old_values.flags, new_values.flags - ); - return ChildRestyleRequirement::MustCascadeChildren; - } - - match difference.change { - StyleChange::Unchanged => return ChildRestyleRequirement::CanSkipCascade, - StyleChange::Changed { reset_only } => { - // If inherited properties changed, the best we can do is - // cascade the children. - if !reset_only { - return ChildRestyleRequirement::MustCascadeChildren; - } - }, - } - - let old_display = old_values.clone_display(); - let new_display = new_values.clone_display(); - - if old_display != new_display { - // If we used to be a display: none element, and no longer are, our - // children need to be restyled because they're unstyled. - if old_display == Display::None { - return ChildRestyleRequirement::MustCascadeChildren; - } - // Blockification of children may depend on our display value, - // so we need to actually do the recascade. We could potentially - // do better, but it doesn't seem worth it. - if old_display.is_item_container() != new_display.is_item_container() { - return ChildRestyleRequirement::MustCascadeChildren; - } - // We may also need to blockify and un-blockify descendants if our - // display goes from / to display: contents, since the "layout - // parent style" changes. - if old_display.is_contents() || new_display.is_contents() { - return ChildRestyleRequirement::MustCascadeChildren; - } - // Line break suppression may also be affected if the display - // type changes from ruby to non-ruby. - #[cfg(feature = "gecko")] - { - if old_display.is_ruby_type() != new_display.is_ruby_type() { - return ChildRestyleRequirement::MustCascadeChildren; - } - } - } - - // Children with justify-items: auto may depend on our - // justify-items property value. - // - // Similarly, we could potentially do better, but this really - // seems not common enough to care about. - #[cfg(feature = "gecko")] - { - use crate::values::specified::align::AlignFlags; - - let old_justify_items = old_values.get_position().clone_justify_items(); - let new_justify_items = new_values.get_position().clone_justify_items(); - - let was_legacy_justify_items = - old_justify_items.computed.0.contains(AlignFlags::LEGACY); - - let is_legacy_justify_items = new_justify_items.computed.0.contains(AlignFlags::LEGACY); - - if is_legacy_justify_items != was_legacy_justify_items { - return ChildRestyleRequirement::MustCascadeChildren; - } - - if was_legacy_justify_items && old_justify_items.computed != new_justify_items.computed - { - return ChildRestyleRequirement::MustCascadeChildren; - } - } - - #[cfg(feature = "servo")] - { - // We may need to set or propagate the CAN_BE_FRAGMENTED bit - // on our children. - if old_values.is_multicol() != new_values.is_multicol() { - return ChildRestyleRequirement::MustCascadeChildren; - } - } - - // We could prove that, if our children don't inherit reset - // properties, we can stop the cascade. - ChildRestyleRequirement::MustCascadeChildrenIfInheritResetStyle - } -} - -impl PrivateMatchMethods for E {} - -/// The public API that elements expose for selector matching. -pub trait MatchMethods: TElement { - /// Returns the closest parent element that doesn't have a display: contents - /// style (and thus generates a box). - /// - /// This is needed to correctly handle blockification of flex and grid - /// items. - /// - /// Returns itself if the element has no parent. In practice this doesn't - /// happen because the root element is blockified per spec, but it could - /// happen if we decide to not blockify for roots of disconnected subtrees, - /// which is a kind of dubious behavior. - fn layout_parent(&self) -> Self { - let mut current = self.clone(); - loop { - current = match current.traversal_parent() { - Some(el) => el, - None => return current, - }; - - let is_display_contents = current - .borrow_data() - .unwrap() - .styles - .primary() - .is_display_contents(); - - if !is_display_contents { - return current; - } - } - } - - /// Updates the styles with the new ones, diffs them, and stores the restyle - /// damage. - fn finish_restyle( - &self, - context: &mut StyleContext, - data: &mut ElementData, - mut new_styles: ResolvedElementStyles, - important_rules_changed: bool, - ) -> ChildRestyleRequirement { - use std::cmp; - - self.process_animations( - context, - &mut data.styles, - &mut new_styles, - data.hint, - important_rules_changed, - ); - - // First of all, update the styles. - let old_styles = data.set_styles(new_styles); - - let new_primary_style = data.styles.primary.as_ref().unwrap(); - - let mut restyle_requirement = ChildRestyleRequirement::CanSkipCascade; - let is_root = new_primary_style - .flags - .contains(ComputedValueFlags::IS_ROOT_ELEMENT_STYLE); - let is_container = !new_primary_style - .get_box() - .clone_container_type() - .is_normal(); - if is_root || is_container { - let new_font_size = new_primary_style.get_font().clone_font_size(); - let old_font_size = old_styles - .primary - .as_ref() - .map(|s| s.get_font().clone_font_size()); - - if old_font_size != Some(new_font_size) { - if is_root { - let device = context.shared.stylist.device(); - debug_assert!(self.owner_doc_matches_for_testing(device)); - device.set_root_font_size(new_font_size.computed_size().into()); - if device.used_root_font_size() { - // If the root font-size changed since last time, and something - // in the document did use rem units, ensure we recascade the - // entire tree. - restyle_requirement = ChildRestyleRequirement::MustCascadeDescendants; - } - } - - if is_container && old_font_size.is_some() { - // TODO(emilio): Maybe only do this if we were matched - // against relative font sizes? - // Also, maybe we should do this as well for font-family / - // etc changes (for ex/ch/ic units to work correctly)? We - // should probably do the optimization mentioned above if - // so. - restyle_requirement = ChildRestyleRequirement::MustMatchDescendants; - } - } - } - - if context.shared.stylist.quirks_mode() == QuirksMode::Quirks { - if self.is_html_document_body_element() { - // NOTE(emilio): We _could_ handle dynamic changes to it if it - // changes and before we reach our children the cascade stops, - // but we don't track right now whether we use the document body - // color, and nobody else handles that properly anyway. - let device = context.shared.stylist.device(); - - // Needed for the "inherit from body" quirk. - let text_color = new_primary_style.get_inherited_text().clone_color(); - device.set_body_text_color(text_color); - } - } - - // Don't accumulate damage if we're in the final animation traversal. - if context - .shared - .traversal_flags - .contains(TraversalFlags::FinalAnimationTraversal) - { - return ChildRestyleRequirement::MustCascadeChildren; - } - - // Also, don't do anything if there was no style. - let old_primary_style = match old_styles.primary { - Some(s) => s, - None => return ChildRestyleRequirement::MustCascadeChildren, - }; - - let old_container_type = old_primary_style.clone_container_type(); - let new_container_type = new_primary_style.clone_container_type(); - if old_container_type != new_container_type && !new_container_type.is_size_container_type() - { - // Stopped being a size container. Re-evaluate container queries and units on all our descendants. - // Changes into and between different size containment is handled in `UpdateContainerQueryStyles`. - restyle_requirement = ChildRestyleRequirement::MustMatchDescendants; - } else if old_container_type.is_size_container_type() && - !old_primary_style.is_display_contents() && - new_primary_style.is_display_contents() - { - // Also re-evaluate when a container gets 'display: contents', since size queries will now evaluate to unknown. - // Other displays like 'inline' will keep generating a box, so they are handled in `UpdateContainerQueryStyles`. - restyle_requirement = ChildRestyleRequirement::MustMatchDescendants; - } - - restyle_requirement = cmp::max( - restyle_requirement, - self.accumulate_damage_for( - context.shared, - &mut data.damage, - &old_primary_style, - new_primary_style, - None, - ), - ); - - if data.styles.pseudos.is_empty() && old_styles.pseudos.is_empty() { - // This is the common case; no need to examine pseudos here. - return restyle_requirement; - } - - let pseudo_styles = old_styles - .pseudos - .as_array() - .iter() - .zip(data.styles.pseudos.as_array().iter()); - - for (i, (old, new)) in pseudo_styles.enumerate() { - match (old, new) { - (&Some(ref old), &Some(ref new)) => { - self.accumulate_damage_for( - context.shared, - &mut data.damage, - old, - new, - Some(&PseudoElement::from_eager_index(i)), - ); - }, - (&None, &None) => {}, - _ => { - // It's possible that we're switching from not having - // ::before/::after at all to having styles for them but not - // actually having a useful pseudo-element. Check for that - // case. - let pseudo = PseudoElement::from_eager_index(i); - let new_pseudo_should_exist = - new.as_ref().map_or(false, |s| pseudo.should_exist(s)); - let old_pseudo_should_exist = - old.as_ref().map_or(false, |s| pseudo.should_exist(s)); - if new_pseudo_should_exist != old_pseudo_should_exist { - data.damage |= RestyleDamage::reconstruct(); - return restyle_requirement; - } - }, - } - } - - restyle_requirement - } - - /// Updates the rule nodes without re-running selector matching, using just - /// the rule tree. - /// - /// Returns true if an !important rule was replaced. - fn replace_rules( - &self, - replacements: RestyleHint, - context: &mut StyleContext, - cascade_inputs: &mut ElementCascadeInputs, - ) -> bool { - let mut result = false; - result |= self.replace_rules_internal( - replacements, - context, - CascadeVisitedMode::Unvisited, - cascade_inputs, - ); - result |= self.replace_rules_internal( - replacements, - context, - CascadeVisitedMode::Visited, - cascade_inputs, - ); - result - } - - /// Given the old and new style of this element, and whether it's a - /// pseudo-element, compute the restyle damage used to determine which - /// kind of layout or painting operations we'll need. - fn compute_style_difference( - &self, - old_values: &ComputedValues, - new_values: &ComputedValues, - pseudo: Option<&PseudoElement>, - ) -> StyleDifference { - debug_assert!(pseudo.map_or(true, |p| p.is_eager())); - RestyleDamage::compute_style_difference(old_values, new_values) - } -} - -impl MatchMethods for E {} diff --git a/components/style/media_queries/media_list.rs b/components/style/media_queries/media_list.rs deleted file mode 100644 index 3c2ba9ee5c1..00000000000 --- a/components/style/media_queries/media_list.rs +++ /dev/null @@ -1,150 +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 media query list: -//! -//! https://drafts.csswg.org/mediaqueries/#typedef-media-query-list - -use super::{Device, MediaQuery, Qualifier}; -use crate::context::QuirksMode; -use crate::error_reporting::ContextualParseError; -use crate::parser::ParserContext; -use crate::queries::condition::KleeneValue; -use crate::values::computed; -use cssparser::{Delimiter, Parser}; -use cssparser::{ParserInput, Token}; - -/// A type that encapsulates a media query list. -#[derive(Clone, MallocSizeOf, ToCss, ToShmem)] -#[css(comma, derive_debug)] -pub struct MediaList { - /// The list of media queries. - #[css(iterable)] - pub media_queries: Vec, -} - -impl MediaList { - /// Parse a media query list from CSS. - /// - /// Always returns a media query list. If any invalid media query is - /// found, the media query list is only filled with the equivalent of - /// "not all", see: - /// - /// - pub fn parse(context: &ParserContext, input: &mut Parser) -> Self { - if input.is_exhausted() { - return Self::empty(); - } - - let mut media_queries = vec![]; - loop { - let start_position = input.position(); - match input.parse_until_before(Delimiter::Comma, |i| MediaQuery::parse(context, i)) { - Ok(mq) => { - media_queries.push(mq); - }, - Err(err) => { - media_queries.push(MediaQuery::never_matching()); - let location = err.location; - let error = ContextualParseError::InvalidMediaRule( - input.slice_from(start_position), - err, - ); - context.log_css_error(location, error); - }, - } - - match input.next() { - Ok(&Token::Comma) => {}, - Ok(_) => unreachable!(), - Err(_) => break, - } - } - - MediaList { media_queries } - } - - /// Create an empty MediaList. - pub fn empty() -> Self { - MediaList { - media_queries: vec![], - } - } - - /// Evaluate a whole `MediaList` against `Device`. - 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.iter().any(|mq| { - let mut query_match = if mq.media_type.matches(device.media_type()) { - mq.condition - .as_ref() - .map_or(KleeneValue::True, |c| c.matches(context)) - } else { - KleeneValue::False - }; - - // Apply the logical NOT qualifier to the result - if matches!(mq.qualifier, Some(Qualifier::Not)) { - query_match = !query_match; - } - query_match.to_bool(/* unknown = */ false) - }) - }) - } - - /// Whether this `MediaList` contains no media queries. - pub fn is_empty(&self) -> bool { - self.media_queries.is_empty() - } - - /// Whether this `MediaList` depends on the viewport size. - pub fn is_viewport_dependent(&self) -> bool { - self.media_queries.iter().any(|q| q.is_viewport_dependent()) - } - - /// Append a new media query item to the media list. - /// - /// - /// Returns true if added, false if fail to parse the medium string. - pub fn append_medium(&mut self, context: &ParserContext, new_medium: &str) -> bool { - let mut input = ParserInput::new(new_medium); - let mut parser = Parser::new(&mut input); - let new_query = match MediaQuery::parse(&context, &mut parser) { - Ok(query) => query, - Err(_) => { - return false; - }, - }; - // This algorithm doesn't actually matches the current spec, - // but it matches the behavior of Gecko and Edge. - // See https://github.com/w3c/csswg-drafts/issues/697 - self.media_queries.retain(|query| query != &new_query); - self.media_queries.push(new_query); - true - } - - /// Delete a media query from the media list. - /// - /// - /// Returns true if found and deleted, false otherwise. - pub fn delete_medium(&mut self, context: &ParserContext, old_medium: &str) -> bool { - let mut input = ParserInput::new(old_medium); - let mut parser = Parser::new(&mut input); - let old_query = match MediaQuery::parse(context, &mut parser) { - Ok(query) => query, - Err(_) => { - return false; - }, - }; - let old_len = self.media_queries.len(); - self.media_queries.retain(|query| query != &old_query); - old_len != self.media_queries.len() - } -} diff --git a/components/style/media_queries/media_query.rs b/components/style/media_queries/media_query.rs deleted file mode 100644 index c30a4453930..00000000000 --- a/components/style/media_queries/media_query.rs +++ /dev/null @@ -1,193 +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 media query: -//! -//! https://drafts.csswg.org/mediaqueries/#typedef-media-query - -use crate::parser::ParserContext; -use crate::queries::{FeatureFlags, FeatureType, QueryCondition}; -use crate::str::string_as_ascii_lowercase; -use crate::values::CustomIdent; -use crate::Atom; -use cssparser::Parser; -use std::fmt::{self, Write}; -use style_traits::{CssWriter, ParseError, ToCss}; - -/// -#[derive(Clone, Copy, Debug, Eq, MallocSizeOf, Parse, PartialEq, ToCss, ToShmem)] -pub enum Qualifier { - /// Hide a media query from legacy UAs: - /// - Only, - /// Negate a media query: - /// - Not, -} - -/// -#[derive(Clone, Debug, Eq, MallocSizeOf, PartialEq, ToShmem)] -pub struct MediaType(pub CustomIdent); - -impl MediaType { - /// The `screen` media type. - pub fn screen() -> Self { - MediaType(CustomIdent(atom!("screen"))) - } - - /// The `print` media type. - pub fn print() -> Self { - MediaType(CustomIdent(atom!("print"))) - } - - fn parse(name: &str) -> Result { - // From https://drafts.csswg.org/mediaqueries/#mq-syntax: - // - // The production does not include the keywords only, not, and, or, and layer. - // - // Here we also perform the to-ascii-lowercase part of the serialization - // algorithm: https://drafts.csswg.org/cssom/#serializing-media-queries - match_ignore_ascii_case! { name, - "not" | "or" | "and" | "only" | "layer" => Err(()), - _ => Ok(MediaType(CustomIdent(Atom::from(string_as_ascii_lowercase(name))))), - } - } -} - -/// A [media query][mq]. -/// -/// [mq]: https://drafts.csswg.org/mediaqueries/ -#[derive(Clone, Debug, MallocSizeOf, PartialEq, ToShmem)] -pub struct MediaQuery { - /// The qualifier for this query. - pub qualifier: Option, - /// The media type for this query, that can be known, unknown, or "all". - pub media_type: MediaQueryType, - /// The condition that this media query contains. This cannot have `or` - /// in the first level. - pub condition: Option, -} - -impl ToCss for MediaQuery { - fn to_css(&self, dest: &mut CssWriter) -> fmt::Result - where - W: Write, - { - if let Some(qual) = self.qualifier { - qual.to_css(dest)?; - dest.write_char(' ')?; - } - - match self.media_type { - MediaQueryType::All => { - // We need to print "all" if there's a qualifier, or there's - // just an empty list of expressions. - // - // Otherwise, we'd serialize media queries like "(min-width: - // 40px)" in "all (min-width: 40px)", which is unexpected. - if self.qualifier.is_some() || self.condition.is_none() { - dest.write_str("all")?; - } - }, - MediaQueryType::Concrete(MediaType(ref desc)) => desc.to_css(dest)?, - } - - let condition = match self.condition { - Some(ref c) => c, - None => return Ok(()), - }; - - if self.media_type != MediaQueryType::All || self.qualifier.is_some() { - dest.write_str(" and ")?; - } - - condition.to_css(dest) - } -} - -impl MediaQuery { - /// Return a media query that never matches, used for when we fail to parse - /// a given media query. - pub fn never_matching() -> Self { - Self { - qualifier: Some(Qualifier::Not), - media_type: MediaQueryType::All, - condition: None, - } - } - - /// Returns whether this media query depends on the viewport. - pub fn is_viewport_dependent(&self) -> bool { - self.condition.as_ref().map_or(false, |c| { - return c - .cumulative_flags() - .contains(FeatureFlags::VIEWPORT_DEPENDENT); - }) - } - - /// Parse a media query given css input. - /// - /// Returns an error if any of the expressions is unknown. - pub fn parse<'i, 't>( - context: &ParserContext, - input: &mut Parser<'i, 't>, - ) -> Result> { - let (qualifier, explicit_media_type) = input - .try_parse(|input| -> Result<_, ()> { - let qualifier = input.try_parse(Qualifier::parse).ok(); - let ident = input.expect_ident().map_err(|_| ())?; - let media_type = MediaQueryType::parse(&ident)?; - Ok((qualifier, Some(media_type))) - }) - .unwrap_or_default(); - - let condition = if explicit_media_type.is_none() { - Some(QueryCondition::parse(context, input, FeatureType::Media)?) - } else if input.try_parse(|i| i.expect_ident_matching("and")).is_ok() { - Some(QueryCondition::parse_disallow_or( - context, - input, - FeatureType::Media, - )?) - } else { - None - }; - - let media_type = explicit_media_type.unwrap_or(MediaQueryType::All); - Ok(Self { - qualifier, - media_type, - condition, - }) - } -} - -/// -#[derive(Clone, Debug, Eq, MallocSizeOf, PartialEq, ToShmem)] -pub enum MediaQueryType { - /// A media type that matches every device. - All, - /// A specific media type. - Concrete(MediaType), -} - -impl MediaQueryType { - fn parse(ident: &str) -> Result { - match_ignore_ascii_case! { ident, - "all" => return Ok(MediaQueryType::All), - _ => (), - }; - - // If parseable, accept this type as a concrete type. - MediaType::parse(ident).map(MediaQueryType::Concrete) - } - - /// Returns whether this media query type matches a MediaType. - pub fn matches(&self, other: MediaType) -> bool { - match *self { - MediaQueryType::All => true, - MediaQueryType::Concrete(ref known_type) => *known_type == other, - } - } -} diff --git a/components/style/media_queries/mod.rs b/components/style/media_queries/mod.rs deleted file mode 100644 index 833f6f53cb9..00000000000 --- a/components/style/media_queries/mod.rs +++ /dev/null @@ -1,18 +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/. */ - -//! [Media queries][mq]. -//! -//! [mq]: https://drafts.csswg.org/mediaqueries/ - -mod media_list; -mod media_query; - -pub use self::media_list::MediaList; -pub use self::media_query::{MediaQuery, MediaQueryType, MediaType, Qualifier}; - -#[cfg(feature = "gecko")] -pub use crate::gecko::media_queries::Device; -#[cfg(feature = "servo")] -pub use crate::servo::media_queries::Device; diff --git a/components/style/parallel.rs b/components/style/parallel.rs deleted file mode 100644 index 2d0e0c7ffcc..00000000000 --- a/components/style/parallel.rs +++ /dev/null @@ -1,197 +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/. */ - -//! Implements parallel traversal over the DOM tree. -//! -//! This traversal is based on Rayon, and therefore its safety is largely -//! verified by the type system. -//! -//! The primary trickiness and fine print for the above relates to the -//! thread safety of the DOM nodes themselves. Accessing a DOM element -//! concurrently on multiple threads is actually mostly "safe", since all -//! the mutable state is protected by an AtomicRefCell, and so we'll -//! generally panic if something goes wrong. Still, we try to to enforce our -//! thread invariants at compile time whenever possible. As such, TNode and -//! TElement are not Send, so ordinary style system code cannot accidentally -//! share them with other threads. In the parallel traversal, we explicitly -//! invoke |unsafe { SendNode::new(n) }| to put nodes in containers that may -//! be sent to other threads. This occurs in only a handful of places and is -//! easy to grep for. At the time of this writing, there is no other unsafe -//! code in the parallel traversal. - -#![deny(missing_docs)] - -use crate::context::{StyleContext, ThreadLocalStyleContext}; -use crate::dom::{OpaqueNode, SendNode, TElement}; -use crate::scoped_tls::ScopedTLS; -use crate::traversal::{DomTraversal, PerLevelTraversalData}; -use rayon; -use std::collections::VecDeque; - -/// The minimum stack size for a thread in the styling pool, in kilobytes. -#[cfg(feature = "gecko")] -pub const STYLE_THREAD_STACK_SIZE_KB: usize = 256; - -/// The minimum stack size for a thread in the styling pool, in kilobytes. -/// Servo requires a bigger stack in debug builds. -#[cfg(feature = "servo")] -pub const STYLE_THREAD_STACK_SIZE_KB: usize = 512; - -/// The stack margin. If we get this deep in the stack, we will skip recursive -/// optimizations to ensure that there is sufficient room for non-recursive work. -/// -/// We allocate large safety margins because certain OS calls can use very large -/// amounts of stack space [1]. Reserving a larger-than-necessary stack costs us -/// address space, but if we keep our safety margin big, we will generally avoid -/// committing those extra pages, and only use them in edge cases that would -/// otherwise cause crashes. -/// -/// When measured with 128KB stacks and 40KB margin, we could support 53 -/// levels of recursion before the limiter kicks in, on x86_64-Linux [2]. When -/// we doubled the stack size, we added it all to the safety margin, so we should -/// be able to get the same amount of recursion. -/// -/// [1] https://bugzilla.mozilla.org/show_bug.cgi?id=1395708#c15 -/// [2] See Gecko bug 1376883 for more discussion on the measurements. -pub const STACK_SAFETY_MARGIN_KB: usize = 168; - -/// A callback to create our thread local context. This needs to be -/// out of line so we don't allocate stack space for the entire struct -/// in the caller. -#[inline(never)] -fn create_thread_local_context<'scope, E>(slot: &mut Option>) -where - E: TElement + 'scope, -{ - *slot = Some(ThreadLocalStyleContext::new()); -} - -// Sends one chunk of work to the thread-pool. -fn distribute_one_chunk<'a, 'scope, E, D>( - items: VecDeque>, - traversal_root: OpaqueNode, - work_unit_max: usize, - traversal_data: PerLevelTraversalData, - scope: &'a rayon::ScopeFifo<'scope>, - traversal: &'scope D, - tls: &'scope ScopedTLS<'scope, ThreadLocalStyleContext>, -) where - E: TElement + 'scope, - D: DomTraversal, -{ - scope.spawn_fifo(move |scope| { - #[cfg(feature = "gecko")] - gecko_profiler_label!(Layout, StyleComputation); - let mut tlc = tls.ensure(create_thread_local_context); - let mut context = StyleContext { - shared: traversal.shared_context(), - thread_local: &mut *tlc, - }; - style_trees( - &mut context, - items, - traversal_root, - work_unit_max, - static_prefs::pref!("layout.css.stylo-local-work-queue.in-worker") as usize, - traversal_data, - Some(scope), - traversal, - Some(tls), - ); - }) -} - -/// Distributes all items into the thread pool, in `work_unit_max` chunks. -fn distribute_work<'a, 'scope, E, D>( - mut items: VecDeque>, - traversal_root: OpaqueNode, - work_unit_max: usize, - traversal_data: PerLevelTraversalData, - scope: &'a rayon::ScopeFifo<'scope>, - traversal: &'scope D, - tls: &'scope ScopedTLS<'scope, ThreadLocalStyleContext>, -) where - E: TElement + 'scope, - D: DomTraversal, -{ - while items.len() > work_unit_max { - let rest = items.split_off(work_unit_max); - distribute_one_chunk( - items, - traversal_root, - work_unit_max, - traversal_data, - scope, - traversal, - tls, - ); - items = rest; - } - distribute_one_chunk( - items, - traversal_root, - work_unit_max, - traversal_data, - scope, - traversal, - tls, - ); -} - -/// Processes `discovered` items, possibly spawning work in other threads as needed. -#[inline] -pub fn style_trees<'a, 'scope, E, D>( - context: &mut StyleContext, - mut discovered: VecDeque>, - traversal_root: OpaqueNode, - work_unit_max: usize, - local_queue_size: usize, - mut traversal_data: PerLevelTraversalData, - scope: Option<&'a rayon::ScopeFifo<'scope>>, - traversal: &'scope D, - tls: Option<&'scope ScopedTLS<'scope, ThreadLocalStyleContext>>, -) where - E: TElement + 'scope, - D: DomTraversal, -{ - let mut nodes_remaining_at_current_depth = discovered.len(); - while let Some(node) = discovered.pop_front() { - let mut children_to_process = 0isize; - traversal.process_preorder(&traversal_data, context, *node, |n| { - children_to_process += 1; - discovered.push_back(unsafe { SendNode::new(n) }); - }); - - traversal.handle_postorder_traversal(context, traversal_root, *node, children_to_process); - - nodes_remaining_at_current_depth -= 1; - - // If we have enough children at the next depth in the DOM, spawn them to a different job - // relatively soon, while keeping always at least `local_queue_size` worth of work for - // ourselves. - let discovered_children = discovered.len() - nodes_remaining_at_current_depth; - if discovered_children >= work_unit_max && - discovered.len() >= local_queue_size + work_unit_max && - scope.is_some() - { - let kept_work = std::cmp::max(nodes_remaining_at_current_depth, local_queue_size); - let mut traversal_data_copy = traversal_data.clone(); - traversal_data_copy.current_dom_depth += 1; - distribute_work( - discovered.split_off(kept_work), - traversal_root, - work_unit_max, - traversal_data_copy, - scope.unwrap(), - traversal, - tls.unwrap(), - ); - } - - if nodes_remaining_at_current_depth == 0 { - traversal_data.current_dom_depth += 1; - nodes_remaining_at_current_depth = discovered.len(); - } - } -} diff --git a/components/style/parser.rs b/components/style/parser.rs deleted file mode 100644 index 8d8d408f53f..00000000000 --- a/components/style/parser.rs +++ /dev/null @@ -1,210 +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/. */ - -//! The context within which CSS code is parsed. - -use crate::context::QuirksMode; -use crate::error_reporting::{ContextualParseError, ParseErrorReporter}; -use crate::stylesheets::{CssRuleType, CssRuleTypes, Namespaces, Origin, UrlExtraData}; -use crate::use_counters::UseCounters; -use cssparser::{Parser, SourceLocation, UnicodeRange}; -use std::borrow::Cow; -use style_traits::{OneOrMoreSeparated, ParseError, ParsingMode, Separator}; - -/// Asserts that all ParsingMode flags have a matching ParsingMode value in gecko. -#[cfg(feature = "gecko")] -#[inline] -pub fn assert_parsing_mode_match() { - use crate::gecko_bindings::structs; - - macro_rules! check_parsing_modes { - ( $( $a:ident => $b:path ),*, ) => { - if cfg!(debug_assertions) { - let mut modes = ParsingMode::all(); - $( - assert_eq!(structs::$a as usize, $b.bits() as usize, stringify!($b)); - modes.remove($b); - )* - assert_eq!(modes, ParsingMode::empty(), "all ParsingMode bits should have an assertion"); - } - } - } - - check_parsing_modes! { - ParsingMode_Default => ParsingMode::DEFAULT, - ParsingMode_AllowUnitlessLength => ParsingMode::ALLOW_UNITLESS_LENGTH, - ParsingMode_AllowAllNumericValues => ParsingMode::ALLOW_ALL_NUMERIC_VALUES, - } -} - -/// The data that the parser needs from outside in order to parse a stylesheet. -pub struct ParserContext<'a> { - /// The `Origin` of the stylesheet, whether it's a user, author or - /// user-agent stylesheet. - pub stylesheet_origin: Origin, - /// The extra data we need for resolving url values. - pub url_data: &'a UrlExtraData, - /// The current rule types, if any. - pub rule_types: CssRuleTypes, - /// The mode to use when parsing. - pub parsing_mode: ParsingMode, - /// The quirks mode of this stylesheet. - pub quirks_mode: QuirksMode, - /// The active error reporter, or none if error reporting is disabled. - error_reporter: Option<&'a dyn ParseErrorReporter>, - /// The currently active namespaces. - pub namespaces: Cow<'a, Namespaces>, - /// The use counters we want to record while parsing style rules, if any. - pub use_counters: Option<&'a UseCounters>, -} - -impl<'a> ParserContext<'a> { - /// Create a parser context. - #[inline] - pub fn new( - stylesheet_origin: Origin, - url_data: &'a UrlExtraData, - rule_type: Option, - parsing_mode: ParsingMode, - quirks_mode: QuirksMode, - namespaces: Cow<'a, Namespaces>, - error_reporter: Option<&'a dyn ParseErrorReporter>, - use_counters: Option<&'a UseCounters>, - ) -> Self { - Self { - stylesheet_origin, - url_data, - rule_types: rule_type.map(CssRuleTypes::from).unwrap_or_default(), - parsing_mode, - quirks_mode, - error_reporter, - namespaces, - use_counters, - } - } - - /// Temporarily sets the rule_type and executes the callback function, returning its result. - pub fn nest_for_rule( - &mut self, - rule_type: CssRuleType, - cb: impl FnOnce(&mut Self) -> R, - ) -> R { - let old_rule_types = self.rule_types; - self.rule_types.insert(rule_type); - let r = cb(self); - self.rule_types = old_rule_types; - r - } - - /// Whether we're in a @page rule. - #[inline] - pub fn in_page_rule(&self) -> bool { - self.rule_types.contains(CssRuleType::Page) - } - - /// Get the rule type, which assumes that one is available. - pub fn rule_types(&self) -> CssRuleTypes { - self.rule_types - } - - /// Returns whether CSS error reporting is enabled. - #[inline] - pub fn error_reporting_enabled(&self) -> bool { - self.error_reporter.is_some() - } - - /// Record a CSS parse error with this context’s error reporting. - pub fn log_css_error(&self, location: SourceLocation, error: ContextualParseError) { - let error_reporter = match self.error_reporter { - Some(r) => r, - None => return, - }; - - error_reporter.report_error(self.url_data, location, error) - } - - /// Whether we're in a user-agent stylesheet. - #[inline] - pub fn in_ua_sheet(&self) -> bool { - self.stylesheet_origin == Origin::UserAgent - } - - /// Returns whether chrome-only rules should be parsed. - #[inline] - pub fn chrome_rules_enabled(&self) -> bool { - self.url_data.chrome_rules_enabled() || self.stylesheet_origin == Origin::User - } - - /// Whether we're in a user-agent stylesheet or chrome rules are enabled. - #[inline] - pub fn in_ua_or_chrome_sheet(&self) -> bool { - self.in_ua_sheet() || self.chrome_rules_enabled() - } -} - -/// A trait to abstract parsing of a specified value given a `ParserContext` and -/// CSS input. -/// -/// This can be derived on keywords with `#[derive(Parse)]`. -/// -/// The derive code understands the following attributes on each of the variants: -/// -/// * `#[parse(aliases = "foo,bar")]` can be used to alias a value with another -/// at parse-time. -/// -/// * `#[parse(condition = "function")]` can be used to make the parsing of the -/// value conditional on `function`, which needs to fulfill -/// `fn(&ParserContext) -> bool`. -pub trait Parse: Sized { - /// Parse a value of this type. - /// - /// Returns an error on failure. - fn parse<'i, 't>( - context: &ParserContext, - input: &mut Parser<'i, 't>, - ) -> Result>; -} - -impl Parse for Vec -where - T: Parse + OneOrMoreSeparated, - ::S: Separator, -{ - fn parse<'i, 't>( - context: &ParserContext, - input: &mut Parser<'i, 't>, - ) -> Result> { - ::S::parse(input, |i| T::parse(context, i)) - } -} - -impl Parse for Box -where - T: Parse, -{ - fn parse<'i, 't>( - context: &ParserContext, - input: &mut Parser<'i, 't>, - ) -> Result> { - T::parse(context, input).map(Box::new) - } -} - -impl Parse for crate::OwnedStr { - fn parse<'i, 't>( - _: &ParserContext, - input: &mut Parser<'i, 't>, - ) -> Result> { - Ok(input.expect_string()?.as_ref().to_owned().into()) - } -} - -impl Parse for UnicodeRange { - fn parse<'i, 't>( - _: &ParserContext, - input: &mut Parser<'i, 't>, - ) -> Result> { - Ok(UnicodeRange::parse(input)?) - } -} diff --git a/components/style/piecewise_linear.rs b/components/style/piecewise_linear.rs deleted file mode 100644 index 84ccb7061c3..00000000000 --- a/components/style/piecewise_linear.rs +++ /dev/null @@ -1,293 +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 -use crate::values::computed::Percentage; -use core::slice::Iter; -/// draft as in https://github.com/w3c/csswg-drafts/pull/6533. -use euclid::approxeq::ApproxEq; -use itertools::Itertools; -use std::fmt::{self, Write}; -use style_traits::{CssWriter, ToCss}; - -use crate::values::CSSFloat; - -type ValueType = CSSFloat; -/// a single entry in a piecewise linear function. -#[allow(missing_docs)] -#[derive( - Clone, - Copy, - Debug, - MallocSizeOf, - PartialEq, - SpecifiedValueInfo, - ToResolvedValue, - Serialize, - Deserialize, -)] -#[repr(C)] -pub struct PiecewiseLinearFunctionEntry { - pub x: ValueType, - pub y: ValueType, -} - -impl ToCss for PiecewiseLinearFunctionEntry { - fn to_css(&self, dest: &mut CssWriter) -> fmt::Result - where - W: fmt::Write, - { - self.y.to_css(dest)?; - dest.write_char(' ')?; - Percentage(self.x).to_css(dest) - } -} - -/// Representation of a piecewise linear function, a series of linear functions. -#[derive( - Default, - Clone, - Debug, - MallocSizeOf, - PartialEq, - SpecifiedValueInfo, - ToResolvedValue, - ToCss, - Serialize, - Deserialize, -)] -#[repr(C)] -#[css(comma)] -pub struct PiecewiseLinearFunction { - #[css(iterable)] - entries: crate::OwnedSlice, -} - -/// Parameters to define one linear stop. -pub type PiecewiseLinearFunctionBuildParameters = (CSSFloat, Option); - -impl PiecewiseLinearFunction { - /// Interpolate y value given x and two points. The linear function will be rooted at the asymptote. - fn interpolate( - x: ValueType, - prev: PiecewiseLinearFunctionEntry, - next: PiecewiseLinearFunctionEntry, - asymptote: &PiecewiseLinearFunctionEntry, - ) -> ValueType { - // Short circuit if the x is on prev or next. - // `next` point is preferred as per spec. - if x.approx_eq(&next.x) { - return next.y; - } - if x.approx_eq(&prev.x) { - return prev.y; - } - // Avoid division by zero. - if prev.x.approx_eq(&next.x) { - return next.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, as per - /// https://drafts.csswg.org/css-easing-2/#linear-easing-function-output - 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 (point_b, point_a) in self.entries.iter().rev().tuple_windows() { - // Need to let point A be the _last_ point where its x is less than the input x, - // hence the reverse traversal. - if x < point_a.x { - continue; - } - return Self::interpolate(x, *point_a, *point_b, point_a); - } - unreachable!("Input is supposed to be within the entries' min & max!"); - } - - /// Create the piecewise linear function from an iterator that generates the parameter tuple. - pub fn from_iter(iter: Iter) -> Self - where - Iter: Iterator + ExactSizeIterator, - { - let mut builder = PiecewiseLinearFunctionBuilder::with_capacity(iter.len()); - for (y, x_start) in iter { - builder = builder.push(y, x_start); - } - builder.build() - } - - #[allow(missing_docs)] - pub fn iter(&self) -> Iter { - self.entries.iter() - } -} - -/// 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() - } - - /// Create a builder for a known amount of linear stop entries. - pub fn with_capacity(len: usize) -> Self { - PiecewiseLinearFunctionBuilder { - largest_x: None, - smallest_x: None, - entries: Vec::with_capacity(len), - } - } - - fn create_entry(&mut self, y: ValueType, x: Option) { - let x = match x { - Some(x) if x.is_finite() => x, - _ if self.entries.is_empty() => 0.0, // First x is 0 if not specified (Or not finite) - _ => { - 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) -> Self { - self.create_entry(y, x_start); - 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(&[PiecewiseLinearFunctionEntry { - x: 0., - y: self.entries[0].y, - }]), - }; - } - // Guaranteed at least two elements. - // Start element's x value should've been assigned when the first value was pushed. - debug_assert!( - self.entries[0].x.is_some(), - "Expected an entry with x defined!" - ); - // Spec asserts that if the last entry does not have an x value, it is assigned the largest seen x value. - self.entries - .last_mut() - .unwrap() - .x - .get_or_insert(self.largest_x.filter(|x| x > &1.0).unwrap_or(1.0)); - // Now we have at least two elements with x values, with start & end x values guaranteed. - - let mut result = Vec::with_capacity(self.entries.len()); - result.push(PiecewiseLinearFunctionEntry { - 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!"); - PiecewiseLinearFunctionEntry { - x: increment * (j + 1) as ValueType + start_x, - y: e.y, - } - }), - ); - } - result.push(PiecewiseLinearFunctionEntry { - 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/Mako-1.1.2-py2.py3-none-any.whl b/components/style/properties/Mako-1.1.2-py2.py3-none-any.whl deleted file mode 100644 index 9593025a4739374da2fde2966afdaffa1d0b8e05..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 75521 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

wOg(?)H>s{J*n4v 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%0A>{n;f&I{@ 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 diff --git a/components/style/properties/build.py b/components/style/properties/build.py deleted file mode 100644 index c03d9023a7e..00000000000 --- a/components/style/properties/build.py +++ /dev/null @@ -1,172 +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/. - -import json -import os.path -import re -import sys - -BASE = os.path.dirname(__file__.replace("\\", "/")) -sys.path.insert(0, os.path.join(BASE, "Mako-1.1.2-py2.py3-none-any.whl")) -sys.path.insert(0, BASE) # For importing `data.py` - -from mako import exceptions -from mako.lookup import TemplateLookup -from mako.template import Template - -import data - -RE_PYTHON_ADDR = re.compile(r"<.+? object at 0x[0-9a-fA-F]+>") - -OUT_DIR = os.environ.get("OUT_DIR", "") - -STYLE_STRUCT_LIST = [ - "background", - "border", - "box", - "column", - "counters", - "effects", - "font", - "inherited_box", - "inherited_svg", - "inherited_table", - "inherited_text", - "inherited_ui", - "list", - "margin", - "outline", - "page", - "padding", - "position", - "svg", - "table", - "text", - "ui", - "xul", -] - - -def main(): - usage = ( - "Usage: %s [ servo | gecko ] [ style-crate | geckolib