From 8e15389caedd9b8e1b87cc9e4bfe8350a581546d Mon Sep 17 00:00:00 2001 From: Oriol Brufau Date: Wed, 16 Aug 2023 01:11:39 +0200 Subject: [PATCH] Backport several style changes from Gecko (5) (#30099) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * style: Simplify selector flag setting now that flag setting is atomic These bits are write-only, actually, and we don't even need to read them. Differential Revision: https://phabricator.services.mozilla.com/D141888 * Further changes required by Servo * style: Support media queries for dynamic-range and video-dynamic-range This is a stub that only matches "standard" for all platforms. Differential Revision: https://phabricator.services.mozilla.com/D141053 * style: Remove :-moz-lwtheme-{brighttext,darktext} They are just convenience for :root[lwthemetextcolor="light"] (and dark, respectively), but they generally shouldn't be used for dark mode theming. In the past it was the only way to do it but now we have prefers-color-scheme. While at it, change lwthemetextcolor to be "lwtheme-brighttext" for consistency with similar code we have for popups etc, and move it to _setDarkModeAttributes. While at it, remove layout.css.moz-lwtheme.content.enabled (which is false always, we unshipped these from content successfully). Differential Revision: https://phabricator.services.mozilla.com/D141593 * style: layout.css.moz-locale-dir.content.enabled We successfully removed these from content in bug 1740230 (Firefox 96). Differential Revision: https://phabricator.services.mozilla.com/D141727 * style: Really honor background-color: transparent in HCM even for form controls I forgot we were doing this "revert-or-initial" shenanigans (which is needed for stuff like link colors to be honored), so we need to early-return. Use a more explicit test rather than a reftest for this. Differential Revision: https://phabricator.services.mozilla.com/D142063 * style: Ignore unchanged property for scroll-linked effect detector I think this is cleaner. Differential Revision: https://phabricator.services.mozilla.com/D141737 * style: Allow to derive Parse/ToCss/SpecifiedValueInfo on bitflags We keep getting this pattern of properties that have a set of joint and disjoint flags, and copy-pasting or writing the same parsing and serialization code in slightly different ways. container-type is one such type, and I think we should have a single way of dealing with this, thus implement deriving for various traits for bitflags, with an attribute that says which flags are single vs mixed. See docs and properties I ported. The remaining ones I left TODOs with, they are a bit trickier but can be ported with some care. Differential Revision: https://phabricator.services.mozilla.com/D142418 * Further changes required by Servo * style: Implement parsing / serialization for container{,-type,-name} CSS properties Two noteworthy details that may seem random otherwise: * Moving values around in nsStyleDisplay is needed so that the struct remains under the size limit that we have to avoid jumping allocator buckets. * All the test expectation churn is because tests depend on `container-type: size` parsing to run, and now they run. Tests for the relevant bits I implemented are passing, with the only exception of some `container-name-computed.html` failures which are https://github.com/w3c/csswg-drafts/issues/7181. Safari agrees with us there. Other notes when looking at the spec and seeing how it matches the implementation: * `container` syntax doesn't match spec, but matches tests and sanity: https://github.com/w3c/csswg-drafts/issues/7180 * `container-type` syntax doesn't _quite_ match spec, but matches tests and I think it's a spec bug since the definition for the missing keyword is gone: https://github.com/w3c/csswg-drafts/issues/7179 Differential Revision: https://phabricator.services.mozilla.com/D142419 * style: Remove assert that doesn't hold for text-decorations because of presentation hints MANUAL PUSH: Orange fix CLOSED TREE * style: Migrate `` `text-align` behaviour from presentation hint to UA CSS Differential Revision: https://phabricator.services.mozilla.com/D142494 * style: Deduplicate TokenList values faster Remember whether we have already de-duplicated them once and avoid doing that again. This is an alternative approach that doesn't add overhead to attribute setting in the general case. Differential Revision: https://phabricator.services.mozilla.com/D142813 * style: Inherit used color-scheme from embedder elements This allows popups and sidebars to use the chrome preferred color-scheme. This moves the responsibility of setting the content-preferred color scheme to the appropriate browsers to the front-end (via tabs.css). We still return the PreferredColorSchemeForContent() when there's no pres context (e.g., for display:none in-process iframes). We could potentially move a bunch of the pres-context data to the document instead, but that should be acceptable IMO as for general web content there's no behavior change in any case. Differential Revision: https://phabricator.services.mozilla.com/D142578 * Further changes required by Servo * style: Add basic @container rule parsing and boilerplate For now parse a MediaFeatureCondition. That needs being made more specific, but that is probably worth its own patch. Differential Revision: https://phabricator.services.mozilla.com/D143192 * Further changes required by Servo * style: Tweak cascade priority to split writing-mode and font properties This makes the worst case for cascade performance slightly more expensive (4 rather than three declaration walks), but my hope is that it will make the average case faster, since the best case is now just two walks instead of three, and writing mode properties are somewhat rare. This needs a test, but needs to wait until the writing-mode dependent viewport units land (will wait to land with a test). Differential Revision: https://phabricator.services.mozilla.com/D143261 * style: Implement new {small,large,dynamic} viewport units Differential Revision: https://phabricator.services.mozilla.com/D143252 * Further changes required by Servo * style: Implement new *vi and *vb units Differential Revision: https://phabricator.services.mozilla.com/D143253 * style: Implement prefers-contrast: custom and let prefers-contrast ride the trains Differential Revision: https://phabricator.services.mozilla.com/D143198 * style: Join servo style threads during shutdown I was unable to change the BLOOM_KEY field to no longer be leaked, as the TLS is also accessed on the main thread, which is not exited before the leak checker shuts down. Differential Revision: https://phabricator.services.mozilla.com/D143529 * Further changes required by Servo * style: Fix visited handling after bug 1763750 Before bug 1763750, we unconditionally called compute_writing_mode, which got the writing mode from the cascade mode for visited styles. However after that bug we only do that if we apply any writing-mode-related property. We could just call compute_writing_mode unconditionally, but instead it seems better to skip all that work for visited cascade and reuse the mechanism introduced in that bug to only apply the visited-dependent longhands. We assert that all visited-dependent longhands are "late" longhands, so as to also avoid applying the font group and such. Differential Revision: https://phabricator.services.mozilla.com/D143490 * style: Clean-up viewport unit resolution a bit I should've caught this when reviewing the new viewport units but alas :-) Differential Revision: https://phabricator.services.mozilla.com/D143856 * style: Implement `contain: inline-size` Differential Revision: https://phabricator.services.mozilla.com/D143501 * style: Simplify media query evaluation code a bit This patch: * Removes generic support for media features. These were used for some privileged media features but are no longer used. * Simplifies media feature getters by shifting the responsibility of dealing with RangeOrOperator to the caller. This makes it easier to implement container-query / mediaqueries-4 syntax, and also cleans up the code a bunch. There should be no change in behavior. Differential Revision: https://phabricator.services.mozilla.com/D144051 * Further changes required by Servo * style: Move transitions and animations to nsStyleUIReset This mostly just moves code around, to minimize potential behavior changes. There are some cleanups that we should try to do long term (this "have an array with n different counts" is pretty weird). But for now this should unblock people. The destination struct (nsStyleUIReset) was chosen mainly because it's small and non-inherited, and it doesn't seem like a worse place than nsStyleDisplay. Differential Revision: https://phabricator.services.mozilla.com/D144183 * Further changes required by Servo * style: Make media feature evaluation take a computed::Context This has no behavior change right now, but will simplify sharing code with container queries. Container queries will have container information in the computed::Context (this is necessary anyways for container-based units), so this avoids having to have different code for media and container queries. Differential Revision: https://phabricator.services.mozilla.com/D144152 * Further changes required by Servo * style: Add scroll() to animation-timeline for style system scroll() is defined in the spec proposal, and there is a temporary spec: https://drafts.csswg.org/scroll-animations-1/rewrite#scroll-notation. The spec is still under development, so we don't drop the orignal scroll-timeline at rule. Instead, we add a new scroll() notation to animation-timeline, and support both syntax for now. Differential Revision: https://phabricator.services.mozilla.com/D143417 * style: Tweak contain bitflag definition order to avoid static constructors This has no behavior change otherwise. The STRICT definition depended on SIZE, which was defined later. That's fine in Rust, but in C++ it causes the initialization to be dynamic because it doesn't have the definition of SIZE yet (ugh). This is the fix for the regression, though the following patch turns on constexpr support in cbindgen, which would've caught this at build-time, and guarantees that we don't have extra static constructors. Differential Revision: https://phabricator.services.mozilla.com/D144316 * style: Move some of the media query code to a more generic queries module No behavior change, just moving and renaming files. The code in the "queries" module will be shared between @media and @container. @media has some other code that container queries doesn't need like MediaList / MediaType / etc. That remains in the media_queries module. Differential Revision: https://phabricator.services.mozilla.com/D144435 * Further changes required by Servo * style: cleanup animation-name Make the representation the same between Gecko and Servo code. This will enable further clean-ups in the future. Make serialization be correct, serializing as identifier unless it's an invalid one (in which case we serialize as a string). This changes our stringification behavior in the specified style, but now it will match the computed style and be more correct over-all. Differential Revision: https://phabricator.services.mozilla.com/D144473 * Further changes required by Servo * style: Add support for parsing container-query-specific features There are some mediaqueries-5 features that we still don't support and explain the remaining failures in at-container-{parsing,serialization}. Differential Revision: https://phabricator.services.mozilla.com/D144446 * Further changes required by Servo * style: Introduce Optional to represent optional values in the style system cross-fade() was kinda doing this in its own way with PercentOrNone, but since now we have more use-cases for this we should probably make this a slightly more general solution. I added some convenience APIs, but they're unused as of this patch so let me know if you want them gone. Differential Revision: https://phabricator.services.mozilla.com/D144831 * style: Fix insertRule with layer statements before imports We need to do a bit more nuanced check because @layer statements might go before imports. Differential Revision: https://phabricator.services.mozilla.com/D144996 * style: Factor out parsing the query feature name No behavior change. Differential Revision: https://phabricator.services.mozilla.com/D145229 * style: Refactor media feature expression representation in preparation to support multi-range syntax No behavior change. Depends on D145229 Differential Revision: https://phabricator.services.mozilla.com/D145230 * style: Implement media feature expression multi-range syntax Differential Revision: https://phabricator.services.mozilla.com/D145231 * style: Remove proton places tooltip code There's nobody working on it, and tooltips should hopefully be nice enough after recent changes (bug 1765423). Having it enabled causes artifacts like bug 1767815 comment 3. We can always rescue this from hg history if needed. Differential Revision: https://phabricator.services.mozilla.com/D145621 * style: Simplify selector flags setup even more In my investigation for bug 1766439, I am digging into why selector matching regressed. It doesn't help that the selector-matching code is instantiated a gazillion times (so there's a ton of copies of the relevant functions). This was needed in the past because we had different ways of setting the selector flags on elements, but I unified that recently and now we only need to either set them or not. That is the kind of thing that MatchingContext is really good for, so pass that instead on MatchingContext creation. Differential Revision: https://phabricator.services.mozilla.com/D145428 * Further changes required by Servo * style: Track @container condition id in style rules Much like we track layer rules. Consolidate that "containing rule state we pass down while building the cascade data" in a single struct that we can easily restore. For now, do nothing with it. I want to land this patch separately because it touches the Rule struct and CascadeData rebuilds, which both are performance sensitive. Its layout shouldn't change because I also changed LayerId to be a u16 (this shouldn't matter in practice, since LayerOrder is already a u16). Differential Revision: https://phabricator.services.mozilla.com/D145243 * Further changes required by Servo * style: Fix layer statement rules with multiple layer names MANUAL PUSH: Trivial orange fix CLOSED TREE. * style: Implement piecewise linear function Differential Revision: https://phabricator.services.mozilla.com/D145256 * style: Convert specified value tests to compile-time tests These were written at a time where std::mem::size_of wasn't a `const fn` in Rust. Now that it is, we can make these tests live in the style crate, and the build not to compile if they fail. Differential Revision: https://phabricator.services.mozilla.com/D146103 * Further changes required by Servo * style: Move size of tests to compile-time tests in the style crate Same reasoning as the previous commit. Differential Revision: https://phabricator.services.mozilla.com/D146104 * Further changes required by Servo * style: Lint and 32-bit build fix. MANUAL PUSH: Bustage fix CLOSED TREE * style: Implement 'update' media feature Differential Revision: https://phabricator.services.mozilla.com/D146338 * style: Clean up unused -moz-window-shadow values After bug 1768278 and bug 1767815 there's no more uses of the cliprounded value in the tree (also it causes artifacts on HiDPI screens so we probably don't want new usages). The "sheet" value is unused, and the other values other than "default" and "none" are only derived from "default", so they don't need to be exposed in the style system. Differential Revision: https://phabricator.services.mozilla.com/D145821 * style: More container queries plumbing Provide container information in computed::Context and use it to resolve the container queries. This still fails a lot of tests because we are not ensuring that layout is up-to-date when we style the container descendants, but that's expected. Differential Revision: https://phabricator.services.mozilla.com/D146478 * Further changes required by Servo * style: Ensure options in listbox selects are not stacking contexts by default We could have a different property or something but this seems reasonable as well probably. Differential Revision: https://phabricator.services.mozilla.com/D146994 * style: Implement overflow-clip-margin: Differential Revision: https://phabricator.services.mozilla.com/D146432 * style: Change order of container shorthand Since the initial value of container-type is an open issue [1], I'm leaving that as-is for now. [1] https://github.com/w3c/csswg-drafts/issues/7202 Differential Revision: https://phabricator.services.mozilla.com/D147338 * style: Make modal dialog code more generic, and make it apply to fullscreen too behind a pref For now, don't turn it on by default yet, because I want to wait for more discussion in https://github.com/w3c/csswg-drafts/issues/6965 and so on. But I think the code is simple enough to land this. Differential Revision: https://phabricator.services.mozilla.com/D147295 * style: Cache computed styles objects display: none subtrees This reuses our existing undisplayed style generation, but in a per-document rather than per-nsComputedDOMStyle object, which means that we can avoid re-resolving styles of elements in display: none subtrees much more often. This brings the test-case in the bug to par with other browsers or better, and is much simpler than the initial approach I tried back in the day. Differential Revision: https://phabricator.services.mozilla.com/D147547 * Further changes required by Servo * style: Parse scroll-snap-stop style and propagate it to APZ side Depends on D146147 Differential Revision: https://phabricator.services.mozilla.com/D145850 * style: Update color-mix() syntax to match the current spec Test expectation updates for this in the latest patch of the bug. Differential Revision: https://phabricator.services.mozilla.com/D147002 * Further changes required by Servo * style: Make the color interpolation code more generic It's really piece-wise premultiplied interpolation, with a special-case for hue, so centralize the implementation. Differential Revision: https://phabricator.services.mozilla.com/D147003 * style: Implement more color-mix() color-spaces We had code to convert between these and the latest draft supports them so... Differential Revision: https://phabricator.services.mozilla.com/D147004 * style: Fix color-mix() percentage normalization Differential Revision: https://phabricator.services.mozilla.com/D147005 * style: Fix hue adjustment to match the spec The value to sum is tau, not pi. This was caught by some tests, see https://drafts.csswg.org/css-color/#shorter Differential Revision: https://phabricator.services.mozilla.com/D147006 * style: Do hue interpolations in degrees rather than radians. r=barret This gives us a bit more precision. Differential Revision: https://phabricator.services.mozilla.com/D147007 * style: Improve Percentage -> LengthPercentage conversion This doesn't change behavior because we only use them for images that have no clamping. Depends on D147008 Differential Revision: https://phabricator.services.mozilla.com/D147511 * style: Remove some dead vibrancy code Drive-by cleanup. Differential Revision: https://phabricator.services.mozilla.com/D147698 * style: Fix warnings about whitelist/blocklist functions being deprecated in bindgen 0.59 Differential Revision: https://phabricator.services.mozilla.com/D147695 * style: Update style to arrayvec 0.7 Differential Revision: https://phabricator.services.mozilla.com/D147476 * style: Update style to uluru 3.0 Differential Revision: https://phabricator.services.mozilla.com/D147477 * style: Remove -moz-scrollbar-thumb-proportional It unconditionally matches on all platforms, so it's not returning any useful information. Depends on D147689 Differential Revision: https://phabricator.services.mozilla.com/D147690 * style: Use ColorMix for interpolated colors in the computed style rather than ComplexColorRatios This among other things preserves the right color-space when interpolating currentColor. Differential Revision: https://phabricator.services.mozilla.com/D147512 * Further changes required by Servo * style: Add an input-region-margin to widgets, and implement it on Linux Recompute the input region when resizing the widget and so on, and use it to check for rollups. Depends on D148211 Differential Revision: https://phabricator.services.mozilla.com/D148222 * Avoid complaints from ./mach test-tidy * Update test expectations --------- Co-authored-by: Emilio Cobos Álvarez Co-authored-by: Brad Werth Co-authored-by: David Shin Co-authored-by: Hiroyuki Ikezoe Co-authored-by: Nika Layzell Co-authored-by: Boris Chiou Co-authored-by: Autumn on Tape Co-authored-by: Mike Hommey --- Cargo.lock | 17 +- components/atoms/static_atoms.txt | 6 + components/derive_common/cg.rs | 5 + components/layout/Cargo.toml | 4 +- components/layout/display_list/builder.rs | 21 +- components/layout/display_list/gradient.rs | 4 +- components/layout/fragment.rs | 7 + components/layout/lib.rs | 2 + components/layout/query.rs | 10 +- components/layout/table.rs | 8 +- components/layout/table_cell.rs | 10 +- components/layout/table_row.rs | 28 +- components/layout/tests/size_of.rs | 21 - .../layout_2020/display_list/gradient.rs | 4 +- components/layout_2020/display_list/mod.rs | 19 +- .../display_list/stacking_context.rs | 2 +- components/layout_2020/query.rs | 8 +- components/layout_2020/style_ext.rs | 2 +- components/layout_thread/lib.rs | 3 +- components/msg/Cargo.toml | 4 +- components/msg/constellation_msg.rs | 9 + components/msg/lib.rs | 2 + components/msg/tests/size_of.rs | 31 - components/script/dom/cssrule.rs | 5 +- components/script/dom/element.rs | 18 +- components/script/dom/node.rs | 14 +- components/script/layout_dom/element.rs | 36 +- components/selectors/Cargo.toml | 1 + components/selectors/context.rs | 19 + components/selectors/lib.rs | 3 + components/selectors/matching.rs | 108 +- components/selectors/parser.rs | 4 + components/selectors/rustfmt.toml | 1 + components/selectors/tree.rs | 31 +- components/size_of_test/Cargo.toml | 3 + components/size_of_test/lib.rs | 32 +- components/style/Cargo.toml | 3 +- components/style/animation.rs | 32 +- components/style/applicable_declarations.rs | 11 +- components/style/build_gecko.rs | 6 +- components/style/context.rs | 77 +- components/style/custom_properties.rs | 29 +- components/style/data.rs | 6 + components/style/dom.rs | 18 +- components/style/dom_apis.rs | 13 +- components/style/element_state.rs | 8 +- components/style/gecko/arc_types.rs | 7 +- components/style/gecko/data.rs | 14 +- components/style/gecko/media_features.rs | 502 +++----- components/style/gecko/media_queries.rs | 60 +- .../style/gecko/non_ts_pseudo_class_list.rs | 4 +- components/style/gecko/selector_parser.rs | 31 +- components/style/gecko/snapshot_helpers.rs | 4 +- components/style/gecko/wrapper.rs | 83 +- components/style/global_style_data.rs | 62 +- .../invalidation/element/document_state.rs | 3 +- .../invalidation/element/element_wrapper.rs | 16 +- .../invalidation/element/invalidation_map.rs | 2 + .../element/state_and_attributes.rs | 12 +- components/style/invalidation/stylesheets.rs | 3 +- components/style/lib.rs | 9 +- components/style/logical_geometry.rs | 63 +- components/style/macros.rs | 8 + components/style/matching.rs | 69 +- .../media_queries/media_feature_expression.rs | 535 -------- components/style/media_queries/media_list.rs | 12 +- components/style/media_queries/media_query.rs | 8 +- components/style/media_queries/mod.rs | 6 - components/style/piecewise_linear.rs | 223 ++++ components/style/properties/cascade.rs | 220 ++-- .../properties/counted_unknown_properties.py | 1 - components/style/properties/data.py | 2 + components/style/properties/gecko.mako.rs | 488 ++++--- .../helpers/animated_properties.mako.rs | 4 +- .../style/properties/longhands/box.mako.rs | 220 +--- .../style/properties/longhands/margin.mako.rs | 10 + .../style/properties/longhands/svg.mako.rs | 6 +- .../style/properties/longhands/ui.mako.rs | 204 ++- .../style/properties/properties.mako.rs | 252 ++-- .../style/properties/shorthands/box.mako.rs | 351 +---- .../style/properties/shorthands/ui.mako.rs | 317 +++++ .../condition.rs} | 117 +- .../media_feature.rs => queries/feature.rs} | 118 +- .../style/queries/feature_expression.rs | 711 ++++++++++ components/style/queries/mod.rs | 19 + components/style/queries/values.rs | 36 + components/style/rule_collector.rs | 14 +- components/style/rule_tree/core.rs | 8 +- components/style/rule_tree/mod.rs | 2 +- components/style/selector_map.rs | 65 +- components/style/servo/media_queries.rs | 33 +- components/style/sharing/checks.rs | 5 +- components/style/sharing/mod.rs | 36 +- components/style/style_resolver.rs | 42 +- .../style/stylesheets/container_rule.rs | 281 ++++ components/style/stylesheets/layer_rule.rs | 13 +- components/style/stylesheets/media_rule.rs | 4 +- components/style/stylesheets/mod.rs | 26 +- components/style/stylesheets/rule_list.rs | 16 +- components/style/stylesheets/rule_parser.rs | 89 +- .../style/stylesheets/rules_iterator.rs | 4 + .../style/stylesheets/scroll_timeline_rule.rs | 1 - components/style/stylesheets/stylesheet.rs | 1 + components/style/stylesheets/viewport_rule.rs | 3 +- components/style/stylist.rs | 268 ++-- components/style/traversal.rs | 36 +- components/style/values/animated/color.rs | 1140 ++++++++--------- components/style/values/computed/box.rs | 8 +- components/style/values/computed/color.rs | 88 +- components/style/values/computed/image.rs | 5 +- components/style/values/computed/length.rs | 2 +- components/style/values/computed/mod.rs | 57 +- components/style/values/computed/page.rs | 8 +- .../style/values/computed/percentage.rs | 7 + components/style/values/computed/ratio.rs | 10 + components/style/values/computed/svg.rs | 4 +- components/style/values/generics/calc.rs | 20 + components/style/values/generics/color.rs | 293 ++++- components/style/values/generics/image.rs | 17 +- components/style/values/generics/mod.rs | 71 + components/style/values/generics/page.rs | 10 +- components/style/values/mod.rs | 192 +-- components/style/values/specified/box.rs | 456 +++---- components/style/values/specified/calc.rs | 20 + components/style/values/specified/color.rs | 236 ++-- components/style/values/specified/image.rs | 50 +- components/style/values/specified/length.rs | 409 +++++- components/style/values/specified/mod.rs | 9 +- components/style/values/specified/page.rs | 8 +- .../style/values/specified/percentage.rs | 33 +- components/style/values/specified/position.rs | 1 + .../values/specified/source_size_list.rs | 18 +- components/style/values/specified/text.rs | 111 +- components/style_derive/parse.rs | 79 +- components/style_derive/rustfmt.toml | 1 + .../style_derive/specified_value_info.rs | 9 +- components/style_derive/to_css.rs | 120 +- components/style_traits/Cargo.toml | 1 + components/style_traits/lib.rs | 7 + components/style_traits/values.rs | 40 +- servo-tidy.toml | 29 +- tests/unit/style/Cargo.toml | 1 - tests/unit/style/lib.rs | 5 - tests/unit/style/size_of.rs | 51 - tests/unit/style/specified_values.rs | 50 - tests/unit/style/stylist.rs | 4 +- .../viewport-units-compute.html.ini | 90 -- .../viewport-units-keyframes.html.ini | 60 - .../viewport-units-media-queries.html.ini | 30 - .../viewport-units-parsing.html.ini | 60 - .../viewport-units-media-queries.html.ini | 30 - .../viewport-units-parsing.html.ini | 60 - 152 files changed, 5857 insertions(+), 4617 deletions(-) delete mode 100644 components/layout/tests/size_of.rs delete mode 100644 components/msg/tests/size_of.rs create mode 100644 components/selectors/rustfmt.toml delete mode 100644 components/style/media_queries/media_feature_expression.rs create mode 100644 components/style/piecewise_linear.rs create mode 100644 components/style/properties/shorthands/ui.mako.rs rename components/style/{media_queries/media_condition.rs => queries/condition.rs} (57%) rename components/style/{media_queries/media_feature.rs => queries/feature.rs} (57%) create mode 100644 components/style/queries/feature_expression.rs create mode 100644 components/style/queries/mod.rs create mode 100644 components/style/queries/values.rs create mode 100644 components/style/stylesheets/container_rule.rs create mode 100644 components/style_derive/rustfmt.toml delete mode 100644 tests/unit/style/size_of.rs delete mode 100644 tests/unit/style/specified_values.rs delete mode 100644 tests/wpt/meta-legacy-layout/css/css-values/viewport-units-compute.html.ini delete mode 100644 tests/wpt/meta-legacy-layout/css/css-values/viewport-units-keyframes.html.ini delete mode 100644 tests/wpt/meta-legacy-layout/css/css-values/viewport-units-parsing.html.ini delete mode 100644 tests/wpt/meta/css/css-values/viewport-units-parsing.html.ini diff --git a/Cargo.lock b/Cargo.lock index 910e8e4c197..060e603caf2 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5372,6 +5372,7 @@ dependencies = [ "phf_codegen", "precomputed-hash", "servo_arc", + "size_of_test", "smallvec", "to_shmem", "to_shmem_derive", @@ -5915,6 +5916,9 @@ checksum = "fa8f3741c7372e75519bd9346068370c9cdaabcc1f9599cbcf2a2719352286b7" [[package]] name = "size_of_test" version = "0.0.1" +dependencies = [ + "static_assertions", +] [[package]] name = "slab" @@ -6042,6 +6046,12 @@ version = "1.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dba1a27d3efae4351c8051072d619e3ade2820635c3958d826bfea39d59b54c8" +[[package]] +name = "static_assertions" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" + [[package]] name = "std_test_override" version = "0.0.1" @@ -6145,6 +6155,7 @@ dependencies = [ "servo_url", "smallbitvec", "smallvec", + "static_assertions", "string_cache", "style_derive", "style_traits", @@ -6186,7 +6197,6 @@ dependencies = [ "servo_atoms", "servo_config", "servo_url", - "size_of_test", "std_test_override", "style", "style_traits", @@ -6208,6 +6218,7 @@ dependencies = [ "servo_arc", "servo_atoms", "servo_url", + "size_of_test", "to_shmem", "to_shmem_derive", "webrender_api", @@ -6691,9 +6702,9 @@ checksum = "9e79c4d996edb816c91e4308506774452e55e95c3c9de07b6729e17e15a5ef81" [[package]] name = "uluru" -version = "2.2.0" +version = "3.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "308dcc9d947b227796f851adb99936fb060681a89c002c9c1928404a3b6c2d72" +checksum = "794a32261a1f5eb6a4462c81b59cec87b5c27d5deea7dd1ac8fc781c41d226db" dependencies = [ "arrayvec 0.7.1", ] diff --git a/components/atoms/static_atoms.txt b/components/atoms/static_atoms.txt index 7e4f385169e..9ef4e7c5f94 100644 --- a/components/atoms/static_atoms.txt +++ b/components/atoms/static_atoms.txt @@ -1,3 +1,4 @@ +-moz-content-preferred-color-scheme -moz-gtk-csd-close-button-position -moz-gtk-csd-maximize-button-position -moz-gtk-csd-menu-radius @@ -12,7 +13,9 @@ animationcancel animationend animationiteration animationstart +aspect-ratio beforeunload +block-size button canplay canplaythrough @@ -50,11 +53,13 @@ fullscreenchange fullscreenerror gattserverdisconnected hashchange +height hidden icecandidate iceconnectionstatechange icegatheringstatechange image +inline-size input inputsourceschange invalid @@ -82,6 +87,7 @@ none number onchange open +orientation pagehide pageshow password diff --git a/components/derive_common/cg.rs b/components/derive_common/cg.rs index 93b624483c7..022a589eb58 100644 --- a/components/derive_common/cg.rs +++ b/components/derive_common/cg.rs @@ -373,6 +373,11 @@ pub fn to_css_identifier(mut camel_case: &str) -> String { result } +/// Transforms foo-bar to FOO_BAR. +pub fn to_scream_case(css_case: &str) -> String { + css_case.to_uppercase().replace('-', "_") +} + /// Given "FooBar", returns "Foo" and sets `camel_case` to "Bar". fn split_camel_segment<'input>(camel_case: &mut &'input str) -> Option<&'input str> { let index = match camel_case.chars().next() { diff --git a/components/layout/Cargo.toml b/components/layout/Cargo.toml index d11f508c502..fe501517c6e 100644 --- a/components/layout/Cargo.toml +++ b/components/layout/Cargo.toml @@ -44,6 +44,7 @@ servo_atoms = { path = "../atoms" } servo_config = { path = "../config" } servo_geometry = { path = "../geometry" } servo_url = { path = "../url" } +size_of_test = { path = "../size_of_test" } smallvec = { workspace = true, features = ["union"] } style = { path = "../style", features = ["servo"] } style_traits = { path = "../style_traits" } @@ -52,6 +53,3 @@ unicode-script = { workspace = true } webrender_api = { workspace = true } xi-unicode = { workspace = true } -[dev-dependencies] -size_of_test = { path = "../size_of_test" } - diff --git a/components/layout/display_list/builder.rs b/components/layout/display_list/builder.rs index e6b16710d22..93da81c3532 100644 --- a/components/layout/display_list/builder.rs +++ b/components/layout/display_list/builder.rs @@ -653,7 +653,7 @@ impl Fragment { absolute_bounds: Rect, ) { let background = style.get_background(); - let background_color = style.resolve_color(background.background_color); + let background_color = style.resolve_color(background.background_color.clone()); // XXXManishearth the below method should ideally use an iterator over // backgrounds self.build_display_list_for_background_if_applicable_with_background( @@ -1037,7 +1037,9 @@ impl Fragment { webrender_api::BoxShadowDisplayItem { common: items::empty_common_item_properties(), box_bounds: absolute_bounds.to_layout(), - color: style.resolve_color(box_shadow.base.color).to_layout(), + color: style + .resolve_color(box_shadow.base.color.clone()) + .to_layout(), offset: LayoutVector2D::new( box_shadow.base.horizontal.px(), box_shadow.base.vertical.px(), @@ -1083,10 +1085,10 @@ impl Fragment { let border_style_struct = style.get_border(); let mut colors = SideOffsets2D::new( - border_style_struct.border_top_color, - border_style_struct.border_right_color, - border_style_struct.border_bottom_color, - border_style_struct.border_left_color, + border_style_struct.border_top_color.clone(), + border_style_struct.border_right_color.clone(), + border_style_struct.border_bottom_color.clone(), + border_style_struct.border_left_color.clone(), ); let mut border_style = SideOffsets2D::new( border_style_struct.border_top_style, @@ -1316,7 +1318,7 @@ impl Fragment { // Append the outline to the display list. let color = style - .resolve_color(style.get_outline().outline_color) + .resolve_color(style.get_outline().outline_color.clone()) .to_layout(); let base = state.create_base_display_item( clip, @@ -1451,7 +1453,8 @@ impl Fragment { // TODO: Allow non-text fragments to be selected too. if scanned_text_fragment_info.selected() { let style = self.selected_style(); - let background_color = style.resolve_color(style.get_background().background_color); + let background_color = + style.resolve_color(style.get_background().background_color.clone()); let base = state.create_base_display_item( stacking_relative_border_box, self.node, @@ -2055,7 +2058,7 @@ impl Fragment { base: base.clone(), shadow: webrender_api::Shadow { offset: LayoutVector2D::new(shadow.horizontal.px(), shadow.vertical.px()), - color: self.style.resolve_color(shadow.color).to_layout(), + color: self.style.resolve_color(shadow.color.clone()).to_layout(), blur_radius: shadow.blur.px(), }, }, diff --git a/components/layout/display_list/gradient.rs b/components/layout/display_list/gradient.rs index 0f5394b69c9..4ec54479972 100644 --- a/components/layout/display_list/gradient.rs +++ b/components/layout/display_list/gradient.rs @@ -86,7 +86,7 @@ fn convert_gradient_stops( // Only keep the color stops, discard the color interpolation hints. let mut stop_items = gradient_items .iter() - .filter_map(|item| match *item { + .filter_map(|item| match item { GradientItem::SimpleColorStop(color) => Some(ColorStop { color, position: None, @@ -191,7 +191,7 @@ fn convert_gradient_stops( assert!(offset.is_finite()); stops.push(GradientStop { offset: offset, - color: style.resolve_color(stop.color).to_layout(), + color: style.resolve_color(stop.color.clone()).to_layout(), }) } stops diff --git a/components/layout/fragment.rs b/components/layout/fragment.rs index 5cf56b6cbdf..7f36e110499 100644 --- a/components/layout/fragment.rs +++ b/components/layout/fragment.rs @@ -158,6 +158,11 @@ pub struct Fragment { pub established_reference_frame: Option, } +#[cfg(debug_assertions)] +size_of_test!(Fragment, 176); +#[cfg(not(debug_assertions))] +size_of_test!(Fragment, 152); + impl Serialize for Fragment { fn serialize(&self, serializer: S) -> Result { let mut serializer = serializer.serialize_struct("fragment", 3)?; @@ -212,6 +217,8 @@ pub enum SpecificFragmentInfo { TruncatedFragment(Box), } +size_of_test!(SpecificFragmentInfo, 24); + impl SpecificFragmentInfo { fn restyle_damage(&self) -> RestyleDamage { let flow = match *self { diff --git a/components/layout/lib.rs b/components/layout/lib.rs index 3f41157c1cd..aab98048a54 100644 --- a/components/layout/lib.rs +++ b/components/layout/lib.rs @@ -16,6 +16,8 @@ extern crate log; extern crate range; #[macro_use] extern crate serde; +#[macro_use] +extern crate size_of_test; #[macro_use] pub mod layout_debug; diff --git a/components/layout/query.rs b/components/layout/query.rs index 175ddb2679f..d54003971f5 100644 --- a/components/layout/query.rs +++ b/components/layout/query.rs @@ -860,7 +860,7 @@ where shared: &context.style_context, thread_local: &mut tlc, }; - let styles = resolve_style(&mut context, element, RuleInclusion::All, None); + let styles = resolve_style(&mut context, element, RuleInclusion::All, None, None); styles.primary().clone() } } else { @@ -916,7 +916,13 @@ pub fn process_resolved_style_request<'dom>( thread_local: &mut tlc, }; - let styles = resolve_style(&mut context, element, RuleInclusion::All, pseudo.as_ref()); + let styles = resolve_style( + &mut context, + element, + RuleInclusion::All, + pseudo.as_ref(), + None, + ); let style = styles.primary(); let longhand_id = match *property { PropertyId::LonghandAlias(id, _) | PropertyId::Longhand(id) => id, diff --git a/components/layout/table.rs b/components/layout/table.rs index ca4097635f9..36c78d9bb81 100644 --- a/components/layout/table.rs +++ b/components/layout/table.rs @@ -794,7 +794,7 @@ fn perform_border_collapse_for_row( child_table_row .final_collapsed_borders .inline - .push_or_set(i, *this_inline_border); + .push_or_set(i, this_inline_border.clone()); if i == 0 { child_table_row.final_collapsed_borders.inline[i].combine(&table_inline_borders.start); } else if i + 1 == number_of_borders_inline_direction { @@ -821,7 +821,7 @@ fn perform_border_collapse_for_row( this_border.combine(&previous_block_borders[i]); } }, - PreviousBlockCollapsedBorders::FromTable(table_border) => { + PreviousBlockCollapsedBorders::FromTable(ref table_border) => { this_border.combine(&table_border); }, } @@ -837,7 +837,7 @@ fn perform_border_collapse_for_row( .iter() .enumerate() { - let next_block = next_block.push_or_set(i, *this_block_border); + let next_block = next_block.push_or_set(i, this_block_border.clone()); match next_block_borders { NextBlockCollapsedBorders::FromNextRow(next_block_borders) => { if next_block_borders.len() > i { @@ -1358,7 +1358,7 @@ impl<'table> TableCellStyleInfo<'table> { if background as *const Background == initial.get_background() as *const _ { return; } - let background_color = sty.resolve_color(background.background_color); + let background_color = sty.resolve_color(background.background_color.clone()); cell_flow.build_display_list_for_background_if_applicable_with_background( state, background, diff --git a/components/layout/table_cell.rs b/components/layout/table_cell.rs index 3a70158be26..6408da05dee 100644 --- a/components/layout/table_cell.rs +++ b/components/layout/table_cell.rs @@ -375,7 +375,7 @@ impl fmt::Debug for TableCellFlow { } } -#[derive(Clone, Copy, Debug, Serialize)] +#[derive(Clone, Debug, Serialize)] pub struct CollapsedBordersForCell { pub inline_start_border: CollapsedBorder, pub inline_end_border: CollapsedBorder, @@ -494,10 +494,10 @@ impl CollapsedBordersForCell { ) { let logical_border_colors = LogicalMargin::new( writing_mode, - self.block_start_border.color, - self.inline_end_border.color, - self.block_end_border.color, - self.inline_start_border.color, + self.block_start_border.color.clone(), + self.inline_end_border.color.clone(), + self.block_end_border.color.clone(), + self.inline_start_border.color.clone(), ); *border_colors = logical_border_colors.to_physical(writing_mode); diff --git a/components/layout/table_row.rs b/components/layout/table_row.rs index 0d959b7456c..b9090c39c94 100644 --- a/components/layout/table_row.rs +++ b/components/layout/table_row.rs @@ -718,7 +718,7 @@ impl CollapsedBorderSpacingForRow { } /// All aspects of a border that can collapse with adjacent borders. See CSS 2.1 § 17.6.2.1. -#[derive(Clone, Copy, Debug)] +#[derive(Clone, Debug)] pub struct CollapsedBorder { /// The style of the border. pub style: BorderStyle, @@ -771,7 +771,7 @@ impl CollapsedBorder { CollapsedBorder { style: css_style.get_border().border_top_style, width: Au::from(css_style.get_border().border_top_width), - color: css_style.get_border().border_top_color, + color: css_style.get_border().border_top_color.clone(), provenance: provenance, } } @@ -782,7 +782,7 @@ impl CollapsedBorder { CollapsedBorder { style: css_style.get_border().border_right_style, width: Au::from(css_style.get_border().border_right_width), - color: css_style.get_border().border_right_color, + color: css_style.get_border().border_right_color.clone(), provenance: provenance, } } @@ -796,7 +796,7 @@ impl CollapsedBorder { CollapsedBorder { style: css_style.get_border().border_bottom_style, width: Au::from(css_style.get_border().border_bottom_width), - color: css_style.get_border().border_bottom_color, + color: css_style.get_border().border_bottom_color.clone(), provenance: provenance, } } @@ -807,7 +807,7 @@ impl CollapsedBorder { CollapsedBorder { style: css_style.get_border().border_left_style, width: Au::from(css_style.get_border().border_left_width), - color: css_style.get_border().border_left_color, + color: css_style.get_border().border_left_color.clone(), provenance: provenance, } } @@ -883,18 +883,18 @@ impl CollapsedBorder { match (self.style, other.style) { // Step 1. (BorderStyle::Hidden, _) => {}, - (_, BorderStyle::Hidden) => *self = *other, + (_, BorderStyle::Hidden) => *self = other.clone(), // Step 2. - (BorderStyle::None, _) => *self = *other, + (BorderStyle::None, _) => *self = other.clone(), (_, BorderStyle::None) => {}, // Step 3. _ if self.width > other.width => {}, - _ if self.width < other.width => *self = *other, + _ if self.width < other.width => *self = other.clone(), (this_style, other_style) if this_style > other_style => {}, - (this_style, other_style) if this_style < other_style => *self = *other, + (this_style, other_style) if this_style < other_style => *self = other.clone(), // Step 4. _ if (self.provenance as i8) >= other.provenance as i8 => {}, - _ => *self = *other, + _ => *self = other.clone(), } } } @@ -1013,22 +1013,22 @@ fn set_inline_position_of_child_flow( .collapsed_borders_for_row .inline .get(child_index) - .map_or(CollapsedBorder::new(), |x| *x), + .map_or(CollapsedBorder::new(), |x| x.clone()), inline_end_border: border_collapse_info .collapsed_borders_for_row .inline .get(child_index + 1) - .map_or(CollapsedBorder::new(), |x| *x), + .map_or(CollapsedBorder::new(), |x| x.clone()), block_start_border: border_collapse_info .collapsed_borders_for_row .block_start .get(child_index) - .map_or(CollapsedBorder::new(), |x| *x), + .map_or(CollapsedBorder::new(), |x| x.clone()), block_end_border: border_collapse_info .collapsed_borders_for_row .block_end .get(child_index) - .map_or(CollapsedBorder::new(), |x| *x), + .map_or(CollapsedBorder::new(), |x| x.clone()), inline_start_width: border_collapse_info .collapsed_border_spacing_for_row .inline diff --git a/components/layout/tests/size_of.rs b/components/layout/tests/size_of.rs deleted file mode 100644 index dec10937177..00000000000 --- a/components/layout/tests/size_of.rs +++ /dev/null @@ -1,21 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ - -#![cfg(target_pointer_width = "64")] - -#[macro_use] -extern crate size_of_test; - -use layout_2013::Fragment; -use layout_2013::SpecificFragmentInfo; - -#[cfg(debug_assertions)] -size_of_test!(test_size_of_fragment, Fragment, 176); -#[cfg(not(debug_assertions))] -size_of_test!(test_size_of_fragment, Fragment, 152); -size_of_test!( - test_size_of_specific_fragment_info, - SpecificFragmentInfo, - 24 -); diff --git a/components/layout_2020/display_list/gradient.rs b/components/layout_2020/display_list/gradient.rs index c9738da49be..da8ef61eb86 100644 --- a/components/layout_2020/display_list/gradient.rs +++ b/components/layout_2020/display_list/gradient.rs @@ -275,11 +275,11 @@ fn fixup_stops( for item in items { match item { GradientItem::SimpleColorStop(color) => stops.push(ColorStop { - color: super::rgba(style.resolve_color(*color)), + color: super::rgba(style.resolve_color(color.clone())), position: None, }), GradientItem::ComplexColorStop { color, position } => stops.push(ColorStop { - color: super::rgba(style.resolve_color(*color)), + color: super::rgba(style.resolve_color(color.clone())), position: Some(if gradient_line_length.px() == 0. { 0. } else { diff --git a/components/layout_2020/display_list/mod.rs b/components/layout_2020/display_list/mod.rs index 80670108991..c2c7713df13 100644 --- a/components/layout_2020/display_list/mod.rs +++ b/components/layout_2020/display_list/mod.rs @@ -352,7 +352,7 @@ impl Fragment { let text_decoration_color = fragment .parent_style .clone_text_decoration_color() - .to_rgba(color); + .into_rgba(color); let text_decoration_style = fragment.parent_style.clone_text_decoration_style(); if text_decoration_style == ComputedTextDecorationStyle::MozNone { return; @@ -535,7 +535,7 @@ impl<'a> BuilderForBoxFragment<'a> { let source = background::Source::Fragment; let style = &self.fragment.style; let b = style.get_background(); - let background_color = style.resolve_color(b.background_color); + let background_color = style.resolve_color(b.background_color.clone()); if background_color.alpha > 0 { // https://drafts.csswg.org/css-backgrounds/#background-color // “The background color is clipped according to the background-clip @@ -685,10 +685,15 @@ impl<'a> BuilderForBoxFragment<'a> { } let common = builder.common_properties(self.border_rect, &self.fragment.style); let details = wr::BorderDetails::Normal(wr::NormalBorder { - top: self.build_border_side(border.border_top_style, border.border_top_color), - right: self.build_border_side(border.border_right_style, border.border_right_color), - bottom: self.build_border_side(border.border_bottom_style, border.border_bottom_color), - left: self.build_border_side(border.border_left_style, border.border_left_color), + top: self.build_border_side(border.border_top_style, border.border_top_color.clone()), + right: self + .build_border_side(border.border_right_style, border.border_right_color.clone()), + bottom: self.build_border_side( + border.border_bottom_style, + border.border_bottom_color.clone(), + ), + left: self + .build_border_side(border.border_left_style, border.border_left_color.clone()), radius: self.border_radius, do_aa: true, }); @@ -718,7 +723,7 @@ impl<'a> BuilderForBoxFragment<'a> { OutlineStyle::Auto => BorderStyle::Solid, OutlineStyle::BorderStyle(s) => s, }; - let side = self.build_border_side(style, outline.outline_color); + let side = self.build_border_side(style, outline.outline_color.clone()); let details = wr::BorderDetails::Normal(wr::NormalBorder { top: side, right: side, diff --git a/components/layout_2020/display_list/stacking_context.rs b/components/layout_2020/display_list/stacking_context.rs index 666e3225bed..77dc310b471 100644 --- a/components/layout_2020/display_list/stacking_context.rs +++ b/components/layout_2020/display_list/stacking_context.rs @@ -404,7 +404,7 @@ impl StackingContext { .union(&fragment_tree.scrollable_overflow) .to_webrender(); - let background_color = style.resolve_color(style.get_background().background_color); + let background_color = style.resolve_color(style.get_background().background_color.clone()); if background_color.alpha > 0 { let common = builder.common_properties(painting_area, &style); let color = super::rgba(background_color); diff --git a/components/layout_2020/query.rs b/components/layout_2020/query.rs index a2524bb6bcd..e409f5bdd59 100644 --- a/components/layout_2020/query.rs +++ b/components/layout_2020/query.rs @@ -361,7 +361,13 @@ pub fn process_resolved_style_request_for_unstyled_node<'dom>( }; let element = node.as_element().unwrap(); - let styles = resolve_style(&mut context, element, RuleInclusion::All, pseudo.as_ref()); + let styles = resolve_style( + &mut context, + element, + RuleInclusion::All, + pseudo.as_ref(), + None, + ); let style = styles.primary(); let longhand_id = match *property { PropertyId::LonghandAlias(id, _) | PropertyId::Longhand(id) => id, diff --git a/components/layout_2020/style_ext.rs b/components/layout_2020/style_ext.rs index 274f581eb87..8d8740bb961 100644 --- a/components/layout_2020/style_ext.rs +++ b/components/layout_2020/style_ext.rs @@ -465,7 +465,7 @@ impl ComputedValuesExt for ComputedValues { /// Whether or not this style specifies a non-transparent background. fn background_is_transparent(&self) -> bool { let background = self.get_background(); - let color = self.resolve_color(background.background_color); + let color = self.resolve_color(background.background_color.clone()); color.alpha == 0 && background .background_image diff --git a/components/layout_thread/lib.rs b/components/layout_thread/lib.rs index 3d0bc1a1cc7..2202bb123a1 100644 --- a/components/layout_thread/lib.rs +++ b/components/layout_thread/lib.rs @@ -1796,7 +1796,8 @@ fn get_root_flow_background_color(flow: &mut dyn Flow) -> ColorF { .fragment .style .get_background() - .background_color, + .background_color + .clone(), ); ColorF::new( color.red_f32(), diff --git a/components/msg/Cargo.toml b/components/msg/Cargo.toml index 8bcf5680a2d..49dfb7232bd 100644 --- a/components/msg/Cargo.toml +++ b/components/msg/Cargo.toml @@ -19,7 +19,5 @@ malloc_size_of = { path = "../malloc_size_of" } malloc_size_of_derive = { workspace = true } parking_lot = { workspace = true } serde = { workspace = true } -webrender_api = { workspace = true } - -[dev-dependencies] size_of_test = { path = "../size_of_test" } +webrender_api = { workspace = true } diff --git a/components/msg/constellation_msg.rs b/components/msg/constellation_msg.rs index 72223cdb387..3dcff217485 100644 --- a/components/msg/constellation_msg.rs +++ b/components/msg/constellation_msg.rs @@ -198,6 +198,9 @@ pub struct PipelineNamespaceId(pub u32); namespace_id! {PipelineId, PipelineIndex} +size_of_test!(PipelineId, 8); +size_of_test!(Option, 8); + impl PipelineId { pub fn new() -> PipelineId { PIPELINE_NAMESPACE.with(|tls| { @@ -240,6 +243,9 @@ impl fmt::Display for PipelineId { namespace_id! {BrowsingContextId, BrowsingContextIndex} +size_of_test!(BrowsingContextId, 8); +size_of_test!(Option, 8); + impl BrowsingContextId { pub fn new() -> BrowsingContextId { PIPELINE_NAMESPACE.with(|tls| { @@ -269,6 +275,9 @@ thread_local!(pub static TOP_LEVEL_BROWSING_CONTEXT_ID: Cell, 8); + impl TopLevelBrowsingContextId { pub fn new() -> TopLevelBrowsingContextId { TopLevelBrowsingContextId(BrowsingContextId::new()) diff --git a/components/msg/lib.rs b/components/msg/lib.rs index 4fe53230c46..03aa40eb8f1 100644 --- a/components/msg/lib.rs +++ b/components/msg/lib.rs @@ -12,5 +12,7 @@ extern crate malloc_size_of; extern crate malloc_size_of_derive; #[macro_use] extern crate serde; +#[macro_use] +extern crate size_of_test; pub mod constellation_msg; diff --git a/components/msg/tests/size_of.rs b/components/msg/tests/size_of.rs deleted file mode 100644 index 4fc1861ea31..00000000000 --- a/components/msg/tests/size_of.rs +++ /dev/null @@ -1,31 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ - -#![cfg(target_pointer_width = "64")] - -#[macro_use] -extern crate size_of_test; - -use msg::constellation_msg::BrowsingContextId; -use msg::constellation_msg::PipelineId; -use msg::constellation_msg::TopLevelBrowsingContextId; - -size_of_test!(test_size_of_pipeline_id, PipelineId, 8); -size_of_test!(test_size_of_optional_pipeline_id, Option, 8); -size_of_test!(test_size_of_browsing_context_id, BrowsingContextId, 8); -size_of_test!( - test_size_of_optional_browsing_context_id, - Option, - 8 -); -size_of_test!( - test_size_of_top_level_browsing_context_id, - TopLevelBrowsingContextId, - 8 -); -size_of_test!( - test_size_of_top_level_optional_browsing_context_id, - Option, - 8 -); diff --git a/components/script/dom/cssrule.rs b/components/script/dom/cssrule.rs index 560124ef030..b9cc5c19dbd 100644 --- a/components/script/dom/cssrule.rs +++ b/components/script/dom/cssrule.rs @@ -98,8 +98,9 @@ impl CSSRule { DomRoot::upcast(CSSSupportsRule::new(window, parent_stylesheet, s)) }, StyleCssRule::Page(_) => unreachable!(), - StyleCssRule::Document(_) => unimplemented!(), // TODO - StyleCssRule::Viewport(_) => unimplemented!(), // TODO + StyleCssRule::Container(_) => unimplemented!(), // TODO + StyleCssRule::Document(_) => unimplemented!(), // TODO + StyleCssRule::Viewport(_) => unimplemented!(), // TODO StyleCssRule::LayerBlock(_) => unimplemented!(), // TODO StyleCssRule::LayerStatement(_) => unimplemented!(), // TODO StyleCssRule::ScrollTimeline(_) => unimplemented!(), // TODO diff --git a/components/script/dom/element.rs b/components/script/dom/element.rs index 3304e23cb64..ed04d2e562c 100644 --- a/components/script/dom/element.rs +++ b/components/script/dom/element.rs @@ -111,6 +111,7 @@ use std::cell::Cell; use std::default::Default; use std::fmt; use std::mem; +use std::ops::Deref; use std::rc::Rc; use std::str::FromStr; use style::applicable_declarations::ApplicableDeclarationBlock; @@ -3218,15 +3219,11 @@ impl<'a> SelectorsElement for DomRoot { Element::namespace(self) == Element::namespace(other) } - fn match_non_ts_pseudo_class( + fn match_non_ts_pseudo_class( &self, pseudo_class: &NonTSPseudoClass, _: &mut MatchingContext, - _: &mut F, - ) -> bool - where - F: FnMut(&Self, ElementSelectorFlags), - { + ) -> bool { match *pseudo_class { // https://github.com/servo/servo/issues/8718 NonTSPseudoClass::Link | NonTSPseudoClass::AnyLink => self.is_link(), @@ -3307,6 +3304,15 @@ impl<'a> SelectorsElement for DomRoot { fn is_html_slot_element(&self) -> bool { self.is_html_element() && self.local_name() == &local_name!("slot") } + + fn set_selector_flags(&self, flags: ElementSelectorFlags) { + #[allow(unsafe_code)] + unsafe { + Dom::from_ref(self.deref()) + .to_layout() + .insert_selector_flags(flags); + } + } } impl Element { diff --git a/components/script/dom/node.rs b/components/script/dom/node.rs index a4913faa683..7e3367b9b2a 100644 --- a/components/script/dom/node.rs +++ b/components/script/dom/node.rs @@ -79,7 +79,9 @@ use script_layout_interface::{HTMLCanvasData, HTMLMediaData, LayoutElementType, use script_layout_interface::{SVGSVGData, StyleAndOpaqueLayoutData, TrustedNodeAddress}; use script_traits::DocumentActivity; use script_traits::UntrustedNodeAddress; -use selectors::matching::{matches_selector_list, MatchingContext, MatchingMode}; +use selectors::matching::{ + matches_selector_list, MatchingContext, MatchingMode, NeedsSelectorFlags, +}; use selectors::parser::SelectorList; use servo_arc::Arc; use servo_url::ServoUrl; @@ -473,6 +475,7 @@ impl<'a> Iterator for QuerySelectorIterator { None, None, node.owner_doc().quirks_mode(), + NeedsSelectorFlags::No, ); if let Some(element) = DomRoot::downcast(node) { if matches_selector_list(selectors, &element, &mut ctx) { @@ -956,8 +959,13 @@ impl Node { // Step 3. Ok(selectors) => { // FIXME(bholley): Consider an nth-index cache here. - let mut ctx = - MatchingContext::new(MatchingMode::Normal, None, None, doc.quirks_mode()); + let mut ctx = MatchingContext::new( + MatchingMode::Normal, + None, + None, + doc.quirks_mode(), + NeedsSelectorFlags::No, + ); Ok(self .traverse_preorder(ShadowIncluding::No) .filter_map(DomRoot::downcast) diff --git a/components/script/layout_dom/element.rs b/components/script/layout_dom/element.rs index b85e72443c3..91be4baa6ed 100644 --- a/components/script/layout_dom/element.rs +++ b/components/script/layout_dom/element.rs @@ -358,14 +358,6 @@ impl<'dom, LayoutDataType: LayoutDataTrait> style::dom::TElement false } - unsafe fn set_selector_flags(&self, flags: ElementSelectorFlags) { - self.element.insert_selector_flags(flags); - } - - fn has_selector_flags(&self, flags: ElementSelectorFlags) -> bool { - self.element.has_selector_flags(flags) - } - fn has_animations(&self, context: &SharedStyleContext) -> bool { // This is not used for pseudo elements currently so we can pass None. return self.has_css_animations(context, /* pseudo_element = */ None) || @@ -460,6 +452,10 @@ impl<'dom, LayoutDataType: LayoutDataTrait> style::dom::TElement fn namespace(&self) -> &Namespace { self.element.namespace() } + + fn primary_box_size(&self) -> euclid::default::Size2D { + todo!(); + } } impl<'dom, LayoutDataType: LayoutDataTrait> ::selectors::Element @@ -573,15 +569,11 @@ impl<'dom, LayoutDataType: LayoutDataTrait> ::selectors::Element false } - fn match_non_ts_pseudo_class( + fn match_non_ts_pseudo_class( &self, pseudo_class: &NonTSPseudoClass, _: &mut MatchingContext, - _: &mut F, - ) -> bool - where - F: FnMut(&Self, ElementSelectorFlags), - { + ) -> bool { match *pseudo_class { // https://github.com/servo/servo/issues/8718 NonTSPseudoClass::Link | NonTSPseudoClass::AnyLink => self.is_link(), @@ -670,6 +662,10 @@ impl<'dom, LayoutDataType: LayoutDataTrait> ::selectors::Element fn is_html_element_in_html_document(&self) -> bool { self.element.is_html_element() && self.as_node().owner_doc().is_html_document() } + + fn set_selector_flags(&self, flags: ElementSelectorFlags) { + self.element.insert_selector_flags(flags); + } } /// A wrapper around elements that ensures layout can only @@ -858,15 +854,11 @@ impl<'dom, LayoutDataType: LayoutDataTrait> ::selectors::Element } } - fn match_non_ts_pseudo_class( + fn match_non_ts_pseudo_class( &self, _: &NonTSPseudoClass, _: &mut MatchingContext, - _: &mut F, - ) -> bool - where - F: FnMut(&Self, ElementSelectorFlags), - { + ) -> bool { // NB: This could maybe be implemented warn!("ServoThreadSafeLayoutElement::match_non_ts_pseudo_class called"); false @@ -907,6 +899,10 @@ impl<'dom, LayoutDataType: LayoutDataTrait> ::selectors::Element warn!("ServoThreadSafeLayoutElement::is_root called"); false } + + fn set_selector_flags(&self, flags: ElementSelectorFlags) { + self.element.element.insert_selector_flags(flags); + } } impl<'dom, LayoutDataType: LayoutDataTrait> GetStyleAndOpaqueLayoutData<'dom> diff --git a/components/selectors/Cargo.toml b/components/selectors/Cargo.toml index 2a91f50c636..880c4db0c61 100644 --- a/components/selectors/Cargo.toml +++ b/components/selectors/Cargo.toml @@ -28,6 +28,7 @@ log = "0.4" phf = "0.10" precomputed-hash = "0.1" servo_arc = { version = "0.2", path = "../servo_arc" } +size_of_test = { path = "../size_of_test" } smallvec = "1.0" to_shmem = { version = "0.0.0", path = "../to_shmem", optional = true } to_shmem_derive = { version = "0.0.0", path = "../to_shmem_derive", optional = true } diff --git a/components/selectors/context.rs b/components/selectors/context.rs index 9146b133e38..f595389d2f2 100644 --- a/components/selectors/context.rs +++ b/components/selectors/context.rs @@ -68,6 +68,14 @@ impl VisitedHandlingMode { } } +/// Whether we need to set selector invalidation flags on elements for this +/// match request. +#[derive(Clone, Copy, Debug, PartialEq)] +pub enum NeedsSelectorFlags { + No, + Yes, +} + /// Which quirks mode is this document in. /// /// See: https://quirks.spec.whatwg.org/ @@ -140,6 +148,7 @@ where pub extra_data: Impl::ExtraMatchingData, quirks_mode: QuirksMode, + needs_selector_flags: NeedsSelectorFlags, classes_and_ids_case_sensitivity: CaseSensitivity, _impl: ::std::marker::PhantomData, } @@ -154,6 +163,7 @@ where bloom_filter: Option<&'a BloomFilter>, nth_index_cache: Option<&'a mut NthIndexCache>, quirks_mode: QuirksMode, + needs_selector_flags: NeedsSelectorFlags, ) -> Self { Self::new_for_visited( matching_mode, @@ -161,6 +171,7 @@ where nth_index_cache, VisitedHandlingMode::AllLinksUnvisited, quirks_mode, + needs_selector_flags, ) } @@ -171,6 +182,7 @@ where nth_index_cache: Option<&'a mut NthIndexCache>, visited_handling: VisitedHandlingMode, quirks_mode: QuirksMode, + needs_selector_flags: NeedsSelectorFlags, ) -> Self { Self { matching_mode, @@ -179,6 +191,7 @@ where nth_index_cache, quirks_mode, classes_and_ids_case_sensitivity: quirks_mode.classes_and_ids_case_sensitivity(), + needs_selector_flags, scope_element: None, current_host: None, nesting_level: 0, @@ -213,6 +226,12 @@ where self.matching_mode } + /// Whether we need to set selector flags. + #[inline] + pub fn needs_selector_flags(&self) -> bool { + self.needs_selector_flags == NeedsSelectorFlags::Yes + } + /// The case-sensitivity for class and ID selectors #[inline] pub fn classes_and_ids_case_sensitivity(&self) -> CaseSensitivity { diff --git a/components/selectors/lib.rs b/components/selectors/lib.rs index 17f81c535b6..2b785a8cd65 100644 --- a/components/selectors/lib.rs +++ b/components/selectors/lib.rs @@ -5,6 +5,9 @@ // Make |cargo bench| work. #![cfg_attr(feature = "bench", feature(test))] +#[macro_use] +extern crate size_of_test; + pub mod attr; pub mod bloom; mod builder; diff --git a/components/selectors/matching.rs b/components/selectors/matching.rs index 92c13958985..3ded93e44b9 100644 --- a/components/selectors/matching.rs +++ b/components/selectors/matching.rs @@ -78,8 +78,7 @@ where // This is pretty much any(..) but manually inlined because the compiler // refuses to do so from querySelector / querySelectorAll. for selector in &selector_list.0 { - let matches = matches_selector(selector, 0, None, element, context, &mut |_, _| {}); - + let matches = matches_selector(selector, 0, None, element, context); if matches { return true; } @@ -184,17 +183,15 @@ enum MatchesHoverAndActiveQuirk { /// unncessary cache miss for cases when we can fast-reject with AncestorHashes /// (which the caller can store inline with the selector pointer). #[inline(always)] -pub fn matches_selector( +pub fn matches_selector( selector: &Selector, offset: usize, hashes: Option<&AncestorHashes>, element: &E, context: &mut MatchingContext, - flags_setter: &mut F, ) -> bool where E: Element, - F: FnMut(&E, ElementSelectorFlags), { // Use the bloom filter to fast-reject. if let Some(hashes) = hashes { @@ -205,7 +202,7 @@ where } } - matches_complex_selector(selector.iter_from(offset), element, context, flags_setter) + matches_complex_selector(selector.iter_from(offset), element, context) } /// Whether a compound selector matched, and whether it was the rightmost @@ -277,7 +274,7 @@ where ); for component in iter { - if !matches_simple_selector(component, element, &mut local_context, &mut |_, _| {}) { + if !matches_simple_selector(component, element, &mut local_context) { return CompoundSelectorMatchingResult::NotMatched; } } @@ -293,15 +290,13 @@ where /// Matches a complex selector. #[inline(always)] -pub fn matches_complex_selector( +pub fn matches_complex_selector( mut iter: SelectorIter, element: &E, context: &mut MatchingContext, - flags_setter: &mut F, ) -> bool where E: Element, - F: FnMut(&E, ElementSelectorFlags), { // If this is the special pseudo-element mode, consume the ::pseudo-element // before proceeding, since the caller has already handled that part. @@ -334,7 +329,7 @@ where } let result = - matches_complex_selector_internal(iter, element, context, flags_setter, Rightmost::Yes); + matches_complex_selector_internal(iter, element, context, Rightmost::Yes); matches!(result, SelectorMatchingResult::Matched) } @@ -458,16 +453,14 @@ where } } -fn matches_complex_selector_internal( +fn matches_complex_selector_internal( mut selector_iter: SelectorIter, element: &E, context: &mut MatchingContext, - flags_setter: &mut F, rightmost: Rightmost, ) -> SelectorMatchingResult where E: Element, - F: FnMut(&E, ElementSelectorFlags), { debug!( "Matching complex selector {:?} for {:?}", @@ -478,16 +471,16 @@ where &mut selector_iter, element, context, - flags_setter, rightmost, ); let combinator = selector_iter.next_sequence(); if combinator.map_or(false, |c| c.is_sibling()) { - flags_setter( - element, - ElementSelectorFlags::HAS_SLOW_SELECTOR_LATER_SIBLINGS, - ); + if context.needs_selector_flags() { + element.apply_selector_flags( + ElementSelectorFlags::HAS_SLOW_SELECTOR_LATER_SIBLINGS + ); + } } if !matches_compound_selector { @@ -532,7 +525,6 @@ where selector_iter.clone(), &element, context, - flags_setter, Rightmost::No, ) }); @@ -595,16 +587,14 @@ where /// Determines whether the given element matches the given compound selector. #[inline] -fn matches_compound_selector( +fn matches_compound_selector( selector_iter: &mut SelectorIter, element: &E, context: &mut MatchingContext, - flags_setter: &mut F, rightmost: Rightmost, ) -> bool where E: Element, - F: FnMut(&E, ElementSelectorFlags), { let matches_hover_and_active_quirk = matches_hover_and_active_quirk(&selector_iter, context, rightmost); @@ -643,19 +633,17 @@ where }; iter::once(selector) .chain(selector_iter) - .all(|simple| matches_simple_selector(simple, element, &mut local_context, flags_setter)) + .all(|simple| matches_simple_selector(simple, element, &mut local_context)) } /// Determines whether the given element matches the given single selector. -fn matches_simple_selector( +fn matches_simple_selector( selector: &Component, element: &E, context: &mut LocalMatchingContext, - flags_setter: &mut F, ) -> bool where E: Element, - F: FnMut(&E, ElementSelectorFlags), { debug_assert!(context.shared.is_nested() || !context.shared.in_negation()); @@ -703,7 +691,7 @@ where // are never flattened tree slottables. !element.is_html_slot_element() && context.shared.nest(|context| { - matches_complex_selector(selector.iter(), element, context, flags_setter) + matches_complex_selector(selector.iter(), element, context) }) }, Component::PseudoElement(ref pseudo) => { @@ -795,16 +783,18 @@ where return false; } - element.match_non_ts_pseudo_class(pc, &mut context.shared, flags_setter) + element.match_non_ts_pseudo_class(pc, &mut context.shared) }, - Component::FirstChild => matches_first_child(element, flags_setter), - Component::LastChild => matches_last_child(element, flags_setter), + Component::FirstChild => matches_first_child(element, context.shared), + Component::LastChild => matches_last_child(element, context.shared), Component::OnlyChild => { - matches_first_child(element, flags_setter) && matches_last_child(element, flags_setter) + matches_first_child(element, context.shared) && matches_last_child(element, context.shared) }, Component::Root => element.is_root(), Component::Empty => { - flags_setter(element, ElementSelectorFlags::HAS_EMPTY_SELECTOR); + if context.shared.needs_selector_flags() { + element.apply_selector_flags(ElementSelectorFlags::HAS_EMPTY_SELECTOR); + } element.is_empty() }, Component::Host(ref selector) => { @@ -814,7 +804,7 @@ where .map_or(false, |host| host == element.opaque()) && selector.as_ref().map_or(true, |selector| { context.shared.nest(|context| { - matches_complex_selector(selector.iter(), element, context, flags_setter) + matches_complex_selector(selector.iter(), element, context) }) }) }, @@ -823,30 +813,30 @@ where None => element.is_root(), }, Component::NthChild(a, b) => { - matches_generic_nth_child(element, context, a, b, false, false, flags_setter) + matches_generic_nth_child(element, context.shared, a, b, false, false) }, Component::NthLastChild(a, b) => { - matches_generic_nth_child(element, context, a, b, false, true, flags_setter) + matches_generic_nth_child(element, context.shared, a, b, false, true) }, Component::NthOfType(a, b) => { - matches_generic_nth_child(element, context, a, b, true, false, flags_setter) + matches_generic_nth_child(element, context.shared, a, b, true, false) }, Component::NthLastOfType(a, b) => { - matches_generic_nth_child(element, context, a, b, true, true, flags_setter) + matches_generic_nth_child(element, context.shared, a, b, true, true) }, Component::FirstOfType => { - matches_generic_nth_child(element, context, 0, 1, true, false, flags_setter) + matches_generic_nth_child(element, context.shared, 0, 1, true, false) }, Component::LastOfType => { - matches_generic_nth_child(element, context, 0, 1, true, true, flags_setter) + matches_generic_nth_child(element, context.shared, 0, 1, true, true) }, Component::OnlyOfType => { - matches_generic_nth_child(element, context, 0, 1, true, false, flags_setter) && - matches_generic_nth_child(element, context, 0, 1, true, true, flags_setter) + matches_generic_nth_child(element, context.shared, 0, 1, true, false) && + matches_generic_nth_child(element, context.shared, 0, 1, true, true) }, Component::Is(ref list) | Component::Where(ref list) => context.shared.nest(|context| { for selector in &**list { - if matches_complex_selector(selector.iter(), element, context, flags_setter) { + if matches_complex_selector(selector.iter(), element, context) { return true; } } @@ -854,7 +844,7 @@ where }), Component::Negation(ref list) => context.shared.nest_for_negation(|context| { for selector in &**list { - if matches_complex_selector(selector.iter(), element, context, flags_setter) { + if matches_complex_selector(selector.iter(), element, context) { return false; } } @@ -873,35 +863,31 @@ fn select_name<'a, T>(is_html: bool, local_name: &'a T, local_name_lower: &'a T) } #[inline] -fn matches_generic_nth_child( +fn matches_generic_nth_child( element: &E, - context: &mut LocalMatchingContext, + context: &mut MatchingContext, a: i32, b: i32, is_of_type: bool, is_from_end: bool, - flags_setter: &mut F, ) -> bool where E: Element, - F: FnMut(&E, ElementSelectorFlags), { if element.ignores_nth_child_selectors() { return false; } - flags_setter( - element, - if is_from_end { + if context.needs_selector_flags() { + element.apply_selector_flags(if is_from_end { ElementSelectorFlags::HAS_SLOW_SELECTOR } else { ElementSelectorFlags::HAS_SLOW_SELECTOR_LATER_SIBLINGS - }, - ); + }); + } // Grab a reference to the appropriate cache. let mut cache = context - .shared .nth_index_cache .as_mut() .map(|c| c.get(is_of_type, is_from_end)); @@ -992,21 +978,23 @@ where } #[inline] -fn matches_first_child(element: &E, flags_setter: &mut F) -> bool +fn matches_first_child(element: &E, context: &MatchingContext) -> bool where E: Element, - F: FnMut(&E, ElementSelectorFlags), { - flags_setter(element, ElementSelectorFlags::HAS_EDGE_CHILD_SELECTOR); + if context.needs_selector_flags() { + element.apply_selector_flags(ElementSelectorFlags::HAS_EDGE_CHILD_SELECTOR); + } element.prev_sibling_element().is_none() } #[inline] -fn matches_last_child(element: &E, flags_setter: &mut F) -> bool +fn matches_last_child(element: &E, context: &MatchingContext) -> bool where E: Element, - F: FnMut(&E, ElementSelectorFlags), { - flags_setter(element, ElementSelectorFlags::HAS_EDGE_CHILD_SELECTOR); + if context.needs_selector_flags() { + element.apply_selector_flags(ElementSelectorFlags::HAS_EDGE_CHILD_SELECTOR); + } element.next_sibling_element().is_none() } diff --git a/components/selectors/parser.rs b/components/selectors/parser.rs index c6650102616..cf342fc8ea8 100644 --- a/components/selectors/parser.rs +++ b/components/selectors/parser.rs @@ -160,6 +160,8 @@ impl SelectorParsingState { pub type SelectorParseError<'i> = ParseError<'i, SelectorParseErrorKind<'i>>; +size_of_test!(SelectorParseError, 48); + #[derive(Clone, Debug, PartialEq)] pub enum SelectorParseErrorKind<'i> { NoQualifiedNameInAttributeSelector(Token<'i>), @@ -184,6 +186,8 @@ pub enum SelectorParseErrorKind<'i> { ClassNeedsIdent(Token<'i>), } +size_of_test!(SelectorParseErrorKind, 40); + macro_rules! with_all_bounds { ( [ $( $InSelector: tt )* ] diff --git a/components/selectors/rustfmt.toml b/components/selectors/rustfmt.toml new file mode 100644 index 00000000000..c7ad93bafe3 --- /dev/null +++ b/components/selectors/rustfmt.toml @@ -0,0 +1 @@ +disable_all_formatting = true diff --git a/components/selectors/tree.rs b/components/selectors/tree.rs index 47cea73a208..c1f0f9341c7 100644 --- a/components/selectors/tree.rs +++ b/components/selectors/tree.rs @@ -77,14 +77,11 @@ pub trait Element: Sized + Clone + Debug { operation: &AttrSelectorOperation<&::AttrValue>, ) -> bool; - fn match_non_ts_pseudo_class( + fn match_non_ts_pseudo_class( &self, pc: &::NonTSPseudoClass, context: &mut MatchingContext, - flags_setter: &mut F, - ) -> bool - where - F: FnMut(&Self, ElementSelectorFlags); + ) -> bool; fn match_pseudo_element( &self, @@ -92,6 +89,30 @@ pub trait Element: Sized + Clone + Debug { context: &mut MatchingContext, ) -> bool; + /// Sets selector flags, which indicate what kinds of selectors may have + /// matched on this element and therefore what kind of work may need to + /// be performed when DOM state changes. + /// + /// You probably don't want to use this directly and want to use + /// apply_selector_flags, since that sets flags on the parent as needed. + fn set_selector_flags(&self, flags: ElementSelectorFlags); + + fn apply_selector_flags(&self, flags: ElementSelectorFlags) { + // Handle flags that apply to the element. + let self_flags = flags.for_self(); + if !self_flags.is_empty() { + self.set_selector_flags(self_flags); + } + + // Handle flags that apply to the parent. + let parent_flags = flags.for_parent(); + if !parent_flags.is_empty() { + if let Some(p) = self.parent_element() { + p.set_selector_flags(parent_flags); + } + } + } + /// Whether this element is a `link`. fn is_link(&self) -> bool; diff --git a/components/size_of_test/Cargo.toml b/components/size_of_test/Cargo.toml index 4776581cc52..faea55c5c1c 100644 --- a/components/size_of_test/Cargo.toml +++ b/components/size_of_test/Cargo.toml @@ -8,3 +8,6 @@ publish = false [lib] path = "lib.rs" + +[dependencies] +static_assertions = "1.1" diff --git a/components/size_of_test/lib.rs b/components/size_of_test/lib.rs index 4e2452232f3..18e45175e8c 100644 --- a/components/size_of_test/lib.rs +++ b/components/size_of_test/lib.rs @@ -2,33 +2,13 @@ * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ +pub use static_assertions::const_assert_eq; + +/// Asserts the size of a type at compile time. #[macro_export] macro_rules! size_of_test { - ($testname: ident, $t: ty, $expected_size: expr) => { - #[test] - fn $testname() { - let new = ::std::mem::size_of::<$t>(); - let old = $expected_size; - if new < old { - panic!( - "Your changes have decreased the stack size of {} from {} to {}. \ - Good work! Please update the expected size in {}.", - stringify!($t), - old, - new, - file!() - ) - } else if new > old { - panic!( - "Your changes have increased the stack size of {} from {} to {}. \ - Please consider choosing a design which avoids this increase. \ - If you feel that the increase is necessary, update the size in {}.", - stringify!($t), - old, - new, - file!() - ) - } - } + ($t: ty, $expected_size: expr) => { + #[cfg(target_pointer_width = "64")] + $crate::const_assert_eq!(std::mem::size_of::<$t>(), $expected_size); }; } diff --git a/components/style/Cargo.toml b/components/style/Cargo.toml index 6c5196d93f8..44ac2998e83 100644 --- a/components/style/Cargo.toml +++ b/components/style/Cargo.toml @@ -62,13 +62,14 @@ servo_config = { path = "../config", optional = true } servo_url = { path = "../url", optional = true } smallbitvec = "2.3.0" smallvec = "1.0" +static_assertions = "1.1" string_cache = { version = "0.8", optional = true } style_derive = { path = "../style_derive" } style_traits = { path = "../style_traits" } time = "0.1" to_shmem = { path = "../to_shmem" } to_shmem_derive = { path = "../to_shmem_derive" } -uluru = "2" +uluru = "3.0" unicode-bidi = "0.3" unicode-segmentation = "1.0" void = "1.0.2" diff --git a/components/style/animation.rs b/components/style/animation.rs index 7f0c7e6a588..1df3dc08cca 100644 --- a/components/style/animation.rs +++ b/components/style/animation.rs @@ -455,8 +455,8 @@ pub struct Animation { impl Animation { /// Whether or not this animation is cancelled by changes from a new style. fn is_cancelled_in_new_style(&self, new_style: &Arc) -> bool { - let index = new_style - .get_box() + let new_ui = new_style.get_ui(); + let index = new_ui .animation_name_iter() .position(|animation_name| Some(&self.name) == animation_name.as_atom()); let index = match index { @@ -464,7 +464,7 @@ impl Animation { None => return true, }; - new_style.get_box().animation_duration_mod(index).seconds() == 0. + new_ui.animation_duration_mod(index).seconds() == 0. } /// Given the current time, advances this animation to the next iteration, @@ -1073,10 +1073,10 @@ impl ElementAnimationSet { old_style: &ComputedValues, new_style: &Arc, ) { - let box_style = new_style.get_box(); - let timing_function = box_style.transition_timing_function_mod(index); - let duration = box_style.transition_duration_mod(index); - let delay = box_style.transition_delay_mod(index).seconds() as f64; + let style = new_style.get_ui(); + let timing_function = style.transition_timing_function_mod(index); + let duration = style.transition_duration_mod(index); + let delay = style.transition_delay_mod(index).seconds() as f64; let now = context.current_time_for_animations; // Only start a new transition if the style actually changes between @@ -1344,15 +1344,15 @@ pub fn maybe_start_animations( ) where E: TElement, { - let box_style = new_style.get_box(); - for (i, name) in box_style.animation_name_iter().enumerate() { + let style = new_style.get_ui(); + for (i, name) in style.animation_name_iter().enumerate() { let name = match name.as_atom() { Some(atom) => atom, None => continue, }; debug!("maybe_start_animations: name={}", name); - let duration = box_style.animation_duration_mod(i).seconds() as f64; + let duration = style.animation_duration_mod(i).seconds() as f64; if duration == 0. { continue; } @@ -1375,14 +1375,14 @@ pub fn maybe_start_animations( // NB: This delay may be negative, meaning that the animation may be created // in a state where we have advanced one or more iterations or even that the // animation begins in a finished state. - let delay = box_style.animation_delay_mod(i).seconds(); + let delay = style.animation_delay_mod(i).seconds(); - let iteration_state = match box_style.animation_iteration_count_mod(i) { + let iteration_state = match style.animation_iteration_count_mod(i) { AnimationIterationCount::Infinite => KeyframesIterationState::Infinite(0.0), AnimationIterationCount::Number(n) => KeyframesIterationState::Finite(0.0, n.into()), }; - let animation_direction = box_style.animation_direction_mod(i); + let animation_direction = style.animation_direction_mod(i); let initial_direction = match animation_direction { AnimationDirection::Normal | AnimationDirection::Alternate => { @@ -1396,7 +1396,7 @@ pub fn maybe_start_animations( let now = context.current_time_for_animations; let started_at = now + delay as f64; let mut starting_progress = (now - started_at) / duration; - let state = match box_style.animation_play_state_mod(i) { + let state = match style.animation_play_state_mod(i) { AnimationPlayState::Paused => AnimationState::Paused(starting_progress), AnimationPlayState::Running => AnimationState::Pending, }; @@ -1406,7 +1406,7 @@ pub fn maybe_start_animations( &keyframe_animation, context, new_style, - new_style.get_box().animation_timing_function_mod(i), + style.animation_timing_function_mod(i), resolver, ); @@ -1416,7 +1416,7 @@ pub fn maybe_start_animations( computed_steps, started_at, duration, - fill_mode: box_style.animation_fill_mode_mod(i), + fill_mode: style.animation_fill_mode_mod(i), delay: delay as f64, iteration_state, state, diff --git a/components/style/applicable_declarations.rs b/components/style/applicable_declarations.rs index 5849e7c2250..387dcdab07d 100644 --- a/components/style/applicable_declarations.rs +++ b/components/style/applicable_declarations.rs @@ -42,13 +42,7 @@ pub struct CascadePriority { layer_order: LayerOrder, } -#[allow(dead_code)] -fn size_assert() { - #[allow(unsafe_code)] - unsafe { - std::mem::transmute::(0u32) - }; -} +const_assert_eq!(std::mem::size_of::(), std::mem::size_of::()); impl PartialOrd for CascadePriority { #[inline] @@ -208,3 +202,6 @@ impl ApplicableDeclarationBlock { (self.source, self.cascade_priority) } } + +// Size of this struct determines sorting and selector-matching performance. +size_of_test!(ApplicableDeclarationBlock, 24); diff --git a/components/style/build_gecko.rs b/components/style/build_gecko.rs index 9c411b3ddb2..538b641d706 100644 --- a/components/style/build_gecko.rs +++ b/components/style/build_gecko.rs @@ -285,11 +285,11 @@ fn generate_structs() { let mut fixups = vec![]; let builder = BuilderWithConfig::new(builder, CONFIG["structs"].as_table().unwrap()) .handle_common(&mut fixups) - .handle_str_items("whitelist-functions", |b, item| b.allowlist_function(item)) + .handle_str_items("allowlist-functions", |b, item| b.allowlist_function(item)) .handle_str_items("bitfield-enums", |b, item| b.bitfield_enum(item)) .handle_str_items("rusty-enums", |b, item| b.rustified_enum(item)) - .handle_str_items("whitelist-vars", |b, item| b.allowlist_var(item)) - .handle_str_items("whitelist-types", |b, item| b.allowlist_type(item)) + .handle_str_items("allowlist-vars", |b, item| b.allowlist_var(item)) + .handle_str_items("allowlist-types", |b, item| b.allowlist_type(item)) .handle_str_items("opaque-types", |b, item| b.opaque_type(item)) .handle_table_items("cbindgen-types", |b, item| { let gecko = item["gecko"].as_str().unwrap(); diff --git a/components/style/context.rs b/components/style/context.rs index a7d5aa4a67b..633479c5a7a 100644 --- a/components/style/context.rs +++ b/components/style/context.rs @@ -27,8 +27,8 @@ use crate::traversal_flags::TraversalFlags; use app_units::Au; use euclid::default::Size2D; use euclid::Scale; +#[cfg(feature = "servo")] use fxhash::FxHashMap; -use selectors::matching::ElementSelectorFlags; use selectors::NthIndexCache; #[cfg(feature = "gecko")] use servo_arc::Arc; @@ -41,7 +41,6 @@ use style_traits::DevicePixel; #[cfg(feature = "servo")] use style_traits::SpeculativePainter; use time; -use uluru::LRUCache; pub use selectors::matching::QuirksMode; @@ -517,65 +516,6 @@ impl SequentialTask { } } -type CacheItem = (SendElement, ElementSelectorFlags); - -/// Map from Elements to ElementSelectorFlags. Used to defer applying selector -/// flags until after the traversal. -pub struct SelectorFlagsMap { - /// The hashmap storing the flags to apply. - map: FxHashMap, ElementSelectorFlags>, - /// An LRU cache to avoid hashmap lookups, which can be slow if the map - /// gets big. - cache: LRUCache, { 4 + 1 }>, -} - -#[cfg(debug_assertions)] -impl Drop for SelectorFlagsMap { - fn drop(&mut self) { - debug_assert!(self.map.is_empty()); - } -} - -impl SelectorFlagsMap { - /// Creates a new empty SelectorFlagsMap. - pub fn new() -> Self { - SelectorFlagsMap { - map: FxHashMap::default(), - cache: LRUCache::default(), - } - } - - /// Inserts some flags into the map for a given element. - pub fn insert_flags(&mut self, element: E, flags: ElementSelectorFlags) { - let el = unsafe { SendElement::new(element) }; - // Check the cache. If the flags have already been noted, we're done. - if let Some(item) = self.cache.find(|x| x.0 == el) { - if !item.1.contains(flags) { - item.1.insert(flags); - self.map.get_mut(&el).unwrap().insert(flags); - } - return; - } - - let f = self.map.entry(el).or_insert(ElementSelectorFlags::empty()); - *f |= flags; - - self.cache - .insert((unsafe { SendElement::new(element) }, *f)) - } - - /// Applies the flags. Must be called on the main thread. - fn apply_flags(&mut self) { - debug_assert_eq!(thread_state::get(), ThreadState::LAYOUT); - self.cache.clear(); - for (el, flags) in self.map.drain() { - unsafe { - el.set_selector_flags(flags); - } - } - } -} - /// A list of SequentialTasks that get executed on Drop. pub struct SequentialTaskList(Vec>) where @@ -697,11 +637,6 @@ pub struct ThreadLocalStyleContext { /// filter, to ensure they're dropped before we execute the tasks, which /// could create another ThreadLocalStyleContext for style computation. pub tasks: SequentialTaskList, - /// ElementSelectorFlags that need to be applied after the traversal is - /// complete. This map is used in cases where the matching algorithm needs - /// to set flags on elements it doesn't have exclusive access to (i.e. other - /// than the current element). - pub selector_flags: SelectorFlagsMap, /// Statistics about the traversal. pub statistics: PerThreadTraversalStatistics, /// A checker used to ensure that parallel.rs does not recurse indefinitely @@ -719,7 +654,6 @@ impl ThreadLocalStyleContext { rule_cache: RuleCache::new(), bloom_filter: StyleBloom::new(), tasks: SequentialTaskList(Vec::new()), - selector_flags: SelectorFlagsMap::new(), statistics: PerThreadTraversalStatistics::default(), stack_limit_checker: StackLimitChecker::new( (STYLE_THREAD_STACK_SIZE_KB - STACK_SAFETY_MARGIN_KB) * 1024, @@ -729,15 +663,6 @@ impl ThreadLocalStyleContext { } } -impl Drop for ThreadLocalStyleContext { - fn drop(&mut self) { - debug_assert_eq!(thread_state::get(), ThreadState::LAYOUT); - - // Apply any slow selector flags that need to be set on parents. - self.selector_flags.apply_flags(); - } -} - /// A `StyleContext` is just a simple container for a immutable reference to a /// shared style context, and a mutable reference to a local one. pub struct StyleContext<'a, E: TElement + 'a> { diff --git a/components/style/custom_properties.rs b/components/style/custom_properties.rs index 7be700ec350..054e5a17c74 100644 --- a/components/style/custom_properties.rs +++ b/components/style/custom_properties.rs @@ -64,6 +64,27 @@ fn get_safearea_inset_right(device: &Device) -> VariableValue { VariableValue::pixels(device.safe_area_insets().right) } +#[cfg(feature = "gecko")] +fn get_content_preferred_color_scheme(device: &Device) -> VariableValue { + use crate::gecko::media_features::PrefersColorScheme; + let prefers_color_scheme = unsafe { + crate::gecko_bindings::bindings::Gecko_MediaFeatures_PrefersColorScheme( + device.document(), + /* use_content = */ true, + ) + }; + VariableValue::ident(match prefers_color_scheme { + PrefersColorScheme::Light => "light", + PrefersColorScheme::Dark => "dark", + }) +} + +#[cfg(feature = "servo")] +fn get_content_preferred_color_scheme(_device: &Device) -> VariableValue { + // TODO: implement this. + VariableValue::ident("light") +} + static ENVIRONMENT_VARIABLES: [EnvironmentVariable; 4] = [ make_variable!(atom!("safe-area-inset-top"), get_safearea_inset_top), make_variable!(atom!("safe-area-inset-bottom"), get_safearea_inset_bottom), @@ -99,7 +120,7 @@ macro_rules! lnf_int_variable { }}; } -static CHROME_ENVIRONMENT_VARIABLES: [EnvironmentVariable; 5] = [ +static CHROME_ENVIRONMENT_VARIABLES: [EnvironmentVariable; 6] = [ lnf_int_variable!( atom!("-moz-gtk-csd-titlebar-radius"), TitlebarRadius, @@ -121,6 +142,7 @@ static CHROME_ENVIRONMENT_VARIABLES: [EnvironmentVariable; 5] = [ GTKCSDMaximizeButtonPosition, integer ), + make_variable!(atom!("-moz-content-preferred-color-scheme"), get_content_preferred_color_scheme), ]; impl CssEnvironment { @@ -328,6 +350,11 @@ impl VariableValue { }) } + /// Create VariableValue from an int. + fn ident(ident: &'static str) -> Self { + Self::from_token(Token::Ident(ident.into())) + } + /// Create VariableValue from a float amount of CSS pixels. fn pixels(number: f32) -> Self { // FIXME (https://github.com/servo/rust-cssparser/issues/266): diff --git a/components/style/data.rs b/components/style/data.rs index 758adf57a68..2fa0b39c24d 100644 --- a/components/style/data.rs +++ b/components/style/data.rs @@ -159,6 +159,9 @@ pub struct ElementStyles { pub pseudos: EagerPseudoStyles, } +// There's one of these per rendered elements so it better be small. +size_of_test!(ElementStyles, 16); + impl ElementStyles { /// Returns the primary style. pub fn get_primary(&self) -> Option<&Arc> { @@ -249,6 +252,9 @@ pub struct ElementData { pub flags: ElementDataFlags, } +// There's one of these per rendered elements so it better be small. +size_of_test!(ElementData, 24); + /// The kind of restyle that a single element should do. #[derive(Debug)] pub enum RestyleKind { diff --git a/components/style/dom.rs b/components/style/dom.rs index 308006efd11..83466eb21e6 100644 --- a/components/style/dom.rs +++ b/components/style/dom.rs @@ -22,7 +22,7 @@ use crate::traversal_flags::TraversalFlags; use crate::values::AtomIdent; use crate::{LocalName, Namespace, WeakAtom}; use atomic_refcell::{AtomicRef, AtomicRefMut}; -use selectors::matching::{ElementSelectorFlags, QuirksMode, VisitedHandlingMode}; +use selectors::matching::{QuirksMode, VisitedHandlingMode}; use selectors::sink::Push; use selectors::Element as SelectorsElement; use servo_arc::{Arc, ArcBorrow}; @@ -734,19 +734,6 @@ pub trait TElement: /// native anonymous content can opt out of this style fixup.) fn skip_item_display_fixup(&self) -> bool; - /// Sets selector flags, which indicate what kinds of selectors may have - /// matched on this element and therefore what kind of work may need to - /// be performed when DOM state changes. - /// - /// This is unsafe, like all the flag-setting methods, because it's only safe - /// to call with exclusive access to the element. When setting flags on the - /// parent during parallel traversal, we use SequentialTask to queue up the - /// set to run after the threads join. - unsafe fn set_selector_flags(&self, flags: ElementSelectorFlags); - - /// Returns true if the element has all the specified selector flags. - fn has_selector_flags(&self, flags: ElementSelectorFlags) -> bool; - /// In Gecko, element has a flag that represents the element may have /// any type of animations or not to bail out animation stuff early. /// Whereas Servo doesn't have such flag. @@ -941,6 +928,9 @@ pub trait TElement: /// Returns element's namespace. fn namespace(&self) -> &::BorrowedNamespaceUrl; + + /// Returns the size of the primary box of the element. + fn primary_box_size(&self) -> euclid::default::Size2D; } /// TNode and TElement aren't Send because we want to be careful and explicit diff --git a/components/style/dom_apis.rs b/components/style/dom_apis.rs index 6d19801cfc5..e6f1cbf565c 100644 --- a/components/style/dom_apis.rs +++ b/components/style/dom_apis.rs @@ -12,7 +12,7 @@ use crate::invalidation::element::invalidator::{DescendantInvalidationLists, Inv use crate::invalidation::element::invalidator::{InvalidationProcessor, InvalidationVector}; use crate::values::AtomIdent; use selectors::attr::CaseSensitivity; -use selectors::matching::{self, MatchingContext, MatchingMode}; +use selectors::matching::{self, MatchingContext, MatchingMode, NeedsSelectorFlags}; use selectors::parser::{Combinator, Component, LocalName, SelectorImpl}; use selectors::{Element, NthIndexCache, SelectorList}; use smallvec::SmallVec; @@ -26,7 +26,13 @@ pub fn element_matches( where E: Element, { - let mut context = MatchingContext::new(MatchingMode::Normal, None, None, quirks_mode); + let mut context = MatchingContext::new( + MatchingMode::Normal, + None, + None, + quirks_mode, + NeedsSelectorFlags::No, + ); context.scope_element = Some(element.opaque()); context.current_host = element.containing_shadow_host().map(|e| e.opaque()); matching::matches_selector_list(selector_list, element, &mut context) @@ -48,6 +54,7 @@ where None, Some(&mut nth_index_cache), quirks_mode, + NeedsSelectorFlags::No, ); context.scope_element = Some(element.opaque()); context.current_host = element.containing_shadow_host().map(|e| e.opaque()); @@ -618,8 +625,8 @@ pub fn query_selector( None, Some(&mut nth_index_cache), quirks_mode, + NeedsSelectorFlags::No, ); - let root_element = root.as_element(); matching_context.scope_element = root_element.map(|e| e.opaque()); matching_context.current_host = match root_element { diff --git a/components/style/element_state.rs b/components/style/element_state.rs index 30c65dc9feb..f8f4629ef9b 100644 --- a/components/style/element_state.rs +++ b/components/style/element_state.rs @@ -118,8 +118,8 @@ bitflags! { const IN_MODAL_DIALOG_STATE = 1 << 42; /// const IN_MOZINERT_STATE = 1 << 43; - /// State for the topmost dialog element in top layer - const IN_TOPMOST_MODAL_DIALOG_STATE = 1 << 44; + /// State for the topmost modal element in top layer + const IN_TOPMOST_MODAL_TOP_LAYER_STATE = 1 << 44; /// Initially used for the devtools highlighter, but now somehow only /// used for the devtools accessibility inspector. const IN_DEVTOOLS_HIGHLIGHTED_STATE = 1 << 45; @@ -148,9 +148,5 @@ bitflags! { const LTR_LOCALE = 1 << 2; /// LWTheme status const LWTHEME = 1 << 3; - /// LWTheme status - const LWTHEME_BRIGHTTEXT = 1 << 4; - /// LWTheme status - const LWTHEME_DARKTEXT = 1 << 5; } } diff --git a/components/style/gecko/arc_types.rs b/components/style/gecko/arc_types.rs index 4fb08c3a309..893812ea710 100644 --- a/components/style/gecko/arc_types.rs +++ b/components/style/gecko/arc_types.rs @@ -15,7 +15,7 @@ use crate::gecko_bindings::structs::{ RawServoKeyframesRule, RawServoLayerBlockRule, RawServoLayerStatementRule, RawServoMediaList, RawServoMediaRule, RawServoMozDocumentRule, RawServoNamespaceRule, RawServoPageRule, RawServoScrollTimelineRule, RawServoStyleRule, RawServoStyleSheetContents, - RawServoSupportsRule, ServoCssRules, + RawServoSupportsRule, RawServoContainerRule, ServoCssRules, }; use crate::gecko_bindings::sugar::ownership::{HasArcFFI, HasFFI, Strong}; use crate::media_queries::MediaList; @@ -26,7 +26,7 @@ use crate::stylesheets::keyframes_rule::Keyframe; use crate::stylesheets::{ CounterStyleRule, CssRules, DocumentRule, FontFaceRule, FontFeatureValuesRule, ImportRule, KeyframesRule, LayerBlockRule, LayerStatementRule, MediaRule, NamespaceRule, PageRule, - ScrollTimelineRule, StyleRule, StylesheetContents, SupportsRule, + ScrollTimelineRule, StyleRule, StylesheetContents, SupportsRule, ContainerRule, }; use servo_arc::{Arc, ArcBorrow}; use std::{mem, ptr}; @@ -98,6 +98,9 @@ impl_arc_ffi!(Locked => RawServoScrollTimelineRule impl_arc_ffi!(Locked => RawServoSupportsRule [Servo_SupportsRule_AddRef, Servo_SupportsRule_Release]); +impl_arc_ffi!(Locked => RawServoContainerRule + [Servo_ContainerRule_AddRef, Servo_ContainerRule_Release]); + impl_arc_ffi!(Locked => RawServoMozDocumentRule [Servo_DocumentRule_AddRef, Servo_DocumentRule_Release]); diff --git a/components/style/gecko/data.rs b/components/style/gecko/data.rs index 39f7d7cc8d8..cabae61d563 100644 --- a/components/style/gecko/data.rs +++ b/components/style/gecko/data.rs @@ -129,6 +129,12 @@ impl StylesheetInDocument for GeckoStyleSheet { pub struct PerDocumentStyleDataImpl { /// Rule processor. pub stylist: Stylist, + + /// A cache from element to resolved style. + pub undisplayed_style_cache: crate::traversal::UndisplayedStyleCache, + + /// The generation for which our cache is valid. + pub undisplayed_style_cache_generation: u64, } /// The data itself is an `AtomicRefCell`, which guarantees the proper semantics @@ -143,6 +149,8 @@ impl PerDocumentStyleData { PerDocumentStyleData(AtomicRefCell::new(PerDocumentStyleDataImpl { stylist: Stylist::new(device, quirks_mode.into()), + undisplayed_style_cache: Default::default(), + undisplayed_style_cache_generation: 0, })) } @@ -177,12 +185,6 @@ impl PerDocumentStyleDataImpl { self.stylist.device().default_computed_values_arc() } - /// Returns whether visited styles are enabled. - #[inline] - pub fn visited_styles_enabled(&self) -> bool { - unsafe { bindings::Gecko_VisitedStylesEnabled(self.stylist.device().document()) } - } - /// Measure heap usage. pub fn add_size_of(&self, ops: &mut MallocSizeOfOps, sizes: &mut ServoStyleSetSizes) { self.stylist.add_size_of(ops, sizes); diff --git a/components/style/gecko/media_features.rs b/components/style/gecko/media_features.rs index af57be431f9..8c93dfa24ce 100644 --- a/components/style/gecko/media_features.rs +++ b/components/style/gecko/media_features.rs @@ -6,13 +6,10 @@ use crate::gecko_bindings::bindings; use crate::gecko_bindings::structs; -use crate::media_queries::media_feature::{AllowsRanges, ParsingRequirements}; -use crate::media_queries::media_feature::{Evaluator, MediaFeatureDescription}; -use crate::media_queries::media_feature_expression::RangeOrOperator; +use crate::queries::feature::{AllowsRanges, Evaluator, FeatureFlags, QueryFeatureDescription}; +use crate::queries::values::Orientation; use crate::media_queries::{Device, MediaType}; -use crate::values::computed::CSSPixelLength; -use crate::values::computed::Ratio; -use crate::values::computed::Resolution; +use crate::values::computed::{Context, CSSPixelLength, Ratio, Resolution}; use app_units::Au; use euclid::default::Size2D; @@ -26,150 +23,56 @@ fn device_size(device: &Device) -> Size2D { } /// https://drafts.csswg.org/mediaqueries-4/#width -fn eval_width( - device: &Device, - value: Option, - range_or_operator: Option, -) -> bool { - RangeOrOperator::evaluate( - range_or_operator, - value.map(Au::from), - device.au_viewport_size().width, - ) +fn eval_width(context: &Context) -> CSSPixelLength { + CSSPixelLength::new(context.device().au_viewport_size().width.to_f32_px()) } /// https://drafts.csswg.org/mediaqueries-4/#device-width -fn eval_device_width( - device: &Device, - value: Option, - range_or_operator: Option, -) -> bool { - RangeOrOperator::evaluate( - range_or_operator, - value.map(Au::from), - device_size(device).width, - ) +fn eval_device_width(context: &Context) -> CSSPixelLength { + CSSPixelLength::new(device_size(context.device()).width.to_f32_px()) } /// https://drafts.csswg.org/mediaqueries-4/#height -fn eval_height( - device: &Device, - value: Option, - range_or_operator: Option, -) -> bool { - RangeOrOperator::evaluate( - range_or_operator, - value.map(Au::from), - device.au_viewport_size().height, - ) +fn eval_height(context: &Context) -> CSSPixelLength { + CSSPixelLength::new(context.device().au_viewport_size().height.to_f32_px()) } /// https://drafts.csswg.org/mediaqueries-4/#device-height -fn eval_device_height( - device: &Device, - value: Option, - range_or_operator: Option, -) -> bool { - RangeOrOperator::evaluate( - range_or_operator, - value.map(Au::from), - device_size(device).height, - ) +fn eval_device_height(context: &Context) -> CSSPixelLength { + CSSPixelLength::new(device_size(context.device()).height.to_f32_px()) } -fn eval_aspect_ratio_for( - device: &Device, - query_value: Option, - range_or_operator: Option, - get_size: F, -) -> bool +fn eval_aspect_ratio_for(context: &Context, get_size: F) -> Ratio where F: FnOnce(&Device) -> Size2D, { - // A ratio of 0/0 behaves as the ratio 1/0, so we need to call used_value() - // to convert it if necessary. - // FIXME: we may need to update here once - // https://github.com/w3c/csswg-drafts/issues/4954 got resolved. - let query_value = match query_value { - Some(v) => v.used_value(), - None => return true, - }; - - let size = get_size(device); - let value = Ratio::new(size.width.0 as f32, size.height.0 as f32); - RangeOrOperator::evaluate_with_query_value(range_or_operator, query_value, value) + let size = get_size(context.device()); + Ratio::new(size.width.0 as f32, size.height.0 as f32) } /// https://drafts.csswg.org/mediaqueries-4/#aspect-ratio -fn eval_aspect_ratio( - device: &Device, - query_value: Option, - range_or_operator: Option, -) -> bool { - eval_aspect_ratio_for( - device, - query_value, - range_or_operator, - Device::au_viewport_size, - ) +fn eval_aspect_ratio(context: &Context) -> Ratio { + eval_aspect_ratio_for(context, Device::au_viewport_size) } /// https://drafts.csswg.org/mediaqueries-4/#device-aspect-ratio -fn eval_device_aspect_ratio( - device: &Device, - query_value: Option, - range_or_operator: Option, -) -> bool { - eval_aspect_ratio_for(device, query_value, range_or_operator, device_size) +fn eval_device_aspect_ratio(context: &Context) -> Ratio { + eval_aspect_ratio_for(context, device_size) } /// https://compat.spec.whatwg.org/#css-media-queries-webkit-device-pixel-ratio -fn eval_device_pixel_ratio( - device: &Device, - query_value: Option, - range_or_operator: Option, -) -> bool { - eval_resolution( - device, - query_value.map(Resolution::from_dppx), - range_or_operator, - ) -} - -#[derive(Clone, Copy, Debug, FromPrimitive, Parse, ToCss)] -#[repr(u8)] -enum Orientation { - Landscape, - Portrait, -} - -fn eval_orientation_for(device: &Device, value: Option, get_size: F) -> bool -where - F: FnOnce(&Device) -> Size2D, -{ - let query_orientation = match value { - Some(v) => v, - None => return true, - }; - - let size = get_size(device); - - // Per spec, square viewports should be 'portrait' - let is_landscape = size.width > size.height; - match query_orientation { - Orientation::Landscape => is_landscape, - Orientation::Portrait => !is_landscape, - } +fn eval_device_pixel_ratio(context: &Context) -> f32 { + eval_resolution(context).dppx() } /// https://drafts.csswg.org/mediaqueries-4/#orientation -fn eval_orientation(device: &Device, value: Option) -> bool { - eval_orientation_for(device, value, Device::au_viewport_size) +fn eval_orientation(context: &Context, value: Option) -> bool { + Orientation::eval(context.device().au_viewport_size(), value) } /// FIXME: There's no spec for `-moz-device-orientation`. -fn eval_device_orientation(device: &Device, value: Option) -> bool { - eval_orientation_for(device, value, device_size) +fn eval_device_orientation(context: &Context, value: Option) -> bool { + Orientation::eval(device_size(context.device()), value) } /// Values for the display-mode media feature. @@ -184,25 +87,23 @@ pub enum DisplayMode { } /// https://w3c.github.io/manifest/#the-display-mode-media-feature -fn eval_display_mode(device: &Device, query_value: Option) -> bool { +fn eval_display_mode(context: &Context, query_value: Option) -> bool { match query_value { - Some(v) => v == unsafe { bindings::Gecko_MediaFeatures_GetDisplayMode(device.document()) }, + Some(v) => v == unsafe { bindings::Gecko_MediaFeatures_GetDisplayMode(context.device().document()) }, None => true, } } /// https://drafts.csswg.org/mediaqueries-4/#grid -fn eval_grid(_: &Device, query_value: Option, _: Option) -> bool { +fn eval_grid(_: &Context) -> bool { // Gecko doesn't support grid devices (e.g., ttys), so the 'grid' feature // is always 0. - let supports_grid = false; - query_value.map_or(supports_grid, |v| v == supports_grid) + false } /// https://compat.spec.whatwg.org/#css-media-queries-webkit-transform-3d -fn eval_transform_3d(_: &Device, query_value: Option, _: Option) -> bool { - let supports_transforms = true; - query_value.map_or(supports_transforms, |v| v == supports_transforms) +fn eval_transform_3d(_: &Context) -> bool { + true } #[derive(Clone, Copy, Debug, FromPrimitive, Parse, ToCss)] @@ -213,58 +114,33 @@ enum Scan { } /// https://drafts.csswg.org/mediaqueries-4/#scan -fn eval_scan(_: &Device, _: Option) -> bool { +fn eval_scan(_: &Context, _: Option) -> bool { // Since Gecko doesn't support the 'tv' media type, the 'scan' feature never // matches. false } /// https://drafts.csswg.org/mediaqueries-4/#color -fn eval_color( - device: &Device, - query_value: Option, - range_or_operator: Option, -) -> bool { - let color_bits_per_channel = - unsafe { bindings::Gecko_MediaFeatures_GetColorDepth(device.document()) }; - RangeOrOperator::evaluate(range_or_operator, query_value, color_bits_per_channel) +fn eval_color(context: &Context) -> u32 { + unsafe { bindings::Gecko_MediaFeatures_GetColorDepth(context.device().document()) } } /// https://drafts.csswg.org/mediaqueries-4/#color-index -fn eval_color_index( - _: &Device, - query_value: Option, - range_or_operator: Option, -) -> bool { +fn eval_color_index(_: &Context) -> u32 { // We should return zero if the device does not use a color lookup table. - let index = 0; - RangeOrOperator::evaluate(range_or_operator, query_value, index) + 0 } /// https://drafts.csswg.org/mediaqueries-4/#monochrome -fn eval_monochrome( - device: &Device, - query_value: Option, - range_or_operator: Option, -) -> bool { +fn eval_monochrome(context: &Context) -> u32 { // For color devices we should return 0. - let depth = - unsafe { bindings::Gecko_MediaFeatures_GetMonochromeBitsPerPixel(device.document()) }; - RangeOrOperator::evaluate(range_or_operator, query_value, depth) + unsafe { bindings::Gecko_MediaFeatures_GetMonochromeBitsPerPixel(context.device().document()) } } /// https://drafts.csswg.org/mediaqueries-4/#resolution -fn eval_resolution( - device: &Device, - query_value: Option, - range_or_operator: Option, -) -> bool { - let resolution_dppx = unsafe { bindings::Gecko_MediaFeatures_GetResolution(device.document()) }; - RangeOrOperator::evaluate( - range_or_operator, - query_value.map(|r| r.dppx()), - resolution_dppx, - ) +fn eval_resolution(context: &Context) -> Resolution { + let resolution_dppx = unsafe { bindings::Gecko_MediaFeatures_GetResolution(context.device().document()) }; + Resolution::from_dppx(resolution_dppx) } #[derive(Clone, Copy, Debug, FromPrimitive, Parse, ToCss)] @@ -283,10 +159,22 @@ pub enum PrefersColorScheme { Dark, } +/// Values for the dynamic-range and video-dynamic-range media features. +/// https://drafts.csswg.org/mediaqueries-5/#dynamic-range +/// This implements PartialOrd so that lower values will correctly match +/// higher capabilities. +#[derive(Clone, Copy, Debug, FromPrimitive, Parse, PartialEq, PartialOrd, ToCss)] +#[repr(u8)] +#[allow(missing_docs)] +pub enum DynamicRange { + Standard, + High, +} + /// https://drafts.csswg.org/mediaqueries-5/#prefers-reduced-motion -fn eval_prefers_reduced_motion(device: &Device, query_value: Option) -> bool { +fn eval_prefers_reduced_motion(context: &Context, query_value: Option) -> bool { let prefers_reduced = - unsafe { bindings::Gecko_MediaFeatures_PrefersReducedMotion(device.document()) }; + unsafe { bindings::Gecko_MediaFeatures_PrefersReducedMotion(context.device().document()) }; let query_value = match query_value { Some(v) => v, None => return prefers_reduced, @@ -303,19 +191,20 @@ fn eval_prefers_reduced_motion(device: &Device, query_value: Option) -> bool { +fn eval_prefers_contrast(context: &Context, query_value: Option) -> bool { let prefers_contrast = - unsafe { bindings::Gecko_MediaFeatures_PrefersContrast(device.document()) }; + unsafe { bindings::Gecko_MediaFeatures_PrefersContrast(context.device().document()) }; match query_value { Some(v) => v == prefers_contrast, None => prefers_contrast != PrefersContrast::NoPreference, @@ -334,8 +223,8 @@ pub enum ForcedColors { } /// https://drafts.csswg.org/mediaqueries-5/#forced-colors -fn eval_forced_colors(device: &Device, query_value: Option) -> bool { - let forced = !device.use_document_colors(); +fn eval_forced_colors(context: &Context, query_value: Option) -> bool { + let forced = !context.device().use_document_colors(); match query_value { Some(query_value) => forced == (query_value == ForcedColors::Active), None => forced, @@ -352,7 +241,7 @@ enum OverflowBlock { } /// https://drafts.csswg.org/mediaqueries-4/#mf-overflow-block -fn eval_overflow_block(device: &Device, query_value: Option) -> bool { +fn eval_overflow_block(context: &Context, query_value: Option) -> bool { // For the time being, assume that printing (including previews) // is the only time when we paginate, and we are otherwise always // scrolling. This is true at the moment in Firefox, but may need @@ -360,7 +249,7 @@ fn eval_overflow_block(device: &Device, query_value: Option) -> b // billboard mode that doesn't support overflow at all). // // If this ever changes, don't forget to change eval_overflow_inline too. - let scrolling = device.media_type() != MediaType::print(); + let scrolling = context.device().media_type() != MediaType::print(); let query_value = match query_value { Some(v) => v, None => return true, @@ -381,9 +270,9 @@ enum OverflowInline { } /// https://drafts.csswg.org/mediaqueries-4/#mf-overflow-inline -fn eval_overflow_inline(device: &Device, query_value: Option) -> bool { +fn eval_overflow_inline(context: &Context, query_value: Option) -> bool { // See the note in eval_overflow_block. - let scrolling = device.media_type() != MediaType::print(); + let scrolling = context.device().media_type() != MediaType::print(); let query_value = match query_value { Some(v) => v, None => return scrolling, @@ -395,13 +284,41 @@ fn eval_overflow_inline(device: &Device, query_value: Option) -> } } +#[derive(Clone, Copy, Debug, FromPrimitive, Parse, ToCss)] +#[repr(u8)] +enum Update { + None, + Slow, + Fast, +} + +/// https://drafts.csswg.org/mediaqueries-4/#update +fn eval_update(context: &Context, query_value: Option) -> bool { + // This has similar caveats to those described in eval_overflow_block. + // For now, we report that print (incl. print media simulation, + // which can in fact update but is limited to the developer tools) + // is `update: none` and that all other contexts are `update: fast`, + // which may not be true for future platforms, like e-ink devices. + let can_update = context.device().media_type() != MediaType::print(); + let query_value = match query_value { + Some(v) => v, + None => return can_update, + }; + + match query_value { + Update::None => !can_update, + Update::Slow => false, + Update::Fast => can_update, + } +} + fn do_eval_prefers_color_scheme( - device: &Device, + context: &Context, use_content: bool, query_value: Option, ) -> bool { let prefers_color_scheme = - unsafe { bindings::Gecko_MediaFeatures_PrefersColorScheme(device.document(), use_content) }; + unsafe { bindings::Gecko_MediaFeatures_PrefersColorScheme(context.device().document(), use_content) }; match query_value { Some(v) => prefers_color_scheme == v, None => true, @@ -409,15 +326,34 @@ fn do_eval_prefers_color_scheme( } /// https://drafts.csswg.org/mediaqueries-5/#prefers-color-scheme -fn eval_prefers_color_scheme(device: &Device, query_value: Option) -> bool { - do_eval_prefers_color_scheme(device, /* use_content = */ false, query_value) +fn eval_prefers_color_scheme(context: &Context, query_value: Option) -> bool { + do_eval_prefers_color_scheme(context, /* use_content = */ false, query_value) } fn eval_content_prefers_color_scheme( - device: &Device, + context: &Context, query_value: Option, ) -> bool { - do_eval_prefers_color_scheme(device, /* use_content = */ true, query_value) + do_eval_prefers_color_scheme(context, /* use_content = */ true, query_value) +} + +/// https://drafts.csswg.org/mediaqueries-5/#dynamic-range +fn eval_dynamic_range(context: &Context, query_value: Option) -> bool { + let dynamic_range = + unsafe { bindings::Gecko_MediaFeatures_DynamicRange(context.device().document()) }; + match query_value { + Some(v) => dynamic_range >= v, + None => false, + } +} +/// https://drafts.csswg.org/mediaqueries-5/#video-dynamic-range +fn eval_video_dynamic_range(context: &Context, query_value: Option) -> bool { + let dynamic_range = + unsafe { bindings::Gecko_MediaFeatures_VideoDynamicRange(context.device().document()) }; + match query_value { + Some(v) => dynamic_range >= v, + None => false, + } } bitflags! { @@ -429,15 +365,15 @@ bitflags! { } } -fn primary_pointer_capabilities(device: &Device) -> PointerCapabilities { +fn primary_pointer_capabilities(context: &Context) -> PointerCapabilities { PointerCapabilities::from_bits_truncate(unsafe { - bindings::Gecko_MediaFeatures_PrimaryPointerCapabilities(device.document()) + bindings::Gecko_MediaFeatures_PrimaryPointerCapabilities(context.device().document()) }) } -fn all_pointer_capabilities(device: &Device) -> PointerCapabilities { +fn all_pointer_capabilities(context: &Context) -> PointerCapabilities { PointerCapabilities::from_bits_truncate(unsafe { - bindings::Gecko_MediaFeatures_AllPointerCapabilities(device.document()) + bindings::Gecko_MediaFeatures_AllPointerCapabilities(context.device().document()) }) } @@ -466,13 +402,13 @@ fn eval_pointer_capabilities( } /// https://drafts.csswg.org/mediaqueries-4/#pointer -fn eval_pointer(device: &Device, query_value: Option) -> bool { - eval_pointer_capabilities(query_value, primary_pointer_capabilities(device)) +fn eval_pointer(context: &Context, query_value: Option) -> bool { + eval_pointer_capabilities(query_value, primary_pointer_capabilities(context)) } /// https://drafts.csswg.org/mediaqueries-4/#descdef-media-any-pointer -fn eval_any_pointer(device: &Device, query_value: Option) -> bool { - eval_pointer_capabilities(query_value, all_pointer_capabilities(device)) +fn eval_any_pointer(context: &Context, query_value: Option) -> bool { + eval_pointer_capabilities(query_value, all_pointer_capabilities(context)) } #[derive(Clone, Copy, Debug, FromPrimitive, Parse, ToCss)] @@ -499,54 +435,33 @@ fn eval_hover_capabilities( } /// https://drafts.csswg.org/mediaqueries-4/#hover -fn eval_hover(device: &Device, query_value: Option) -> bool { - eval_hover_capabilities(query_value, primary_pointer_capabilities(device)) +fn eval_hover(context: &Context, query_value: Option) -> bool { + eval_hover_capabilities(query_value, primary_pointer_capabilities(context)) } /// https://drafts.csswg.org/mediaqueries-4/#descdef-media-any-hover -fn eval_any_hover(device: &Device, query_value: Option) -> bool { - eval_hover_capabilities(query_value, all_pointer_capabilities(device)) +fn eval_any_hover(context: &Context, query_value: Option) -> bool { + eval_hover_capabilities(query_value, all_pointer_capabilities(context)) } -fn eval_moz_is_glyph( - device: &Device, - query_value: Option, - _: Option, -) -> bool { - let is_glyph = device.document().mIsSVGGlyphsDocument(); - query_value.map_or(is_glyph, |v| v == is_glyph) +fn eval_moz_is_glyph(context: &Context) -> bool { + context.device().document().mIsSVGGlyphsDocument() } -fn eval_moz_print_preview( - device: &Device, - query_value: Option, - _: Option, -) -> bool { - let is_print_preview = device.is_print_preview(); +fn eval_moz_print_preview(context: &Context) -> bool { + let is_print_preview = context.device().is_print_preview(); if is_print_preview { - debug_assert_eq!(device.media_type(), MediaType::print()); + debug_assert_eq!(context.device().media_type(), MediaType::print()); } - query_value.map_or(is_print_preview, |v| v == is_print_preview) + is_print_preview } -fn eval_moz_non_native_content_theme( - device: &Device, - query_value: Option, - _: Option, -) -> bool { - let non_native_theme = - unsafe { bindings::Gecko_MediaFeatures_ShouldAvoidNativeTheme(device.document()) }; - query_value.map_or(non_native_theme, |v| v == non_native_theme) +fn eval_moz_non_native_content_theme(context: &Context) -> bool { + unsafe { bindings::Gecko_MediaFeatures_ShouldAvoidNativeTheme(context.device().document()) } } -fn eval_moz_is_resource_document( - device: &Device, - query_value: Option, - _: Option, -) -> bool { - let is_resource_doc = - unsafe { bindings::Gecko_MediaFeatures_IsResourceDocument(device.document()) }; - query_value.map_or(is_resource_doc, |v| v == is_resource_doc) +fn eval_moz_is_resource_document(context: &Context) -> bool { + unsafe { bindings::Gecko_MediaFeatures_IsResourceDocument(context.device().document()) } } /// Allows front-end CSS to discern platform via media queries. @@ -572,7 +487,7 @@ pub enum Platform { WindowsWin10, } -fn eval_moz_platform(_: &Device, query_value: Option) -> bool { +fn eval_moz_platform(_: &Context, query_value: Option) -> bool { let query_value = match query_value { Some(v) => v, None => return false, @@ -581,32 +496,22 @@ fn eval_moz_platform(_: &Device, query_value: Option) -> bool { unsafe { bindings::Gecko_MediaFeatures_MatchesPlatform(query_value) } } -fn eval_moz_windows_non_native_menus( - device: &Device, - query_value: Option, - _: Option, -) -> bool { +fn eval_moz_windows_non_native_menus(context: &Context) -> bool { let use_non_native_menus = match static_prefs::pref!("browser.display.windows.non_native_menus") { 0 => false, 1 => true, _ => { - eval_moz_platform(device, Some(Platform::WindowsWin10)) && + eval_moz_platform(context, Some(Platform::WindowsWin10)) && get_lnf_int_as_bool(bindings::LookAndFeel_IntID::WindowsDefaultTheme as i32) }, }; - query_value.map_or(use_non_native_menus, |v| v == use_non_native_menus) + use_non_native_menus } -fn eval_moz_overlay_scrollbars( - device: &Device, - query_value: Option, - _: Option, -) -> bool { - let use_overlay = - unsafe { bindings::Gecko_MediaFeatures_UseOverlayScrollbars(device.document()) }; - query_value.map_or(use_overlay, |v| v == use_overlay) +fn eval_moz_overlay_scrollbars(context: &Context) -> bool { + unsafe { bindings::Gecko_MediaFeatures_UseOverlayScrollbars(context.device().document()) } } fn get_lnf_int(int_id: i32) -> i32 { @@ -635,16 +540,15 @@ fn get_scrollbar_end_forward(int_id: i32) -> bool { macro_rules! lnf_int_feature { ($feature_name:expr, $int_id:ident, $get_value:ident) => {{ - fn __eval(_: &Device, query_value: Option, _: Option) -> bool { - let value = $get_value(bindings::LookAndFeel_IntID::$int_id as i32); - query_value.map_or(value, |v| v == value) + fn __eval(_: &Context) -> bool { + $get_value(bindings::LookAndFeel_IntID::$int_id as i32) } feature!( $feature_name, AllowsRanges::No, Evaluator::BoolInteger(__eval), - ParsingRequirements::CHROME_AND_UA_ONLY, + FeatureFlags::CHROME_AND_UA_ONLY, ) }}; ($feature_name:expr, $int_id:ident) => {{ @@ -661,18 +565,18 @@ macro_rules! lnf_int_feature { /// pref, with `rust: true`. The feature name needs to be defined in /// `StaticAtoms.py` just like the others. In order to support dynamic changes, /// you also need to add them to kMediaQueryPrefs in nsXPLookAndFeel.cpp +#[allow(unused)] macro_rules! bool_pref_feature { ($feature_name:expr, $pref:tt) => {{ - fn __eval(_: &Device, query_value: Option, _: Option) -> bool { - let value = static_prefs::pref!($pref); - query_value.map_or(value, |v| v == value) + fn __eval(_: &Context) -> bool { + static_prefs::pref!($pref) } feature!( $feature_name, AllowsRanges::No, Evaluator::BoolInteger(__eval), - ParsingRequirements::CHROME_AND_UA_ONLY, + FeatureFlags::CHROME_AND_UA_ONLY, ) }}; } @@ -682,54 +586,54 @@ macro_rules! bool_pref_feature { /// to support new types in these entries and (2) ensuring that either /// nsPresContext::MediaFeatureValuesChanged is called when the value that /// would be returned by the evaluator function could change. -pub static MEDIA_FEATURES: [MediaFeatureDescription; 58] = [ +pub static MEDIA_FEATURES: [QueryFeatureDescription; 59] = [ feature!( atom!("width"), AllowsRanges::Yes, Evaluator::Length(eval_width), - ParsingRequirements::empty(), + FeatureFlags::empty(), ), feature!( atom!("height"), AllowsRanges::Yes, Evaluator::Length(eval_height), - ParsingRequirements::empty(), + FeatureFlags::empty(), ), feature!( atom!("aspect-ratio"), AllowsRanges::Yes, Evaluator::NumberRatio(eval_aspect_ratio), - ParsingRequirements::empty(), + FeatureFlags::empty(), ), feature!( atom!("orientation"), AllowsRanges::No, keyword_evaluator!(eval_orientation, Orientation), - ParsingRequirements::empty(), + FeatureFlags::empty(), ), feature!( atom!("device-width"), AllowsRanges::Yes, Evaluator::Length(eval_device_width), - ParsingRequirements::empty(), + FeatureFlags::empty(), ), feature!( atom!("device-height"), AllowsRanges::Yes, Evaluator::Length(eval_device_height), - ParsingRequirements::empty(), + FeatureFlags::empty(), ), feature!( atom!("device-aspect-ratio"), AllowsRanges::Yes, Evaluator::NumberRatio(eval_device_aspect_ratio), - ParsingRequirements::empty(), + FeatureFlags::empty(), ), feature!( atom!("-moz-device-orientation"), AllowsRanges::No, keyword_evaluator!(eval_device_orientation, Orientation), - ParsingRequirements::empty(), + FeatureFlags::empty(), ), // Webkit extensions that we support for de-facto web compatibility. // -webkit-{min|max}-device-pixel-ratio (controlled with its own pref): @@ -737,68 +641,68 @@ pub static MEDIA_FEATURES: [MediaFeatureDescription; 58] = [ atom!("device-pixel-ratio"), AllowsRanges::Yes, Evaluator::Float(eval_device_pixel_ratio), - ParsingRequirements::WEBKIT_PREFIX, + FeatureFlags::WEBKIT_PREFIX, ), // -webkit-transform-3d. feature!( atom!("transform-3d"), AllowsRanges::No, Evaluator::BoolInteger(eval_transform_3d), - ParsingRequirements::WEBKIT_PREFIX, + FeatureFlags::WEBKIT_PREFIX, ), feature!( atom!("-moz-device-pixel-ratio"), AllowsRanges::Yes, Evaluator::Float(eval_device_pixel_ratio), - ParsingRequirements::empty(), + FeatureFlags::empty(), ), feature!( atom!("resolution"), AllowsRanges::Yes, Evaluator::Resolution(eval_resolution), - ParsingRequirements::empty(), + FeatureFlags::empty(), ), feature!( atom!("display-mode"), AllowsRanges::No, keyword_evaluator!(eval_display_mode, DisplayMode), - ParsingRequirements::empty(), + FeatureFlags::empty(), ), feature!( atom!("grid"), AllowsRanges::No, Evaluator::BoolInteger(eval_grid), - ParsingRequirements::empty(), + FeatureFlags::empty(), ), feature!( atom!("scan"), AllowsRanges::No, keyword_evaluator!(eval_scan, Scan), - ParsingRequirements::empty(), + FeatureFlags::empty(), ), feature!( atom!("color"), AllowsRanges::Yes, Evaluator::Integer(eval_color), - ParsingRequirements::empty(), + FeatureFlags::empty(), ), feature!( atom!("color-index"), AllowsRanges::Yes, Evaluator::Integer(eval_color_index), - ParsingRequirements::empty(), + FeatureFlags::empty(), ), feature!( atom!("monochrome"), AllowsRanges::Yes, Evaluator::Integer(eval_monochrome), - ParsingRequirements::empty(), + FeatureFlags::empty(), ), feature!( atom!("prefers-reduced-motion"), AllowsRanges::No, keyword_evaluator!(eval_prefers_reduced_motion, PrefersReducedMotion), - ParsingRequirements::empty(), + FeatureFlags::empty(), ), feature!( atom!("prefers-contrast"), @@ -809,31 +713,49 @@ pub static MEDIA_FEATURES: [MediaFeatureDescription; 58] = [ // layout.css.prefers-contrast.enabled preference. See // disabed_by_pref in media_feature_expression.rs for how that // is done. - ParsingRequirements::empty(), + FeatureFlags::empty(), ), feature!( atom!("forced-colors"), AllowsRanges::No, keyword_evaluator!(eval_forced_colors, ForcedColors), - ParsingRequirements::empty(), + FeatureFlags::empty(), ), feature!( atom!("overflow-block"), AllowsRanges::No, keyword_evaluator!(eval_overflow_block, OverflowBlock), - ParsingRequirements::empty(), + FeatureFlags::empty(), ), feature!( atom!("overflow-inline"), AllowsRanges::No, keyword_evaluator!(eval_overflow_inline, OverflowInline), - ParsingRequirements::empty(), + FeatureFlags::empty(), + ), + feature!( + atom!("update"), + AllowsRanges::No, + keyword_evaluator!(eval_update, Update), + FeatureFlags::empty(), ), feature!( atom!("prefers-color-scheme"), AllowsRanges::No, keyword_evaluator!(eval_prefers_color_scheme, PrefersColorScheme), - ParsingRequirements::empty(), + FeatureFlags::empty(), + ), + feature!( + atom!("dynamic-range"), + AllowsRanges::No, + keyword_evaluator!(eval_dynamic_range, DynamicRange), + FeatureFlags::empty(), + ), + feature!( + atom!("video-dynamic-range"), + AllowsRanges::No, + keyword_evaluator!(eval_video_dynamic_range, DynamicRange), + FeatureFlags::empty(), ), // Evaluates to the preferred color scheme for content. Only useful in // chrome context, where the chrome color-scheme and the content @@ -842,31 +764,31 @@ pub static MEDIA_FEATURES: [MediaFeatureDescription; 58] = [ atom!("-moz-content-prefers-color-scheme"), AllowsRanges::No, keyword_evaluator!(eval_content_prefers_color_scheme, PrefersColorScheme), - ParsingRequirements::CHROME_AND_UA_ONLY, + FeatureFlags::CHROME_AND_UA_ONLY, ), feature!( atom!("pointer"), AllowsRanges::No, keyword_evaluator!(eval_pointer, Pointer), - ParsingRequirements::empty(), + FeatureFlags::empty(), ), feature!( atom!("any-pointer"), AllowsRanges::No, keyword_evaluator!(eval_any_pointer, Pointer), - ParsingRequirements::empty(), + FeatureFlags::empty(), ), feature!( atom!("hover"), AllowsRanges::No, keyword_evaluator!(eval_hover, Hover), - ParsingRequirements::empty(), + FeatureFlags::empty(), ), feature!( atom!("any-hover"), AllowsRanges::No, keyword_evaluator!(eval_any_hover, Hover), - ParsingRequirements::empty(), + FeatureFlags::empty(), ), // Internal -moz-is-glyph media feature: applies only inside SVG glyphs. // Internal because it is really only useful in the user agent anyway @@ -875,43 +797,43 @@ pub static MEDIA_FEATURES: [MediaFeatureDescription; 58] = [ atom!("-moz-is-glyph"), AllowsRanges::No, Evaluator::BoolInteger(eval_moz_is_glyph), - ParsingRequirements::CHROME_AND_UA_ONLY, + FeatureFlags::CHROME_AND_UA_ONLY, ), feature!( atom!("-moz-is-resource-document"), AllowsRanges::No, Evaluator::BoolInteger(eval_moz_is_resource_document), - ParsingRequirements::CHROME_AND_UA_ONLY, + FeatureFlags::CHROME_AND_UA_ONLY, ), feature!( atom!("-moz-platform"), AllowsRanges::No, keyword_evaluator!(eval_moz_platform, Platform), - ParsingRequirements::CHROME_AND_UA_ONLY, + FeatureFlags::CHROME_AND_UA_ONLY, ), feature!( atom!("-moz-print-preview"), AllowsRanges::No, Evaluator::BoolInteger(eval_moz_print_preview), - ParsingRequirements::CHROME_AND_UA_ONLY, + FeatureFlags::CHROME_AND_UA_ONLY, ), feature!( atom!("-moz-non-native-content-theme"), AllowsRanges::No, Evaluator::BoolInteger(eval_moz_non_native_content_theme), - ParsingRequirements::CHROME_AND_UA_ONLY, + FeatureFlags::CHROME_AND_UA_ONLY, ), feature!( atom!("-moz-windows-non-native-menus"), AllowsRanges::No, Evaluator::BoolInteger(eval_moz_windows_non_native_menus), - ParsingRequirements::CHROME_AND_UA_ONLY, + FeatureFlags::CHROME_AND_UA_ONLY, ), feature!( atom!("-moz-overlay-scrollbars"), AllowsRanges::No, Evaluator::BoolInteger(eval_moz_overlay_scrollbars), - ParsingRequirements::CHROME_AND_UA_ONLY, + FeatureFlags::CHROME_AND_UA_ONLY, ), lnf_int_feature!( atom!("-moz-scrollbar-start-backward"), @@ -933,10 +855,6 @@ pub static MEDIA_FEATURES: [MediaFeatureDescription; 58] = [ ScrollArrowStyle, get_scrollbar_end_forward ), - lnf_int_feature!( - atom!("-moz-scrollbar-thumb-proportional"), - ScrollSliderStyle - ), lnf_int_feature!(atom!("-moz-menubar-drag"), MenuBarDrag), lnf_int_feature!(atom!("-moz-windows-default-theme"), WindowsDefaultTheme), lnf_int_feature!(atom!("-moz-mac-graphite-theme"), MacGraphiteTheme), @@ -959,8 +877,4 @@ pub static MEDIA_FEATURES: [MediaFeatureDescription; 58] = [ GTKCSDReversedPlacement ), lnf_int_feature!(atom!("-moz-system-dark-theme"), SystemUsesDarkTheme), - bool_pref_feature!( - atom!("-moz-proton-places-tooltip"), - "browser.proton.places-tooltip.enabled" - ), ]; diff --git a/components/style/gecko/media_queries.rs b/components/style/gecko/media_queries.rs index e4634d7eab9..837cc61fffe 100644 --- a/components/style/gecko/media_queries.rs +++ b/components/style/gecko/media_queries.rs @@ -17,6 +17,7 @@ use crate::values::computed::font::GenericFontFamily; use crate::values::computed::{ColorScheme, Length}; use crate::values::specified::color::SystemColor; use crate::values::specified::font::FONT_MEDIUM_PX; +use crate::values::specified::ViewportVariant; use crate::values::{CustomIdent, KeyframesName}; use app_units::{Au, AU_PER_PX}; use cssparser::RGBA; @@ -58,6 +59,9 @@ pub struct Device { /// Whether any styles computed in the document relied on the viewport size /// by using vw/vh/vmin/vmax units. used_viewport_size: AtomicBool, + /// Whether any styles computed in the document relied on the viewport size + /// by using dvw/dvh/dvmin/dvmax units. + used_dynamic_viewport_size: AtomicBool, /// The CssEnvironment object responsible of getting CSS environment /// variables. environment: CssEnvironment, @@ -100,6 +104,7 @@ impl Device { used_root_font_size: AtomicBool::new(false), used_font_metrics: AtomicBool::new(false), used_viewport_size: AtomicBool::new(false), + used_dynamic_viewport_size: AtomicBool::new(false), environment: CssEnvironment, } } @@ -267,6 +272,8 @@ impl Device { self.used_root_font_size.store(false, Ordering::Relaxed); self.used_font_metrics.store(false, Ordering::Relaxed); self.used_viewport_size.store(false, Ordering::Relaxed); + self.used_dynamic_viewport_size + .store(false, Ordering::Relaxed); } /// Returns whether we ever looked up the root font size of the Device. @@ -337,7 +344,10 @@ impl Device { /// Returns the current viewport size in app units, recording that it's been /// used for viewport unit resolution. - pub fn au_viewport_size_for_viewport_unit_resolution(&self) -> Size2D { + pub fn au_viewport_size_for_viewport_unit_resolution( + &self, + variant: ViewportVariant, + ) -> Size2D { self.used_viewport_size.store(true, Ordering::Relaxed); let pc = match self.pres_context() { Some(pc) => pc, @@ -348,8 +358,42 @@ impl Device { return self.page_size_minus_default_margin(pc); } - let size = &pc.mSizeForViewportUnits; - Size2D::new(Au(size.width), Au(size.height)) + match variant { + ViewportVariant::UADefault => { + let size = &pc.mSizeForViewportUnits; + Size2D::new(Au(size.width), Au(size.height)) + }, + ViewportVariant::Small => { + let size = &pc.mVisibleArea; + Size2D::new(Au(size.width), Au(size.height)) + }, + ViewportVariant::Large => { + let size = &pc.mVisibleArea; + // Looks like IntCoordTyped is treated as if it's u32 in Rust. + debug_assert!( + /* pc.mDynamicToolbarMaxHeight >=0 && */ + pc.mDynamicToolbarMaxHeight < i32::MAX as u32 + ); + Size2D::new( + Au(size.width), + Au(size.height + + pc.mDynamicToolbarMaxHeight as i32 * pc.mCurAppUnitsPerDevPixel), + ) + }, + ViewportVariant::Dynamic => { + self.used_dynamic_viewport_size.store(true, Ordering::Relaxed); + let size = &pc.mVisibleArea; + // Looks like IntCoordTyped is treated as if it's u32 in Rust. + debug_assert!( + /* pc.mDynamicToolbarHeight >=0 && */ + pc.mDynamicToolbarHeight < i32::MAX as u32 + ); + Size2D::new( + Au(size.width), + Au(size.height + pc.mDynamicToolbarHeight as i32 * pc.mCurAppUnitsPerDevPixel), + ) + }, + } } /// Returns whether we ever looked up the viewport size of the Device. @@ -357,11 +401,21 @@ impl Device { self.used_viewport_size.load(Ordering::Relaxed) } + /// Returns whether we ever looked up the dynamic viewport size of the Device. + pub fn used_dynamic_viewport_size(&self) -> bool { + self.used_dynamic_viewport_size.load(Ordering::Relaxed) + } + /// Returns whether font metrics have been queried. pub fn used_font_metrics(&self) -> bool { self.used_font_metrics.load(Ordering::Relaxed) } + /// Returns whether visited styles are enabled. + pub fn visited_styles_enabled(&self) -> bool { + unsafe { bindings::Gecko_VisitedStylesEnabled(self.document()) } + } + /// Returns the device pixel ratio. pub fn device_pixel_ratio(&self) -> Scale { let pc = match self.pres_context() { diff --git a/components/style/gecko/non_ts_pseudo_class_list.rs b/components/style/gecko/non_ts_pseudo_class_list.rs index feb28feb0df..672e8104fe7 100644 --- a/components/style/gecko/non_ts_pseudo_class_list.rs +++ b/components/style/gecko/non_ts_pseudo_class_list.rs @@ -54,7 +54,7 @@ macro_rules! apply_non_ts_list { ("-moz-styleeditor-transitioning", MozStyleeditorTransitioning, IN_STYLEEDITOR_TRANSITIONING_STATE, PSEUDO_CLASS_ENABLED_IN_UA_SHEETS), ("fullscreen", Fullscreen, IN_FULLSCREEN_STATE, _), ("-moz-modal-dialog", MozModalDialog, IN_MODAL_DIALOG_STATE, PSEUDO_CLASS_ENABLED_IN_UA_SHEETS), - ("-moz-topmost-modal-dialog", MozTopmostModalDialog, IN_TOPMOST_MODAL_DIALOG_STATE, PSEUDO_CLASS_ENABLED_IN_UA_SHEETS), + ("-moz-topmost-modal", MozTopmostModal, IN_TOPMOST_MODAL_TOP_LAYER_STATE, PSEUDO_CLASS_ENABLED_IN_UA_SHEETS), ("-moz-broken", MozBroken, IN_BROKEN_STATE, _), ("-moz-loading", MozLoading, IN_LOADING_STATE, _), ("-moz-has-dir-attr", MozHasDirAttr, IN_HAS_DIR_ATTR_STATE, PSEUDO_CLASS_ENABLED_IN_UA_SHEETS), @@ -92,8 +92,6 @@ macro_rules! apply_non_ts_list { ("-moz-is-html", MozIsHTML, _, _), ("-moz-placeholder", MozPlaceholder, _, _), ("-moz-lwtheme", MozLWTheme, _, PSEUDO_CLASS_ENABLED_IN_UA_SHEETS_AND_CHROME), - ("-moz-lwtheme-brighttext", MozLWThemeBrightText, _, PSEUDO_CLASS_ENABLED_IN_UA_SHEETS_AND_CHROME), - ("-moz-lwtheme-darktext", MozLWThemeDarkText, _, PSEUDO_CLASS_ENABLED_IN_UA_SHEETS_AND_CHROME), ("-moz-window-inactive", MozWindowInactive, _, _), ] } diff --git a/components/style/gecko/selector_parser.rs b/components/style/gecko/selector_parser.rs index 8fc0900aece..7cfb01e2794 100644 --- a/components/style/gecko/selector_parser.rs +++ b/components/style/gecko/selector_parser.rs @@ -139,15 +139,6 @@ impl NonTSPseudoClass { /// Returns whether the pseudo-class is enabled in content sheets. #[inline] fn is_enabled_in_content(&self) -> bool { - if matches!( - *self, - Self::MozLWTheme | Self::MozLWThemeBrightText | Self::MozLWThemeDarkText - ) { - return static_prefs::pref!("layout.css.moz-lwtheme.content.enabled"); - } - if let NonTSPseudoClass::MozLocaleDir(..) = *self { - return static_prefs::pref!("layout.css.moz-locale-dir.content.enabled"); - } !self.has_any_flag(NonTSPseudoClassFlag::PSEUDO_CLASS_ENABLED_IN_UA_SHEETS_AND_CHROME) } @@ -184,8 +175,6 @@ impl NonTSPseudoClass { }, NonTSPseudoClass::MozWindowInactive => DocumentState::WINDOW_INACTIVE, NonTSPseudoClass::MozLWTheme => DocumentState::LWTHEME, - NonTSPseudoClass::MozLWThemeBrightText => DocumentState::LWTHEME_BRIGHTTEXT, - NonTSPseudoClass::MozLWThemeDarkText => DocumentState::LWTHEME_DARKTEXT, _ => DocumentState::empty(), } } @@ -208,15 +197,13 @@ impl NonTSPseudoClass { NonTSPseudoClass::MozNativeAnonymous | // :-moz-placeholder is parsed but never matches. NonTSPseudoClass::MozPlaceholder | - // :-moz-locale-dir and :-moz-window-inactive depend only on - // the state of the document, which is invariant across all - // the elements involved in a given style cache. - NonTSPseudoClass::MozLocaleDir(_) | - NonTSPseudoClass::MozWindowInactive | - // Similar for the document themes. + // :-moz-lwtheme, :-moz-locale-dir and + // :-moz-window-inactive depend only on the state of the + // document, which is invariant across all the elements + // involved in a given style cache. NonTSPseudoClass::MozLWTheme | - NonTSPseudoClass::MozLWThemeBrightText | - NonTSPseudoClass::MozLWThemeDarkText + NonTSPseudoClass::MozLocaleDir(_) | + NonTSPseudoClass::MozWindowInactive ) } } @@ -458,3 +445,9 @@ unsafe impl HasFFI for SelectorList { } unsafe impl HasSimpleFFI for SelectorList {} unsafe impl HasBoxFFI for SelectorList {} + +// Selector and component sizes are important for matching performance. +size_of_test!(selectors::parser::Selector, 8); +size_of_test!(selectors::parser::Component, 24); +size_of_test!(PseudoElement, 16); +size_of_test!(NonTSPseudoClass, 16); diff --git a/components/style/gecko/snapshot_helpers.rs b/components/style/gecko/snapshot_helpers.rs index 3cb325bb6b1..d75cd275310 100644 --- a/components/style/gecko/snapshot_helpers.rs +++ b/components/style/gecko/snapshot_helpers.rs @@ -44,14 +44,14 @@ unsafe fn get_class_or_part_from_attr(attr: &structs::nsAttrValue) -> Class { structs::nsAttrValue_ValueType_eAtomArray ); // NOTE: Bindgen doesn't deal with AutoTArray, so cast it below. - let array: *mut u8 = *(*container) + let attr_array: *mut _ = *(*container) .__bindgen_anon_1 .mValue .as_ref() .__bindgen_anon_1 .mAtomArray .as_ref(); - let array = array as *const structs::nsTArray>; + let array = (*attr_array).mArray.as_ptr() as *const structs::nsTArray>; return Class::More(&**array); } debug_assert_eq!(base_type, structs::nsAttrValue_ValueBaseType_eStringBase); diff --git a/components/style/gecko/wrapper.rs b/components/style/gecko/wrapper.rs index ca2c3816ec9..9c4c3c43118 100644 --- a/components/style/gecko/wrapper.rs +++ b/components/style/gecko/wrapper.rs @@ -66,6 +66,8 @@ use crate::stylist::CascadeData; use crate::values::{AtomIdent, AtomString}; use crate::CaseSensitivityExt; use crate::LocalName; +use app_units::Au; +use euclid::default::Size2D; use atomic_refcell::{AtomicRef, AtomicRefCell, AtomicRefMut}; use fxhash::FxHashMap; use selectors::attr::{AttrSelectorOperation, AttrSelectorOperator}; @@ -321,6 +323,11 @@ impl<'ln> GeckoNode<'ln> { self.flags() & (NODE_IS_IN_SHADOW_TREE as u32) != 0 } + #[inline] + fn is_connected(&self) -> bool { + self.get_bool_flag(nsINode_BooleanFlag::IsConnected) + } + /// WARNING: This logic is duplicated in Gecko's FlattenedTreeParentIsParent. /// Make sure to mirror any modifications in both places. #[inline] @@ -1030,6 +1037,21 @@ impl<'le> TElement for GeckoElement<'le> { } } + #[inline] + fn primary_box_size(&self) -> Size2D { + if !self.as_node().is_connected() { + return Size2D::zero(); + } + + unsafe { + let frame = self.0._base._base._base.__bindgen_anon_1.mPrimaryFrame.as_ref(); + if frame.is_null() { + return Size2D::zero(); + } + Size2D::new(Au((**frame).mRect.width), Au((**frame).mRect.height)) + } + } + /// Return the list of slotted nodes of this node. #[inline] fn slotted_nodes(&self) -> &[Self::ConcreteNode] { @@ -1393,16 +1415,6 @@ impl<'le> TElement for GeckoElement<'le> { self.is_root_of_native_anonymous_subtree() } - unsafe fn set_selector_flags(&self, flags: ElementSelectorFlags) { - debug_assert!(!flags.is_empty()); - self.set_flags(selector_flags_to_node_flags(flags)); - } - - fn has_selector_flags(&self, flags: ElementSelectorFlags) -> bool { - let node_flags = selector_flags_to_node_flags(flags); - (self.flags() & node_flags) == node_flags - } - #[inline] fn may_have_animations(&self) -> bool { if let Some(pseudo) = self.implemented_pseudo_element() { @@ -1501,7 +1513,7 @@ impl<'le> TElement for GeckoElement<'le> { ) -> bool { use crate::properties::LonghandIdSet; - let after_change_box_style = after_change_style.get_box(); + let after_change_ui_style = after_change_style.get_ui(); let existing_transitions = self.css_transitions_info(); let mut transitions_to_keep = LonghandIdSet::new(); for transition_property in after_change_style.transition_properties() { @@ -1511,7 +1523,7 @@ impl<'le> TElement for GeckoElement<'le> { transitions_to_keep.insert(physical_longhand); if self.needs_transitions_update_per_property( physical_longhand, - after_change_box_style.transition_combined_duration_at(transition_property.index), + after_change_ui_style.transition_combined_duration_at(transition_property.index), before_change_style, after_change_style, &existing_transitions, @@ -1593,23 +1605,9 @@ impl<'le> TElement for GeckoElement<'le> { use crate::properties::longhands::_x_lang::SpecifiedValue as SpecifiedLang; use crate::properties::longhands::_x_text_zoom::SpecifiedValue as SpecifiedZoom; use crate::properties::longhands::color::SpecifiedValue as SpecifiedColor; - use crate::properties::longhands::text_align::SpecifiedValue as SpecifiedTextAlign; use crate::stylesheets::layer_rule::LayerOrder; use crate::values::specified::color::Color; lazy_static! { - static ref TH_RULE: ApplicableDeclarationBlock = { - let global_style_data = &*GLOBAL_STYLE_DATA; - let pdb = PropertyDeclarationBlock::with_one( - PropertyDeclaration::TextAlign(SpecifiedTextAlign::MozCenterOrInherit), - Importance::Normal, - ); - let arc = Arc::new_leaked(global_style_data.shared_lock.wrap(pdb)); - ApplicableDeclarationBlock::from_declarations( - arc, - ServoCascadeLevel::PresHints, - LayerOrder::root(), - ) - }; static ref TABLE_COLOR_RULE: ApplicableDeclarationBlock = { let global_style_data = &*GLOBAL_STYLE_DATA; let pdb = PropertyDeclarationBlock::with_one( @@ -1654,9 +1652,7 @@ impl<'le> TElement for GeckoElement<'le> { let ns = self.namespace_id(); // elements get a default MozCenterOrInherit which may get overridden if ns == structs::kNameSpaceID_XHTML as i32 { - if self.local_name().as_ptr() == atom!("th").as_ptr() { - hints.push(TH_RULE.clone()); - } else if self.local_name().as_ptr() == atom!("table").as_ptr() && + if self.local_name().as_ptr() == atom!("table").as_ptr() && self.as_node().owner_doc().quirks_mode() == QuirksMode::Quirks { hints.push(TABLE_COLOR_RULE.clone()); @@ -1848,6 +1844,11 @@ impl<'le> ::selectors::Element for GeckoElement<'le> { None } + fn set_selector_flags(&self, flags: ElementSelectorFlags) { + debug_assert!(!flags.is_empty()); + self.set_flags(selector_flags_to_node_flags(flags)); + } + fn attr_matches( &self, ns: &NamespaceConstraint<&Namespace>, @@ -1960,15 +1961,11 @@ impl<'le> ::selectors::Element for GeckoElement<'le> { self.local_name() == other.local_name() && self.namespace() == other.namespace() } - fn match_non_ts_pseudo_class( + fn match_non_ts_pseudo_class( &self, pseudo_class: &NonTSPseudoClass, context: &mut MatchingContext, - flags_setter: &mut F, - ) -> bool - where - F: FnMut(&Self, ElementSelectorFlags), - { + ) -> bool { use selectors::matching::*; match *pseudo_class { NonTSPseudoClass::Autofill | @@ -2009,7 +2006,7 @@ impl<'le> ::selectors::Element for GeckoElement<'le> { NonTSPseudoClass::MozDirAttrRTL | NonTSPseudoClass::MozDirAttrLikeAuto | NonTSPseudoClass::MozModalDialog | - NonTSPseudoClass::MozTopmostModalDialog | + NonTSPseudoClass::MozTopmostModal | NonTSPseudoClass::Active | NonTSPseudoClass::Hover | NonTSPseudoClass::MozAutofillPreview | @@ -2024,7 +2021,9 @@ impl<'le> ::selectors::Element for GeckoElement<'le> { self.is_link() && context.visited_handling().matches_visited() }, NonTSPseudoClass::MozFirstNode => { - flags_setter(self, ElementSelectorFlags::HAS_EDGE_CHILD_SELECTOR); + if context.needs_selector_flags() { + self.apply_selector_flags(ElementSelectorFlags::HAS_EDGE_CHILD_SELECTOR); + } let mut elem = self.as_node(); while let Some(prev) = elem.prev_sibling() { if prev.contains_non_whitespace_content() { @@ -2035,7 +2034,9 @@ impl<'le> ::selectors::Element for GeckoElement<'le> { true }, NonTSPseudoClass::MozLastNode => { - flags_setter(self, ElementSelectorFlags::HAS_EDGE_CHILD_SELECTOR); + if context.needs_selector_flags() { + self.apply_selector_flags(ElementSelectorFlags::HAS_EDGE_CHILD_SELECTOR); + } let mut elem = self.as_node(); while let Some(next) = elem.next_sibling() { if next.contains_non_whitespace_content() { @@ -2046,7 +2047,9 @@ impl<'le> ::selectors::Element for GeckoElement<'le> { true }, NonTSPseudoClass::MozOnlyWhitespace => { - flags_setter(self, ElementSelectorFlags::HAS_EMPTY_SELECTOR); + if context.needs_selector_flags() { + self.apply_selector_flags(ElementSelectorFlags::HAS_EMPTY_SELECTOR); + } if self .as_node() .dom_children() @@ -2068,8 +2071,6 @@ impl<'le> ::selectors::Element for GeckoElement<'le> { NonTSPseudoClass::MozIsHTML => self.is_html_element_in_html_document(), NonTSPseudoClass::MozLWTheme | - NonTSPseudoClass::MozLWThemeBrightText | - NonTSPseudoClass::MozLWThemeDarkText | NonTSPseudoClass::MozLocaleDir(..) | NonTSPseudoClass::MozWindowInactive => { let state_bit = pseudo_class.document_state_flag(); diff --git a/components/style/global_style_data.rs b/components/style/global_style_data.rs index 80626267cdd..576ac425b5a 100644 --- a/components/style/global_style_data.rs +++ b/components/style/global_style_data.rs @@ -12,11 +12,11 @@ use crate::shared_lock::SharedRwLock; use crate::thread_state; #[cfg(feature = "gecko")] use gecko_profiler; -use parking_lot::{RwLock, RwLockReadGuard}; +use parking_lot::{Mutex, RwLock, RwLockReadGuard}; use rayon; use std::env; -use std::sync::atomic::{AtomicUsize, Ordering}; -use std::sync::Mutex; +use std::io; +use std::thread; /// Global style data pub struct GlobalStyleData { @@ -43,12 +43,32 @@ fn thread_name(index: usize) -> String { format!("Style#{}", index) } -// A counter so that we can wait for shutdown of all threads. See -// StyleThreadPool::shutdown. -static ALIVE_WORKER_THREADS: AtomicUsize = AtomicUsize::new(0); +lazy_static! { + /// JoinHandles for spawned style threads. These will be joined during + /// StyleThreadPool::shutdown() after exiting the thread pool. + /// + /// This would be quite inefficient if rayon destroyed and re-created + /// threads regularly during threadpool operation in response to demand, + /// however rayon actually never destroys its threads until the entire + /// thread pool is shut-down, so the size of this list is bounded. + static ref STYLE_THREAD_JOIN_HANDLES: Mutex>> = + Mutex::new(Vec::new()); +} + +fn thread_spawn(options: rayon::ThreadBuilder) -> io::Result<()> { + let mut b = thread::Builder::new(); + if let Some(name) = options.name() { + b = b.name(name.to_owned()); + } + if let Some(stack_size) = options.stack_size() { + b = b.stack_size(stack_size); + } + let join_handle = b.spawn(|| options.run())?; + STYLE_THREAD_JOIN_HANDLES.lock().push(join_handle); + Ok(()) +} fn thread_startup(_index: usize) { - ALIVE_WORKER_THREADS.fetch_add(1, Ordering::Relaxed); thread_state::initialize_layout_worker_thread(); #[cfg(feature = "gecko")] unsafe { @@ -64,33 +84,24 @@ fn thread_shutdown(_: usize) { gecko_profiler::unregister_thread(); bindings::Gecko_SetJemallocThreadLocalArena(false); } - ALIVE_WORKER_THREADS.fetch_sub(1, Ordering::Relaxed); } impl StyleThreadPool { /// Shuts down the thread pool, waiting for all work to complete. pub fn shutdown() { - if ALIVE_WORKER_THREADS.load(Ordering::Relaxed) == 0 { + if STYLE_THREAD_JOIN_HANDLES.lock().is_empty() { return; } { // Drop the pool. let _ = STYLE_THREAD_POOL.lock().unwrap().style_thread_pool.write().take(); } - // Spin until all our threads are done. This will usually be pretty - // fast, as on shutdown there should be basically no threads left - // running. - // - // This still _technically_ doesn't give us the guarantee of TLS - // destructors running on the worker threads. For that we'd need help - // from rayon to properly join the threads. - // - // See https://github.com/rayon-rs/rayon/issues/688 - // - // So we instead intentionally leak TLS stuff (see BLOOM_KEY and co) for - // now until that's fixed. - while ALIVE_WORKER_THREADS.load(Ordering::Relaxed) != 0 { - std::thread::yield_now(); + + // Join spawned threads until all of the threads have been joined. This + // will usually be pretty fast, as on shutdown there should be basically + // no threads left running. + while let Some(join_handle) = STYLE_THREAD_JOIN_HANDLES.lock().pop() { + let _ = join_handle.join(); } } @@ -105,7 +116,7 @@ impl StyleThreadPool { lazy_static! { /// Global thread pool - pub static ref STYLE_THREAD_POOL: Mutex = { + pub static ref STYLE_THREAD_POOL: std::sync::Mutex = { let stylo_threads = env::var("STYLO_THREADS") .map(|s| s.parse::().expect("invalid STYLO_THREADS value")); let mut num_threads = match stylo_threads { @@ -149,6 +160,7 @@ lazy_static! { None } else { let workers = rayon::ThreadPoolBuilder::new() + .spawn_handler(thread_spawn) .num_threads(num_threads) .thread_name(thread_name) .start_handler(thread_startup) @@ -158,7 +170,7 @@ lazy_static! { workers.ok() }; - Mutex::new(StyleThreadPool { + std::sync::Mutex::new(StyleThreadPool { num_threads: if num_threads > 0 { Some(num_threads) } else { diff --git a/components/style/invalidation/element/document_state.rs b/components/style/invalidation/element/document_state.rs index 9ee97344a4c..358257feac0 100644 --- a/components/style/invalidation/element/document_state.rs +++ b/components/style/invalidation/element/document_state.rs @@ -11,7 +11,7 @@ use crate::invalidation::element::invalidator::{DescendantInvalidationLists, Inv use crate::invalidation::element::invalidator::{Invalidation, InvalidationProcessor}; use crate::invalidation::element::state_and_attributes; use crate::stylist::CascadeData; -use selectors::matching::{MatchingContext, MatchingMode, QuirksMode, VisitedHandlingMode}; +use selectors::matching::{MatchingContext, MatchingMode, QuirksMode, VisitedHandlingMode, NeedsSelectorFlags}; /// A struct holding the members necessary to invalidate document state /// selectors. @@ -47,6 +47,7 @@ impl<'a, E: TElement, I> DocumentStateInvalidationProcessor<'a, E, I> { None, VisitedHandlingMode::AllLinksVisitedAndUnvisited, quirks_mode, + NeedsSelectorFlags::No, ); matching_context.extra_data = InvalidationMatchingData { diff --git a/components/style/invalidation/element/element_wrapper.rs b/components/style/invalidation/element/element_wrapper.rs index 2aa5749fdee..84d0e5c351a 100644 --- a/components/style/invalidation/element/element_wrapper.rs +++ b/components/style/invalidation/element/element_wrapper.rs @@ -166,15 +166,11 @@ where { type Impl = SelectorImpl; - fn match_non_ts_pseudo_class( + fn match_non_ts_pseudo_class( &self, pseudo_class: &NonTSPseudoClass, context: &mut MatchingContext, - _setter: &mut F, - ) -> bool - where - F: FnMut(&Self, ElementSelectorFlags), - { + ) -> bool { // Some pseudo-classes need special handling to evaluate them against // the snapshot. match *pseudo_class { @@ -232,16 +228,20 @@ where if flag.is_empty() { return self .element - .match_non_ts_pseudo_class(pseudo_class, context, &mut |_, _| {}); + .match_non_ts_pseudo_class(pseudo_class, context); } match self.snapshot().and_then(|s| s.state()) { Some(snapshot_state) => snapshot_state.intersects(flag), None => self .element - .match_non_ts_pseudo_class(pseudo_class, context, &mut |_, _| {}), + .match_non_ts_pseudo_class(pseudo_class, context), } } + fn set_selector_flags(&self, _flags: ElementSelectorFlags) { + debug_assert!(false, "Shouldn't need selector flags for invalidation"); + } + fn match_pseudo_element( &self, pseudo_element: &PseudoElement, diff --git a/components/style/invalidation/element/invalidation_map.rs b/components/style/invalidation/element/invalidation_map.rs index 0bba423a17f..f083225b4b7 100644 --- a/components/style/invalidation/element/invalidation_map.rs +++ b/components/style/invalidation/element/invalidation_map.rs @@ -67,6 +67,8 @@ pub struct Dependency { pub parent: Option>, } +size_of_test!(Dependency, 24); + /// The kind of elements down the tree this dependency may affect. #[derive(Debug, Eq, PartialEq)] pub enum DependencyInvalidationKind { diff --git a/components/style/invalidation/element/state_and_attributes.rs b/components/style/invalidation/element/state_and_attributes.rs index bbb1fb46a80..1d02d52947b 100644 --- a/components/style/invalidation/element/state_and_attributes.rs +++ b/components/style/invalidation/element/state_and_attributes.rs @@ -19,8 +19,7 @@ use crate::selector_parser::Snapshot; use crate::stylesheets::origin::OriginSet; use crate::{Atom, WeakAtom}; use selectors::attr::CaseSensitivity; -use selectors::matching::matches_selector; -use selectors::matching::{MatchingContext, MatchingMode, VisitedHandlingMode}; +use selectors::matching::{matches_selector, MatchingContext, MatchingMode, VisitedHandlingMode, NeedsSelectorFlags}; use selectors::NthIndexCache; use smallvec::SmallVec; @@ -67,6 +66,7 @@ impl<'a, 'b: 'a, E: TElement + 'b> StateAndAttrInvalidationProcessor<'a, 'b, E> Some(nth_index_cache), VisitedHandlingMode::AllLinksVisitedAndUnvisited, shared_context.quirks_mode(), + NeedsSelectorFlags::No, ); Self { @@ -84,7 +84,7 @@ pub fn check_dependency( dependency: &Dependency, element: &E, wrapper: &W, - mut context: &mut MatchingContext<'_, E::Impl>, + context: &mut MatchingContext<'_, E::Impl>, ) -> bool where E: TElement, @@ -95,8 +95,7 @@ where dependency.selector_offset, None, element, - &mut context, - &mut |_, _| {}, + context, ); let matched_then = matches_selector( @@ -104,8 +103,7 @@ where dependency.selector_offset, None, wrapper, - &mut context, - &mut |_, _| {}, + context, ); matched_then != matches_now diff --git a/components/style/invalidation/stylesheets.rs b/components/style/invalidation/stylesheets.rs index 53130de7a2e..3c4ee845055 100644 --- a/components/style/invalidation/stylesheets.rs +++ b/components/style/invalidation/stylesheets.rs @@ -556,6 +556,7 @@ impl StylesheetInvalidationSet { FontFace(..) | Keyframes(..) | ScrollTimeline(..) | + Container(..) | Style(..) => { if is_generic_change { // TODO(emilio): We need to do this for selector / keyframe @@ -610,7 +611,7 @@ impl StylesheetInvalidationSet { } }, Document(..) | Namespace(..) | Import(..) | Media(..) | Supports(..) | - LayerStatement(..) | LayerBlock(..) => { + Container(..) | LayerStatement(..) | LayerBlock(..) => { // Do nothing, relevant nested rules are visited as part of the // iteration. }, diff --git a/components/style/lib.rs b/components/style/lib.rs index 2967fbcf7ce..b1cec716fcf 100644 --- a/components/style/lib.rs +++ b/components/style/lib.rs @@ -63,6 +63,8 @@ pub use servo_arc; #[macro_use] extern crate servo_atoms; #[macro_use] +extern crate static_assertions; +#[macro_use] extern crate style_derive; #[macro_use] extern crate to_shmem_derive; @@ -101,10 +103,12 @@ pub mod invalidation; #[allow(missing_docs)] // TODO. pub mod logical_geometry; pub mod matching; -#[macro_use] pub mod media_queries; pub mod parallel; pub mod parser; +pub mod piecewise_linear; +#[macro_use] +pub mod queries; pub mod rule_cache; pub mod rule_collector; pub mod rule_tree; @@ -184,7 +188,7 @@ pub mod gecko_properties { } macro_rules! reexport_computed_values { - ( $( { $name: ident, $boxed: expr } )+ ) => { + ( $( { $name: ident } )+ ) => { /// Types for [computed values][computed]. /// /// [computed]: https://drafts.csswg.org/css-cascade/#computed @@ -198,7 +202,6 @@ macro_rules! reexport_computed_values { } } longhand_properties_idents!(reexport_computed_values); - #[cfg(feature = "gecko")] use crate::gecko_string_cache::WeakAtom; #[cfg(feature = "servo")] diff --git a/components/style/logical_geometry.rs b/components/style/logical_geometry.rs index d4d058adc61..c848462deff 100644 --- a/components/style/logical_geometry.rs +++ b/components/style/logical_geometry.rs @@ -168,6 +168,11 @@ impl WritingMode { flags } + /// Returns the `horizontal-tb` value. + pub fn horizontal_tb() -> Self { + Self::from_bits_truncate(0) + } + #[inline] pub fn is_vertical(&self) -> bool { self.intersects(WritingMode::VERTICAL) @@ -872,10 +877,10 @@ impl LogicalMargin { inline_start: T, ) -> LogicalMargin { LogicalMargin { - block_start: block_start, - inline_end: inline_end, - block_end: block_end, - inline_start: inline_start, + block_start, + inline_end, + block_end, + inline_start, debug_writing_mode: DebugWritingMode::new(mode), } } @@ -1050,6 +1055,18 @@ impl LogicalMargin { } } + #[inline] + pub fn convert(&self, mode_from: WritingMode, mode_to: WritingMode) -> LogicalMargin { + if mode_from == mode_to { + self.debug_writing_mode.check(mode_from); + *self + } else { + LogicalMargin::from_physical(mode_to, self.to_physical(mode_from)) + } + } +} + +impl LogicalMargin { #[inline] pub fn to_physical(&self, mode: WritingMode) -> SideOffsets2D { self.debug_writing_mode.check(mode); @@ -1059,42 +1076,32 @@ impl LogicalMargin { let left; if mode.is_vertical() { if mode.is_vertical_lr() { - left = self.block_start; - right = self.block_end; + left = self.block_start.clone(); + right = self.block_end.clone(); } else { - right = self.block_start; - left = self.block_end; + right = self.block_start.clone(); + left = self.block_end.clone(); } if mode.is_inline_tb() { - top = self.inline_start; - bottom = self.inline_end; + top = self.inline_start.clone(); + bottom = self.inline_end.clone(); } else { - bottom = self.inline_start; - top = self.inline_end; + bottom = self.inline_start.clone(); + top = self.inline_end.clone(); } } else { - top = self.block_start; - bottom = self.block_end; + top = self.block_start.clone(); + bottom = self.block_end.clone(); if mode.is_bidi_ltr() { - left = self.inline_start; - right = self.inline_end; + left = self.inline_start.clone(); + right = self.inline_end.clone(); } else { - right = self.inline_start; - left = self.inline_end; + right = self.inline_start.clone(); + left = self.inline_end.clone(); } } SideOffsets2D::new(top, right, bottom, left) } - - #[inline] - pub fn convert(&self, mode_from: WritingMode, mode_to: WritingMode) -> LogicalMargin { - if mode_from == mode_to { - self.debug_writing_mode.check(mode_from); - *self - } else { - LogicalMargin::from_physical(mode_to, self.to_physical(mode_from)) - } - } } impl LogicalMargin { diff --git a/components/style/macros.rs b/components/style/macros.rs index e27a554acf4..3084d33dd3d 100644 --- a/components/style/macros.rs +++ b/components/style/macros.rs @@ -128,3 +128,11 @@ macro_rules! local_name { $crate::values::AtomIdent(atom!($s)) }; } + +/// Asserts the size of a type at compile time. +macro_rules! size_of_test { + ($t: ty, $expected_size: expr) => { + #[cfg(target_pointer_width = "64")] + const_assert_eq!(std::mem::size_of::<$t>(), $expected_size); + }; +} diff --git a/components/style/matching.rs b/components/style/matching.rs index f2d300df4b2..2e18a8de962 100644 --- a/components/style/matching.rs +++ b/components/style/matching.rs @@ -8,7 +8,7 @@ #![deny(missing_docs)] use crate::computed_value_flags::ComputedValueFlags; -use crate::context::{CascadeInputs, ElementCascadeInputs, QuirksMode, SelectorFlagsMap}; +use crate::context::{CascadeInputs, ElementCascadeInputs, QuirksMode}; use crate::context::{SharedStyleContext, StyleContext}; use crate::data::{ElementData, ElementStyles}; use crate::dom::TElement; @@ -26,7 +26,6 @@ use crate::style_resolver::{PseudoElementResolution, StyleResolverForElement}; use crate::stylesheets::layer_rule::LayerOrder; use crate::stylist::RuleInclusion; use crate::traversal_flags::TraversalFlags; -use selectors::matching::ElementSelectorFlags; use servo_arc::{Arc, ArcBorrow}; /// Represents the result of comparing an element's old and new style. @@ -255,8 +254,8 @@ trait PrivateMatchMethods: TElement { new_style: &ComputedValues, pseudo_element: Option, ) -> bool { - let new_box_style = new_style.get_box(); - let new_style_specifies_animations = new_box_style.specifies_animations(); + let new_ui_style = new_style.get_ui(); + let new_style_specifies_animations = new_ui_style.specifies_animations(); let has_animations = self.has_css_animations(&context.shared, pseudo_element); if !new_style_specifies_animations && !has_animations { @@ -283,7 +282,7 @@ trait PrivateMatchMethods: TElement { }, }; - let old_box_style = old_style.get_box(); + let old_ui_style = old_style.get_ui(); let keyframes_or_timeline_could_have_changed = context .shared @@ -302,12 +301,12 @@ trait PrivateMatchMethods: TElement { } // If the animations changed, well... - if !old_box_style.animations_equals(new_box_style) { + if !old_ui_style.animations_equals(new_ui_style) { return true; } - let old_display = old_box_style.clone_display(); - let new_display = new_box_style.clone_display(); + let old_display = old_style.clone_display(); + let new_display = new_style.clone_display(); // If we were display: none, we may need to trigger animations. if old_display == Display::None && new_display != Display::None { @@ -342,14 +341,13 @@ trait PrivateMatchMethods: TElement { None => return false, }; - let new_box_style = new_style.get_box(); if !self.has_css_transitions(context.shared, pseudo_element) && - !new_box_style.specifies_transitions() + !new_style.get_ui().specifies_transitions() { return false; } - if new_box_style.clone_display().is_none() || old_style.clone_display().is_none() { + if new_style.clone_display().is_none() || old_style.clone_display().is_none() { return false; } @@ -766,8 +764,8 @@ trait PrivateMatchMethods: TElement { }, } - let old_display = old_values.get_box().clone_display(); - let new_display = new_values.get_box().clone_display(); + let old_display = old_values.clone_display(); + let new_display = new_values.clone_display(); if old_display != new_display { // If we used to be a display: none element, and no longer are, our @@ -1007,51 +1005,6 @@ pub trait MatchMethods: TElement { cascade_requirement } - /// Applies selector flags to an element, deferring mutations of the parent - /// until after the traversal. - /// - /// TODO(emilio): This is somewhat inefficient, because it doesn't take - /// advantage of us knowing that the traversal is sequential. - fn apply_selector_flags( - &self, - map: &mut SelectorFlagsMap, - element: &Self, - flags: ElementSelectorFlags, - ) { - // Handle flags that apply to the element. - let self_flags = flags.for_self(); - if !self_flags.is_empty() { - if element == self { - // If this is the element we're styling, we have exclusive - // access to the element, and thus it's fine inserting them, - // even from the worker. - unsafe { - element.set_selector_flags(self_flags); - } - } else { - // Otherwise, this element is an ancestor of the current element - // we're styling, and thus multiple children could write to it - // if we did from here. - // - // Instead, we can read them, and post them if necessary as a - // sequential task in order for them to be processed later. - if !element.has_selector_flags(self_flags) { - map.insert_flags(*element, self_flags); - } - } - } - - // Handle flags that apply to the parent. - let parent_flags = flags.for_parent(); - if !parent_flags.is_empty() { - if let Some(p) = element.parent_element() { - if !p.has_selector_flags(parent_flags) { - map.insert_flags(p, parent_flags); - } - } - } - } - /// Updates the rule nodes without re-running selector matching, using just /// the rule tree. /// diff --git a/components/style/media_queries/media_feature_expression.rs b/components/style/media_queries/media_feature_expression.rs deleted file mode 100644 index 80827af401c..00000000000 --- a/components/style/media_queries/media_feature_expression.rs +++ /dev/null @@ -1,535 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ - -//! Parsing for media feature expressions, like `(foo: bar)` or -//! `(width >= 400px)`. - -use super::media_feature::{Evaluator, MediaFeatureDescription}; -use super::media_feature::{KeywordDiscriminant, ParsingRequirements}; -use super::Device; -use crate::context::QuirksMode; -#[cfg(feature = "gecko")] -use crate::gecko::media_features::MEDIA_FEATURES; -use crate::parser::{Parse, ParserContext}; -#[cfg(feature = "servo")] -use crate::servo::media_queries::MEDIA_FEATURES; -use crate::str::{starts_with_ignore_ascii_case, string_as_ascii_lowercase}; -use crate::values::computed::{self, Ratio, ToComputedValue}; -use crate::values::specified::{Integer, Length, Number, Resolution}; -use crate::values::{serialize_atom_identifier, CSSFloat}; -use crate::{Atom, Zero}; -use cssparser::{Parser, Token}; -use std::cmp::{Ordering, PartialOrd}; -use std::fmt::{self, Write}; -use style_traits::{CssWriter, ParseError, StyleParseErrorKind, ToCss}; - -/// The kind of matching that should be performed on a media feature value. -#[derive(Clone, Copy, Debug, Eq, MallocSizeOf, PartialEq, ToShmem)] -pub enum Range { - /// At least the specified value. - Min, - /// At most the specified value. - Max, -} - -/// The operator that was specified in this media feature. -#[derive(Clone, Copy, Debug, Eq, MallocSizeOf, PartialEq, ToShmem)] -pub enum Operator { - /// = - Equal, - /// > - GreaterThan, - /// >= - GreaterThanEqual, - /// < - LessThan, - /// <= - LessThanEqual, -} - -impl ToCss for Operator { - fn to_css(&self, dest: &mut CssWriter) -> fmt::Result - where - W: fmt::Write, - { - dest.write_str(match *self { - Operator::Equal => "=", - Operator::LessThan => "<", - Operator::LessThanEqual => "<=", - Operator::GreaterThan => ">", - Operator::GreaterThanEqual => ">=", - }) - } -} - -/// Either a `Range` or an `Operator`. -/// -/// Ranged media features are not allowed with operations (that'd make no -/// sense). -#[derive(Clone, Copy, Debug, Eq, MallocSizeOf, PartialEq, ToShmem)] -pub enum RangeOrOperator { - /// A `Range`. - Range(Range), - /// An `Operator`. - Operator(Operator), -} - -impl RangeOrOperator { - /// Evaluate a given range given an optional query value and a value from - /// the browser. - pub fn evaluate(range_or_op: Option, query_value: Option, value: T) -> bool - where - T: PartialOrd + Zero, - { - match query_value { - Some(v) => Self::evaluate_with_query_value(range_or_op, v, value), - None => !value.is_zero(), - } - } - - /// Evaluate a given range given a non-optional query value and a value from - /// the browser. - pub fn evaluate_with_query_value(range_or_op: Option, query_value: T, value: T) -> bool - where - T: PartialOrd, - { - let cmp = match value.partial_cmp(&query_value) { - Some(c) => c, - None => return false, - }; - - let range_or_op = match range_or_op { - Some(r) => r, - None => return cmp == Ordering::Equal, - }; - - match range_or_op { - RangeOrOperator::Range(range) => { - cmp == Ordering::Equal || - match range { - Range::Min => cmp == Ordering::Greater, - Range::Max => cmp == Ordering::Less, - } - }, - RangeOrOperator::Operator(op) => match op { - Operator::Equal => cmp == Ordering::Equal, - Operator::GreaterThan => cmp == Ordering::Greater, - Operator::GreaterThanEqual => cmp == Ordering::Equal || cmp == Ordering::Greater, - Operator::LessThan => cmp == Ordering::Less, - Operator::LessThanEqual => cmp == Ordering::Equal || cmp == Ordering::Less, - }, - } - } -} - -/// A feature expression contains a reference to the media feature, the value -/// the media query contained, and the range to evaluate. -#[derive(Clone, Debug, MallocSizeOf, ToShmem)] -pub struct MediaFeatureExpression { - feature_index: usize, - value: Option, - range_or_operator: Option, -} - -impl PartialEq for MediaFeatureExpression { - fn eq(&self, other: &Self) -> bool { - self.feature_index == other.feature_index && - self.value == other.value && - self.range_or_operator == other.range_or_operator - } -} - -impl ToCss for MediaFeatureExpression { - fn to_css(&self, dest: &mut CssWriter) -> fmt::Result - where - W: fmt::Write, - { - dest.write_str("(")?; - - let feature = self.feature(); - - if feature - .requirements - .contains(ParsingRequirements::WEBKIT_PREFIX) - { - dest.write_str("-webkit-")?; - } - - if let Some(RangeOrOperator::Range(range)) = self.range_or_operator { - match range { - Range::Min => dest.write_str("min-")?, - Range::Max => dest.write_str("max-")?, - } - } - - // NB: CssStringWriter not needed, feature names are under control. - write!(dest, "{}", feature.name)?; - - if let Some(RangeOrOperator::Operator(op)) = self.range_or_operator { - dest.write_char(' ')?; - op.to_css(dest)?; - dest.write_char(' ')?; - } else if self.value.is_some() { - dest.write_str(": ")?; - } - - if let Some(ref val) = self.value { - val.to_css(dest, self)?; - } - - dest.write_str(")") - } -} - -/// Consumes an operation or a colon, or returns an error. -fn consume_operation_or_colon(input: &mut Parser) -> Result, ()> { - let first_delim = { - let next_token = match input.next() { - Ok(t) => t, - Err(..) => return Err(()), - }; - - match *next_token { - Token::Colon => return Ok(None), - Token::Delim(oper) => oper, - _ => return Err(()), - } - }; - let operator = match first_delim { - '=' => return Ok(Some(Operator::Equal)), - '>' => Operator::GreaterThan, - '<' => Operator::LessThan, - _ => return Err(()), - }; - - // https://drafts.csswg.org/mediaqueries-4/#mq-syntax: - // - // No whitespace is allowed between the “<” or “>” - // s and the following “=” , if it’s - // present. - // - // TODO(emilio): Maybe we should ignore comments as well? - // https://github.com/w3c/csswg-drafts/issues/6248 - let parsed_equal = input - .try_parse(|i| { - let t = i.next_including_whitespace().map_err(|_| ())?; - if !matches!(t, Token::Delim('=')) { - return Err(()); - } - Ok(()) - }) - .is_ok(); - - if !parsed_equal { - return Ok(Some(operator)); - } - - Ok(Some(match operator { - Operator::GreaterThan => Operator::GreaterThanEqual, - Operator::LessThan => Operator::LessThanEqual, - _ => unreachable!(), - })) -} - -#[allow(unused_variables)] -fn disabled_by_pref(feature: &Atom, context: &ParserContext) -> bool { - #[cfg(feature = "gecko")] - { - if *feature == atom!("forced-colors") { - // forced-colors is always enabled in the ua and chrome. On - // the web it is hidden behind a preference, which is defaulted - // to 'true' as of bug 1659511. - return !context.in_ua_or_chrome_sheet() && - !static_prefs::pref!("layout.css.forced-colors.enabled"); - } - // prefers-contrast is always enabled in the ua and chrome. On - // the web it is hidden behind a preference. - if *feature == atom!("prefers-contrast") { - return !context.in_ua_or_chrome_sheet() && - !static_prefs::pref!("layout.css.prefers-contrast.enabled"); - } - } - false -} - -impl MediaFeatureExpression { - fn new( - feature_index: usize, - value: Option, - range_or_operator: Option, - ) -> Self { - debug_assert!(feature_index < MEDIA_FEATURES.len()); - Self { - feature_index, - value, - range_or_operator, - } - } - - fn feature(&self) -> &'static MediaFeatureDescription { - &MEDIA_FEATURES[self.feature_index] - } - - /// Parse a media expression of the form: - /// - /// ``` - /// (media-feature: media-value) - /// ``` - pub fn parse<'i, 't>( - context: &ParserContext, - input: &mut Parser<'i, 't>, - ) -> Result> { - input.expect_parenthesis_block()?; - input.parse_nested_block(|input| Self::parse_in_parenthesis_block(context, input)) - } - - /// Parse a media feature expression where we've already consumed the - /// parenthesis. - pub fn parse_in_parenthesis_block<'i, 't>( - context: &ParserContext, - input: &mut Parser<'i, 't>, - ) -> Result> { - let mut requirements = ParsingRequirements::empty(); - let location = input.current_source_location(); - let ident = input.expect_ident()?; - - if context.in_ua_or_chrome_sheet() { - requirements.insert(ParsingRequirements::CHROME_AND_UA_ONLY); - } - - let mut feature_name = &**ident; - - if starts_with_ignore_ascii_case(feature_name, "-webkit-") { - feature_name = &feature_name[8..]; - requirements.insert(ParsingRequirements::WEBKIT_PREFIX); - } - - let range = if starts_with_ignore_ascii_case(feature_name, "min-") { - feature_name = &feature_name[4..]; - Some(Range::Min) - } else if starts_with_ignore_ascii_case(feature_name, "max-") { - feature_name = &feature_name[4..]; - Some(Range::Max) - } else { - None - }; - - let atom = Atom::from(string_as_ascii_lowercase(feature_name)); - - let (feature_index, feature) = match MEDIA_FEATURES - .iter() - .enumerate() - .find(|(_, f)| f.name == atom) - { - Some((i, f)) => (i, f), - None => { - return Err(location.new_custom_error( - StyleParseErrorKind::MediaQueryExpectedFeatureName(ident.clone()), - )) - }, - }; - - if disabled_by_pref(&feature.name, context) || - !requirements.contains(feature.requirements) || - (range.is_some() && !feature.allows_ranges()) - { - return Err(location.new_custom_error( - StyleParseErrorKind::MediaQueryExpectedFeatureName(ident.clone()), - )); - } - - let operator = input.try_parse(consume_operation_or_colon); - let operator = match operator { - Err(..) => { - // If there's no colon, this is a media query of the - // form '()', that is, there's no value - // specified. - // - // Gecko doesn't allow ranged expressions without a - // value, so just reject them here too. - if range.is_some() { - return Err( - input.new_custom_error(StyleParseErrorKind::RangedExpressionWithNoValue) - ); - } - - return Ok(Self::new(feature_index, None, None)); - }, - Ok(operator) => operator, - }; - - let range_or_operator = match range { - Some(range) => { - if operator.is_some() { - return Err( - input.new_custom_error(StyleParseErrorKind::MediaQueryUnexpectedOperator) - ); - } - Some(RangeOrOperator::Range(range)) - }, - None => match operator { - Some(operator) => { - if !feature.allows_ranges() { - return Err(input - .new_custom_error(StyleParseErrorKind::MediaQueryUnexpectedOperator)); - } - Some(RangeOrOperator::Operator(operator)) - }, - None => None, - }, - }; - - let value = MediaExpressionValue::parse(feature, context, input).map_err(|err| { - err.location - .new_custom_error(StyleParseErrorKind::MediaQueryExpectedFeatureValue) - })?; - - Ok(Self::new(feature_index, Some(value), range_or_operator)) - } - - /// Returns whether this media query evaluates to true for the given device. - pub fn matches(&self, device: &Device, quirks_mode: QuirksMode) -> bool { - let value = self.value.as_ref(); - - macro_rules! expect { - ($variant:ident) => { - value.map(|value| match *value { - MediaExpressionValue::$variant(ref v) => v, - _ => unreachable!("Unexpected MediaExpressionValue"), - }) - }; - } - - match self.feature().evaluator { - Evaluator::Length(eval) => { - let computed = expect!(Length).map(|specified| { - computed::Context::for_media_query_evaluation(device, quirks_mode, |context| { - specified.to_computed_value(context) - }) - }); - eval(device, computed, self.range_or_operator) - }, - Evaluator::Integer(eval) => { - eval(device, expect!(Integer).cloned(), self.range_or_operator) - }, - Evaluator::Float(eval) => eval(device, expect!(Float).cloned(), self.range_or_operator), - Evaluator::NumberRatio(eval) => eval( - device, - expect!(NumberRatio).cloned(), - self.range_or_operator, - ), - Evaluator::Resolution(eval) => { - let computed = expect!(Resolution).map(|specified| { - computed::Context::for_media_query_evaluation(device, quirks_mode, |context| { - specified.to_computed_value(context) - }) - }); - eval(device, computed, self.range_or_operator) - }, - Evaluator::Enumerated { evaluator, .. } => { - evaluator(device, expect!(Enumerated).cloned(), self.range_or_operator) - }, - Evaluator::Ident(eval) => eval(device, expect!(Ident).cloned(), self.range_or_operator), - Evaluator::BoolInteger(eval) => eval( - device, - expect!(BoolInteger).cloned(), - self.range_or_operator, - ), - } - } -} - -/// A value found or expected in a media expression. -/// -/// FIXME(emilio): How should calc() serialize in the Number / Integer / -/// BoolInteger / NumberRatio case, as computed or as specified value? -/// -/// If the first, this would need to store the relevant values. -/// -/// See: https://github.com/w3c/csswg-drafts/issues/1968 -#[derive(Clone, Debug, MallocSizeOf, PartialEq, ToShmem)] -pub enum MediaExpressionValue { - /// A length. - Length(Length), - /// A (non-negative) integer. - Integer(u32), - /// A floating point value. - Float(CSSFloat), - /// A boolean value, specified as an integer (i.e., either 0 or 1). - BoolInteger(bool), - /// A single non-negative number or two non-negative numbers separated by '/', - /// with optional whitespace on either side of the '/'. - NumberRatio(Ratio), - /// A resolution. - Resolution(Resolution), - /// An enumerated value, defined by the variant keyword table in the - /// feature's `mData` member. - Enumerated(KeywordDiscriminant), - /// An identifier. - Ident(Atom), -} - -impl MediaExpressionValue { - fn to_css(&self, dest: &mut CssWriter, for_expr: &MediaFeatureExpression) -> fmt::Result - where - W: fmt::Write, - { - match *self { - MediaExpressionValue::Length(ref l) => l.to_css(dest), - MediaExpressionValue::Integer(v) => v.to_css(dest), - MediaExpressionValue::Float(v) => v.to_css(dest), - MediaExpressionValue::BoolInteger(v) => dest.write_str(if v { "1" } else { "0" }), - MediaExpressionValue::NumberRatio(ratio) => ratio.to_css(dest), - MediaExpressionValue::Resolution(ref r) => r.to_css(dest), - MediaExpressionValue::Ident(ref ident) => serialize_atom_identifier(ident, dest), - MediaExpressionValue::Enumerated(value) => match for_expr.feature().evaluator { - Evaluator::Enumerated { serializer, .. } => dest.write_str(&*serializer(value)), - _ => unreachable!(), - }, - } - } - - fn parse<'i, 't>( - for_feature: &MediaFeatureDescription, - context: &ParserContext, - input: &mut Parser<'i, 't>, - ) -> Result> { - Ok(match for_feature.evaluator { - Evaluator::Length(..) => { - let length = Length::parse_non_negative(context, input)?; - MediaExpressionValue::Length(length) - }, - Evaluator::Integer(..) => { - let integer = Integer::parse_non_negative(context, input)?; - MediaExpressionValue::Integer(integer.value() as u32) - }, - Evaluator::BoolInteger(..) => { - let integer = Integer::parse_non_negative(context, input)?; - let value = integer.value(); - if value > 1 { - return Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError)); - } - MediaExpressionValue::BoolInteger(value == 1) - }, - Evaluator::Float(..) => { - let number = Number::parse(context, input)?; - MediaExpressionValue::Float(number.get()) - }, - Evaluator::NumberRatio(..) => { - use crate::values::specified::Ratio as SpecifiedRatio; - let ratio = SpecifiedRatio::parse(context, input)?; - MediaExpressionValue::NumberRatio(Ratio::new(ratio.0.get(), ratio.1.get())) - }, - Evaluator::Resolution(..) => { - MediaExpressionValue::Resolution(Resolution::parse(context, input)?) - }, - Evaluator::Enumerated { parser, .. } => { - MediaExpressionValue::Enumerated(parser(context, input)?) - }, - Evaluator::Ident(..) => { - MediaExpressionValue::Ident(Atom::from(input.expect_ident()?.as_ref())) - }, - }) - } -} diff --git a/components/style/media_queries/media_list.rs b/components/style/media_queries/media_list.rs index abc2ae6dbc8..8ce317db534 100644 --- a/components/style/media_queries/media_list.rs +++ b/components/style/media_queries/media_list.rs @@ -10,6 +10,7 @@ use super::{Device, MediaQuery, Qualifier}; use crate::context::QuirksMode; use crate::error_reporting::ContextualParseError; use crate::parser::ParserContext; +use crate::values::computed; use cssparser::{Delimiter, Parser}; use cssparser::{ParserInput, Token}; @@ -74,15 +75,17 @@ impl MediaList { pub fn evaluate(&self, device: &Device, quirks_mode: QuirksMode) -> bool { // Check if it is an empty media query list or any queries match. // https://drafts.csswg.org/mediaqueries-4/#mq-list - self.media_queries.is_empty() || + if self.media_queries.is_empty() { + return true; + } + + computed::Context::for_media_query_evaluation(device, quirks_mode, |context| { self.media_queries.iter().any(|mq| { let media_match = mq.media_type.matches(device.media_type()); // Check if the media condition match. let query_match = media_match && - mq.condition - .as_ref() - .map_or(true, |c| c.matches(device, quirks_mode)); + mq.condition.as_ref().map_or(true, |c| c.matches(context)); // Apply the logical NOT qualifier to the result match mq.qualifier { @@ -90,6 +93,7 @@ impl MediaList { _ => query_match, } }) + }) } /// Whether this `MediaList` contains no media queries. diff --git a/components/style/media_queries/media_query.rs b/components/style/media_queries/media_query.rs index 83e34c5037f..1e737fa695f 100644 --- a/components/style/media_queries/media_query.rs +++ b/components/style/media_queries/media_query.rs @@ -6,7 +6,7 @@ //! //! https://drafts.csswg.org/mediaqueries/#typedef-media-query -use super::media_condition::MediaCondition; +use crate::queries::{QueryCondition, FeatureType}; use crate::parser::ParserContext; use crate::str::string_as_ascii_lowercase; use crate::values::CustomIdent; @@ -66,7 +66,7 @@ pub struct MediaQuery { pub media_type: MediaQueryType, /// The condition that this media query contains. This cannot have `or` /// in the first level. - pub condition: Option, + pub condition: Option, } impl ToCss for MediaQuery { @@ -134,9 +134,9 @@ impl MediaQuery { .unwrap_or_default(); let condition = if explicit_media_type.is_none() { - Some(MediaCondition::parse(context, input)?) + Some(QueryCondition::parse(context, input, FeatureType::Media)?) } else if input.try_parse(|i| i.expect_ident_matching("and")).is_ok() { - Some(MediaCondition::parse_disallow_or(context, input)?) + Some(QueryCondition::parse_disallow_or(context, input, FeatureType::Media)?) } else { None }; diff --git a/components/style/media_queries/mod.rs b/components/style/media_queries/mod.rs index ca385857481..833f6f53cb9 100644 --- a/components/style/media_queries/mod.rs +++ b/components/style/media_queries/mod.rs @@ -6,15 +6,9 @@ //! //! [mq]: https://drafts.csswg.org/mediaqueries/ -mod media_condition; mod media_list; mod media_query; -#[macro_use] -pub mod media_feature; -pub mod media_feature_expression; -pub use self::media_condition::MediaCondition; -pub use self::media_feature_expression::MediaFeatureExpression; pub use self::media_list::MediaList; pub use self::media_query::{MediaQuery, MediaQueryType, MediaType, Qualifier}; diff --git a/components/style/piecewise_linear.rs b/components/style/piecewise_linear.rs new file mode 100644 index 00000000000..0ac0220e44a --- /dev/null +++ b/components/style/piecewise_linear.rs @@ -0,0 +1,223 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ + +//! A piecewise linear function, following CSS linear easing +/// draft as in https://github.com/w3c/csswg-drafts/pull/6533. +use euclid::approxeq::ApproxEq; +use itertools::Itertools; + +use crate::values::CSSFloat; + +type ValueType = CSSFloat; +/// a single entry in a piecewise linear function. +#[derive(Clone, Copy)] +#[repr(C)] +struct Entry { + x: ValueType, + y: ValueType, +} + +/// Representation of a piecewise linear function, a series of linear functions. +#[derive(Default)] +#[repr(C)] +pub struct PiecewiseLinearFunction { + entries: crate::OwnedSlice, +} + +impl PiecewiseLinearFunction { + /// Interpolate y value given x and two points. The linear function will be rooted at the asymptote. + fn interpolate(x: ValueType, prev: Entry, next: Entry, asymptote: &Entry) -> ValueType { + // Line is vertical, or the two points are identical. Avoid infinite slope by pretending + // the line is flat. + if prev.x.approx_eq(&next.x) { + return asymptote.y; + } + let slope = (next.y - prev.y) / (next.x - prev.x); + return slope * (x - asymptote.x) + asymptote.y; + } + + /// Get the y value of the piecewise linear function given the x value. + pub fn at(&self, x: ValueType) -> ValueType { + if !x.is_finite() { + return if x > 0.0 { 1.0 } else { 0.0 }; + } + if self.entries.is_empty() { + // Implied y = x, as per spec. + return x; + } + if self.entries.len() == 1 { + // Implied y = , as per spec. + return self.entries[0].y; + } + // Spec dictates the valid input domain is [0, 1]. Outside of this range, the output + // should be calculated as if the slopes at start and end extend to infinity. However, if the + // start/end have two points of the same position, the line should extend along the x-axis. + // The function doesn't have to cover the input domain, in which case the extension logic + // applies even if the input falls in the input domain. + // Also, we're guaranteed to have at least two elements at this point. + if x < self.entries[0].x { + return Self::interpolate(x, self.entries[0], self.entries[1], &self.entries[0]); + } + let mut rev_iter = self.entries.iter().rev(); + let last = rev_iter.next().unwrap(); + if x > last.x { + let second_last = rev_iter.next().unwrap(); + return Self::interpolate(x, *second_last, *last, last); + } + + // Now we know the input sits within the domain explicitly defined by our function. + for (prev, next) in self.entries.iter().tuple_windows() { + if x > next.x { + continue; + } + // Prefer left hand side value + if x.approx_eq(&prev.x) { + return prev.y; + } + if x.approx_eq(&next.x) { + return next.y; + } + return Self::interpolate(x, *prev, *next, prev); + } + unreachable!("Input is supposed to be within the entries' min & max!"); + } +} + +/// Entry of a piecewise linear function while building, where the calculation of x value can be deferred. +#[derive(Clone, Copy)] +struct BuildEntry { + x: Option, + y: ValueType, +} + +/// Builder object to generate a linear function. +#[derive(Default)] +pub struct PiecewiseLinearFunctionBuilder { + largest_x: Option, + smallest_x: Option, + entries: Vec, +} + +impl PiecewiseLinearFunctionBuilder { + #[allow(missing_docs)] + pub fn new() -> Self { + PiecewiseLinearFunctionBuilder::default() + } + + fn create_entry(&mut self, y: ValueType, x: Option) { + let x = match x { + Some(x) if x.is_finite() => x, + _ => { + self.entries.push(BuildEntry { x: None, y }); + return; + }, + }; + // Specified x value cannot regress, as per spec. + let x = match self.largest_x { + Some(largest_x) => x.max(largest_x), + None => x, + }; + self.largest_x = Some(x); + // Whatever we see the earliest is the smallest value. + if self.smallest_x.is_none() { + self.smallest_x = Some(x); + } + self.entries.push(BuildEntry { x: Some(x), y }); + } + + /// Add a new entry into the piecewise linear function with specified y value. + /// If the start x value is given, that is where the x value will be. Otherwise, + /// the x value is calculated later. If the end x value is specified, a flat segment + /// is generated. If start x value is not specified but end x is, it is treated as + /// start x. + pub fn push(mut self, y: CSSFloat, x_start: Option, x_end: Option) -> Self { + self.create_entry(y, x_start); + if x_end.is_some() { + self.create_entry(y, x_end.map(|x| x)); + } + self + } + + /// Finish building the piecewise linear function by resolving all undefined x values, + /// then return the result. + pub fn build(mut self) -> PiecewiseLinearFunction { + if self.entries.is_empty() { + return PiecewiseLinearFunction::default(); + } + if self.entries.len() == 1 { + // Don't bother resolving anything. + return PiecewiseLinearFunction { + entries: crate::OwnedSlice::from_slice(&[Entry { + x: 0., + y: self.entries[0].y, + }]), + }; + } + // Guaranteed at least two elements. + // Start and end elements guaranteed to have defined x value. + // Note(dshin): Spec asserts that start/end elements are supposed to have 0/1 assigned + // respectively if their x values are undefined at this time; however, the spec does + // not disallow negative/100%+ inputs, and inputs like `linear(0, 0.1 -10%, 0.9 110%, 1.0)` + // would break the assumption that the x values in the list increase monotonically. + // Otherwise, we still want 0/1 assigned to the start/end values regardless of + // adjacent x values (i.e. `linear(0, 0.1 10%, 0.9 90%, 1.0)` == + // `linear(0 0%, 0.1 10%, 0.9 90%, 1.0)` != `linear(0 10%, 0.1 10%, 0.9 90%, 1.0 90%)`) + self.entries[0] + .x + .get_or_insert(self.smallest_x.filter(|x| x < &0.0).unwrap_or(0.0)); + self.entries + .last_mut() + .unwrap() + .x + .get_or_insert(self.largest_x.filter(|x| x > &1.0).unwrap_or(1.0)); + + let mut result = Vec::with_capacity(self.entries.len()); + result.push(Entry { + x: self.entries[0].x.unwrap(), + y: self.entries[0].y, + }); + for (i, e) in self.entries.iter().enumerate().skip(1) { + if e.x.is_none() { + // Need to calculate x values by first finding an entry with the first + // defined x value (Guaranteed to exist as the list end has it defined). + continue; + } + // x is defined for this element. + let divisor = i - result.len() + 1; + // Any element(s) with undefined x to assign? + if divisor != 1 { + // Have at least one element in result at all times. + let start_x = result.last().unwrap().x; + let increment = (e.x.unwrap() - start_x) / divisor as ValueType; + // Grab every element with undefined x to this point, which starts at the end of the result + // array, and ending right before the current index. Then, assigned the evenly divided + // x values. + result.extend( + self.entries[result.len()..i] + .iter() + .enumerate() + .map(|(j, e)| { + debug_assert!(e.x.is_none(), "Expected an entry with x undefined!"); + Entry { + x: increment * (j + 1) as ValueType + start_x, + y: e.y, + } + }), + ); + } + result.push(Entry { + x: e.x.unwrap(), + y: e.y, + }); + } + debug_assert_eq!( + result.len(), + self.entries.len(), + "Should've mapped one-to-one!" + ); + PiecewiseLinearFunction { + entries: result.into(), + } + } +} diff --git a/components/style/properties/cascade.rs b/components/style/properties/cascade.rs index 37d47e1ce61..3d3c6b833b8 100644 --- a/components/style/properties/cascade.rs +++ b/components/style/properties/cascade.rs @@ -28,34 +28,8 @@ use smallvec::SmallVec; use std::borrow::Cow; use std::cell::RefCell; -/// We split the cascade in two phases: 'early' properties, and 'late' -/// properties. -/// -/// Early properties are the ones that don't have dependencies _and_ other -/// properties depend on, for example, writing-mode related properties, color -/// (for currentColor), or font-size (for em, etc). -/// -/// Late properties are all the others. -trait CascadePhase { - fn is_early() -> bool; -} - -struct EarlyProperties; -impl CascadePhase for EarlyProperties { - fn is_early() -> bool { - true - } -} - -struct LateProperties; -impl CascadePhase for LateProperties { - fn is_early() -> bool { - false - } -} - #[derive(Clone, Copy, Debug, Eq, PartialEq)] -enum ApplyResetProperties { +enum CanHaveLogicalProperties { No, Yes, } @@ -283,6 +257,7 @@ where let inherited_style = parent_style.unwrap_or(device.default_computed_values()); let mut declarations = SmallVec::<[(&_, CascadePriority); 32]>::new(); + let mut referenced_properties = LonghandIdSet::default(); let custom_properties = { let mut builder = CustomPropertiesBuilder::new(inherited_style.custom_properties(), device); @@ -290,6 +265,8 @@ where declarations.push((declaration, priority)); if let PropertyDeclaration::Custom(ref declaration) = *declaration { builder.cascade(declaration, priority); + } else { + referenced_properties.insert(declaration.id().as_longhand().unwrap()); } } @@ -315,46 +292,74 @@ where in_media_query: false, for_smil_animation: false, for_non_inherited_property: None, + container_info: None, quirks_mode, rule_cache_conditions: RefCell::new(rule_cache_conditions), }; - let using_cached_reset_properties = { - let mut cascade = Cascade::new(&mut context, cascade_mode); - let mut shorthand_cache = ShorthandsWithPropertyReferencesCache::default(); + let using_cached_reset_properties; + let mut cascade = Cascade::new(&mut context, cascade_mode, &referenced_properties); + let mut shorthand_cache = ShorthandsWithPropertyReferencesCache::default(); - cascade.apply_properties::( - ApplyResetProperties::Yes, - declarations.iter().cloned(), - &mut shorthand_cache, - ); + let properties_to_apply = match cascade.cascade_mode { + CascadeMode::Visited { writing_mode } => { + cascade.context.builder.writing_mode = writing_mode; + // We never insert visited styles into the cache so we don't need to + // try looking it up. It also wouldn't be super-profitable, only a + // handful reset properties are non-inherited. + using_cached_reset_properties = false; + LonghandIdSet::visited_dependent() + }, + CascadeMode::Unvisited { visited_rules } => { + if cascade.apply_properties( + CanHaveLogicalProperties::No, + LonghandIdSet::writing_mode_group(), + declarations.iter().cloned(), + &mut shorthand_cache, + ) { + cascade.compute_writing_mode(); + } - cascade.compute_visited_style_if_needed( - element, - parent_style, - parent_style_ignoring_first_line, - layout_parent_style, - guards, - ); + if cascade.apply_properties( + CanHaveLogicalProperties::No, + LonghandIdSet::fonts_and_color_group(), + declarations.iter().cloned(), + &mut shorthand_cache, + ) { + cascade.fixup_font_stuff(); + } - let using_cached_reset_properties = - cascade.try_to_use_cached_reset_properties(rule_cache, guards); + if let Some(visited_rules) = visited_rules { + cascade.compute_visited_style_if_needed( + element, + parent_style, + parent_style_ignoring_first_line, + layout_parent_style, + visited_rules, + guards, + ); + } - let apply_reset = if using_cached_reset_properties { - ApplyResetProperties::No - } else { - ApplyResetProperties::Yes - }; + using_cached_reset_properties = + cascade.try_to_use_cached_reset_properties(rule_cache, guards); - cascade.apply_properties::( - apply_reset, - declarations.iter().cloned(), - &mut shorthand_cache, - ); - - using_cached_reset_properties + if using_cached_reset_properties { + LonghandIdSet::late_group_only_inherited() + } else { + LonghandIdSet::late_group() + } + } }; + cascade.apply_properties( + CanHaveLogicalProperties::Yes, + properties_to_apply, + declarations.iter().cloned(), + &mut shorthand_cache, + ); + + cascade.finished_applying_properties(); + context.builder.clear_modified_reset(); if matches!(cascade_mode, CascadeMode::Unvisited { .. }) { @@ -413,7 +418,7 @@ fn tweak_when_ignoring_colors( fn alpha_channel(color: &Color, context: &computed::Context) -> u8 { // We assume here currentColor is opaque. - let color = color.to_computed_value(context).to_rgba(RGBA::new(0, 0, 0, 255)); + let color = color.to_computed_value(context).into_rgba(RGBA::new(0, 0, 0, 255)); color.alpha } @@ -428,14 +433,17 @@ fn tweak_when_ignoring_colors( // otherwise, this is needed to preserve semi-transparent // backgrounds. // - // NOTE(emilio): We revert even for alpha == 0. Not doing so would - // be a bit special casey, even though it causes issues like - // bug 1625036. The reasoning is that the conditions that trigger - // that (having mismatched widget and default backgrounds) are both - // uncommon, and broken in other applications as well, and not - // honoring transparent makes stuff uglier or break unconditionally + // NOTE(emilio): We honor transparent unconditionally, like we do + // for color, even though it causes issues like bug 1625036. The + // reasoning is that the conditions that trigger that (having + // mismatched widget and default backgrounds) are both uncommon, and + // broken in other applications as well, and not honoring + // transparent makes stuff uglier or break unconditionally // (bug 1666059, bug 1755713). let alpha = alpha_channel(color, context); + if alpha == 0 { + return; + } let mut color = context.builder.device.default_background_color(); color.alpha = alpha; declarations_to_apply_unless_overriden @@ -497,6 +505,8 @@ fn tweak_when_ignoring_colors( struct Cascade<'a, 'b: 'a> { context: &'a mut computed::Context<'b>, cascade_mode: CascadeMode<'a>, + /// All the properties that have a declaration in the cascade. + referenced: &'a LonghandIdSet, seen: LonghandIdSet, author_specified: LonghandIdSet, reverted_set: LonghandIdSet, @@ -504,10 +514,15 @@ struct Cascade<'a, 'b: 'a> { } impl<'a, 'b: 'a> Cascade<'a, 'b> { - fn new(context: &'a mut computed::Context<'b>, cascade_mode: CascadeMode<'a>) -> Self { + fn new( + context: &'a mut computed::Context<'b>, + cascade_mode: CascadeMode<'a>, + referenced: &'a LonghandIdSet, + ) -> Self { Self { context, cascade_mode, + referenced, seen: LonghandIdSet::default(), author_specified: LonghandIdSet::default(), reverted_set: Default::default(), @@ -575,23 +590,21 @@ impl<'a, 'b: 'a> Cascade<'a, 'b> { (CASCADE_PROPERTY[discriminant])(declaration, &mut self.context); } - fn apply_properties<'decls, Phase, I>( + fn apply_properties<'decls, I>( &mut self, - apply_reset: ApplyResetProperties, + can_have_logical_properties: CanHaveLogicalProperties, + properties_to_apply: &'a LonghandIdSet, declarations: I, mut shorthand_cache: &mut ShorthandsWithPropertyReferencesCache, - ) where - Phase: CascadePhase, + ) -> bool + where I: Iterator, { - let apply_reset = apply_reset == ApplyResetProperties::Yes; + if !self.referenced.contains_any(properties_to_apply) { + return false; + } - debug_assert!( - !Phase::is_early() || apply_reset, - "Should always apply reset properties in the early phase, since we \ - need to know font-size / writing-mode to decide whether to use the \ - cached reset properties" - ); + let can_have_logical_properties = can_have_logical_properties == CanHaveLogicalProperties::Yes; let ignore_colors = !self.context.builder.device.use_document_colors(); let mut declarations_to_apply_unless_overriden = DeclarationsToApplyUnlessOverriden::new(); @@ -605,20 +618,15 @@ impl<'a, 'b: 'a> Cascade<'a, 'b> { PropertyDeclarationId::Custom(..) => continue, }; - let inherited = longhand_id.inherited(); - if !apply_reset && !inherited { + if !properties_to_apply.contains(longhand_id) { continue; } - if Phase::is_early() != longhand_id.is_early_property() { - continue; - } - - debug_assert!(!Phase::is_early() || !longhand_id.is_logical()); - let physical_longhand_id = if Phase::is_early() { - longhand_id - } else { + debug_assert!(can_have_logical_properties || !longhand_id.is_logical()); + let physical_longhand_id = if can_have_logical_properties { longhand_id.to_physical(self.context.builder.writing_mode) + } else { + longhand_id }; if self.seen.contains(physical_longhand_id) { @@ -633,15 +641,6 @@ impl<'a, 'b: 'a> Cascade<'a, 'b> { } } - // Only a few properties are allowed to depend on the visited state - // of links. When cascading visited styles, we can save time by - // only processing these properties. - if matches!(self.cascade_mode, CascadeMode::Visited { .. }) && - !physical_longhand_id.is_visited_dependent() - { - continue; - } - let mut declaration = self.substitute_variables_if_needed(declaration, &mut shorthand_cache); @@ -675,8 +674,8 @@ impl<'a, 'b: 'a> Cascade<'a, 'b> { continue; }, CSSWideKeyword::Unset => true, - CSSWideKeyword::Inherit => inherited, - CSSWideKeyword::Initial => !inherited, + CSSWideKeyword::Inherit => longhand_id.inherited(), + CSSWideKeyword::Initial => !longhand_id.inherited(), }, None => false, }; @@ -710,22 +709,13 @@ impl<'a, 'b: 'a> Cascade<'a, 'b> { } } - if Phase::is_early() { - self.fixup_font_stuff(); - self.compute_writing_mode(); - } else { - self.finished_applying_properties(); - } + true } fn compute_writing_mode(&mut self) { - let writing_mode = match self.cascade_mode { - CascadeMode::Unvisited { .. } => { - WritingMode::new(self.context.builder.get_inherited_box()) - }, - CascadeMode::Visited { writing_mode } => writing_mode, - }; - self.context.builder.writing_mode = writing_mode; + debug_assert!(matches!(self.cascade_mode, CascadeMode::Unvisited { .. })); + self.context.builder.writing_mode = + WritingMode::new(self.context.builder.get_inherited_box()) } fn compute_visited_style_if_needed( @@ -734,20 +724,12 @@ impl<'a, 'b: 'a> Cascade<'a, 'b> { parent_style: Option<&ComputedValues>, parent_style_ignoring_first_line: Option<&ComputedValues>, layout_parent_style: Option<&ComputedValues>, + visited_rules: &StrongRuleNode, guards: &StylesheetGuards, ) where E: TElement, { - let visited_rules = match self.cascade_mode { - CascadeMode::Unvisited { visited_rules } => visited_rules, - CascadeMode::Visited { .. } => return, - }; - - let visited_rules = match visited_rules { - Some(rules) => rules, - None => return, - }; - + debug_assert!(matches!(self.cascade_mode, CascadeMode::Unvisited { .. })); let is_link = self.context.builder.pseudo.is_none() && element.unwrap().is_link(); macro_rules! visited_parent { diff --git a/components/style/properties/counted_unknown_properties.py b/components/style/properties/counted_unknown_properties.py index 473f2f599d2..997fd167a07 100644 --- a/components/style/properties/counted_unknown_properties.py +++ b/components/style/properties/counted_unknown_properties.py @@ -73,7 +73,6 @@ COUNTED_UNKNOWN_PROPERTIES = [ "-webkit-perspective-origin-y", "-webkit-margin-before-collapse", "-webkit-border-before-style", - "scroll-snap-stop", "-webkit-margin-bottom-collapse", "-webkit-ruby-position", "-webkit-column-break-after", diff --git a/components/style/properties/data.py b/components/style/properties/data.py index 84e0b482569..ac1de39f877 100644 --- a/components/style/properties/data.py +++ b/components/style/properties/data.py @@ -444,6 +444,7 @@ class Longhand(Property): "ColumnCount", "Contain", "ContentVisibility", + "ContainerType", "Display", "FillRule", "Float", @@ -491,6 +492,7 @@ class Longhand(Property): "ScrollbarGutter", "ScrollSnapAlign", "ScrollSnapAxis", + "ScrollSnapStop", "ScrollSnapStrictness", "ScrollSnapType", "TextAlign", diff --git a/components/style/properties/gecko.mako.rs b/components/style/properties/gecko.mako.rs index bc0a78183a8..0edfbf84451 100644 --- a/components/style/properties/gecko.mako.rs +++ b/components/style/properties/gecko.mako.rs @@ -755,7 +755,6 @@ fn static_assert() { <%self:impl_trait style_struct_name="Margin" skip_longhands="${skip_margin_longhands} ${skip_scroll_margin_longhands}"> - % for side in SIDES: <% impl_split_style_coord("margin_%s" % side.ident, "mMargin", @@ -1181,11 +1180,7 @@ fn static_assert() { <% skip_box_longhands= """display - animation-name animation-delay animation-duration - animation-direction animation-fill-mode animation-play-state - animation-iteration-count animation-timeline animation-timing-function - clear transition-duration transition-delay - transition-timing-function transition-property + clear -webkit-line-clamp""" %> <%self:impl_trait style_struct_name="Box" skip_longhands="${skip_box_longhands}"> #[inline] @@ -1227,245 +1222,6 @@ fn static_assert() { ) %> ${impl_keyword('clear', 'mBreakType', clear_keyword)} - ${impl_transition_time_value('delay', 'Delay')} - ${impl_transition_time_value('duration', 'Duration')} - ${impl_animation_or_transition_timing_function('transition')} - - pub fn transition_combined_duration_at(&self, index: usize) -> f32 { - // https://drafts.csswg.org/css-transitions/#transition-combined-duration - self.gecko.mTransitions[index % self.gecko.mTransitionDurationCount as usize].mDuration.max(0.0) - + self.gecko.mTransitions[index % self.gecko.mTransitionDelayCount as usize].mDelay - } - - pub fn set_transition_property(&mut self, v: I) - where I: IntoIterator, - I::IntoIter: ExactSizeIterator - { - use crate::gecko_bindings::structs::nsCSSPropertyID::eCSSPropertyExtra_no_properties; - use crate::gecko_bindings::structs::nsCSSPropertyID::eCSSPropertyExtra_variable; - use crate::gecko_bindings::structs::nsCSSPropertyID::eCSSProperty_UNKNOWN; - - let v = v.into_iter(); - - if v.len() != 0 { - self.gecko.mTransitions.ensure_len(v.len()); - self.gecko.mTransitionPropertyCount = v.len() as u32; - for (servo, gecko) in v.zip(self.gecko.mTransitions.iter_mut()) { - unsafe { gecko.mUnknownProperty.clear() }; - - match servo { - TransitionProperty::Unsupported(ident) => { - gecko.mProperty = eCSSProperty_UNKNOWN; - gecko.mUnknownProperty.mRawPtr = ident.0.into_addrefed(); - }, - TransitionProperty::Custom(name) => { - gecko.mProperty = eCSSPropertyExtra_variable; - gecko.mUnknownProperty.mRawPtr = name.into_addrefed(); - } - _ => gecko.mProperty = servo.to_nscsspropertyid().unwrap(), - } - } - } else { - // In gecko |none| is represented by eCSSPropertyExtra_no_properties. - self.gecko.mTransitionPropertyCount = 1; - self.gecko.mTransitions[0].mProperty = eCSSPropertyExtra_no_properties; - } - } - - /// Returns whether there are any transitions specified. - pub fn specifies_transitions(&self) -> bool { - use crate::gecko_bindings::structs::nsCSSPropertyID::eCSSPropertyExtra_all_properties; - if self.gecko.mTransitionPropertyCount == 1 && - self.gecko.mTransitions[0].mProperty == eCSSPropertyExtra_all_properties && - self.transition_combined_duration_at(0) <= 0.0f32 { - return false; - } - - self.gecko.mTransitionPropertyCount > 0 - } - - pub fn transition_property_at(&self, index: usize) - -> longhands::transition_property::computed_value::SingleComputedValue { - use crate::gecko_bindings::structs::nsCSSPropertyID::eCSSPropertyExtra_no_properties; - use crate::gecko_bindings::structs::nsCSSPropertyID::eCSSPropertyExtra_variable; - use crate::gecko_bindings::structs::nsCSSPropertyID::eCSSProperty_UNKNOWN; - - let property = self.gecko.mTransitions[index].mProperty; - if property == eCSSProperty_UNKNOWN { - let atom = self.gecko.mTransitions[index].mUnknownProperty.mRawPtr; - debug_assert!(!atom.is_null()); - TransitionProperty::Unsupported(CustomIdent(unsafe{ - Atom::from_raw(atom) - })) - } else if property == eCSSPropertyExtra_variable { - let atom = self.gecko.mTransitions[index].mUnknownProperty.mRawPtr; - debug_assert!(!atom.is_null()); - TransitionProperty::Custom(unsafe{ - Atom::from_raw(atom) - }) - } else if property == eCSSPropertyExtra_no_properties { - // Actually, we don't expect TransitionProperty::Unsupported also - // represents "none", but if the caller wants to convert it, it is - // fine. Please use it carefully. - // - // FIXME(emilio): This is a hack, is this reachable? - TransitionProperty::Unsupported(CustomIdent(atom!("none"))) - } else { - property.into() - } - } - - pub fn transition_nscsspropertyid_at(&self, index: usize) -> nsCSSPropertyID { - self.gecko.mTransitions[index].mProperty - } - - pub fn copy_transition_property_from(&mut self, other: &Self) { - use crate::gecko_bindings::structs::nsCSSPropertyID::eCSSPropertyExtra_variable; - use crate::gecko_bindings::structs::nsCSSPropertyID::eCSSProperty_UNKNOWN; - self.gecko.mTransitions.ensure_len(other.gecko.mTransitions.len()); - - let count = other.gecko.mTransitionPropertyCount; - self.gecko.mTransitionPropertyCount = count; - - for (index, transition) in self.gecko.mTransitions.iter_mut().enumerate().take(count as usize) { - transition.mProperty = other.gecko.mTransitions[index].mProperty; - unsafe { transition.mUnknownProperty.clear() }; - if transition.mProperty == eCSSProperty_UNKNOWN || - transition.mProperty == eCSSPropertyExtra_variable { - let atom = other.gecko.mTransitions[index].mUnknownProperty.mRawPtr; - debug_assert!(!atom.is_null()); - transition.mUnknownProperty.mRawPtr = unsafe { Atom::from_raw(atom) }.into_addrefed(); - } - } - } - - pub fn reset_transition_property(&mut self, other: &Self) { - self.copy_transition_property_from(other) - } - - ${impl_transition_count('property', 'Property')} - - pub fn animations_equals(&self, other: &Self) -> bool { - return self.gecko.mAnimationNameCount == other.gecko.mAnimationNameCount - && self.gecko.mAnimationDelayCount == other.gecko.mAnimationDelayCount - && self.gecko.mAnimationDirectionCount == other.gecko.mAnimationDirectionCount - && self.gecko.mAnimationDurationCount == other.gecko.mAnimationDurationCount - && self.gecko.mAnimationFillModeCount == other.gecko.mAnimationFillModeCount - && self.gecko.mAnimationIterationCountCount == other.gecko.mAnimationIterationCountCount - && self.gecko.mAnimationPlayStateCount == other.gecko.mAnimationPlayStateCount - && self.gecko.mAnimationTimingFunctionCount == other.gecko.mAnimationTimingFunctionCount - && unsafe { bindings::Gecko_StyleAnimationsEquals(&self.gecko.mAnimations, &other.gecko.mAnimations) } - } - - pub fn set_animation_name(&mut self, v: I) - where I: IntoIterator, - I::IntoIter: ExactSizeIterator - { - let v = v.into_iter(); - debug_assert_ne!(v.len(), 0); - self.gecko.mAnimations.ensure_len(v.len()); - - self.gecko.mAnimationNameCount = v.len() as u32; - for (servo, gecko) in v.zip(self.gecko.mAnimations.iter_mut()) { - let atom = match servo.0 { - None => atom!(""), - Some(ref name) => name.as_atom().clone(), - }; - unsafe { bindings::Gecko_SetAnimationName(gecko, atom.into_addrefed()); } - } - } - pub fn animation_name_at(&self, index: usize) - -> longhands::animation_name::computed_value::SingleComputedValue { - use crate::properties::longhands::animation_name::single_value::SpecifiedValue as AnimationName; - - let atom = self.gecko.mAnimations[index].mName.mRawPtr; - if atom == atom!("").as_ptr() { - return AnimationName(None) - } - AnimationName(Some(KeyframesName::from_atom(unsafe { Atom::from_raw(atom) }))) - } - pub fn copy_animation_name_from(&mut self, other: &Self) { - self.gecko.mAnimationNameCount = other.gecko.mAnimationNameCount; - unsafe { bindings::Gecko_CopyAnimationNames(&mut self.gecko.mAnimations, &other.gecko.mAnimations); } - } - - pub fn reset_animation_name(&mut self, other: &Self) { - self.copy_animation_name_from(other) - } - - ${impl_animation_count('name', 'Name')} - - ${impl_animation_time_value('delay', 'Delay')} - ${impl_animation_time_value('duration', 'Duration')} - - ${impl_animation_keyword('direction', 'Direction', - data.longhands_by_name["animation-direction"].keyword)} - ${impl_animation_keyword('fill_mode', 'FillMode', - data.longhands_by_name["animation-fill-mode"].keyword)} - ${impl_animation_keyword('play_state', 'PlayState', - data.longhands_by_name["animation-play-state"].keyword)} - - pub fn set_animation_iteration_count(&mut self, v: I) - where - I: IntoIterator, - I::IntoIter: ExactSizeIterator + Clone - { - use std::f32; - use crate::values::generics::box_::AnimationIterationCount; - - let v = v.into_iter(); - - debug_assert_ne!(v.len(), 0); - let input_len = v.len(); - self.gecko.mAnimations.ensure_len(input_len); - - self.gecko.mAnimationIterationCountCount = input_len as u32; - for (gecko, servo) in self.gecko.mAnimations.iter_mut().take(input_len as usize).zip(v) { - match servo { - AnimationIterationCount::Number(n) => gecko.mIterationCount = n, - AnimationIterationCount::Infinite => gecko.mIterationCount = f32::INFINITY, - } - } - } - - pub fn animation_iteration_count_at( - &self, - index: usize, - ) -> values::computed::AnimationIterationCount { - use crate::values::generics::box_::AnimationIterationCount; - - if self.gecko.mAnimations[index].mIterationCount.is_infinite() { - AnimationIterationCount::Infinite - } else { - AnimationIterationCount::Number(self.gecko.mAnimations[index].mIterationCount) - } - } - - ${impl_animation_count('iteration_count', 'IterationCount')} - ${impl_copy_animation_value('iteration_count', 'IterationCount')} - ${impl_animation_or_transition_timing_function('animation')} - - pub fn set_animation_timeline(&mut self, v: I) - where - I: IntoIterator, - I::IntoIter: ExactSizeIterator - { - let v = v.into_iter(); - debug_assert_ne!(v.len(), 0); - let input_len = v.len(); - self.gecko.mAnimations.ensure_len(input_len); - - self.gecko.mAnimationTimelineCount = input_len as u32; - for (gecko, servo) in self.gecko.mAnimations.iter_mut().take(input_len as usize).zip(v) { - gecko.mTimeline = servo; - } - } - pub fn animation_timeline_at(&self, index: usize) -> values::specified::box_::AnimationTimeline { - self.gecko.mAnimations[index].mTimeline.clone() - } - ${impl_animation_count('timeline', 'Timeline')} - ${impl_copy_animation_value('timeline', 'Timeline')} - #[allow(non_snake_case)] pub fn set__webkit_line_clamp(&mut self, v: longhands::_webkit_line_clamp::computed_value::T) { self.gecko.mLineClamp = match v { @@ -2020,7 +1776,247 @@ mask-mode mask-repeat mask-clip mask-origin mask-composite mask-position-x mask- } -<%self:impl_trait style_struct_name="UI"> +<% skip_ui_longhands = """animation-name animation-delay animation-duration + animation-direction animation-fill-mode + animation-play-state animation-iteration-count + animation-timeline animation-timing-function + transition-duration transition-delay + transition-timing-function transition-property""" %> + +<%self:impl_trait style_struct_name="UI" skip_longhands="${skip_ui_longhands}"> + ${impl_transition_time_value('delay', 'Delay')} + ${impl_transition_time_value('duration', 'Duration')} + ${impl_animation_or_transition_timing_function('transition')} + + pub fn transition_combined_duration_at(&self, index: usize) -> f32 { + // https://drafts.csswg.org/css-transitions/#transition-combined-duration + self.gecko.mTransitions[index % self.gecko.mTransitionDurationCount as usize].mDuration.max(0.0) + + self.gecko.mTransitions[index % self.gecko.mTransitionDelayCount as usize].mDelay + } + + pub fn set_transition_property(&mut self, v: I) + where I: IntoIterator, + I::IntoIter: ExactSizeIterator + { + use crate::gecko_bindings::structs::nsCSSPropertyID::eCSSPropertyExtra_no_properties; + use crate::gecko_bindings::structs::nsCSSPropertyID::eCSSPropertyExtra_variable; + use crate::gecko_bindings::structs::nsCSSPropertyID::eCSSProperty_UNKNOWN; + + let v = v.into_iter(); + + if v.len() != 0 { + self.gecko.mTransitions.ensure_len(v.len()); + self.gecko.mTransitionPropertyCount = v.len() as u32; + for (servo, gecko) in v.zip(self.gecko.mTransitions.iter_mut()) { + unsafe { gecko.mUnknownProperty.clear() }; + + match servo { + TransitionProperty::Unsupported(ident) => { + gecko.mProperty = eCSSProperty_UNKNOWN; + gecko.mUnknownProperty.mRawPtr = ident.0.into_addrefed(); + }, + TransitionProperty::Custom(name) => { + gecko.mProperty = eCSSPropertyExtra_variable; + gecko.mUnknownProperty.mRawPtr = name.into_addrefed(); + } + _ => gecko.mProperty = servo.to_nscsspropertyid().unwrap(), + } + } + } else { + // In gecko |none| is represented by eCSSPropertyExtra_no_properties. + self.gecko.mTransitionPropertyCount = 1; + self.gecko.mTransitions[0].mProperty = eCSSPropertyExtra_no_properties; + } + } + + /// Returns whether there are any transitions specified. + pub fn specifies_transitions(&self) -> bool { + use crate::gecko_bindings::structs::nsCSSPropertyID::eCSSPropertyExtra_all_properties; + if self.gecko.mTransitionPropertyCount == 1 && + self.gecko.mTransitions[0].mProperty == eCSSPropertyExtra_all_properties && + self.transition_combined_duration_at(0) <= 0.0f32 { + return false; + } + + self.gecko.mTransitionPropertyCount > 0 + } + + pub fn transition_property_at(&self, index: usize) + -> longhands::transition_property::computed_value::SingleComputedValue { + use crate::gecko_bindings::structs::nsCSSPropertyID::eCSSPropertyExtra_no_properties; + use crate::gecko_bindings::structs::nsCSSPropertyID::eCSSPropertyExtra_variable; + use crate::gecko_bindings::structs::nsCSSPropertyID::eCSSProperty_UNKNOWN; + + let property = self.gecko.mTransitions[index].mProperty; + if property == eCSSProperty_UNKNOWN { + let atom = self.gecko.mTransitions[index].mUnknownProperty.mRawPtr; + debug_assert!(!atom.is_null()); + TransitionProperty::Unsupported(CustomIdent(unsafe{ + Atom::from_raw(atom) + })) + } else if property == eCSSPropertyExtra_variable { + let atom = self.gecko.mTransitions[index].mUnknownProperty.mRawPtr; + debug_assert!(!atom.is_null()); + TransitionProperty::Custom(unsafe{ + Atom::from_raw(atom) + }) + } else if property == eCSSPropertyExtra_no_properties { + // Actually, we don't expect TransitionProperty::Unsupported also + // represents "none", but if the caller wants to convert it, it is + // fine. Please use it carefully. + // + // FIXME(emilio): This is a hack, is this reachable? + TransitionProperty::Unsupported(CustomIdent(atom!("none"))) + } else { + property.into() + } + } + + pub fn transition_nscsspropertyid_at(&self, index: usize) -> nsCSSPropertyID { + self.gecko.mTransitions[index].mProperty + } + + pub fn copy_transition_property_from(&mut self, other: &Self) { + use crate::gecko_bindings::structs::nsCSSPropertyID::eCSSPropertyExtra_variable; + use crate::gecko_bindings::structs::nsCSSPropertyID::eCSSProperty_UNKNOWN; + self.gecko.mTransitions.ensure_len(other.gecko.mTransitions.len()); + + let count = other.gecko.mTransitionPropertyCount; + self.gecko.mTransitionPropertyCount = count; + + for (index, transition) in self.gecko.mTransitions.iter_mut().enumerate().take(count as usize) { + transition.mProperty = other.gecko.mTransitions[index].mProperty; + unsafe { transition.mUnknownProperty.clear() }; + if transition.mProperty == eCSSProperty_UNKNOWN || + transition.mProperty == eCSSPropertyExtra_variable { + let atom = other.gecko.mTransitions[index].mUnknownProperty.mRawPtr; + debug_assert!(!atom.is_null()); + transition.mUnknownProperty.mRawPtr = unsafe { Atom::from_raw(atom) }.into_addrefed(); + } + } + } + + pub fn reset_transition_property(&mut self, other: &Self) { + self.copy_transition_property_from(other) + } + + ${impl_transition_count('property', 'Property')} + + pub fn animations_equals(&self, other: &Self) -> bool { + return self.gecko.mAnimationNameCount == other.gecko.mAnimationNameCount + && self.gecko.mAnimationDelayCount == other.gecko.mAnimationDelayCount + && self.gecko.mAnimationDirectionCount == other.gecko.mAnimationDirectionCount + && self.gecko.mAnimationDurationCount == other.gecko.mAnimationDurationCount + && self.gecko.mAnimationFillModeCount == other.gecko.mAnimationFillModeCount + && self.gecko.mAnimationIterationCountCount == other.gecko.mAnimationIterationCountCount + && self.gecko.mAnimationPlayStateCount == other.gecko.mAnimationPlayStateCount + && self.gecko.mAnimationTimingFunctionCount == other.gecko.mAnimationTimingFunctionCount + && unsafe { bindings::Gecko_StyleAnimationsEquals(&self.gecko.mAnimations, &other.gecko.mAnimations) } + } + + pub fn set_animation_name(&mut self, v: I) + where I: IntoIterator, + I::IntoIter: ExactSizeIterator + { + let v = v.into_iter(); + debug_assert_ne!(v.len(), 0); + self.gecko.mAnimations.ensure_len(v.len()); + + self.gecko.mAnimationNameCount = v.len() as u32; + for (servo, gecko) in v.zip(self.gecko.mAnimations.iter_mut()) { + let atom = servo.0.as_atom().clone(); + unsafe { bindings::Gecko_SetAnimationName(gecko, atom.into_addrefed()); } + } + } + pub fn animation_name_at(&self, index: usize) + -> longhands::animation_name::computed_value::SingleComputedValue { + use crate::properties::longhands::animation_name::single_value::SpecifiedValue as AnimationName; + + let atom = self.gecko.mAnimations[index].mName.mRawPtr; + AnimationName(KeyframesName::from_atom(unsafe { Atom::from_raw(atom) })) + } + pub fn copy_animation_name_from(&mut self, other: &Self) { + self.gecko.mAnimationNameCount = other.gecko.mAnimationNameCount; + unsafe { bindings::Gecko_CopyAnimationNames(&mut self.gecko.mAnimations, &other.gecko.mAnimations); } + } + + pub fn reset_animation_name(&mut self, other: &Self) { + self.copy_animation_name_from(other) + } + + ${impl_animation_count('name', 'Name')} + + ${impl_animation_time_value('delay', 'Delay')} + ${impl_animation_time_value('duration', 'Duration')} + + ${impl_animation_keyword('direction', 'Direction', + data.longhands_by_name["animation-direction"].keyword)} + ${impl_animation_keyword('fill_mode', 'FillMode', + data.longhands_by_name["animation-fill-mode"].keyword)} + ${impl_animation_keyword('play_state', 'PlayState', + data.longhands_by_name["animation-play-state"].keyword)} + + pub fn set_animation_iteration_count(&mut self, v: I) + where + I: IntoIterator, + I::IntoIter: ExactSizeIterator + Clone + { + use std::f32; + use crate::values::generics::box_::AnimationIterationCount; + + let v = v.into_iter(); + + debug_assert_ne!(v.len(), 0); + let input_len = v.len(); + self.gecko.mAnimations.ensure_len(input_len); + + self.gecko.mAnimationIterationCountCount = input_len as u32; + for (gecko, servo) in self.gecko.mAnimations.iter_mut().take(input_len as usize).zip(v) { + match servo { + AnimationIterationCount::Number(n) => gecko.mIterationCount = n, + AnimationIterationCount::Infinite => gecko.mIterationCount = f32::INFINITY, + } + } + } + + pub fn animation_iteration_count_at( + &self, + index: usize, + ) -> values::computed::AnimationIterationCount { + use crate::values::generics::box_::AnimationIterationCount; + + if self.gecko.mAnimations[index].mIterationCount.is_infinite() { + AnimationIterationCount::Infinite + } else { + AnimationIterationCount::Number(self.gecko.mAnimations[index].mIterationCount) + } + } + + ${impl_animation_count('iteration_count', 'IterationCount')} + ${impl_copy_animation_value('iteration_count', 'IterationCount')} + ${impl_animation_or_transition_timing_function('animation')} + + pub fn set_animation_timeline(&mut self, v: I) + where + I: IntoIterator, + I::IntoIter: ExactSizeIterator + { + let v = v.into_iter(); + debug_assert_ne!(v.len(), 0); + let input_len = v.len(); + self.gecko.mAnimations.ensure_len(input_len); + + self.gecko.mAnimationTimelineCount = input_len as u32; + for (gecko, servo) in self.gecko.mAnimations.iter_mut().take(input_len as usize).zip(v) { + gecko.mTimeline = servo; + } + } + pub fn animation_timeline_at(&self, index: usize) -> values::specified::box_::AnimationTimeline { + self.gecko.mAnimations[index].mTimeline.clone() + } + ${impl_animation_count('timeline', 'Timeline')} + ${impl_copy_animation_value('timeline', 'Timeline')} + <%self:impl_trait style_struct_name="XUL"> diff --git a/components/style/properties/helpers/animated_properties.mako.rs b/components/style/properties/helpers/animated_properties.mako.rs index 5ffea97ba50..b62ddb25ce4 100644 --- a/components/style/properties/helpers/animated_properties.mako.rs +++ b/components/style/properties/helpers/animated_properties.mako.rs @@ -799,7 +799,7 @@ impl<'a> TransitionPropertyIterator<'a> { pub fn from_style(style: &'a ComputedValues) -> Self { Self { style, - index_range: 0..style.get_box().transition_property_count(), + index_range: 0..style.get_ui().transition_property_count(), longhand_iterator: None, } } @@ -832,7 +832,7 @@ impl<'a> Iterator for TransitionPropertyIterator<'a> { } let index = self.index_range.next()?; - match self.style.get_box().transition_property_at(index) { + match self.style.get_ui().transition_property_at(index) { TransitionProperty::Longhand(longhand_id) => { return Some(TransitionPropertyIteration { longhand_id, diff --git a/components/style/properties/longhands/box.mako.rs b/components/style/properties/longhands/box.mako.rs index 1fccedcc652..6598885c3a1 100644 --- a/components/style/properties/longhands/box.mako.rs +++ b/components/style/properties/longhands/box.mako.rs @@ -3,7 +3,7 @@ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ <%namespace name="helpers" file="/helpers.mako.rs" /> -<% from data import ALL_AXES, DEFAULT_RULES_EXCEPT_KEYFRAME, Keyword, Method, to_rust_ident, to_camel_case%> +<% from data import ALL_AXES, Keyword, Method, to_rust_ident, to_camel_case%> <% data.new_style_struct("Box", inherited=False, @@ -150,193 +150,6 @@ ${helpers.predefined_type( animation_value_type="discrete", )} -<% transition_extra_prefixes = "moz:layout.css.prefixes.transitions webkit" %> - -${helpers.predefined_type( - "transition-duration", - "Time", - "computed::Time::zero()", - engines="gecko servo", - initial_specified_value="specified::Time::zero()", - parse_method="parse_non_negative", - vector=True, - need_index=True, - animation_value_type="none", - extra_prefixes=transition_extra_prefixes, - spec="https://drafts.csswg.org/css-transitions/#propdef-transition-duration", -)} - -${helpers.predefined_type( - "transition-timing-function", - "TimingFunction", - "computed::TimingFunction::ease()", - engines="gecko servo", - initial_specified_value="specified::TimingFunction::ease()", - vector=True, - need_index=True, - animation_value_type="none", - extra_prefixes=transition_extra_prefixes, - spec="https://drafts.csswg.org/css-transitions/#propdef-transition-timing-function", -)} - -${helpers.predefined_type( - "transition-property", - "TransitionProperty", - "computed::TransitionProperty::all()", - engines="gecko servo", - initial_specified_value="specified::TransitionProperty::all()", - vector=True, - allow_empty="NotInitial", - need_index=True, - animation_value_type="none", - extra_prefixes=transition_extra_prefixes, - spec="https://drafts.csswg.org/css-transitions/#propdef-transition-property", -)} - -${helpers.predefined_type( - "transition-delay", - "Time", - "computed::Time::zero()", - engines="gecko servo", - initial_specified_value="specified::Time::zero()", - vector=True, - need_index=True, - animation_value_type="none", - extra_prefixes=transition_extra_prefixes, - spec="https://drafts.csswg.org/css-transitions/#propdef-transition-delay", -)} - -<% animation_extra_prefixes = "moz:layout.css.prefixes.animations webkit" %> - -${helpers.predefined_type( - "animation-name", - "AnimationName", - "computed::AnimationName::none()", - engines="gecko servo", - initial_specified_value="specified::AnimationName::none()", - vector=True, - need_index=True, - animation_value_type="none", - extra_prefixes=animation_extra_prefixes, - rule_types_allowed=DEFAULT_RULES_EXCEPT_KEYFRAME, - spec="https://drafts.csswg.org/css-animations/#propdef-animation-name", -)} - -${helpers.predefined_type( - "animation-duration", - "Time", - "computed::Time::zero()", - engines="gecko servo", - initial_specified_value="specified::Time::zero()", - parse_method="parse_non_negative", - vector=True, - need_index=True, - animation_value_type="none", - extra_prefixes=animation_extra_prefixes, - spec="https://drafts.csswg.org/css-transitions/#propdef-transition-duration", -)} - -// animation-timing-function is the exception to the rule for allowed_in_keyframe_block: -// https://drafts.csswg.org/css-animations/#keyframes -${helpers.predefined_type( - "animation-timing-function", - "TimingFunction", - "computed::TimingFunction::ease()", - engines="gecko servo", - initial_specified_value="specified::TimingFunction::ease()", - vector=True, - need_index=True, - animation_value_type="none", - extra_prefixes=animation_extra_prefixes, - spec="https://drafts.csswg.org/css-transitions/#propdef-animation-timing-function", -)} - -${helpers.predefined_type( - "animation-iteration-count", - "AnimationIterationCount", - "computed::AnimationIterationCount::one()", - engines="gecko servo", - initial_specified_value="specified::AnimationIterationCount::one()", - vector=True, - need_index=True, - animation_value_type="none", - extra_prefixes=animation_extra_prefixes, - rule_types_allowed=DEFAULT_RULES_EXCEPT_KEYFRAME, - spec="https://drafts.csswg.org/css-animations/#propdef-animation-iteration-count", -)} - -<% animation_direction_custom_consts = { "alternate-reverse": "Alternate_reverse" } %> -${helpers.single_keyword( - "animation-direction", - "normal reverse alternate alternate-reverse", - engines="gecko servo", - need_index=True, - animation_value_type="none", - vector=True, - gecko_enum_prefix="PlaybackDirection", - custom_consts=animation_direction_custom_consts, - extra_prefixes=animation_extra_prefixes, - gecko_inexhaustive=True, - spec="https://drafts.csswg.org/css-animations/#propdef-animation-direction", - rule_types_allowed=DEFAULT_RULES_EXCEPT_KEYFRAME, -)} - -${helpers.single_keyword( - "animation-play-state", - "running paused", - engines="gecko servo", - need_index=True, - animation_value_type="none", - vector=True, - extra_prefixes=animation_extra_prefixes, - gecko_enum_prefix="StyleAnimationPlayState", - spec="https://drafts.csswg.org/css-animations/#propdef-animation-play-state", - rule_types_allowed=DEFAULT_RULES_EXCEPT_KEYFRAME, -)} - -${helpers.single_keyword( - "animation-fill-mode", - "none forwards backwards both", - engines="gecko servo", - need_index=True, - animation_value_type="none", - vector=True, - gecko_enum_prefix="FillMode", - extra_prefixes=animation_extra_prefixes, - gecko_inexhaustive=True, - spec="https://drafts.csswg.org/css-animations/#propdef-animation-fill-mode", - rule_types_allowed=DEFAULT_RULES_EXCEPT_KEYFRAME, -)} - -${helpers.predefined_type( - "animation-delay", - "Time", - "computed::Time::zero()", - engines="gecko servo", - initial_specified_value="specified::Time::zero()", - vector=True, - need_index=True, - animation_value_type="none", - extra_prefixes=animation_extra_prefixes, - spec="https://drafts.csswg.org/css-animations/#propdef-animation-delay", - rule_types_allowed=DEFAULT_RULES_EXCEPT_KEYFRAME, -)} - -${helpers.predefined_type( - "animation-timeline", - "AnimationTimeline", - "computed::AnimationTimeline::auto()", - engines="gecko servo", - servo_pref="layout.unimplemented", - initial_specified_value="specified::AnimationTimeline::auto()", - vector=True, - need_index=True, - animation_value_type="none", - gecko_pref="layout.css.scroll-linked-animations.enabled", - spec="https://drafts.csswg.org/css-animations-2/#propdef-animation-timeline", - rule_types_allowed=DEFAULT_RULES_EXCEPT_KEYFRAME, -)} - <% transform_extra_prefixes = "moz:layout.css.prefixes.transforms webkit" %> ${helpers.predefined_type( @@ -472,6 +285,15 @@ ${helpers.predefined_type( animation_value_type="discrete", )} +${helpers.predefined_type( + "scroll-snap-stop", + "ScrollSnapStop", + "computed::ScrollSnapStop::Normal", + engines="gecko", + spec="https://drafts.csswg.org/css-scroll-snap-1/#scroll-snap-stop", + animation_value_type="discrete", +)} + % for (axis, logical) in ALL_AXES: ${helpers.predefined_type( "overscroll-behavior-" + axis, @@ -623,6 +445,28 @@ ${helpers.predefined_type( animation_value_type="none", )} +${helpers.predefined_type( + "container-type", + "ContainerType", + "computed::ContainerType::NONE", + engines="gecko servo", + animation_value_type="none", + gecko_pref="layout.css.container-queries.enabled", + servo_pref="layout.container-queries.enabled", + spec="https://drafts.csswg.org/css-contain-3/#container-type", +)} + +${helpers.predefined_type( + "container-name", + "ContainerName", + "computed::ContainerName::none()", + engines="gecko servo", + animation_value_type="none", + gecko_pref="layout.css.container-queries.enabled", + servo_pref="layout.container-queries.enabled", + spec="https://drafts.csswg.org/css-contain-3/#container-name", +)} + ${helpers.predefined_type( "appearance", "Appearance", diff --git a/components/style/properties/longhands/margin.mako.rs b/components/style/properties/longhands/margin.mako.rs index b2d3ff3949a..251ee85f265 100644 --- a/components/style/properties/longhands/margin.mako.rs +++ b/components/style/properties/longhands/margin.mako.rs @@ -28,6 +28,16 @@ )} % endfor +${helpers.predefined_type( + "overflow-clip-margin", + "Length", + "computed::Length::zero()", + parse_method="parse_non_negative", + engines="gecko", + spec="https://drafts.csswg.org/css-overflow/#propdef-overflow-clip-margin", + animation_value_type="ComputedValue", +)} + % for side in ALL_SIDES: ${helpers.predefined_type( "scroll-margin-%s" % side[0], diff --git a/components/style/properties/longhands/svg.mako.rs b/components/style/properties/longhands/svg.mako.rs index 3d711462ff3..13704e5953b 100644 --- a/components/style/properties/longhands/svg.mako.rs +++ b/components/style/properties/longhands/svg.mako.rs @@ -20,7 +20,7 @@ ${helpers.single_keyword( ${helpers.predefined_type( "stop-color", "Color", - "RGBA::new(0, 0, 0, 255).into()", + "computed::Color::black()", engines="gecko", animation_value_type="AnimatedRGBA", spec="https://www.w3.org/TR/SVGTiny12/painting.html#StopColorProperty", @@ -40,7 +40,7 @@ ${helpers.predefined_type( ${helpers.predefined_type( "flood-color", "Color", - "RGBA::new(0, 0, 0, 255).into()", + "computed::Color::black()", engines="gecko", animation_value_type="AnimatedColor", spec="https://www.w3.org/TR/SVG/filters.html#FloodColorProperty", @@ -58,7 +58,7 @@ ${helpers.predefined_type( ${helpers.predefined_type( "lighting-color", "Color", - "RGBA::new(255, 255, 255, 255).into()", + "computed::Color::white()", engines="gecko", animation_value_type="AnimatedColor", spec="https://www.w3.org/TR/SVG/filters.html#LightingColorProperty", diff --git a/components/style/properties/longhands/ui.mako.rs b/components/style/properties/longhands/ui.mako.rs index 400eaedf577..8d4bbab038c 100644 --- a/components/style/properties/longhands/ui.mako.rs +++ b/components/style/properties/longhands/ui.mako.rs @@ -3,7 +3,7 @@ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ <%namespace name="helpers" file="/helpers.mako.rs" /> -<% from data import Method %> +<% from data import DEFAULT_RULES_EXCEPT_KEYFRAME, Method %> // CSS Basic User Interface Module Level 1 // https://drafts.csswg.org/css-ui-3/ @@ -51,12 +51,15 @@ ${helpers.single_keyword( spec="None (Nonstandard Firefox-only property)", )} +// TODO(emilio): Maybe make shadow behavior on macOS match Linux / Windows, and remove this +// property. ${helpers.single_keyword( "-moz-window-shadow", - "default none menu tooltip sheet cliprounded", + "default none", engines="gecko", gecko_ffi_name="mWindowShadow", gecko_enum_prefix="StyleWindowShadow", + gecko_inexhaustive=True, animation_value_type="discrete", enabled_in="chrome", spec="None (Nonstandard internal property)", @@ -95,6 +98,16 @@ ${helpers.predefined_type( enabled_in="chrome", )} +${helpers.predefined_type( + "-moz-window-input-region-margin", + "Length", + "computed::Length::zero()", + engines="gecko", + animation_value_type="ComputedValue", + spec="None (Nonstandard internal property)", + enabled_in="chrome", +)} + // TODO(emilio): Probably also should be hidden from content. ${helpers.predefined_type( "-moz-force-broken-image-icon", @@ -104,3 +117,190 @@ ${helpers.predefined_type( animation_value_type="discrete", spec="None (Nonstandard Firefox-only property)", )} + +<% transition_extra_prefixes = "moz:layout.css.prefixes.transitions webkit" %> + +${helpers.predefined_type( + "transition-duration", + "Time", + "computed::Time::zero()", + engines="gecko servo", + initial_specified_value="specified::Time::zero()", + parse_method="parse_non_negative", + vector=True, + need_index=True, + animation_value_type="none", + extra_prefixes=transition_extra_prefixes, + spec="https://drafts.csswg.org/css-transitions/#propdef-transition-duration", +)} + +${helpers.predefined_type( + "transition-timing-function", + "TimingFunction", + "computed::TimingFunction::ease()", + engines="gecko servo", + initial_specified_value="specified::TimingFunction::ease()", + vector=True, + need_index=True, + animation_value_type="none", + extra_prefixes=transition_extra_prefixes, + spec="https://drafts.csswg.org/css-transitions/#propdef-transition-timing-function", +)} + +${helpers.predefined_type( + "transition-property", + "TransitionProperty", + "computed::TransitionProperty::all()", + engines="gecko servo", + initial_specified_value="specified::TransitionProperty::all()", + vector=True, + allow_empty="NotInitial", + need_index=True, + animation_value_type="none", + extra_prefixes=transition_extra_prefixes, + spec="https://drafts.csswg.org/css-transitions/#propdef-transition-property", +)} + +${helpers.predefined_type( + "transition-delay", + "Time", + "computed::Time::zero()", + engines="gecko servo", + initial_specified_value="specified::Time::zero()", + vector=True, + need_index=True, + animation_value_type="none", + extra_prefixes=transition_extra_prefixes, + spec="https://drafts.csswg.org/css-transitions/#propdef-transition-delay", +)} + +<% animation_extra_prefixes = "moz:layout.css.prefixes.animations webkit" %> + +${helpers.predefined_type( + "animation-name", + "AnimationName", + "computed::AnimationName::none()", + engines="gecko servo", + initial_specified_value="specified::AnimationName::none()", + vector=True, + need_index=True, + animation_value_type="none", + extra_prefixes=animation_extra_prefixes, + rule_types_allowed=DEFAULT_RULES_EXCEPT_KEYFRAME, + spec="https://drafts.csswg.org/css-animations/#propdef-animation-name", +)} + +${helpers.predefined_type( + "animation-duration", + "Time", + "computed::Time::zero()", + engines="gecko servo", + initial_specified_value="specified::Time::zero()", + parse_method="parse_non_negative", + vector=True, + need_index=True, + animation_value_type="none", + extra_prefixes=animation_extra_prefixes, + spec="https://drafts.csswg.org/css-transitions/#propdef-transition-duration", +)} + +// animation-timing-function is the exception to the rule for allowed_in_keyframe_block: +// https://drafts.csswg.org/css-animations/#keyframes +${helpers.predefined_type( + "animation-timing-function", + "TimingFunction", + "computed::TimingFunction::ease()", + engines="gecko servo", + initial_specified_value="specified::TimingFunction::ease()", + vector=True, + need_index=True, + animation_value_type="none", + extra_prefixes=animation_extra_prefixes, + spec="https://drafts.csswg.org/css-transitions/#propdef-animation-timing-function", +)} + +${helpers.predefined_type( + "animation-iteration-count", + "AnimationIterationCount", + "computed::AnimationIterationCount::one()", + engines="gecko servo", + initial_specified_value="specified::AnimationIterationCount::one()", + vector=True, + need_index=True, + animation_value_type="none", + extra_prefixes=animation_extra_prefixes, + rule_types_allowed=DEFAULT_RULES_EXCEPT_KEYFRAME, + spec="https://drafts.csswg.org/css-animations/#propdef-animation-iteration-count", +)} + +<% animation_direction_custom_consts = { "alternate-reverse": "Alternate_reverse" } %> +${helpers.single_keyword( + "animation-direction", + "normal reverse alternate alternate-reverse", + engines="gecko servo", + need_index=True, + animation_value_type="none", + vector=True, + gecko_enum_prefix="PlaybackDirection", + custom_consts=animation_direction_custom_consts, + extra_prefixes=animation_extra_prefixes, + gecko_inexhaustive=True, + spec="https://drafts.csswg.org/css-animations/#propdef-animation-direction", + rule_types_allowed=DEFAULT_RULES_EXCEPT_KEYFRAME, +)} + +${helpers.single_keyword( + "animation-play-state", + "running paused", + engines="gecko servo", + need_index=True, + animation_value_type="none", + vector=True, + extra_prefixes=animation_extra_prefixes, + gecko_enum_prefix="StyleAnimationPlayState", + spec="https://drafts.csswg.org/css-animations/#propdef-animation-play-state", + rule_types_allowed=DEFAULT_RULES_EXCEPT_KEYFRAME, +)} + +${helpers.single_keyword( + "animation-fill-mode", + "none forwards backwards both", + engines="gecko servo", + need_index=True, + animation_value_type="none", + vector=True, + gecko_enum_prefix="FillMode", + extra_prefixes=animation_extra_prefixes, + gecko_inexhaustive=True, + spec="https://drafts.csswg.org/css-animations/#propdef-animation-fill-mode", + rule_types_allowed=DEFAULT_RULES_EXCEPT_KEYFRAME, +)} + +${helpers.predefined_type( + "animation-delay", + "Time", + "computed::Time::zero()", + engines="gecko servo", + initial_specified_value="specified::Time::zero()", + vector=True, + need_index=True, + animation_value_type="none", + extra_prefixes=animation_extra_prefixes, + spec="https://drafts.csswg.org/css-animations/#propdef-animation-delay", + rule_types_allowed=DEFAULT_RULES_EXCEPT_KEYFRAME, +)} + +${helpers.predefined_type( + "animation-timeline", + "AnimationTimeline", + "computed::AnimationTimeline::auto()", + engines="gecko servo", + servo_pref="layout.unimplemented", + initial_specified_value="specified::AnimationTimeline::auto()", + vector=True, + need_index=True, + animation_value_type="none", + gecko_pref="layout.css.scroll-linked-animations.enabled", + spec="https://drafts.csswg.org/css-animations-2/#propdef-animation-timeline", + rule_types_allowed=DEFAULT_RULES_EXCEPT_KEYFRAME, +)} diff --git a/components/style/properties/properties.mako.rs b/components/style/properties/properties.mako.rs index fd2bc2cb373..4327a739001 100644 --- a/components/style/properties/properties.mako.rs +++ b/components/style/properties/properties.mako.rs @@ -267,6 +267,9 @@ pub enum PropertyDeclaration { % endfor } +// There's one of these for each parsed declaration so it better be small. +size_of_test!(PropertyDeclaration, 32); + #[repr(C)] struct PropertyDeclarationVariantRepr { tag: u16, @@ -474,9 +477,10 @@ impl NonCustomPropertyId { self.0 } + /// Convert a `NonCustomPropertyId` into a `nsCSSPropertyID`. #[cfg(feature = "gecko")] #[inline] - fn to_nscsspropertyid(self) -> nsCSSPropertyID { + pub fn to_nscsspropertyid(self) -> nsCSSPropertyID { // unsafe: guaranteed by static_assert_nscsspropertyid above. unsafe { std::mem::transmute(self.0 as i32) } } @@ -892,6 +896,72 @@ impl<'a> Iterator for LonghandIdSetIterator<'a> { } } +<% + +CASCADE_GROUPS = { + # The writing-mode group has the most priority of all property groups, as + # sizes like font-size can depend on it. + "writing_mode": [ + "writing-mode", + "direction", + "text-orientation", + ], + # The fonts and colors group has the second priority, as all other lengths + # and colors depend on them. + # + # There are some interdependencies between these, but we fix them up in + # Cascade::fixup_font_stuff. + "fonts_and_color": [ + # Needed to properly compute the zoomed font-size. + # FIXME(emilio): This could probably just be a cascade flag + # like IN_SVG_SUBTREE or such, and we could nuke this property. + "-x-text-zoom", + # Needed to do font-size computation in a language-dependent way. + "-x-lang", + # Needed for ruby to respect language-dependent min-font-size + # preferences properly, see bug 1165538. + "-moz-min-font-size-ratio", + # font-size depends on math-depth's computed value. + "math-depth", + # Needed to compute the first available font, in order to + # compute font-relative units correctly. + "font-size", + "font-weight", + "font-stretch", + "font-style", + "font-family", + # color-scheme affects how system colors resolve. + "color-scheme", + ], +} +def in_late_group(p): + return p.name not in CASCADE_GROUPS["writing_mode"] and p.name not in CASCADE_GROUPS["fonts_and_color"] + +def is_visited_dependent(p): + return p.name in [ + "column-rule-color", + "text-emphasis-color", + "-webkit-text-fill-color", + "-webkit-text-stroke-color", + "text-decoration-color", + "fill", + "stroke", + "caret-color", + "background-color", + "border-top-color", + "border-right-color", + "border-bottom-color", + "border-left-color", + "border-block-start-color", + "border-inline-end-color", + "border-block-end-color", + "border-inline-start-color", + "outline-color", + "color", + ] + +%> + impl LonghandIdSet { #[inline] fn reset() -> &'static Self { @@ -920,7 +990,7 @@ impl LonghandIdSet { /// Returns the set of longhands that are ignored when document colors are /// disabled. #[inline] - pub fn ignored_when_colors_disabled() -> &'static Self { + fn ignored_when_colors_disabled() -> &'static Self { ${static_longhand_id_set( "IGNORED_WHEN_COLORS_DISABLED", lambda p: p.ignored_when_colors_disabled @@ -928,6 +998,48 @@ impl LonghandIdSet { &IGNORED_WHEN_COLORS_DISABLED } + /// Only a few properties are allowed to depend on the visited state of + /// links. When cascading visited styles, we can save time by only + /// processing these properties. + fn visited_dependent() -> &'static Self { + ${static_longhand_id_set( + "VISITED_DEPENDENT", + lambda p: is_visited_dependent(p) + )} + debug_assert!(Self::late_group().contains_all(&VISITED_DEPENDENT)); + &VISITED_DEPENDENT + } + + #[inline] + fn writing_mode_group() -> &'static Self { + ${static_longhand_id_set( + "WRITING_MODE_GROUP", + lambda p: p.name in CASCADE_GROUPS["writing_mode"] + )} + &WRITING_MODE_GROUP + } + + #[inline] + fn fonts_and_color_group() -> &'static Self { + ${static_longhand_id_set( + "FONTS_AND_COLOR_GROUP", + lambda p: p.name in CASCADE_GROUPS["fonts_and_color"] + )} + &FONTS_AND_COLOR_GROUP + } + + #[inline] + fn late_group_only_inherited() -> &'static Self { + ${static_longhand_id_set("LATE_GROUP_ONLY_INHERITED", lambda p: p.style_struct.inherited and in_late_group(p))} + &LATE_GROUP_ONLY_INHERITED + } + + #[inline] + fn late_group() -> &'static Self { + ${static_longhand_id_set("LATE_GROUP", lambda p: in_late_group(p))} + &LATE_GROUP + } + /// Returns the set of properties that are declared as having no effect on /// Gecko elements or their descendant scrollbar parts. #[cfg(debug_assertions)] @@ -1330,91 +1442,12 @@ impl LonghandId { PropertyFlags::from_bits_truncate(FLAGS[self as usize]) } - /// Only a few properties are allowed to depend on the visited state of - /// links. When cascading visited styles, we can save time by only - /// processing these properties. - fn is_visited_dependent(&self) -> bool { - matches!(*self, - % if engine == "gecko": - LonghandId::ColumnRuleColor | - LonghandId::TextEmphasisColor | - LonghandId::WebkitTextFillColor | - LonghandId::WebkitTextStrokeColor | - LonghandId::TextDecorationColor | - LonghandId::Fill | - LonghandId::Stroke | - LonghandId::CaretColor | - % endif - LonghandId::BackgroundColor | - LonghandId::BorderTopColor | - LonghandId::BorderRightColor | - LonghandId::BorderBottomColor | - LonghandId::BorderLeftColor | - LonghandId::OutlineColor | - LonghandId::Color - ) - } - /// Returns true if the property is one that is ignored when document /// colors are disabled. #[inline] fn ignored_when_document_colors_disabled(self) -> bool { LonghandIdSet::ignored_when_colors_disabled().contains(self) } - - /// The computed value of some properties depends on the (sometimes - /// computed) value of *other* properties. - /// - /// So we classify properties into "early" and "other", such that the only - /// dependencies can be from "other" to "early". - /// - /// Unfortunately, it’s not easy to check that this classification is - /// correct. - fn is_early_property(&self) -> bool { - matches!(*self, - % if engine == "gecko": - - // Needed to properly compute the writing mode, to resolve logical - // properties, and similar stuff. In this block instead of along - // `WritingMode` and `Direction` just for convenience, since it's - // Gecko-only (for now at least). - // - // see WritingMode::new. - LonghandId::TextOrientation | - - // Needed to properly compute the zoomed font-size. - // - // FIXME(emilio): This could probably just be a cascade flag like - // IN_SVG_SUBTREE or such, and we could nuke this property. - LonghandId::XTextZoom | - - // Needed to do font-size computation in a language-dependent way. - LonghandId::XLang | - // Needed for ruby to respect language-dependent min-font-size - // preferences properly, see bug 1165538. - LonghandId::MozMinFontSizeRatio | - - // font-size depends on math-depth's computed value. - LonghandId::MathDepth | - - // color-scheme affects how system colors resolve. - LonghandId::ColorScheme | - % endif - - // Needed to compute the first available font, in order to - // compute font-relative units correctly. - LonghandId::FontSize | - LonghandId::FontWeight | - LonghandId::FontStretch | - LonghandId::FontStyle | - LonghandId::FontFamily | - - // Needed to properly compute the writing mode, to resolve logical - // properties, and similar stuff. - LonghandId::WritingMode | - LonghandId::Direction - ) - } } /// An iterator over all the property ids that are enabled for a given @@ -2560,9 +2593,11 @@ impl PropertyDeclaration { } } -type SubpropertiesVec = ArrayVec; +const SUB_PROPERTIES_ARRAY_CAP: usize = + ${max(len(s.sub_properties) for s in data.shorthands_except_all()) \ + if data.shorthands_except_all() else 0}; + +type SubpropertiesVec = ArrayVec; /// A stack-allocated vector of `PropertyDeclaration` /// large enough to parse one CSS `key: value` declaration. @@ -2574,6 +2609,10 @@ pub struct SourcePropertyDeclaration { all_shorthand: AllShorthand, } +// This is huge, but we allocate it on the stack and then never move it, +// we only pass `&mut SourcePropertyDeclaration` references around. +size_of_test!(SourcePropertyDeclaration, 568); + impl SourcePropertyDeclaration { /// Create one. It’s big, try not to move it around. #[inline] @@ -2618,11 +2657,7 @@ impl SourcePropertyDeclaration { /// Return type of SourcePropertyDeclaration::drain pub struct SourcePropertyDeclarationDrain<'a> { - declarations: ArrayVecDrain< - 'a, PropertyDeclaration, - ${max(len(s.sub_properties) for s in data.shorthands_except_all()) \ - if data.shorthands_except_all() else 0} - >, + declarations: ArrayVecDrain<'a, PropertyDeclaration, SUB_PROPERTIES_ARRAY_CAP>, all_shorthand: AllShorthand, } @@ -2903,11 +2938,11 @@ pub mod style_structs { % endif % endfor - % if style_struct.name == "Box": + % if style_struct.name == "UI": /// Returns whether there is any animation specified with /// animation-name other than `none`. pub fn specifies_animations(&self) -> bool { - self.animation_name_iter().any(|name| name.0.is_some()) + self.animation_name_iter().any(|name| !name.is_none()) } /// Returns whether there are any transitions specified. @@ -3035,7 +3070,7 @@ impl ComputedValues { /// Returns whether this style's display value is equal to contents. pub fn is_display_contents(&self) -> bool { - self.get_box().clone_display().is_contents() + self.clone_display().is_contents() } /// Gets a reference to the rule node. Panic if no rule node exists. @@ -3184,7 +3219,7 @@ impl ComputedValues { /// style.resolve_color(style.get_border().clone_border_top_color()); #[inline] pub fn resolve_color(&self, color: computed::Color) -> RGBA { - color.to_rgba(self.get_inherited_text().clone_color()) + color.into_rgba(self.get_inherited_text().clone_color()) } /// Returns which longhand properties have different values in the two @@ -4188,7 +4223,7 @@ macro_rules! css_properties_accessors { /// Call the given macro with tokens like this for each longhand properties: /// /// ``` -/// { snake_case_ident, true } +/// { snake_case_ident } /// ``` /// /// … where the boolean indicates whether the property value type @@ -4198,12 +4233,39 @@ macro_rules! longhand_properties_idents { ($macro_name: ident) => { $macro_name! { % for property in data.longhands: - { ${property.ident}, ${"true" if property.boxed else "false"} } + { ${property.ident} } % endfor } } } +// Large pages generate tens of thousands of ComputedValues. +size_of_test!(ComputedValues, 192); +// FFI relies on this. +size_of_test!(Option>, 8); + +// There are two reasons for this test to fail: +// +// * Your changes made a specified value type for a given property go +// over the threshold. In that case, you should try to shrink it again +// or, if not possible, mark the property as boxed in the property +// definition. +// +// * Your changes made a specified value type smaller, so that it no +// longer needs to be boxed. In this case you just need to remove +// boxed=True from the property definition. Nice job! +#[cfg(target_pointer_width = "64")] +#[allow(dead_code)] // https://github.com/rust-lang/rust/issues/96952 +const BOX_THRESHOLD: usize = 24; +% for longhand in data.longhands: +#[cfg(target_pointer_width = "64")] +% if longhand.boxed: +const_assert!(std::mem::size_of::() > BOX_THRESHOLD); +% else: +const_assert!(std::mem::size_of::() <= BOX_THRESHOLD); +% endif +% endfor + % if engine == "servo": % for effect_name in ["repaint", "reflow_out_of_flow", "reflow", "rebuild_and_reflow_inline", "rebuild_and_reflow"]: macro_rules! restyle_damage_${effect_name} { diff --git a/components/style/properties/shorthands/box.mako.rs b/components/style/properties/shorthands/box.mako.rs index a0bc082caeb..a05f7fd461c 100644 --- a/components/style/properties/shorthands/box.mako.rs +++ b/components/style/properties/shorthands/box.mako.rs @@ -24,318 +24,6 @@ ${helpers.two_properties_shorthand( "(https://developer.mozilla.org/en-US/docs/Web/CSS/overflow-clip-box)", )} -macro_rules! try_parse_one { - ($context: expr, $input: expr, $var: ident, $prop_module: ident) => { - if $var.is_none() { - if let Ok(value) = $input.try_parse(|i| { - $prop_module::single_value::parse($context, i) - }) { - $var = Some(value); - continue; - } - } - }; -} - -<%helpers:shorthand name="transition" - engines="gecko servo" - extra_prefixes="moz:layout.css.prefixes.transitions webkit" - sub_properties="transition-property transition-duration - transition-timing-function - transition-delay" - spec="https://drafts.csswg.org/css-transitions/#propdef-transition"> - use crate::parser::Parse; - % for prop in "delay duration property timing_function".split(): - use crate::properties::longhands::transition_${prop}; - % endfor - use crate::values::specified::TransitionProperty; - - pub fn parse_value<'i, 't>( - context: &ParserContext, - input: &mut Parser<'i, 't>, - ) -> Result> { - struct SingleTransition { - % for prop in "duration timing_function delay".split(): - transition_${prop}: transition_${prop}::SingleSpecifiedValue, - % endfor - // Unlike other properties, transition-property uses an Option<> to - // represent 'none' as `None`. - transition_property: Option, - } - - fn parse_one_transition<'i, 't>( - context: &ParserContext, - input: &mut Parser<'i, 't>, - ) -> Result> { - % for prop in "property duration timing_function delay".split(): - let mut ${prop} = None; - % endfor - - let mut parsed = 0; - loop { - parsed += 1; - - try_parse_one!(context, input, duration, transition_duration); - try_parse_one!(context, input, timing_function, transition_timing_function); - try_parse_one!(context, input, delay, transition_delay); - // Must check 'transition-property' after 'transition-timing-function' since - // 'transition-property' accepts any keyword. - if property.is_none() { - if let Ok(value) = input.try_parse(|i| TransitionProperty::parse(context, i)) { - property = Some(Some(value)); - continue; - } - - if input.try_parse(|i| i.expect_ident_matching("none")).is_ok() { - // 'none' is not a valid value for , - // so it's not acceptable in the function above. - property = Some(None); - continue; - } - } - - parsed -= 1; - break - } - - if parsed != 0 { - Ok(SingleTransition { - % for prop in "duration timing_function delay".split(): - transition_${prop}: ${prop}.unwrap_or_else(transition_${prop}::single_value - ::get_initial_specified_value), - % endfor - transition_property: property.unwrap_or( - Some(transition_property::single_value::get_initial_specified_value())), - }) - } else { - Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError)) - } - } - - % for prop in "property duration timing_function delay".split(): - let mut ${prop}s = Vec::new(); - % endfor - - let results = input.parse_comma_separated(|i| parse_one_transition(context, i))?; - let multiple_items = results.len() >= 2; - for result in results { - if let Some(value) = result.transition_property { - propertys.push(value); - } else if multiple_items { - // If there is more than one item, and any of transitions has 'none', - // then it's invalid. Othersize, leave propertys to be empty (which - // means "transition-property: none"); - return Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError)); - } - - % for prop in "duration timing_function delay".split(): - ${prop}s.push(result.transition_${prop}); - % endfor - } - - Ok(expanded! { - % for prop in "property duration timing_function delay".split(): - transition_${prop}: transition_${prop}::SpecifiedValue(${prop}s.into()), - % endfor - }) - } - - impl<'a> ToCss for LonghandsToSerialize<'a> { - fn to_css(&self, dest: &mut CssWriter) -> fmt::Result where W: fmt::Write { - let property_len = self.transition_property.0.len(); - - // There are two cases that we can do shorthand serialization: - // * when all value lists have the same length, or - // * when transition-property is none, and other value lists have exactly one item. - if property_len == 0 { - % for name in "duration delay timing_function".split(): - if self.transition_${name}.0.len() != 1 { - return Ok(()); - } - % endfor - } else { - % for name in "duration delay timing_function".split(): - if self.transition_${name}.0.len() != property_len { - return Ok(()); - } - % endfor - } - - // Representative length. - let len = self.transition_duration.0.len(); - - for i in 0..len { - if i != 0 { - dest.write_str(", ")?; - } - if property_len == 0 { - dest.write_str("none")?; - } else { - self.transition_property.0[i].to_css(dest)?; - } - % for name in "duration timing_function delay".split(): - dest.write_str(" ")?; - self.transition_${name}.0[i].to_css(dest)?; - % endfor - } - Ok(()) - } - } - - -<%helpers:shorthand name="animation" - engines="gecko servo" - extra_prefixes="moz:layout.css.prefixes.animations webkit" - sub_properties="animation-name animation-duration - animation-timing-function animation-delay - animation-iteration-count animation-direction - animation-fill-mode animation-play-state animation-timeline" - rule_types_allowed="Style" - spec="https://drafts.csswg.org/css-animations/#propdef-animation"> - <% - props = "name timeline duration timing_function delay iteration_count \ - direction fill_mode play_state".split() - %> - % for prop in props: - use crate::properties::longhands::animation_${prop}; - % endfor - - pub fn parse_value<'i, 't>( - context: &ParserContext, - input: &mut Parser<'i, 't>, - ) -> Result> { - struct SingleAnimation { - % for prop in props: - animation_${prop}: animation_${prop}::SingleSpecifiedValue, - % endfor - } - - fn scroll_linked_animations_enabled() -> bool { - #[cfg(feature = "gecko")] - return static_prefs::pref!("layout.css.scroll-linked-animations.enabled"); - #[cfg(feature = "servo")] - return false; - } - - fn parse_one_animation<'i, 't>( - context: &ParserContext, - input: &mut Parser<'i, 't>, - ) -> Result> { - % for prop in props: - let mut ${prop} = None; - % endfor - - let mut parsed = 0; - // NB: Name must be the last one here so that keywords valid for other - // longhands are not interpreted as names. - // - // Also, duration must be before delay, see - // https://drafts.csswg.org/css-animations/#typedef-single-animation - loop { - parsed += 1; - try_parse_one!(context, input, duration, animation_duration); - try_parse_one!(context, input, timing_function, animation_timing_function); - try_parse_one!(context, input, delay, animation_delay); - try_parse_one!(context, input, iteration_count, animation_iteration_count); - try_parse_one!(context, input, direction, animation_direction); - try_parse_one!(context, input, fill_mode, animation_fill_mode); - try_parse_one!(context, input, play_state, animation_play_state); - try_parse_one!(context, input, name, animation_name); - if scroll_linked_animations_enabled() { - try_parse_one!(context, input, timeline, animation_timeline); - } - - parsed -= 1; - break - } - - // If nothing is parsed, this is an invalid entry. - if parsed == 0 { - Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError)) - } else { - Ok(SingleAnimation { - % for prop in props: - animation_${prop}: ${prop}.unwrap_or_else(animation_${prop}::single_value - ::get_initial_specified_value), - % endfor - }) - } - } - - % for prop in props: - let mut ${prop}s = vec![]; - % endfor - - let results = input.parse_comma_separated(|i| parse_one_animation(context, i))?; - for result in results.into_iter() { - % for prop in props: - ${prop}s.push(result.animation_${prop}); - % endfor - } - - Ok(expanded! { - % for prop in props: - animation_${prop}: animation_${prop}::SpecifiedValue(${prop}s.into()), - % endfor - }) - } - - impl<'a> ToCss for LonghandsToSerialize<'a> { - fn to_css(&self, dest: &mut CssWriter) -> fmt::Result where W: fmt::Write { - let len = self.animation_name.0.len(); - // There should be at least one declared value - if len == 0 { - return Ok(()); - } - - // If any value list length is differs then we don't do a shorthand serialization - // either. - % for name in props[2:]: - if len != self.animation_${name}.0.len() { - return Ok(()) - } - % endfor - - // If the preference of animation-timeline is disabled, `self.animation_timeline` is - // None. - if self.animation_timeline.map_or(false, |v| len != v.0.len()) { - return Ok(()); - } - - for i in 0..len { - if i != 0 { - dest.write_str(", ")?; - } - - % for name in props[2:]: - self.animation_${name}.0[i].to_css(dest)?; - dest.write_str(" ")?; - % endfor - - self.animation_name.0[i].to_css(dest)?; - - // Based on the spec, the default values of other properties must be output in at - // least the cases necessary to distinguish an animation-name. The serialization - // order of animation-timeline is always later than animation-name, so it's fine - // to not serialize it if it is the default value. It's still possible to - // distinguish them (because we always serialize animation-name). - // https://drafts.csswg.org/css-animations-1/#animation - // https://drafts.csswg.org/css-animations-2/#typedef-single-animation - // - // Note: it's also fine to always serialize this. However, it seems Blink - // doesn't serialize default animation-timeline now, so we follow the same rule. - if let Some(ref timeline) = self.animation_timeline { - if !timeline.0[i].is_auto() { - dest.write_char(' ')?; - timeline.0[i].to_css(dest)?; - } - } - } - Ok(()) - } - } - - ${helpers.two_properties_shorthand( "overscroll-behavior", "overscroll-behavior-x", @@ -345,6 +33,45 @@ ${helpers.two_properties_shorthand( spec="https://wicg.github.io/overscroll-behavior/#overscroll-behavior-properties", )} +<%helpers:shorthand + engines="gecko" + name="container" + sub_properties="container-name container-type" + gecko_pref="layout.css.container-queries.enabled", + spec="https://drafts.csswg.org/css-contain-3/#container-shorthand" +> + pub fn parse_value<'i>( + context: &ParserContext, + input: &mut Parser<'i, '_>, + ) -> Result> { + use crate::parser::Parse; + use crate::values::specified::box_::{ContainerName, ContainerType}; + // See https://github.com/w3c/csswg-drafts/issues/7180 for why we don't + // match the spec. + let container_name = ContainerName::parse(context, input)?; + let container_type = if input.try_parse(|input| input.expect_delim('/')).is_ok() { + ContainerType::parse(context, input)? + } else { + ContainerType::NONE + }; + Ok(expanded! { + container_name: container_name, + container_type: container_type, + }) + } + + impl<'a> ToCss for LonghandsToSerialize<'a> { + fn to_css(&self, dest: &mut CssWriter) -> fmt::Result where W: fmt::Write { + self.container_name.to_css(dest)?; + if !self.container_type.is_empty() { + dest.write_str(" / ")?; + self.container_type.to_css(dest)?; + } + Ok(()) + } + } + + <%helpers:shorthand engines="gecko" name="page-break-before" diff --git a/components/style/properties/shorthands/ui.mako.rs b/components/style/properties/shorthands/ui.mako.rs new file mode 100644 index 00000000000..e2eb8d979a0 --- /dev/null +++ b/components/style/properties/shorthands/ui.mako.rs @@ -0,0 +1,317 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ + +<%namespace name="helpers" file="/helpers.mako.rs" /> + +macro_rules! try_parse_one { + ($context: expr, $input: expr, $var: ident, $prop_module: ident) => { + if $var.is_none() { + if let Ok(value) = $input.try_parse(|i| { + $prop_module::single_value::parse($context, i) + }) { + $var = Some(value); + continue; + } + } + }; +} + +<%helpers:shorthand name="transition" + engines="gecko servo" + extra_prefixes="moz:layout.css.prefixes.transitions webkit" + sub_properties="transition-property transition-duration + transition-timing-function + transition-delay" + spec="https://drafts.csswg.org/css-transitions/#propdef-transition"> + use crate::parser::Parse; + % for prop in "delay duration property timing_function".split(): + use crate::properties::longhands::transition_${prop}; + % endfor + use crate::values::specified::TransitionProperty; + + pub fn parse_value<'i, 't>( + context: &ParserContext, + input: &mut Parser<'i, 't>, + ) -> Result> { + struct SingleTransition { + % for prop in "duration timing_function delay".split(): + transition_${prop}: transition_${prop}::SingleSpecifiedValue, + % endfor + // Unlike other properties, transition-property uses an Option<> to + // represent 'none' as `None`. + transition_property: Option, + } + + fn parse_one_transition<'i, 't>( + context: &ParserContext, + input: &mut Parser<'i, 't>, + ) -> Result> { + % for prop in "property duration timing_function delay".split(): + let mut ${prop} = None; + % endfor + + let mut parsed = 0; + loop { + parsed += 1; + + try_parse_one!(context, input, duration, transition_duration); + try_parse_one!(context, input, timing_function, transition_timing_function); + try_parse_one!(context, input, delay, transition_delay); + // Must check 'transition-property' after 'transition-timing-function' since + // 'transition-property' accepts any keyword. + if property.is_none() { + if let Ok(value) = input.try_parse(|i| TransitionProperty::parse(context, i)) { + property = Some(Some(value)); + continue; + } + + if input.try_parse(|i| i.expect_ident_matching("none")).is_ok() { + // 'none' is not a valid value for , + // so it's not acceptable in the function above. + property = Some(None); + continue; + } + } + + parsed -= 1; + break + } + + if parsed != 0 { + Ok(SingleTransition { + % for prop in "duration timing_function delay".split(): + transition_${prop}: ${prop}.unwrap_or_else(transition_${prop}::single_value + ::get_initial_specified_value), + % endfor + transition_property: property.unwrap_or( + Some(transition_property::single_value::get_initial_specified_value())), + }) + } else { + Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError)) + } + } + + % for prop in "property duration timing_function delay".split(): + let mut ${prop}s = Vec::new(); + % endfor + + let results = input.parse_comma_separated(|i| parse_one_transition(context, i))?; + let multiple_items = results.len() >= 2; + for result in results { + if let Some(value) = result.transition_property { + propertys.push(value); + } else if multiple_items { + // If there is more than one item, and any of transitions has 'none', + // then it's invalid. Othersize, leave propertys to be empty (which + // means "transition-property: none"); + return Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError)); + } + + % for prop in "duration timing_function delay".split(): + ${prop}s.push(result.transition_${prop}); + % endfor + } + + Ok(expanded! { + % for prop in "property duration timing_function delay".split(): + transition_${prop}: transition_${prop}::SpecifiedValue(${prop}s.into()), + % endfor + }) + } + + impl<'a> ToCss for LonghandsToSerialize<'a> { + fn to_css(&self, dest: &mut CssWriter) -> fmt::Result where W: fmt::Write { + let property_len = self.transition_property.0.len(); + + // There are two cases that we can do shorthand serialization: + // * when all value lists have the same length, or + // * when transition-property is none, and other value lists have exactly one item. + if property_len == 0 { + % for name in "duration delay timing_function".split(): + if self.transition_${name}.0.len() != 1 { + return Ok(()); + } + % endfor + } else { + % for name in "duration delay timing_function".split(): + if self.transition_${name}.0.len() != property_len { + return Ok(()); + } + % endfor + } + + // Representative length. + let len = self.transition_duration.0.len(); + + for i in 0..len { + if i != 0 { + dest.write_str(", ")?; + } + if property_len == 0 { + dest.write_str("none")?; + } else { + self.transition_property.0[i].to_css(dest)?; + } + % for name in "duration timing_function delay".split(): + dest.write_str(" ")?; + self.transition_${name}.0[i].to_css(dest)?; + % endfor + } + Ok(()) + } + } + + +<%helpers:shorthand name="animation" + engines="gecko servo" + extra_prefixes="moz:layout.css.prefixes.animations webkit" + sub_properties="animation-name animation-duration + animation-timing-function animation-delay + animation-iteration-count animation-direction + animation-fill-mode animation-play-state animation-timeline" + rule_types_allowed="Style" + spec="https://drafts.csswg.org/css-animations/#propdef-animation"> + <% + props = "name timeline duration timing_function delay iteration_count \ + direction fill_mode play_state".split() + %> + % for prop in props: + use crate::properties::longhands::animation_${prop}; + % endfor + + pub fn parse_value<'i, 't>( + context: &ParserContext, + input: &mut Parser<'i, 't>, + ) -> Result> { + struct SingleAnimation { + % for prop in props: + animation_${prop}: animation_${prop}::SingleSpecifiedValue, + % endfor + } + + fn scroll_linked_animations_enabled() -> bool { + #[cfg(feature = "gecko")] + return static_prefs::pref!("layout.css.scroll-linked-animations.enabled"); + #[cfg(feature = "servo")] + return false; + } + + fn parse_one_animation<'i, 't>( + context: &ParserContext, + input: &mut Parser<'i, 't>, + ) -> Result> { + % for prop in props: + let mut ${prop} = None; + % endfor + + let mut parsed = 0; + // NB: Name must be the last one here so that keywords valid for other + // longhands are not interpreted as names. + // + // Also, duration must be before delay, see + // https://drafts.csswg.org/css-animations/#typedef-single-animation + loop { + parsed += 1; + try_parse_one!(context, input, duration, animation_duration); + try_parse_one!(context, input, timing_function, animation_timing_function); + try_parse_one!(context, input, delay, animation_delay); + try_parse_one!(context, input, iteration_count, animation_iteration_count); + try_parse_one!(context, input, direction, animation_direction); + try_parse_one!(context, input, fill_mode, animation_fill_mode); + try_parse_one!(context, input, play_state, animation_play_state); + try_parse_one!(context, input, name, animation_name); + if scroll_linked_animations_enabled() { + try_parse_one!(context, input, timeline, animation_timeline); + } + + parsed -= 1; + break + } + + // If nothing is parsed, this is an invalid entry. + if parsed == 0 { + Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError)) + } else { + Ok(SingleAnimation { + % for prop in props: + animation_${prop}: ${prop}.unwrap_or_else(animation_${prop}::single_value + ::get_initial_specified_value), + % endfor + }) + } + } + + % for prop in props: + let mut ${prop}s = vec![]; + % endfor + + let results = input.parse_comma_separated(|i| parse_one_animation(context, i))?; + for result in results.into_iter() { + % for prop in props: + ${prop}s.push(result.animation_${prop}); + % endfor + } + + Ok(expanded! { + % for prop in props: + animation_${prop}: animation_${prop}::SpecifiedValue(${prop}s.into()), + % endfor + }) + } + + impl<'a> ToCss for LonghandsToSerialize<'a> { + fn to_css(&self, dest: &mut CssWriter) -> fmt::Result where W: fmt::Write { + let len = self.animation_name.0.len(); + // There should be at least one declared value + if len == 0 { + return Ok(()); + } + + // If any value list length is differs then we don't do a shorthand serialization + // either. + % for name in props[2:]: + if len != self.animation_${name}.0.len() { + return Ok(()) + } + % endfor + + // If the preference of animation-timeline is disabled, `self.animation_timeline` is + // None. + if self.animation_timeline.map_or(false, |v| len != v.0.len()) { + return Ok(()); + } + + for i in 0..len { + if i != 0 { + dest.write_str(", ")?; + } + + % for name in props[2:]: + self.animation_${name}.0[i].to_css(dest)?; + dest.write_str(" ")?; + % endfor + + self.animation_name.0[i].to_css(dest)?; + + // Based on the spec, the default values of other properties must be output in at + // least the cases necessary to distinguish an animation-name. The serialization + // order of animation-timeline is always later than animation-name, so it's fine + // to not serialize it if it is the default value. It's still possible to + // distinguish them (because we always serialize animation-name). + // https://drafts.csswg.org/css-animations-1/#animation + // https://drafts.csswg.org/css-animations-2/#typedef-single-animation + // + // Note: it's also fine to always serialize this. However, it seems Blink + // doesn't serialize default animation-timeline now, so we follow the same rule. + if let Some(ref timeline) = self.animation_timeline { + if !timeline.0[i].is_auto() { + dest.write_char(' ')?; + timeline.0[i].to_css(dest)?; + } + } + } + Ok(()) + } + } + diff --git a/components/style/media_queries/media_condition.rs b/components/style/queries/condition.rs similarity index 57% rename from components/style/media_queries/media_condition.rs rename to components/style/queries/condition.rs index f735704556e..da8ad02d595 100644 --- a/components/style/media_queries/media_condition.rs +++ b/components/style/queries/condition.rs @@ -2,13 +2,14 @@ * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ -//! A media query condition: +//! A query condition: //! //! https://drafts.csswg.org/mediaqueries-4/#typedef-media-condition +//! https://drafts.csswg.org/css-contain-3/#typedef-container-condition -use super::{Device, MediaFeatureExpression}; -use crate::context::QuirksMode; +use super::{QueryFeatureExpression, FeatureType, FeatureFlags}; use crate::parser::ParserContext; +use crate::values::computed; use cssparser::{Parser, Token}; use std::fmt::{self, Write}; use style_traits::{CssWriter, ParseError, StyleParseErrorKind, ToCss}; @@ -28,38 +29,38 @@ enum AllowOr { No, } -/// Represents a media condition. +/// Represents a condition. #[derive(Clone, Debug, MallocSizeOf, PartialEq, ToShmem)] -pub enum MediaCondition { - /// A simple media feature expression, implicitly parenthesized. - Feature(MediaFeatureExpression), +pub enum QueryCondition { + /// A simple feature expression, implicitly parenthesized. + Feature(QueryFeatureExpression), /// A negation of a condition. - Not(Box), + Not(Box), /// A set of joint operations. - Operation(Box<[MediaCondition]>, Operator), + Operation(Box<[QueryCondition]>, Operator), /// A condition wrapped in parenthesis. - InParens(Box), + InParens(Box), } -impl ToCss for MediaCondition { +impl ToCss for QueryCondition { fn to_css(&self, dest: &mut CssWriter) -> fmt::Result where W: fmt::Write, { match *self { - // NOTE(emilio): MediaFeatureExpression already includes the + // NOTE(emilio): QueryFeatureExpression already includes the // parenthesis. - MediaCondition::Feature(ref f) => f.to_css(dest), - MediaCondition::Not(ref c) => { + QueryCondition::Feature(ref f) => f.to_css(dest), + QueryCondition::Not(ref c) => { dest.write_str("not ")?; c.to_css(dest) }, - MediaCondition::InParens(ref c) => { + QueryCondition::InParens(ref c) => { dest.write_char('(')?; c.to_css(dest)?; dest.write_char(')') }, - MediaCondition::Operation(ref list, op) => { + QueryCondition::Operation(ref list, op) => { let mut iter = list.iter(); iter.next().unwrap().to_css(dest)?; for item in iter { @@ -74,28 +75,60 @@ impl ToCss for MediaCondition { } } -impl MediaCondition { - /// Parse a single media condition. +impl QueryCondition { + /// Parse a single condition. pub fn parse<'i, 't>( context: &ParserContext, input: &mut Parser<'i, 't>, + feature_type: FeatureType, ) -> Result> { - Self::parse_internal(context, input, AllowOr::Yes) + Self::parse_internal(context, input, feature_type, AllowOr::Yes) } - /// Parse a single media condition, disallowing `or` expressions. + fn visit(&self, visitor: &mut F) + where + F: FnMut(&Self), + { + visitor(self); + match *self { + Self::Feature(..) => {}, + Self::Not(ref cond) => cond.visit(visitor), + Self::Operation(ref conds, _op) => { + for cond in conds.iter() { + cond.visit(visitor); + } + }, + Self::InParens(ref cond) => cond.visit(visitor), + } + } + + /// Returns the union of all flags in the expression. This is useful for + /// container queries. + pub fn cumulative_flags(&self) -> FeatureFlags { + let mut result = FeatureFlags::empty(); + self.visit(&mut |condition| { + if let Self::Feature(ref f) = condition { + result.insert(f.feature_flags()) + } + }); + result + } + + /// Parse a single condition, disallowing `or` expressions. /// - /// To be used from the legacy media query syntax. + /// To be used from the legacy query syntax. pub fn parse_disallow_or<'i, 't>( context: &ParserContext, input: &mut Parser<'i, 't>, + feature_type: FeatureType, ) -> Result> { - Self::parse_internal(context, input, AllowOr::No) + Self::parse_internal(context, input, feature_type, AllowOr::No) } fn parse_internal<'i, 't>( context: &ParserContext, input: &mut Parser<'i, 't>, + feature_type: FeatureType, allow_or: AllowOr, ) -> Result> { let location = input.current_source_location(); @@ -108,12 +141,12 @@ impl MediaCondition { }; if is_negation { - let inner_condition = Self::parse_in_parens(context, input)?; - return Ok(MediaCondition::Not(Box::new(inner_condition))); + let inner_condition = Self::parse_in_parens(context, input, feature_type)?; + return Ok(QueryCondition::Not(Box::new(inner_condition))); } // ParenthesisBlock. - let first_condition = Self::parse_paren_block(context, input)?; + let first_condition = Self::parse_paren_block(context, input, feature_type)?; let operator = match input.try_parse(Operator::parse) { Ok(op) => op, Err(..) => return Ok(first_condition), @@ -125,7 +158,7 @@ impl MediaCondition { let mut conditions = vec![]; conditions.push(first_condition); - conditions.push(Self::parse_in_parens(context, input)?); + conditions.push(Self::parse_in_parens(context, input, feature_type)?); let delim = match operator { Operator::And => "and", @@ -134,50 +167,52 @@ impl MediaCondition { loop { if input.try_parse(|i| i.expect_ident_matching(delim)).is_err() { - return Ok(MediaCondition::Operation( + return Ok(QueryCondition::Operation( conditions.into_boxed_slice(), operator, )); } - conditions.push(Self::parse_in_parens(context, input)?); + conditions.push(Self::parse_in_parens(context, input, feature_type)?); } } - /// Parse a media condition in parentheses. + /// Parse a condition in parentheses. pub fn parse_in_parens<'i, 't>( context: &ParserContext, input: &mut Parser<'i, 't>, + feature_type: FeatureType, ) -> Result> { input.expect_parenthesis_block()?; - Self::parse_paren_block(context, input) + Self::parse_paren_block(context, input, feature_type) } fn parse_paren_block<'i, 't>( context: &ParserContext, input: &mut Parser<'i, 't>, + feature_type: FeatureType, ) -> Result> { input.parse_nested_block(|input| { // Base case. - if let Ok(inner) = input.try_parse(|i| Self::parse(context, i)) { - return Ok(MediaCondition::InParens(Box::new(inner))); + if let Ok(inner) = input.try_parse(|i| Self::parse(context, i, feature_type)) { + return Ok(QueryCondition::InParens(Box::new(inner))); } - let expr = MediaFeatureExpression::parse_in_parenthesis_block(context, input)?; - Ok(MediaCondition::Feature(expr)) + let expr = QueryFeatureExpression::parse_in_parenthesis_block(context, input, feature_type)?; + Ok(QueryCondition::Feature(expr)) }) } /// Whether this condition matches the device and quirks mode. - pub fn matches(&self, device: &Device, quirks_mode: QuirksMode) -> bool { + pub fn matches(&self, context: &computed::Context) -> bool { match *self { - MediaCondition::Feature(ref f) => f.matches(device, quirks_mode), - MediaCondition::InParens(ref c) => c.matches(device, quirks_mode), - MediaCondition::Not(ref c) => !c.matches(device, quirks_mode), - MediaCondition::Operation(ref conditions, op) => { + QueryCondition::Feature(ref f) => f.matches(context), + QueryCondition::InParens(ref c) => c.matches(context), + QueryCondition::Not(ref c) => !c.matches(context), + QueryCondition::Operation(ref conditions, op) => { let mut iter = conditions.iter(); match op { - Operator::And => iter.all(|c| c.matches(device, quirks_mode)), - Operator::Or => iter.any(|c| c.matches(device, quirks_mode)), + Operator::And => iter.all(|c| c.matches(context)), + Operator::Or => iter.any(|c| c.matches(context)), } }, } diff --git a/components/style/media_queries/media_feature.rs b/components/style/queries/feature.rs similarity index 57% rename from components/style/media_queries/media_feature.rs rename to components/style/queries/feature.rs index e6edff3f68a..9efd64f8c23 100644 --- a/components/style/media_queries/media_feature.rs +++ b/components/style/queries/feature.rs @@ -2,13 +2,10 @@ * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ -//! Media features. +//! Query features. -use super::media_feature_expression::RangeOrOperator; -use super::Device; use crate::parser::ParserContext; -use crate::values::computed::Ratio; -use crate::values::computed::{CSSPixelLength, Resolution}; +use crate::values::computed::{self, CSSPixelLength, Resolution, Ratio}; use crate::Atom; use cssparser::Parser; use std::fmt; @@ -17,12 +14,7 @@ use style_traits::ParseError; /// A generic discriminant for an enum value. pub type KeywordDiscriminant = u8; -type MediaFeatureEvaluator = fn( - device: &Device, - // null == no value was given in the query. - value: Option, - range_or_operator: Option, -) -> bool; +type QueryFeatureGetter = fn(device: &computed::Context) -> T; /// Serializes a given discriminant. /// @@ -36,19 +28,19 @@ pub type KeywordParser = for<'a, 'i, 't> fn( input: &'a mut Parser<'i, 't>, ) -> Result>; -/// An evaluator for a given media feature. +/// An evaluator for a given feature. /// /// This determines the kind of values that get parsed, too. #[allow(missing_docs)] pub enum Evaluator { - Length(MediaFeatureEvaluator), - Integer(MediaFeatureEvaluator), - Float(MediaFeatureEvaluator), - BoolInteger(MediaFeatureEvaluator), + Length(QueryFeatureGetter), + Integer(QueryFeatureGetter), + Float(QueryFeatureGetter), + BoolInteger(QueryFeatureGetter), /// A non-negative number ratio, such as the one from device-pixel-ratio. - NumberRatio(MediaFeatureEvaluator), + NumberRatio(QueryFeatureGetter), /// A resolution. - Resolution(MediaFeatureEvaluator), + Resolution(QueryFeatureGetter), /// A keyword value. Enumerated { /// The parser to get a discriminant given a string. @@ -60,9 +52,8 @@ pub enum Evaluator { serializer: KeywordSerializer, /// The evaluator itself. This is guaranteed to be called with a /// keyword that `parser` has produced. - evaluator: MediaFeatureEvaluator, + evaluator: fn(&computed::Context, Option) -> bool, }, - Ident(MediaFeatureEvaluator), } /// A simple helper macro to create a keyword evaluator. @@ -76,14 +67,14 @@ macro_rules! keyword_evaluator { context: &$crate::parser::ParserContext, input: &mut $crate::cssparser::Parser<'i, 't>, ) -> Result< - $crate::media_queries::media_feature::KeywordDiscriminant, + $crate::queries::feature::KeywordDiscriminant, ::style_traits::ParseError<'i>, > { let kw = <$keyword_type as $crate::parser::Parse>::parse(context, input)?; - Ok(kw as $crate::media_queries::media_feature::KeywordDiscriminant) + Ok(kw as $crate::queries::feature::KeywordDiscriminant) } - fn __serialize(kw: $crate::media_queries::media_feature::KeywordDiscriminant) -> String { + fn __serialize(kw: $crate::queries::feature::KeywordDiscriminant) -> String { // This unwrap is ok because the only discriminants that get // back to us is the ones that `parse` produces. let value: $keyword_type = ::num_traits::cast::FromPrimitive::from_u8(kw).unwrap(); @@ -91,24 +82,17 @@ macro_rules! keyword_evaluator { } fn __evaluate( - device: &$crate::media_queries::Device, - value: Option<$crate::media_queries::media_feature::KeywordDiscriminant>, - range_or_operator: Option< - $crate::media_queries::media_feature_expression::RangeOrOperator, - >, + context: &$crate::values::computed::Context, + value: Option<$crate::queries::feature::KeywordDiscriminant>, ) -> bool { - debug_assert!( - range_or_operator.is_none(), - "Since when do keywords accept ranges?" - ); // This unwrap is ok because the only discriminants that get // back to us is the ones that `parse` produces. let value: Option<$keyword_type> = value.map(|kw| ::num_traits::cast::FromPrimitive::from_u8(kw).unwrap()); - $actual_evaluator(device, value) + $actual_evaluator(context, value) } - $crate::media_queries::media_feature::Evaluator::Enumerated { + $crate::queries::feature::Evaluator::Enumerated { parser: __parse, serializer: __serialize, evaluator: __evaluate, @@ -117,17 +101,46 @@ macro_rules! keyword_evaluator { } bitflags! { - /// Different requirements or toggles that change how a expression is - /// parsed. - pub struct ParsingRequirements: u8 { + /// Different flags or toggles that change how a expression is parsed or + /// evaluated. + #[derive(ToShmem)] + pub struct FeatureFlags : u8 { /// The feature should only be parsed in chrome and ua sheets. const CHROME_AND_UA_ONLY = 1 << 0; /// The feature requires a -webkit- prefix. const WEBKIT_PREFIX = 1 << 1; + /// The feature requires the inline-axis containment. + const CONTAINER_REQUIRES_INLINE_AXIS = 1 << 2; + /// The feature requires the block-axis containment. + const CONTAINER_REQUIRES_BLOCK_AXIS = 1 << 3; + /// The feature requires containment in the physical width axis. + const CONTAINER_REQUIRES_WIDTH_AXIS = 1 << 4; + /// The feature requires containment in the physical height axis. + const CONTAINER_REQUIRES_HEIGHT_AXIS = 1 << 5; } } -/// Whether a media feature allows ranges or not. +impl FeatureFlags { + /// Returns parsing requirement flags. + pub fn parsing_requirements(self) -> Self { + self.intersection(Self::CHROME_AND_UA_ONLY | Self::WEBKIT_PREFIX) + } + + /// Returns all the container axis flags. + pub fn all_container_axes() -> Self { + Self::CONTAINER_REQUIRES_INLINE_AXIS | + Self::CONTAINER_REQUIRES_BLOCK_AXIS | + Self::CONTAINER_REQUIRES_WIDTH_AXIS | + Self::CONTAINER_REQUIRES_HEIGHT_AXIS + } + + /// Returns our subset of container axis flags. + pub fn container_axes(self) -> Self { + self.intersection(Self::all_container_axes()) + } +} + +/// Whether a feature allows ranges or not. #[derive(Clone, Copy, Debug, Eq, PartialEq)] #[allow(missing_docs)] pub enum AllowsRanges { @@ -135,46 +148,45 @@ pub enum AllowsRanges { No, } -/// A description of a media feature. -pub struct MediaFeatureDescription { - /// The media feature name, in ascii lowercase. +/// A description of a feature. +pub struct QueryFeatureDescription { + /// The feature name, in ascii lowercase. pub name: Atom, /// Whether min- / max- prefixes are allowed or not. pub allows_ranges: AllowsRanges, /// The evaluator, which we also use to determine which kind of value to /// parse. pub evaluator: Evaluator, - /// Different requirements that need to hold for the feature to be - /// successfully parsed. - pub requirements: ParsingRequirements, + /// Different feature-specific flags. + pub flags: FeatureFlags, } -impl MediaFeatureDescription { - /// Whether this media feature allows ranges. +impl QueryFeatureDescription { + /// Whether this feature allows ranges. #[inline] pub fn allows_ranges(&self) -> bool { self.allows_ranges == AllowsRanges::Yes } } -/// A simple helper to construct a `MediaFeatureDescription`. +/// A simple helper to construct a `QueryFeatureDescription`. macro_rules! feature { - ($name:expr, $allows_ranges:expr, $evaluator:expr, $reqs:expr,) => { - $crate::media_queries::media_feature::MediaFeatureDescription { + ($name:expr, $allows_ranges:expr, $evaluator:expr, $flags:expr,) => { + $crate::queries::feature::QueryFeatureDescription { name: $name, allows_ranges: $allows_ranges, evaluator: $evaluator, - requirements: $reqs, + flags: $flags, } }; } -impl fmt::Debug for MediaFeatureDescription { +impl fmt::Debug for QueryFeatureDescription { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - f.debug_struct("MediaFeatureExpression") + f.debug_struct("QueryFeatureDescription") .field("name", &self.name) .field("allows_ranges", &self.allows_ranges) - .field("requirements", &self.requirements) + .field("flags", &self.flags) .finish() } } diff --git a/components/style/queries/feature_expression.rs b/components/style/queries/feature_expression.rs new file mode 100644 index 00000000000..aa2e4a04582 --- /dev/null +++ b/components/style/queries/feature_expression.rs @@ -0,0 +1,711 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ + +//! Parsing for query feature expressions, like `(foo: bar)` or +//! `(width >= 400px)`. + +use super::feature::{Evaluator, QueryFeatureDescription}; +use super::feature::{KeywordDiscriminant, FeatureFlags}; +use crate::parser::{Parse, ParserContext}; +use crate::str::{starts_with_ignore_ascii_case, string_as_ascii_lowercase}; +use crate::values::computed::{self, Ratio, ToComputedValue}; +use crate::values::specified::{Integer, Length, Number, Resolution}; +use crate::values::CSSFloat; +use crate::{Atom, Zero}; +use cssparser::{Parser, Token}; +use std::cmp::{Ordering, PartialOrd}; +use std::fmt::{self, Write}; +use style_traits::{CssWriter, ParseError, StyleParseErrorKind, ToCss}; + +/// Whether we're parsing a media or container query feature. +#[derive(Clone, Copy, Debug, Eq, MallocSizeOf, PartialEq, ToShmem)] +pub enum FeatureType { + /// We're parsing a media feature. + Media, + /// We're parsing a container feature. + Container, +} + +impl FeatureType { + fn features(&self) -> &'static [QueryFeatureDescription] { + #[cfg(feature = "gecko")] + let media_features = &crate::gecko::media_features::MEDIA_FEATURES; + #[cfg(feature = "servo")] + let media_features = &*crate::servo::media_queries::MEDIA_FEATURES; + + use crate::stylesheets::container_rule::CONTAINER_FEATURES; + + match *self { + FeatureType::Media => media_features, + FeatureType::Container => &CONTAINER_FEATURES, + } + } + + fn find_feature(&self, name: &Atom) -> Option<(usize, &'static QueryFeatureDescription)> { + self.features().iter().enumerate().find(|(_, f)| f.name == *name) + } +} + +/// The kind of matching that should be performed on a feature value. +#[derive(Clone, Copy, Debug, Eq, MallocSizeOf, PartialEq, ToShmem)] +enum LegacyRange { + /// At least the specified value. + Min, + /// At most the specified value. + Max, +} + +/// The operator that was specified in this feature. +#[derive(Clone, Copy, Debug, Eq, MallocSizeOf, PartialEq, ToShmem)] +enum Operator { + /// = + Equal, + /// > + GreaterThan, + /// >= + GreaterThanEqual, + /// < + LessThan, + /// <= + LessThanEqual, +} + +impl ToCss for Operator { + fn to_css(&self, dest: &mut CssWriter) -> fmt::Result + where + W: fmt::Write, + { + dest.write_str(match *self { + Self::Equal => "=", + Self::LessThan => "<", + Self::LessThanEqual => "<=", + Self::GreaterThan => ">", + Self::GreaterThanEqual => ">=", + }) + } +} + +impl Operator { + fn is_compatible_with(self, right_op: Self) -> bool { + // Some operators are not compatible with each other in multi-range + // context. + match self { + Self::Equal => false, + Self::GreaterThan | Self::GreaterThanEqual => matches!(right_op, Self::GreaterThan | Self::GreaterThanEqual), + Self::LessThan | Self::LessThanEqual => matches!(right_op, Self::LessThan | Self::LessThanEqual), + } + } + + fn evaluate(&self, cmp: Ordering) -> bool { + match *self { + Self::Equal => cmp == Ordering::Equal, + Self::GreaterThan => cmp == Ordering::Greater, + Self::GreaterThanEqual => cmp == Ordering::Equal || cmp == Ordering::Greater, + Self::LessThan => cmp == Ordering::Less, + Self::LessThanEqual => cmp == Ordering::Equal || cmp == Ordering::Less, + } + } + + fn parse<'i>(input: &mut Parser<'i, '_>) -> Result> { + let location = input.current_source_location(); + let operator = match *input.next()? { + Token::Delim('=') => return Ok(Operator::Equal), + Token::Delim('>') => Operator::GreaterThan, + Token::Delim('<') => Operator::LessThan, + ref t => return Err(location.new_unexpected_token_error(t.clone())), + }; + + // https://drafts.csswg.org/mediaqueries-4/#mq-syntax: + // + // No whitespace is allowed between the “<” or “>” + // s and the following “=” , if it’s + // present. + // + // TODO(emilio): Maybe we should ignore comments as well? + // https://github.com/w3c/csswg-drafts/issues/6248 + let parsed_equal = input + .try_parse(|i| { + let t = i.next_including_whitespace().map_err(|_| ())?; + if !matches!(t, Token::Delim('=')) { + return Err(()); + } + Ok(()) + }) + .is_ok(); + + if !parsed_equal { + return Ok(operator); + } + + Ok(match operator { + Operator::GreaterThan => Operator::GreaterThanEqual, + Operator::LessThan => Operator::LessThanEqual, + _ => unreachable!(), + }) + } +} + +#[derive(Clone, Debug, MallocSizeOf, ToShmem, PartialEq)] +enum QueryFeatureExpressionKind { + /// Just the media feature name. + Empty, + + /// A single value. + Single(QueryExpressionValue), + + /// Legacy range syntax (min-*: value) or so. + LegacyRange(LegacyRange, QueryExpressionValue), + + /// Modern range context syntax: + /// https://drafts.csswg.org/mediaqueries-5/#mq-range-context + Range { + left: Option<(Operator, QueryExpressionValue)>, + right: Option<(Operator, QueryExpressionValue)>, + }, +} + +impl QueryFeatureExpressionKind { + /// Evaluate a given range given an optional query value and a value from + /// the browser. + fn evaluate( + &self, + context_value: T, + mut compute: impl FnMut(&QueryExpressionValue) -> T, + ) -> bool + where + T: PartialOrd + Zero, + { + match *self { + Self::Empty => return !context_value.is_zero(), + Self::Single(ref value) => { + let value = compute(value); + let cmp = match context_value.partial_cmp(&value) { + Some(c) => c, + None => return false, + }; + cmp == Ordering::Equal + }, + Self::LegacyRange(ref range, ref value) => { + let value = compute(value); + let cmp = match context_value.partial_cmp(&value) { + Some(c) => c, + None => return false, + }; + cmp == Ordering::Equal || + match range { + LegacyRange::Min => cmp == Ordering::Greater, + LegacyRange::Max => cmp == Ordering::Less, + } + }, + Self::Range { + ref left, + ref right, + } => { + debug_assert!(left.is_some() || right.is_some()); + if let Some((ref op, ref value)) = left { + let value = compute(value); + let cmp = match value.partial_cmp(&context_value) { + Some(c) => c, + None => return false, + }; + if !op.evaluate(cmp) { + return false; + } + } + if let Some((ref op, ref value)) = right { + let value = compute(value); + let cmp = match context_value.partial_cmp(&value) { + Some(c) => c, + None => return false, + }; + if !op.evaluate(cmp) { + return false; + } + } + true + }, + } + } + + /// Non-ranged features only need to compare to one value at most. + fn non_ranged_value(&self) -> Option<&QueryExpressionValue> { + match *self { + Self::Empty => None, + Self::Single(ref v) => Some(v), + Self::LegacyRange(..) | Self::Range { .. } => { + debug_assert!(false, "Unexpected ranged value in non-ranged feature!"); + None + }, + } + } +} + +/// A feature expression contains a reference to the feature, the value the +/// query contained, and the range to evaluate. +#[derive(Clone, Debug, MallocSizeOf, ToShmem, PartialEq)] +pub struct QueryFeatureExpression { + feature_type: FeatureType, + feature_index: usize, + kind: QueryFeatureExpressionKind, +} + +impl ToCss for QueryFeatureExpression { + fn to_css(&self, dest: &mut CssWriter) -> fmt::Result + where + W: fmt::Write, + { + dest.write_str("(")?; + + match self.kind { + QueryFeatureExpressionKind::Empty => self.write_name(dest)?, + QueryFeatureExpressionKind::Single(ref v) | + QueryFeatureExpressionKind::LegacyRange(_, ref v) => { + self.write_name(dest)?; + dest.write_str(": ")?; + v.to_css(dest, self)?; + }, + QueryFeatureExpressionKind::Range { + ref left, + ref right, + } => { + if let Some((ref op, ref val)) = left { + val.to_css(dest, self)?; + dest.write_char(' ')?; + op.to_css(dest)?; + dest.write_char(' ')?; + } + self.write_name(dest)?; + if let Some((ref op, ref val)) = right { + dest.write_char(' ')?; + op.to_css(dest)?; + dest.write_char(' ')?; + val.to_css(dest, self)?; + } + }, + } + dest.write_char(')') + } +} + +fn consume_operation_or_colon<'i>( + input: &mut Parser<'i, '_>, +) -> Result, ParseError<'i>> { + if input.try_parse(|input| input.expect_colon()).is_ok() { + return Ok(None); + } + Operator::parse(input).map(|op| Some(op)) +} + +#[allow(unused_variables)] +fn disabled_by_pref(feature: &Atom, context: &ParserContext) -> bool { + #[cfg(feature = "gecko")] + { + if *feature == atom!("forced-colors") { + // forced-colors is always enabled in the ua and chrome. On + // the web it is hidden behind a preference, which is defaulted + // to 'true' as of bug 1659511. + return !context.in_ua_or_chrome_sheet() && + !static_prefs::pref!("layout.css.forced-colors.enabled"); + } + // prefers-contrast is always enabled in the ua and chrome. On + // the web it is hidden behind a preference. + if *feature == atom!("prefers-contrast") { + return !context.in_ua_or_chrome_sheet() && + !static_prefs::pref!("layout.css.prefers-contrast.enabled"); + } + } + false +} + +impl QueryFeatureExpression { + fn new( + feature_type: FeatureType, + feature_index: usize, + kind: QueryFeatureExpressionKind, + ) -> Self { + debug_assert!(feature_index < feature_type.features().len()); + Self { + feature_type, + feature_index, + kind, + } + } + + fn write_name(&self, dest: &mut CssWriter) -> fmt::Result + where + W: fmt::Write, + { + let feature = self.feature(); + if feature.flags.contains(FeatureFlags::WEBKIT_PREFIX) { + dest.write_str("-webkit-")?; + } + + if let QueryFeatureExpressionKind::LegacyRange(range, _) = self.kind { + match range { + LegacyRange::Min => dest.write_str("min-")?, + LegacyRange::Max => dest.write_str("max-")?, + } + } + + // NB: CssStringWriter not needed, feature names are under control. + write!(dest, "{}", feature.name)?; + + Ok(()) + } + + fn feature(&self) -> &'static QueryFeatureDescription { + &self.feature_type.features()[self.feature_index] + } + + /// Returns the feature flags for our feature. + pub fn feature_flags(&self) -> FeatureFlags { + self.feature().flags + } + + /// Parse a feature expression of the form: + /// + /// ``` + /// (media-feature: media-value) + /// ``` + pub fn parse<'i, 't>( + context: &ParserContext, + input: &mut Parser<'i, 't>, + feature_type: FeatureType, + ) -> Result> { + input.expect_parenthesis_block()?; + input.parse_nested_block(|input| { + Self::parse_in_parenthesis_block(context, input, feature_type) + }) + } + + fn parse_feature_name<'i, 't>( + context: &ParserContext, + input: &mut Parser<'i, 't>, + feature_type: FeatureType, + ) -> Result<(usize, Option), ParseError<'i>> { + let mut flags = FeatureFlags::empty(); + let location = input.current_source_location(); + let ident = input.expect_ident()?; + + if context.in_ua_or_chrome_sheet() { + flags.insert(FeatureFlags::CHROME_AND_UA_ONLY); + } + + let mut feature_name = &**ident; + if starts_with_ignore_ascii_case(feature_name, "-webkit-") { + feature_name = &feature_name[8..]; + flags.insert(FeatureFlags::WEBKIT_PREFIX); + } + + let range = if starts_with_ignore_ascii_case(feature_name, "min-") { + feature_name = &feature_name[4..]; + Some(LegacyRange::Min) + } else if starts_with_ignore_ascii_case(feature_name, "max-") { + feature_name = &feature_name[4..]; + Some(LegacyRange::Max) + } else { + None + }; + + let atom = Atom::from(string_as_ascii_lowercase(feature_name)); + let (feature_index, feature) = match feature_type.find_feature(&atom) { + Some((i, f)) => (i, f), + None => { + return Err(location.new_custom_error( + StyleParseErrorKind::MediaQueryExpectedFeatureName(ident.clone()), + )) + }, + }; + + if disabled_by_pref(&feature.name, context) || + !flags.contains(feature.flags.parsing_requirements()) || + (range.is_some() && !feature.allows_ranges()) + { + return Err(location.new_custom_error( + StyleParseErrorKind::MediaQueryExpectedFeatureName(ident.clone()), + )); + } + + Ok((feature_index, range)) + } + + /// Parses the following range syntax: + /// + /// (feature-value feature-name) + /// (feature-value feature-name feature-value) + fn parse_multi_range_syntax<'i, 't>( + context: &ParserContext, + input: &mut Parser<'i, 't>, + feature_type: FeatureType, + ) -> Result> { + let start = input.state(); + + // To parse the values, we first need to find the feature name. We rely + // on feature values for ranged features not being able to be top-level + // s, which holds. + let feature_index = loop { + // NOTE: parse_feature_name advances the input. + if let Ok((index, range)) = Self::parse_feature_name(context, input, feature_type) { + if range.is_some() { + // Ranged names are not allowed here. + return Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError)); + } + break index; + } + if input.is_exhausted() { + return Err(start + .source_location() + .new_custom_error(StyleParseErrorKind::UnspecifiedError)); + } + }; + + input.reset(&start); + + let feature = &feature_type.features()[feature_index]; + let left_val = QueryExpressionValue::parse(feature, context, input)?; + let left_op = Operator::parse(input)?; + + { + let (parsed_index, _) = Self::parse_feature_name(context, input, feature_type)?; + debug_assert_eq!( + parsed_index, feature_index, + "How did we find a different feature?" + ); + } + + let right_op = input.try_parse(Operator::parse).ok(); + let right = match right_op { + Some(op) => { + if !left_op.is_compatible_with(op) { + return Err( + input.new_custom_error(StyleParseErrorKind::UnspecifiedError) + ); + } + Some((op, QueryExpressionValue::parse(feature, context, input)?)) + }, + None => None, + }; + Ok(Self::new( + feature_type, + feature_index, + QueryFeatureExpressionKind::Range { + left: Some((left_op, left_val)), + right, + }, + )) + } + + /// Parse a feature expression where we've already consumed the parenthesis. + pub fn parse_in_parenthesis_block<'i, 't>( + context: &ParserContext, + input: &mut Parser<'i, 't>, + feature_type: FeatureType, + ) -> Result> { + let (feature_index, range) = + match input.try_parse(|input| Self::parse_feature_name(context, input, feature_type)) { + Ok(v) => v, + Err(e) => { + if let Ok(expr) = Self::parse_multi_range_syntax(context, input, feature_type) { + return Ok(expr); + } + return Err(e); + }, + }; + let operator = input.try_parse(consume_operation_or_colon); + let operator = match operator { + Err(..) => { + // If there's no colon, this is a query of the form + // '()', that is, there's no value specified. + // + // Gecko doesn't allow ranged expressions without a + // value, so just reject them here too. + if range.is_some() { + return Err( + input.new_custom_error(StyleParseErrorKind::RangedExpressionWithNoValue) + ); + } + + return Ok(Self::new( + feature_type, + feature_index, + QueryFeatureExpressionKind::Empty, + )); + }, + Ok(operator) => operator, + }; + + let feature = &feature_type.features()[feature_index]; + + let value = QueryExpressionValue::parse(feature, context, input).map_err(|err| { + err.location + .new_custom_error(StyleParseErrorKind::MediaQueryExpectedFeatureValue) + })?; + + let kind = match range { + Some(range) => { + if operator.is_some() { + return Err( + input.new_custom_error(StyleParseErrorKind::MediaQueryUnexpectedOperator) + ); + } + QueryFeatureExpressionKind::LegacyRange(range, value) + }, + None => match operator { + Some(operator) => { + if !feature.allows_ranges() { + return Err(input + .new_custom_error(StyleParseErrorKind::MediaQueryUnexpectedOperator)); + } + QueryFeatureExpressionKind::Range { + left: None, + right: Some((operator, value)), + } + }, + None => QueryFeatureExpressionKind::Single(value), + }, + }; + + Ok(Self::new(feature_type, feature_index, kind)) + } + + /// Returns whether this query evaluates to true for the given device. + pub fn matches(&self, context: &computed::Context) -> bool { + macro_rules! expect { + ($variant:ident, $v:expr) => { + match *$v { + QueryExpressionValue::$variant(ref v) => v, + _ => unreachable!("Unexpected QueryExpressionValue"), + } + }; + } + + match self.feature().evaluator { + Evaluator::Length(eval) => { + let v = eval(context); + self.kind.evaluate(v, |v| { + expect!(Length, v).to_computed_value(context) + }) + }, + Evaluator::Integer(eval) => { + let v = eval(context); + self.kind.evaluate(v, |v| *expect!(Integer, v)) + }, + Evaluator::Float(eval) => { + let v = eval(context); + self.kind.evaluate(v, |v| *expect!(Float, v)) + } + Evaluator::NumberRatio(eval) => { + let ratio = eval(context); + // A ratio of 0/0 behaves as the ratio 1/0, so we need to call used_value() + // to convert it if necessary. + // FIXME: we may need to update here once + // https://github.com/w3c/csswg-drafts/issues/4954 got resolved. + self.kind.evaluate(ratio, |v| expect!(NumberRatio, v).used_value()) + }, + Evaluator::Resolution(eval) => { + let v = eval(context).dppx(); + self.kind.evaluate(v, |v| { + expect!(Resolution, v).to_computed_value(context).dppx() + }) + }, + Evaluator::Enumerated { evaluator, .. } => { + let computed = self.kind.non_ranged_value().map(|v| *expect!(Enumerated, v)); + evaluator(context, computed) + }, + Evaluator::BoolInteger(eval) => { + let computed = self.kind.non_ranged_value().map(|v| *expect!(BoolInteger, v)); + let boolean = eval(context); + computed.map_or(boolean, |v| v == boolean) + }, + } + } +} + +/// A value found or expected in a expression. +/// +/// FIXME(emilio): How should calc() serialize in the Number / Integer / +/// BoolInteger / NumberRatio case, as computed or as specified value? +/// +/// If the first, this would need to store the relevant values. +/// +/// See: https://github.com/w3c/csswg-drafts/issues/1968 +#[derive(Clone, Debug, MallocSizeOf, PartialEq, ToShmem)] +pub enum QueryExpressionValue { + /// A length. + Length(Length), + /// A (non-negative) integer. + Integer(u32), + /// A floating point value. + Float(CSSFloat), + /// A boolean value, specified as an integer (i.e., either 0 or 1). + BoolInteger(bool), + /// A single non-negative number or two non-negative numbers separated by '/', + /// with optional whitespace on either side of the '/'. + NumberRatio(Ratio), + /// A resolution. + Resolution(Resolution), + /// An enumerated value, defined by the variant keyword table in the + /// feature's `mData` member. + Enumerated(KeywordDiscriminant), +} + +impl QueryExpressionValue { + fn to_css(&self, dest: &mut CssWriter, for_expr: &QueryFeatureExpression) -> fmt::Result + where + W: fmt::Write, + { + match *self { + QueryExpressionValue::Length(ref l) => l.to_css(dest), + QueryExpressionValue::Integer(v) => v.to_css(dest), + QueryExpressionValue::Float(v) => v.to_css(dest), + QueryExpressionValue::BoolInteger(v) => dest.write_str(if v { "1" } else { "0" }), + QueryExpressionValue::NumberRatio(ratio) => ratio.to_css(dest), + QueryExpressionValue::Resolution(ref r) => r.to_css(dest), + QueryExpressionValue::Enumerated(value) => match for_expr.feature().evaluator { + Evaluator::Enumerated { serializer, .. } => dest.write_str(&*serializer(value)), + _ => unreachable!(), + }, + } + } + + fn parse<'i, 't>( + for_feature: &QueryFeatureDescription, + context: &ParserContext, + input: &mut Parser<'i, 't>, + ) -> Result> { + Ok(match for_feature.evaluator { + Evaluator::Length(..) => { + let length = Length::parse_non_negative(context, input)?; + QueryExpressionValue::Length(length) + }, + Evaluator::Integer(..) => { + let integer = Integer::parse_non_negative(context, input)?; + QueryExpressionValue::Integer(integer.value() as u32) + }, + Evaluator::BoolInteger(..) => { + let integer = Integer::parse_non_negative(context, input)?; + let value = integer.value(); + if value > 1 { + return Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError)); + } + QueryExpressionValue::BoolInteger(value == 1) + }, + Evaluator::Float(..) => { + let number = Number::parse(context, input)?; + QueryExpressionValue::Float(number.get()) + }, + Evaluator::NumberRatio(..) => { + use crate::values::specified::Ratio as SpecifiedRatio; + let ratio = SpecifiedRatio::parse(context, input)?; + QueryExpressionValue::NumberRatio(Ratio::new(ratio.0.get(), ratio.1.get())) + }, + Evaluator::Resolution(..) => { + QueryExpressionValue::Resolution(Resolution::parse(context, input)?) + }, + Evaluator::Enumerated { parser, .. } => { + QueryExpressionValue::Enumerated(parser(context, input)?) + }, + }) + } +} diff --git a/components/style/queries/mod.rs b/components/style/queries/mod.rs new file mode 100644 index 00000000000..6bbf197c43b --- /dev/null +++ b/components/style/queries/mod.rs @@ -0,0 +1,19 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ + +//! Code shared between [media queries][mq] and [container queries][cq]. +//! +//! [mq]: https://drafts.csswg.org/mediaqueries/ +//! [cq]: https://drafts.csswg.org/css-contain-3/#container-rule + +mod condition; + +#[macro_use] +pub mod feature; +pub mod feature_expression; +pub mod values; + +pub use self::condition::QueryCondition; +pub use self::feature::FeatureFlags; +pub use self::feature_expression::{QueryFeatureExpression, FeatureType}; diff --git a/components/style/queries/values.rs b/components/style/queries/values.rs new file mode 100644 index 00000000000..5bd1cede48b --- /dev/null +++ b/components/style/queries/values.rs @@ -0,0 +1,36 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ + +//! Common feature values between media and container features. + +/// The orientation media / container feature. +/// https://drafts.csswg.org/mediaqueries-5/#orientation +/// https://drafts.csswg.org/css-contain-3/#orientation +#[derive(Clone, Copy, Debug, FromPrimitive, Parse, ToCss)] +#[repr(u8)] +#[allow(missing_docs)] +pub enum Orientation { + Portrait, + Landscape, +} + +impl Orientation { + /// A helper to evaluate a orientation query given a generic size getter. + pub fn eval(size: euclid::default::Size2D, value: Option) -> bool + where + T: PartialOrd, + { + let query_orientation = match value { + Some(v) => v, + None => return true, + }; + + // Per spec, square viewports should be 'portrait' + let is_landscape = size.width > size.height; + match query_orientation { + Self::Landscape => is_landscape, + Self::Portrait => !is_landscape, + } + } +} diff --git a/components/style/rule_collector.rs b/components/style/rule_collector.rs index dfd4d2bd316..0e18383455c 100644 --- a/components/style/rule_collector.rs +++ b/components/style/rule_collector.rs @@ -13,7 +13,7 @@ use crate::selector_parser::PseudoElement; use crate::shared_lock::Locked; use crate::stylesheets::{layer_rule::LayerOrder, Origin}; use crate::stylist::{AuthorStylesEnabled, CascadeData, Rule, RuleInclusion, Stylist}; -use selectors::matching::{ElementSelectorFlags, MatchingContext, MatchingMode}; +use selectors::matching::{MatchingContext, MatchingMode}; use servo_arc::ArcBorrow; use smallvec::SmallVec; @@ -59,7 +59,7 @@ pub fn containing_shadow_ignoring_svg_use( /// /// This is done basically to be able to organize the cascade in smaller /// functions, and be able to reason about it easily. -pub struct RuleCollector<'a, 'b: 'a, E, F: 'a> +pub struct RuleCollector<'a, 'b: 'a, E> where E: TElement, { @@ -73,16 +73,14 @@ where rule_inclusion: RuleInclusion, rules: &'a mut ApplicableDeclarationList, context: &'a mut MatchingContext<'b, E::Impl>, - flags_setter: &'a mut F, matches_user_and_author_rules: bool, matches_document_author_rules: bool, in_sort_scope: bool, } -impl<'a, 'b: 'a, E, F: 'a> RuleCollector<'a, 'b, E, F> +impl<'a, 'b: 'a, E> RuleCollector<'a, 'b, E> where E: TElement, - F: FnMut(&E, ElementSelectorFlags), { /// Trivially construct a new collector. pub fn new( @@ -95,7 +93,6 @@ where rule_inclusion: RuleInclusion, rules: &'a mut ApplicableDeclarationList, context: &'a mut MatchingContext<'b, E::Impl>, - flags_setter: &'a mut F, ) -> Self { // When we're matching with matching_mode = // `ForStatelessPseudoeElement`, the "target" for the rule hash is the @@ -125,7 +122,6 @@ where animation_declarations, rule_inclusion, context, - flags_setter, rules, matches_user_and_author_rules, matches_document_author_rules: matches_user_and_author_rules, @@ -227,9 +223,9 @@ where part_rules, &mut self.rules, &mut self.context, - &mut self.flags_setter, cascade_level, cascade_data, + &self.stylist, ); } @@ -246,9 +242,9 @@ where self.rule_hash_target, &mut self.rules, &mut self.context, - &mut self.flags_setter, cascade_level, cascade_data, + &self.stylist, ); } diff --git a/components/style/rule_tree/core.rs b/components/style/rule_tree/core.rs index e4632ffa711..85584a0e224 100644 --- a/components/style/rule_tree/core.rs +++ b/components/style/rule_tree/core.rs @@ -203,9 +203,6 @@ impl RuleTree { /// where it likely did not result from a rigorous performance analysis.) const RULE_TREE_GC_INTERVAL: usize = 300; -/// Used for some size assertions. -pub const RULE_NODE_SIZE: usize = std::mem::size_of::(); - /// A node in the rule tree. struct RuleNode { /// The root node. Only the root has no root pointer, for obvious reasons. @@ -768,3 +765,8 @@ impl hash::Hash for StrongRuleNode { (&*self.p as *const RuleNode).hash(state) } } + +// Large pages generate thousands of RuleNode objects. +size_of_test!(RuleNode, 80); +// StrongRuleNode should be pointer-sized even inside an option. +size_of_test!(Option, 8); diff --git a/components/style/rule_tree/mod.rs b/components/style/rule_tree/mod.rs index c2339ee9907..01510e62070 100644 --- a/components/style/rule_tree/mod.rs +++ b/components/style/rule_tree/mod.rs @@ -20,7 +20,7 @@ mod map; mod source; mod unsafe_box; -pub use self::core::{RuleTree, StrongRuleNode, RULE_NODE_SIZE}; +pub use self::core::{RuleTree, StrongRuleNode}; pub use self::level::{CascadeLevel, ShadowCascadeOrder}; pub use self::source::StyleSource; diff --git a/components/style/selector_map.rs b/components/style/selector_map.rs index 8a575ca8386..b2cd8d918ca 100644 --- a/components/style/selector_map.rs +++ b/components/style/selector_map.rs @@ -10,11 +10,11 @@ use crate::context::QuirksMode; use crate::dom::TElement; use crate::rule_tree::CascadeLevel; use crate::selector_parser::SelectorImpl; -use crate::stylist::{CascadeData, Rule}; +use crate::stylist::{Stylist, CascadeData, Rule, ContainerConditionId}; use crate::AllocErr; use crate::{Atom, LocalName, Namespace, ShrinkIfNeeded, WeakAtom}; use precomputed_hash::PrecomputedHash; -use selectors::matching::{matches_selector, ElementSelectorFlags, MatchingContext}; +use selectors::matching::{matches_selector, MatchingContext}; use selectors::parser::{Combinator, Component, SelectorIter}; use smallvec::SmallVec; use std::collections::hash_map; @@ -186,34 +186,33 @@ impl SelectorMap { /// /// Extract matching rules as per element's ID, classes, tag name, etc.. /// Sort the Rules at the end to maintain cascading order. - pub fn get_all_matching_rules( + pub fn get_all_matching_rules( &self, element: E, rule_hash_target: E, matching_rules_list: &mut ApplicableDeclarationList, - context: &mut MatchingContext, - flags_setter: &mut F, + matching_context: &mut MatchingContext, cascade_level: CascadeLevel, cascade_data: &CascadeData, + stylist: &Stylist, ) where E: TElement, - F: FnMut(&E, ElementSelectorFlags), { if self.is_empty() { return; } - let quirks_mode = context.quirks_mode(); + let quirks_mode = matching_context.quirks_mode(); if rule_hash_target.is_root() { SelectorMap::get_matching_rules( element, &self.root, matching_rules_list, - context, - flags_setter, + matching_context, cascade_level, cascade_data, + stylist, ); } @@ -223,10 +222,10 @@ impl SelectorMap { element, rules, matching_rules_list, - context, - flags_setter, + matching_context, cascade_level, cascade_data, + stylist, ) } } @@ -237,10 +236,10 @@ impl SelectorMap { element, rules, matching_rules_list, - context, - flags_setter, + matching_context, cascade_level, cascade_data, + stylist, ) } }); @@ -252,10 +251,10 @@ impl SelectorMap { element, rules, matching_rules_list, - context, - flags_setter, + matching_context, cascade_level, cascade_data, + stylist, ) } }); @@ -266,10 +265,10 @@ impl SelectorMap { element, rules, matching_rules_list, - context, - flags_setter, + matching_context, cascade_level, cascade_data, + stylist, ) } @@ -278,10 +277,10 @@ impl SelectorMap { element, rules, matching_rules_list, - context, - flags_setter, + matching_context, cascade_level, cascade_data, + stylist, ) } @@ -289,38 +288,44 @@ impl SelectorMap { element, &self.other, matching_rules_list, - context, - flags_setter, + matching_context, cascade_level, cascade_data, + stylist, ); } /// Adds rules in `rules` that match `element` to the `matching_rules` list. - pub(crate) fn get_matching_rules( + pub(crate) fn get_matching_rules( element: E, rules: &[Rule], matching_rules: &mut ApplicableDeclarationList, - context: &mut MatchingContext, - flags_setter: &mut F, + matching_context: &mut MatchingContext, cascade_level: CascadeLevel, cascade_data: &CascadeData, + stylist: &Stylist, ) where E: TElement, - F: FnMut(&E, ElementSelectorFlags), { for rule in rules { - if matches_selector( + if !matches_selector( &rule.selector, 0, Some(&rule.hashes), &element, - context, - flags_setter, + matching_context, ) { - matching_rules - .push(rule.to_applicable_declaration_block(cascade_level, cascade_data)); + continue; } + + if rule.container_condition_id != ContainerConditionId::none() { + if !cascade_data.container_condition_matches(rule.container_condition_id, stylist, element) { + continue; + } + } + + matching_rules + .push(rule.to_applicable_declaration_block(cascade_level, cascade_data)); } } } diff --git a/components/style/servo/media_queries.rs b/components/style/servo/media_queries.rs index e1822332ff1..d4a4d581b1f 100644 --- a/components/style/servo/media_queries.rs +++ b/components/style/servo/media_queries.rs @@ -7,13 +7,13 @@ use crate::context::QuirksMode; use crate::custom_properties::CssEnvironment; use crate::font_metrics::FontMetrics; -use crate::media_queries::media_feature::{AllowsRanges, ParsingRequirements}; -use crate::media_queries::media_feature::{Evaluator, MediaFeatureDescription}; -use crate::media_queries::media_feature_expression::RangeOrOperator; +use crate::queries::feature::{AllowsRanges, Evaluator, FeatureFlags, QueryFeatureDescription}; use crate::media_queries::MediaType; use crate::properties::ComputedValues; use crate::values::computed::CSSPixelLength; +use crate::values::computed::Context; use crate::values::specified::font::FONT_MEDIUM_PX; +use crate::values::specified::ViewportVariant; use crate::values::KeyframesName; use app_units::Au; use cssparser::RGBA; @@ -143,8 +143,13 @@ impl Device { } /// Like the above, but records that we've used viewport units. - pub fn au_viewport_size_for_viewport_unit_resolution(&self) -> UntypedSize2D { + pub fn au_viewport_size_for_viewport_unit_resolution( + &self, + _: ViewportVariant, + ) -> UntypedSize2D { self.used_viewport_units.store(true, Ordering::Relaxed); + // Servo doesn't have dynamic UA interfaces that affect the viewport, + // so we can just ignore the ViewportVariant. self.au_viewport_size() } @@ -229,16 +234,8 @@ impl Device { } /// https://drafts.csswg.org/mediaqueries-4/#width -fn eval_width( - device: &Device, - value: Option, - range_or_operator: Option, -) -> bool { - RangeOrOperator::evaluate( - range_or_operator, - value.map(Au::from), - device.au_viewport_size().width, - ) +fn eval_width(context: &Context) -> CSSPixelLength { + CSSPixelLength::new(context.device().au_viewport_size().width.to_f32_px()) } #[derive(Clone, Copy, Debug, FromPrimitive, Parse, ToCss)] @@ -249,7 +246,7 @@ enum Scan { } /// https://drafts.csswg.org/mediaqueries-4/#scan -fn eval_scan(_: &Device, _: Option) -> bool { +fn eval_scan(_: &Context, _: Option) -> bool { // Since we doesn't support the 'tv' media type, the 'scan' feature never // matches. false @@ -257,18 +254,18 @@ fn eval_scan(_: &Device, _: Option) -> bool { lazy_static! { /// A list with all the media features that Servo supports. - pub static ref MEDIA_FEATURES: [MediaFeatureDescription; 2] = [ + pub static ref MEDIA_FEATURES: [QueryFeatureDescription; 2] = [ feature!( atom!("width"), AllowsRanges::Yes, Evaluator::Length(eval_width), - ParsingRequirements::empty(), + FeatureFlags::empty(), ), feature!( atom!("scan"), AllowsRanges::No, keyword_evaluator!(eval_scan, Scan), - ParsingRequirements::empty(), + FeatureFlags::empty(), ), ]; } diff --git a/components/style/sharing/checks.rs b/components/style/sharing/checks.rs index 5e8350e78d3..2f8f410190f 100644 --- a/components/style/sharing/checks.rs +++ b/components/style/sharing/checks.rs @@ -7,7 +7,7 @@ //! elements can indeed share the same style. use crate::bloom::StyleBloom; -use crate::context::{SelectorFlagsMap, SharedStyleContext}; +use crate::context::SharedStyleContext; use crate::dom::TElement; use crate::sharing::{StyleSharingCandidate, StyleSharingTarget}; use selectors::NthIndexCache; @@ -120,7 +120,6 @@ pub fn revalidate( shared_context: &SharedStyleContext, bloom: &StyleBloom, nth_index_cache: &mut NthIndexCache, - selector_flags_map: &mut SelectorFlagsMap, ) -> bool where E: TElement, @@ -128,7 +127,7 @@ where let stylist = &shared_context.stylist; let for_element = - target.revalidation_match_results(stylist, bloom, nth_index_cache, selector_flags_map); + target.revalidation_match_results(stylist, bloom, nth_index_cache); let for_candidate = candidate.revalidation_match_results(stylist, bloom, nth_index_cache); diff --git a/components/style/sharing/mod.rs b/components/style/sharing/mod.rs index 50eb51fba35..91e4f02e084 100644 --- a/components/style/sharing/mod.rs +++ b/components/style/sharing/mod.rs @@ -66,9 +66,8 @@ use crate::applicable_declarations::ApplicableDeclarationBlock; use crate::bloom::StyleBloom; -use crate::context::{SelectorFlagsMap, SharedStyleContext, StyleContext}; +use crate::context::{SharedStyleContext, StyleContext}; use crate::dom::{SendElement, TElement}; -use crate::matching::MatchMethods; use crate::properties::ComputedValues; use crate::rule_tree::StrongRuleNode; use crate::style_resolver::{PrimaryStyle, ResolvedElementStyles}; @@ -76,7 +75,7 @@ use crate::stylist::Stylist; use crate::values::AtomIdent; use atomic_refcell::{AtomicRefCell, AtomicRefMut}; use owning_ref::OwningHandle; -use selectors::matching::{ElementSelectorFlags, VisitedHandlingMode}; +use selectors::matching::{VisitedHandlingMode, NeedsSelectorFlags}; use selectors::NthIndexCache; use servo_arc::Arc; use smallbitvec::SmallBitVec; @@ -223,18 +222,17 @@ impl ValidationData { /// Computes the revalidation results if needed, and returns it. /// Inline so we know at compile time what bloom_known_valid is. #[inline] - fn revalidation_match_results( + fn revalidation_match_results( &mut self, element: E, stylist: &Stylist, bloom: &StyleBloom, nth_index_cache: &mut NthIndexCache, bloom_known_valid: bool, - flags_setter: &mut F, + needs_selector_flags: NeedsSelectorFlags, ) -> &SmallBitVec where E: TElement, - F: FnMut(&E, ElementSelectorFlags), { self.revalidation_match_results.get_or_insert_with(|| { // The bloom filter may already be set up for our element. @@ -257,7 +255,7 @@ impl ValidationData { element, bloom_to_use, nth_index_cache, - flags_setter, + needs_selector_flags, ) }) } @@ -327,7 +325,9 @@ impl StyleSharingCandidate { bloom, nth_index_cache, /* bloom_known_valid = */ false, - &mut |_, _| {}, + // The candidate must already have the right bits already, if + // needed. + NeedsSelectorFlags::No, ) } } @@ -384,7 +384,6 @@ impl StyleSharingTarget { stylist: &Stylist, bloom: &StyleBloom, nth_index_cache: &mut NthIndexCache, - selector_flags_map: &mut SelectorFlagsMap, ) -> &SmallBitVec { // It's important to set the selector flags. Otherwise, if we succeed in // sharing the style, we may not set the slow selector flags for the @@ -401,18 +400,13 @@ impl StyleSharingTarget { // The style sharing cache will get a hit for the second span. When the // child span is subsequently removed from the DOM, missing selector // flags would cause us to miss the restyle on the second span. - let element = self.element; - let mut set_selector_flags = |el: &E, flags: ElementSelectorFlags| { - element.apply_selector_flags(selector_flags_map, el, flags); - }; - self.validation_data.revalidation_match_results( self.element, stylist, bloom, nth_index_cache, /* bloom_known_valid = */ true, - &mut set_selector_flags, + NeedsSelectorFlags::Yes, ) } @@ -423,7 +417,6 @@ impl StyleSharingTarget { ) -> Option { let cache = &mut context.thread_local.sharing_cache; let shared_context = &context.shared; - let selector_flags_map = &mut context.thread_local.selector_flags; let bloom_filter = &context.thread_local.bloom_filter; let nth_index_cache = &mut context.thread_local.nth_index_cache; @@ -443,7 +436,6 @@ impl StyleSharingTarget { cache.share_style_if_possible( shared_context, - selector_flags_map, bloom_filter, nth_index_cache, self, @@ -631,13 +623,13 @@ impl StyleSharingCache { // // These are things we don't check in the candidate match because they // are either uncommon or expensive. - let box_style = style.style().get_box(); - if box_style.specifies_transitions() { + let ui_style = style.style().get_ui(); + if ui_style.specifies_transitions() { debug!("Failing to insert to the cache: transitions"); return; } - if box_style.specifies_animations() { + if ui_style.specifies_animations() { debug!("Failing to insert to the cache: animations"); return; } @@ -667,7 +659,6 @@ impl StyleSharingCache { fn share_style_if_possible( &mut self, shared_context: &SharedStyleContext, - selector_flags_map: &mut SelectorFlagsMap, bloom_filter: &StyleBloom, nth_index_cache: &mut NthIndexCache, target: &mut StyleSharingTarget, @@ -700,7 +691,6 @@ impl StyleSharingCache { &shared_context, bloom_filter, nth_index_cache, - selector_flags_map, shared_context, ) }) @@ -712,7 +702,6 @@ impl StyleSharingCache { shared: &SharedStyleContext, bloom: &StyleBloom, nth_index_cache: &mut NthIndexCache, - selector_flags_map: &mut SelectorFlagsMap, shared_context: &SharedStyleContext, ) -> Option { debug_assert!(!target.is_in_native_anonymous_subtree()); @@ -817,7 +806,6 @@ impl StyleSharingCache { shared, bloom, nth_index_cache, - selector_flags_map, ) { trace!("Miss: Revalidation"); return None; diff --git a/components/style/style_resolver.rs b/components/style/style_resolver.rs index 81517f6cd64..7d6135b876f 100644 --- a/components/style/style_resolver.rs +++ b/components/style/style_resolver.rs @@ -15,7 +15,7 @@ use crate::rule_tree::StrongRuleNode; use crate::selector_parser::{PseudoElement, SelectorImpl}; use crate::stylist::RuleInclusion; use log::Level::Trace; -use selectors::matching::{ElementSelectorFlags, MatchingContext}; +use selectors::matching::{NeedsSelectorFlags, MatchingContext}; use selectors::matching::{MatchingMode, VisitedHandlingMode}; use servo_arc::Arc; @@ -451,7 +451,6 @@ where ); let mut applicable_declarations = ApplicableDeclarationList::new(); - let map = &mut self.context.thread_local.selector_flags; let bloom_filter = self.context.thread_local.bloom_filter.filter(); let nth_index_cache = &mut self.context.thread_local.nth_index_cache; let mut matching_context = MatchingContext::new_for_visited( @@ -460,29 +459,22 @@ where Some(nth_index_cache), visited_handling, self.context.shared.quirks_mode(), + NeedsSelectorFlags::Yes, ); let stylist = &self.context.shared.stylist; let implemented_pseudo = self.element.implemented_pseudo_element(); - { - let resolving_element = self.element; - let mut set_selector_flags = |element: &E, flags: ElementSelectorFlags| { - resolving_element.apply_selector_flags(map, element, flags); - }; - - // Compute the primary rule node. - stylist.push_applicable_declarations( - self.element, - implemented_pseudo.as_ref(), - self.element.style_attribute(), - self.element.smil_override(), - self.element.animation_declarations(self.context.shared), - self.rule_inclusion, - &mut applicable_declarations, - &mut matching_context, - &mut set_selector_flags, - ); - } + // Compute the primary rule node. + stylist.push_applicable_declarations( + self.element, + implemented_pseudo.as_ref(), + self.element.style_attribute(), + self.element.smil_override(), + self.element.animation_declarations(self.context.shared), + self.rule_inclusion, + &mut applicable_declarations, + &mut matching_context, + ); // FIXME(emilio): This is a hack for animations, and should go away. self.element.unset_dirty_style_attribute(); @@ -540,14 +532,9 @@ where Some(nth_index_cache), visited_handling, self.context.shared.quirks_mode(), + NeedsSelectorFlags::Yes, ); - let map = &mut self.context.thread_local.selector_flags; - let resolving_element = self.element; - let mut set_selector_flags = |element: &E, flags: ElementSelectorFlags| { - resolving_element.apply_selector_flags(map, element, flags); - }; - // NB: We handle animation rules for ::before and ::after when // traversing them. stylist.push_applicable_declarations( @@ -559,7 +546,6 @@ where self.rule_inclusion, &mut applicable_declarations, &mut matching_context, - &mut set_selector_flags, ); if applicable_declarations.is_empty() { diff --git a/components/style/stylesheets/container_rule.rs b/components/style/stylesheets/container_rule.rs new file mode 100644 index 00000000000..fe103d8986d --- /dev/null +++ b/components/style/stylesheets/container_rule.rs @@ -0,0 +1,281 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ + +//! A [`@container`][container] rule. +//! +//! [container]: https://drafts.csswg.org/css-contain-3/#container-rule + +use crate::logical_geometry::{WritingMode, LogicalSize}; +use crate::dom::TElement; +use crate::media_queries::Device; +use crate::parser::ParserContext; +use crate::queries::{QueryCondition, FeatureType}; +use crate::queries::feature::{AllowsRanges, Evaluator, FeatureFlags, QueryFeatureDescription}; +use crate::queries::values::Orientation; +use crate::str::CssStringWriter; +use crate::shared_lock::{ + DeepCloneParams, DeepCloneWithLock, Locked, SharedRwLock, SharedRwLockReadGuard, ToCssWithGuard, +}; +use crate::values::specified::ContainerName; +use crate::values::computed::{Context, CSSPixelLength, Ratio}; +use crate::properties::ComputedValues; +use crate::stylesheets::CssRules; +use app_units::Au; +use cssparser::{SourceLocation, Parser}; +use euclid::default::Size2D; +#[cfg(feature = "gecko")] +use malloc_size_of::{MallocSizeOfOps, MallocUnconditionalShallowSizeOf}; +use servo_arc::Arc; +use std::fmt::{self, Write}; +use style_traits::{CssWriter, ToCss, ParseError}; + +/// A container rule. +#[derive(Debug, ToShmem)] +pub struct ContainerRule { + /// The container query and name. + pub condition: Arc, + /// The nested rules inside the block. + pub rules: Arc>, + /// The source position where this rule was found. + pub source_location: SourceLocation, +} + +impl ContainerRule { + /// Returns the query condition. + pub fn query_condition(&self) -> &QueryCondition { + &self.condition.condition + } + + /// Measure heap usage. + #[cfg(feature = "gecko")] + pub fn size_of(&self, guard: &SharedRwLockReadGuard, ops: &mut MallocSizeOfOps) -> usize { + // Measurement of other fields may be added later. + self.rules.unconditional_shallow_size_of(ops) + + self.rules.read_with(guard).size_of(guard, ops) + } +} + +impl DeepCloneWithLock for ContainerRule { + fn deep_clone_with_lock( + &self, + lock: &SharedRwLock, + guard: &SharedRwLockReadGuard, + params: &DeepCloneParams, + ) -> Self { + let rules = self.rules.read_with(guard); + Self { + condition: self.condition.clone(), + rules: Arc::new(lock.wrap(rules.deep_clone_with_lock(lock, guard, params))), + source_location: self.source_location.clone(), + } + } +} + +impl ToCssWithGuard for ContainerRule { + fn to_css(&self, guard: &SharedRwLockReadGuard, dest: &mut CssStringWriter) -> fmt::Result { + dest.write_str("@container ")?; + { + let mut writer = CssWriter::new(dest); + if !self.condition.name.is_none() { + self.condition.name.to_css(&mut writer)?; + writer.write_char(' ')?; + } + self.condition.condition.to_css(&mut writer)?; + } + self.rules.read_with(guard).to_css_block(guard, dest) + } +} + +/// A container condition and filter, combined. +#[derive(Debug, ToShmem)] +pub struct ContainerCondition { + name: ContainerName, + condition: QueryCondition, + flags: FeatureFlags, +} + +impl ContainerCondition { + /// Parse a container condition. + pub fn parse<'a>( + context: &ParserContext, + input: &mut Parser<'a, '_>, + ) -> Result> { + use crate::parser::Parse; + + // FIXME: This is a bit ambiguous: + // https://github.com/w3c/csswg-drafts/issues/7203 + let name = input.try_parse(|input| { + ContainerName::parse(context, input) + }).ok().unwrap_or_else(ContainerName::none); + let condition = QueryCondition::parse(context, input, FeatureType::Container)?; + let flags = condition.cumulative_flags(); + Ok(Self { name, condition, flags }) + } + + fn valid_container_info(&self, potential_container: E) -> Option<(ContainerInfo, Arc)> + where + E: TElement, + { + use crate::values::computed::ContainerType; + + fn container_type_axes(ty_: ContainerType, wm: WritingMode) -> FeatureFlags { + if ty_.intersects(ContainerType::SIZE) { + return FeatureFlags::all_container_axes() + } + if ty_.intersects(ContainerType::INLINE_SIZE) { + let physical_axis = if wm.is_vertical() { + FeatureFlags::CONTAINER_REQUIRES_HEIGHT_AXIS + } else { + FeatureFlags::CONTAINER_REQUIRES_WIDTH_AXIS + }; + return FeatureFlags::CONTAINER_REQUIRES_INLINE_AXIS | physical_axis + } + FeatureFlags::empty() + } + + let data = match potential_container.borrow_data() { + Some(data) => data, + None => return None, + }; + let style = data.styles.primary(); + let wm = style.writing_mode; + let box_style = style.get_box(); + + // Filter by container-type. + let container_type = box_style.clone_container_type(); + let available_axes = container_type_axes(container_type, wm); + if !available_axes.contains(self.flags.container_axes()) { + return None; + } + + // Filter by container-name. + let container_name = box_style.clone_container_name(); + for filter_name in self.name.0.iter() { + if !container_name.0.contains(filter_name) { + return None; + } + } + + let size = potential_container.primary_box_size(); + let style = style.clone(); + Some((ContainerInfo { size, wm }, style)) + } + + fn find_container(&self, mut e: E) -> Option<(ContainerInfo, Arc)> + where + E: TElement, + { + while let Some(element) = e.traversal_parent() { + if let Some(info) = self.valid_container_info(element) { + return Some(info); + } + e = element; + } + + None + } + + /// Tries to match a container query condition for a given element. + pub(crate) fn matches(&self, device: &Device, element: E) -> bool + where + E: TElement, + { + let info = self.find_container(element); + Context::for_container_query_evaluation(device, info, |context| { + self.condition.matches(context) + }) + } +} + + +/// Information needed to evaluate an individual container query. +#[derive(Copy, Clone)] +pub struct ContainerInfo { + size: Size2D, + wm: WritingMode, +} + +fn get_container(context: &Context) -> ContainerInfo { + if let Some(ref info) = context.container_info { + return info.clone() + } + ContainerInfo { + size: context.device().au_viewport_size(), + wm: WritingMode::horizontal_tb(), + } +} + +fn eval_width(context: &Context) -> CSSPixelLength { + let info = get_container(context); + CSSPixelLength::new(info.size.width.to_f32_px()) +} + +fn eval_height(context: &Context) -> CSSPixelLength { + let info = get_container(context); + CSSPixelLength::new(info.size.height.to_f32_px()) +} + +fn eval_inline_size(context: &Context) -> CSSPixelLength { + let info = get_container(context); + CSSPixelLength::new(LogicalSize::from_physical(info.wm, info.size).inline.to_f32_px()) +} + +fn eval_block_size(context: &Context) -> CSSPixelLength { + let info = get_container(context); + CSSPixelLength::new(LogicalSize::from_physical(info.wm, info.size).block.to_f32_px()) +} + +fn eval_aspect_ratio(context: &Context) -> Ratio { + let info = get_container(context); + Ratio::new(info.size.width.0 as f32, info.size.height.0 as f32) +} + +fn eval_orientation(context: &Context, value: Option) -> bool { + let info = get_container(context); + Orientation::eval(info.size, value) +} + +/// https://drafts.csswg.org/css-contain-3/#container-features +/// +/// TODO: Support style queries, perhaps. +pub static CONTAINER_FEATURES: [QueryFeatureDescription; 6] = [ + feature!( + atom!("width"), + AllowsRanges::Yes, + Evaluator::Length(eval_width), + FeatureFlags::CONTAINER_REQUIRES_WIDTH_AXIS, + ), + feature!( + atom!("height"), + AllowsRanges::Yes, + Evaluator::Length(eval_height), + FeatureFlags::CONTAINER_REQUIRES_HEIGHT_AXIS, + ), + feature!( + atom!("inline-size"), + AllowsRanges::Yes, + Evaluator::Length(eval_inline_size), + FeatureFlags::CONTAINER_REQUIRES_INLINE_AXIS, + ), + feature!( + atom!("block-size"), + AllowsRanges::Yes, + Evaluator::Length(eval_block_size), + FeatureFlags::CONTAINER_REQUIRES_BLOCK_AXIS, + ), + feature!( + atom!("aspect-ratio"), + AllowsRanges::Yes, + Evaluator::NumberRatio(eval_aspect_ratio), + // XXX from_bits_truncate is const, but the pipe operator isn't, so this + // works around it. + FeatureFlags::from_bits_truncate(FeatureFlags::CONTAINER_REQUIRES_BLOCK_AXIS.bits() | FeatureFlags::CONTAINER_REQUIRES_INLINE_AXIS.bits()), + ), + feature!( + atom!("orientation"), + AllowsRanges::No, + keyword_evaluator!(eval_orientation, Orientation), + FeatureFlags::from_bits_truncate(FeatureFlags::CONTAINER_REQUIRES_BLOCK_AXIS.bits() | FeatureFlags::CONTAINER_REQUIRES_INLINE_AXIS.bits()), + ), +]; diff --git a/components/style/stylesheets/layer_rule.rs b/components/style/stylesheets/layer_rule.rs index ebff5bb9add..c724fd81f1a 100644 --- a/components/style/stylesheets/layer_rule.rs +++ b/components/style/stylesheets/layer_rule.rs @@ -2,7 +2,7 @@ * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ -//! A [`@layer`][layer] urle. +//! A [`@layer`][layer] rule. //! //! [layer]: https://drafts.csswg.org/css-cascade-5/#layering @@ -61,17 +61,6 @@ impl LayerOrder { } } -/// The id of a given layer, a sequentially-increasing identifier. -#[derive(Clone, Copy, Debug, Eq, MallocSizeOf, PartialEq, PartialOrd, Ord)] -pub struct LayerId(pub u32); - -impl LayerId { - /// The id of the root layer. - pub const fn root() -> Self { - Self(0) - } -} - /// A ``: https://drafts.csswg.org/css-cascade-5/#typedef-layer-name #[derive(Clone, Debug, Eq, Hash, MallocSizeOf, PartialEq, ToShmem)] pub struct LayerName(pub SmallVec<[AtomIdent; 1]>); diff --git a/components/style/stylesheets/media_rule.rs b/components/style/stylesheets/media_rule.rs index ea7441a5c09..cde60a16bf7 100644 --- a/components/style/stylesheets/media_rule.rs +++ b/components/style/stylesheets/media_rule.rs @@ -2,7 +2,7 @@ * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ -//! An [`@media`][media] urle. +//! An [`@media`][media] rule. //! //! [media]: https://drafts.csswg.org/css-conditional/#at-ruledef-media @@ -18,7 +18,7 @@ use servo_arc::Arc; use std::fmt::{self, Write}; use style_traits::{CssWriter, ToCss}; -/// An [`@media`][media] urle. +/// An [`@media`][media] rule. /// /// [media]: https://drafts.csswg.org/css-conditional/#at-ruledef-media #[derive(Debug, ToShmem)] diff --git a/components/style/stylesheets/mod.rs b/components/style/stylesheets/mod.rs index d48b7504797..344fda5ef3c 100644 --- a/components/style/stylesheets/mod.rs +++ b/components/style/stylesheets/mod.rs @@ -12,6 +12,7 @@ pub mod font_feature_values_rule; pub mod import_rule; pub mod keyframes_rule; pub mod layer_rule; +pub mod container_rule; mod loader; mod media_rule; mod namespace_rule; @@ -53,6 +54,7 @@ pub use self::import_rule::ImportRule; pub use self::keyframes_rule::KeyframesRule; pub use self::layer_rule::{LayerBlockRule, LayerStatementRule}; pub use self::loader::StylesheetLoader; +pub use self::container_rule::ContainerRule; pub use self::media_rule::MediaRule; pub use self::namespace_rule::NamespaceRule; pub use self::origin::{Origin, OriginSet, OriginSetIterator, PerOrigin, PerOriginIter}; @@ -253,6 +255,7 @@ pub enum CssRule { Import(Arc>), Style(Arc>), Media(Arc>), + Container(Arc>), FontFace(Arc>), FontFeatureValues(Arc>), CounterStyle(Arc>), @@ -287,6 +290,10 @@ impl CssRule { lock.unconditional_shallow_size_of(ops) + lock.read_with(guard).size_of(guard, ops) }, + CssRule::Container(ref lock) => { + lock.unconditional_shallow_size_of(ops) + lock.read_with(guard).size_of(guard, ops) + }, + CssRule::FontFace(_) => 0, CssRule::FontFeatureValues(_) => 0, CssRule::CounterStyle(_) => 0, @@ -344,6 +351,7 @@ pub enum CssRuleType { LayerBlock = 16, LayerStatement = 17, ScrollTimeline = 18, + Container = 19, } #[allow(missing_docs)] @@ -373,16 +381,7 @@ impl CssRule { CssRule::LayerBlock(_) => CssRuleType::LayerBlock, CssRule::LayerStatement(_) => CssRuleType::LayerStatement, CssRule::ScrollTimeline(_) => CssRuleType::ScrollTimeline, - } - } - - fn rule_state(&self) -> State { - match *self { - // CssRule::Charset(..) => State::Start, - CssRule::Import(..) => State::Imports, - CssRule::Namespace(..) => State::Namespaces, - // TODO(emilio): Do we need something for EarlyLayers? - _ => State::Body, + CssRule::Container(_) => CssRuleType::Container, } } @@ -460,6 +459,12 @@ impl DeepCloneWithLock for CssRule { lock.wrap(rule.deep_clone_with_lock(lock, guard, params)), )) }, + CssRule::Container(ref arc) => { + let rule = arc.read_with(guard); + CssRule::Container(Arc::new( + lock.wrap(rule.deep_clone_with_lock(lock, guard, params)), + )) + }, CssRule::Media(ref arc) => { let rule = arc.read_with(guard); CssRule::Media(Arc::new( @@ -545,6 +550,7 @@ impl ToCssWithGuard for CssRule { CssRule::LayerBlock(ref lock) => lock.read_with(guard).to_css(guard, dest), CssRule::LayerStatement(ref lock) => lock.read_with(guard).to_css(guard, dest), CssRule::ScrollTimeline(ref lock) => lock.read_with(guard).to_css(guard, dest), + CssRule::Container(ref lock) => lock.read_with(guard).to_css(guard, dest), } } } diff --git a/components/style/stylesheets/rule_list.rs b/components/style/stylesheets/rule_list.rs index d84a738bca0..c246d7ae6bb 100644 --- a/components/style/stylesheets/rule_list.rs +++ b/components/style/stylesheets/rule_list.rs @@ -153,21 +153,17 @@ impl CssRulesHelpers for RawOffsetArc> { } // Computes the parser state at the given index + let insert_rule_context = InsertRuleContext { + rule_list: &rules.0, + index, + }; + let state = if nested { State::Body } else if index == 0 { State::Start } else { - rules - .0 - .get(index - 1) - .map(CssRule::rule_state) - .unwrap_or(State::Body) - }; - - let insert_rule_context = InsertRuleContext { - rule_list: &rules.0, - index, + insert_rule_context.max_rule_state_at_index(index - 1) }; // Steps 3, 4, 5, 6 diff --git a/components/style/stylesheets/rule_parser.rs b/components/style/stylesheets/rule_parser.rs index 9c0095bc32d..646a7dbbfd2 100644 --- a/components/style/stylesheets/rule_parser.rs +++ b/components/style/stylesheets/rule_parser.rs @@ -13,6 +13,7 @@ use crate::properties::parse_property_declaration_list; use crate::selector_parser::{SelectorImpl, SelectorParser}; use crate::shared_lock::{Locked, SharedRwLock}; use crate::str::starts_with_ignore_ascii_case; +use crate::stylesheets::container_rule::{ContainerRule, ContainerCondition}; use crate::stylesheets::document_rule::DocumentCondition; use crate::stylesheets::font_feature_values_rule::parse_family_name_list; use crate::stylesheets::import_rule::ImportLayer; @@ -45,6 +46,36 @@ pub struct InsertRuleContext<'a> { pub index: usize, } +impl<'a> InsertRuleContext<'a> { + /// Returns the max rule state allowable for insertion at a given index in + /// the rule list. + pub fn max_rule_state_at_index(&self, index: usize) -> State { + let rule = match self.rule_list.get(index) { + Some(rule) => rule, + None => return State::Body, + }; + match rule { + CssRule::Import(..) => State::Imports, + CssRule::Namespace(..) => State::Namespaces, + CssRule::LayerStatement(..) => { + // If there are @import / @namespace after this layer, then + // we're in the early-layers phase, otherwise we're in the body + // and everything is fair game. + let next_non_layer_statement_rule = self.rule_list[index + 1..] + .iter() + .find(|r| !matches!(*r, CssRule::LayerStatement(..))); + if let Some(non_layer) = next_non_layer_statement_rule { + if matches!(*non_layer, CssRule::Import(..) | CssRule::Namespace(..)) { + return State::EarlyLayers; + } + } + State::Body + } + _ => State::Body, + } + } +} + /// The parser for the top-level rules in a stylesheet. pub struct TopLevelRuleParser<'a> { /// A reference to the lock we need to use to create rules. @@ -102,12 +133,8 @@ impl<'b> TopLevelRuleParser<'b> { None => return true, }; - let next_rule_state = match ctx.rule_list.get(ctx.index) { - None => return true, - Some(rule) => rule.rule_state(), - }; - - if new_state > next_rule_state { + let max_rule_state = ctx.max_rule_state_at_index(ctx.index); + if new_state > max_rule_state { self.dom_error = Some(RulesMutateError::HierarchyRequest); return false; } @@ -162,6 +189,8 @@ pub enum AtRulePrelude { CounterStyle(CustomIdent), /// A @media rule prelude, with its media queries. Media(Arc>), + /// A @container rule prelude. + Container(Arc), /// An @supports rule, with its conditional Supports(SupportsCondition), /// A @viewport rule prelude. @@ -262,11 +291,23 @@ impl<'a, 'i> AtRuleParser<'i> for TopLevelRuleParser<'a> { self.dom_error = Some(RulesMutateError::HierarchyRequest); return Err(input.new_custom_error(StyleParseErrorKind::UnexpectedCharsetRule)) }, - _ => {} - } - - if !self.check_state(State::Body) { - return Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError)); + "layer" => { + let state_to_check = if self.state <= State::EarlyLayers { + // The real state depends on whether there's a block or not. + // We don't know that yet, but the parse_block check deals + // with that. + State::EarlyLayers + } else { + State::Body + }; + if !self.check_state(state_to_check) { + return Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError)); + } + }, + _ => { + // All other rules have blocks, so we do this check early in + // parse_block instead. + } } AtRuleParser::parse_prelude(&mut self.nested(), name, input) @@ -279,6 +320,9 @@ impl<'a, 'i> AtRuleParser<'i> for TopLevelRuleParser<'a> { start: &ParserState, input: &mut Parser<'i, 't>, ) -> Result> { + if !self.check_state(State::Body) { + return Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError)); + } let rule = AtRuleParser::parse_block(&mut self.nested(), prelude, start, input)?; self.state = State::Body; Ok((start.position(), rule)) @@ -410,6 +454,16 @@ impl<'a, 'b> NestedRuleParser<'a, 'b> { } } +fn container_queries_enabled() -> bool { + #[cfg(feature = "gecko")] + return static_prefs::pref!("layout.css.container-queries.enabled"); + #[cfg(feature = "servo")] + return servo_config::prefs::pref_map() + .get("layout.container-queries.enabled") + .as_bool() + .unwrap_or(false); +} + impl<'a, 'b, 'i> AtRuleParser<'i> for NestedRuleParser<'a, 'b> { type Prelude = AtRulePrelude; type AtRule = CssRule; @@ -433,6 +487,10 @@ impl<'a, 'b, 'i> AtRuleParser<'i> for NestedRuleParser<'a, 'b> { "font-face" => { AtRulePrelude::FontFace }, + "container" if container_queries_enabled() => { + let condition = Arc::new(ContainerCondition::parse(self.context, input)?); + AtRulePrelude::Container(condition) + }, "layer" => { let names = input.try_parse(|input| { input.parse_comma_separated(|input| { @@ -614,6 +672,15 @@ impl<'a, 'b, 'i> AtRuleParser<'i> for NestedRuleParser<'a, 'b> { }, )))) }, + AtRulePrelude::Container(condition) => { + Ok(CssRule::Container(Arc::new(self.shared_lock.wrap( + ContainerRule { + condition, + rules: self.parse_nested_rules(input, CssRuleType::Container), + source_location: start.source_location(), + }, + )))) + }, AtRulePrelude::Layer(names) => { let name = match names.len() { 0 | 1 => names.into_iter().next(), diff --git a/components/style/stylesheets/rules_iterator.rs b/components/style/stylesheets/rules_iterator.rs index 417185953a0..c86d4dc96c3 100644 --- a/components/style/stylesheets/rules_iterator.rs +++ b/components/style/stylesheets/rules_iterator.rs @@ -88,6 +88,10 @@ where } Some(doc_rule.rules.read_with(guard).0.iter()) }, + CssRule::Container(ref lock) => { + let container_rule = lock.read_with(guard); + Some(container_rule.rules.read_with(guard).0.iter()) + }, CssRule::Media(ref lock) => { let media_rule = lock.read_with(guard); if !C::process_media(guard, device, quirks_mode, media_rule) { diff --git a/components/style/stylesheets/scroll_timeline_rule.rs b/components/style/stylesheets/scroll_timeline_rule.rs index 12b0d2013a6..f4796ca77aa 100644 --- a/components/style/stylesheets/scroll_timeline_rule.rs +++ b/components/style/stylesheets/scroll_timeline_rule.rs @@ -206,7 +206,6 @@ impl Default for ScrollDirection { } } -// Avoid name collision in cbindgen with StyleOrientation. pub use self::ScrollDirection as Orientation; /// Scroll-timeline offsets. We treat None as an empty vector. diff --git a/components/style/stylesheets/stylesheet.rs b/components/style/stylesheets/stylesheet.rs index aaf350b684d..1410634c281 100644 --- a/components/style/stylesheets/stylesheet.rs +++ b/components/style/stylesheets/stylesheet.rs @@ -370,6 +370,7 @@ impl SanitizationKind { CssRule::Media(..) | CssRule::Supports(..) | CssRule::Import(..) | + CssRule::Container(..) | // TODO(emilio): Perhaps Layer should not be always sanitized? But // we sanitize @media and co, so this seems safer for now. CssRule::LayerStatement(..) | diff --git a/components/style/stylesheets/viewport_rule.rs b/components/style/stylesheets/viewport_rule.rs index 89f604be01b..10e6af5715b 100644 --- a/components/style/stylesheets/viewport_rule.rs +++ b/components/style/stylesheets/viewport_rule.rs @@ -671,7 +671,8 @@ impl MaybeNew for ViewportConstraints { builder: StyleBuilder::for_inheritance(device, None, None), cached_system_font: None, in_media_query: false, - quirks_mode: quirks_mode, + quirks_mode, + container_info: None, for_smil_animation: false, for_non_inherited_property: None, rule_cache_conditions: RefCell::new(&mut conditions), diff --git a/components/style/stylist.rs b/components/style/stylist.rs index a1ad4aab659..21b975c9e92 100644 --- a/components/style/stylist.rs +++ b/components/style/stylist.rs @@ -28,8 +28,9 @@ use crate::selector_parser::{PerPseudoElementMap, PseudoElement, SelectorImpl, S use crate::shared_lock::{Locked, SharedRwLockReadGuard, StylesheetGuards}; use crate::stylesheet_set::{DataValidity, DocumentStylesheetSet, SheetRebuildKind}; use crate::stylesheet_set::{DocumentStylesheetFlusher, SheetCollectionFlusher}; +use crate::stylesheets::container_rule::ContainerCondition; use crate::stylesheets::keyframes_rule::KeyframesAnimation; -use crate::stylesheets::layer_rule::{LayerId, LayerName, LayerOrder}; +use crate::stylesheets::layer_rule::{LayerName, LayerOrder}; use crate::stylesheets::viewport_rule::{self, MaybeNew, ViewportRule}; #[cfg(feature = "gecko")] use crate::stylesheets::{ @@ -39,7 +40,6 @@ use crate::stylesheets::{ CssRule, EffectiveRulesIterator, Origin, OriginSet, PageRule, PerOrigin, PerOriginIter, }; use crate::stylesheets::{StyleRule, StylesheetContents, StylesheetInDocument}; -use crate::thread_state::{self, ThreadState}; use crate::AllocErr; use crate::{Atom, LocalName, Namespace, ShrinkIfNeeded, WeakAtom}; use fxhash::FxHashMap; @@ -49,7 +49,7 @@ use malloc_size_of::MallocUnconditionalShallowSizeOf; use selectors::attr::{CaseSensitivity, NamespaceConstraint}; use selectors::bloom::BloomFilter; use selectors::matching::VisitedHandlingMode; -use selectors::matching::{matches_selector, ElementSelectorFlags, MatchingContext, MatchingMode}; +use selectors::matching::{matches_selector, MatchingContext, MatchingMode, NeedsSelectorFlags}; use selectors::parser::{AncestorHashes, Combinator, Component, Selector, SelectorIter}; use selectors::visitor::SelectorVisitor; use selectors::NthIndexCache; @@ -549,6 +549,47 @@ impl From for RuleInclusion { } } +/// A struct containing state from ancestor rules like @layer / @import / +/// @container. +struct ContainingRuleState { + layer_name: LayerName, + layer_id: LayerId, + container_condition_id: ContainerConditionId, +} + +impl Default for ContainingRuleState { + fn default() -> Self { + Self { + layer_name: LayerName::new_empty(), + layer_id: LayerId::root(), + container_condition_id: ContainerConditionId::none(), + } + } +} + +struct SavedContainingRuleState { + layer_name_len: usize, + layer_id: LayerId, + container_condition_id: ContainerConditionId, +} + +impl ContainingRuleState { + fn save(&self) -> SavedContainingRuleState { + SavedContainingRuleState { + layer_name_len: self.layer_name.0.len(), + layer_id: self.layer_id, + container_condition_id: self.container_condition_id, + } + } + + fn restore(&mut self, saved: &SavedContainingRuleState) { + debug_assert!(self.layer_name.0.len() >= saved.layer_name_len); + self.layer_name.0.truncate(saved.layer_name_len); + self.layer_id = saved.layer_id; + self.container_condition_id = saved.container_condition_id; + } +} + impl Stylist { /// Construct a new `Stylist`, using given `Device` and `QuirksMode`. /// If more members are added here, think about whether they should @@ -1073,38 +1114,12 @@ impl Stylist { { debug_assert!(pseudo.is_lazy()); - // Apply the selector flags. We should be in sequential mode - // already, so we can directly apply the parent flags. - let mut set_selector_flags = |element: &E, flags: ElementSelectorFlags| { - if cfg!(feature = "servo") { - // Servo calls this function from the worker, but only for internal - // pseudos, so we should never generate selector flags here. - unreachable!("internal pseudo generated slow selector flags?"); - } - - // No need to bother setting the selector flags when we're computing - // default styles. - if rule_inclusion == RuleInclusion::DefaultOnly { - return; - } - - // Gecko calls this from sequential mode, so we can directly apply - // the flags. - debug_assert_eq!(thread_state::get(), ThreadState::LAYOUT); - let self_flags = flags.for_self(); - if !self_flags.is_empty() { - unsafe { - element.set_selector_flags(self_flags); - } - } - let parent_flags = flags.for_parent(); - if !parent_flags.is_empty() { - if let Some(p) = element.parent_element() { - unsafe { - p.set_selector_flags(parent_flags); - } - } - } + // No need to bother setting the selector flags when we're computing + // default styles. + let needs_selector_flags = if rule_inclusion == RuleInclusion::DefaultOnly { + NeedsSelectorFlags::No + } else { + NeedsSelectorFlags::Yes }; let mut declarations = ApplicableDeclarationList::new(); @@ -1113,6 +1128,7 @@ impl Stylist { None, None, self.quirks_mode, + needs_selector_flags, ); matching_context.pseudo_element_matching_fn = matching_fn; @@ -1126,7 +1142,6 @@ impl Stylist { rule_inclusion, &mut declarations, &mut matching_context, - &mut set_selector_flags, ); if declarations.is_empty() && is_probe { @@ -1144,6 +1159,7 @@ impl Stylist { None, VisitedHandlingMode::RelevantLinkVisited, self.quirks_mode, + needs_selector_flags, ); matching_context.pseudo_element_matching_fn = matching_fn; @@ -1156,7 +1172,6 @@ impl Stylist { rule_inclusion, &mut declarations, &mut matching_context, - &mut set_selector_flags, ); if !declarations.is_empty() { let rule_node = self.rule_tree.insert_ordered_rules_with_important( @@ -1269,7 +1284,7 @@ impl Stylist { } /// Returns the applicable CSS declarations for the given element. - pub fn push_applicable_declarations( + pub fn push_applicable_declarations( &self, element: E, pseudo_element: Option<&PseudoElement>, @@ -1279,10 +1294,8 @@ impl Stylist { rule_inclusion: RuleInclusion, applicable_declarations: &mut ApplicableDeclarationList, context: &mut MatchingContext, - flags_setter: &mut F, ) where E: TElement, - F: FnMut(&E, ElementSelectorFlags), { RuleCollector::new( self, @@ -1294,7 +1307,6 @@ impl Stylist { rule_inclusion, applicable_declarations, context, - flags_setter, ) .collect_all(); } @@ -1374,16 +1386,15 @@ impl Stylist { /// Computes the match results of a given element against the set of /// revalidation selectors. - pub fn match_revalidation_selectors( + pub fn match_revalidation_selectors( &self, element: E, bloom: Option<&BloomFilter>, nth_index_cache: &mut NthIndexCache, - flags_setter: &mut F, + needs_selector_flags: NeedsSelectorFlags, ) -> SmallBitVec where E: TElement, - F: FnMut(&E, ElementSelectorFlags), { // NB: `MatchingMode` doesn't really matter, given we don't share style // between pseudos. @@ -1392,6 +1403,7 @@ impl Stylist { bloom, Some(nth_index_cache), self.quirks_mode, + needs_selector_flags, ); // Note that, by the time we're revalidating, we're guaranteed that the @@ -1414,7 +1426,6 @@ impl Stylist { Some(&selector_and_hashes.hashes), &element, matching_context, - flags_setter, )); true }, @@ -1437,7 +1448,6 @@ impl Stylist { Some(&selector_and_hashes.hashes), &element, &mut matching_context, - flags_setter, )); true }, @@ -2061,6 +2071,17 @@ impl PartElementAndPseudoRules { } } +/// The id of a given layer, a sequentially-increasing identifier. +#[derive(Clone, Copy, Debug, Eq, MallocSizeOf, PartialEq, PartialOrd, Ord)] +pub struct LayerId(u16); + +impl LayerId { + /// The id of the root layer. + pub const fn root() -> Self { + Self(0) + } +} + #[derive(Clone, Debug, MallocSizeOf)] struct CascadeLayer { id: LayerId, @@ -2078,6 +2099,35 @@ impl CascadeLayer { } } +/// The id of a given container condition, a sequentially-increasing identifier +/// for a given style set. +#[derive(Clone, Copy, Debug, Eq, MallocSizeOf, PartialEq, PartialOrd, Ord)] +pub struct ContainerConditionId(u16); + +impl ContainerConditionId { + /// A special id that represents no container rule all. + pub const fn none() -> Self { + Self(0) + } +} + + +#[derive(Clone, Debug, MallocSizeOf)] +struct ContainerConditionReference { + parent: ContainerConditionId, + #[ignore_malloc_size_of = "Arc"] + condition: Option>, +} + +impl ContainerConditionReference { + const fn none() -> Self { + Self { + parent: ContainerConditionId::none(), + condition: None, + } + } +} + /// Data resulting from performing the CSS cascade that is specific to a given /// origin. /// @@ -2149,6 +2199,9 @@ pub struct CascadeData { /// The list of cascade layers, indexed by their layer id. layers: SmallVec<[CascadeLayer; 1]>, + /// The list of container conditions, indexed by their id. + container_conditions: SmallVec<[ContainerConditionReference; 1]>, + /// Effective media query results cached from the last rebuild. effective_media_query_results: EffectiveMediaQueryResults, @@ -2191,6 +2244,7 @@ impl CascadeData { animations: Default::default(), layer_id: Default::default(), layers: smallvec::smallvec![CascadeLayer::root()], + container_conditions: smallvec::smallvec![ContainerConditionReference::none()], extra_data: ExtraStyleData::default(), effective_media_query_results: EffectiveMediaQueryResults::new(), rules_source_order: 0, @@ -2305,6 +2359,23 @@ impl CascadeData { self.layers[id.0 as usize].order } + pub(crate) fn container_condition_matches(&self, mut id: ContainerConditionId, stylist: &Stylist, element: E) -> bool + where + E: TElement, + { + loop { + let condition_ref = &self.container_conditions[id.0 as usize]; + let condition = match condition_ref.condition { + None => return true, + Some(ref c) => c, + }; + if !condition.matches(stylist.device(), element) { + return false; + } + id = condition_ref.parent; + } + } + fn did_finish_rebuild(&mut self) { self.shrink_maps_if_needed(); self.compute_layer_order(); @@ -2426,8 +2497,7 @@ impl CascadeData { stylesheet: &S, guard: &SharedRwLockReadGuard, rebuild_kind: SheetRebuildKind, - mut current_layer: &mut LayerName, - current_layer_id: LayerId, + containing_rule_state: &mut ContainingRuleState, mut precomputed_pseudo_element_decls: Option<&mut PrecomputedPseudoElementDeclarations>, ) -> Result<(), AllocErr> where @@ -2450,7 +2520,7 @@ impl CascadeData { if pseudo.is_precomputed() { debug_assert!(selector.is_universal()); debug_assert_eq!(stylesheet.contents().origin, Origin::UserAgent); - debug_assert_eq!(current_layer_id, LayerId::root()); + debug_assert_eq!(containing_rule_state.layer_id, LayerId::root()); precomputed_pseudo_element_decls .as_mut() @@ -2477,7 +2547,8 @@ impl CascadeData { hashes, locked.clone(), self.rules_source_order, - current_layer_id, + containing_rule_state.layer_id, + containing_rule_state.container_condition_id, ); if rebuild_kind.should_rebuild_invalidation() { @@ -2555,7 +2626,7 @@ impl CascadeData { self.animations.try_insert_with( name, animation, - current_layer_id, + containing_rule_state.layer_id, compare_keyframes_in_same_layer, )?; }, @@ -2564,25 +2635,35 @@ impl CascadeData { // Note: Bug 1733260: we may drop @scroll-timeline rule once this spec issue // https://github.com/w3c/csswg-drafts/issues/6674 gets landed. self.extra_data - .add_scroll_timeline(guard, rule, current_layer_id)?; + .add_scroll_timeline(guard, rule, containing_rule_state.layer_id)?; }, #[cfg(feature = "gecko")] CssRule::FontFace(ref rule) => { - self.extra_data.add_font_face(rule, current_layer_id); + // NOTE(emilio): We don't care about container_condition_id + // because: + // + // Global, name-defining at-rules such as @keyframes or + // @font-face or @layer that are defined inside container + // queries are not constrained by the container query + // conditions. + // + // https://drafts.csswg.org/css-contain-3/#container-rule + // (Same elsewhere) + self.extra_data.add_font_face(rule, containing_rule_state.layer_id); }, #[cfg(feature = "gecko")] CssRule::FontFeatureValues(ref rule) => { self.extra_data - .add_font_feature_values(rule, current_layer_id); + .add_font_feature_values(rule, containing_rule_state.layer_id); }, #[cfg(feature = "gecko")] CssRule::CounterStyle(ref rule) => { self.extra_data - .add_counter_style(guard, rule, current_layer_id)?; + .add_counter_style(guard, rule, containing_rule_state.layer_id)?; }, #[cfg(feature = "gecko")] CssRule::Page(ref rule) => { - self.extra_data.add_page(guard, rule, current_layer_id)?; + self.extra_data.add_page(guard, rule, containing_rule_state.layer_id)?; }, CssRule::Viewport(..) => {}, _ => { @@ -2623,7 +2704,7 @@ impl CascadeData { if let Some(id) = data.layer_id.get(layer) { return *id; } - let id = LayerId(data.layers.len() as u32); + let id = LayerId(data.layers.len() as u16); let parent_layer_id = if layer.layer_names().len() > 1 { let mut parent = layer.clone(); @@ -2654,9 +2735,8 @@ impl CascadeData { fn maybe_register_layers( data: &mut CascadeData, name: Option<&LayerName>, - current_layer: &mut LayerName, - pushed_layers: &mut usize, - ) -> LayerId { + containing_rule_state: &mut ContainingRuleState, + ) { let anon_name; let name = match name { Some(name) => name, @@ -2665,19 +2745,14 @@ impl CascadeData { &anon_name }, }; - - let mut id = LayerId::root(); for name in name.layer_names() { - current_layer.0.push(name.clone()); - id = maybe_register_layer(data, ¤t_layer); - *pushed_layers += 1; + containing_rule_state.layer_name.0.push(name.clone()); + containing_rule_state.layer_id = maybe_register_layer(data, &containing_rule_state.layer_name); } - debug_assert_ne!(id, LayerId::root()); - id + debug_assert_ne!(containing_rule_state.layer_id, LayerId::root()); } - let mut layer_names_to_pop = 0; - let mut children_layer_id = current_layer_id; + let saved_containing_rule_state = containing_rule_state.save(); match *rule { CssRule::Import(ref lock) => { let import_rule = lock.read_with(guard); @@ -2686,11 +2761,10 @@ impl CascadeData { .saw_effective(import_rule); } if let Some(ref layer) = import_rule.layer { - children_layer_id = maybe_register_layers( + maybe_register_layers( self, layer.name.as_ref(), - &mut current_layer, - &mut layer_names_to_pop, + containing_rule_state ); } }, @@ -2702,25 +2776,29 @@ impl CascadeData { }, CssRule::LayerBlock(ref lock) => { let layer_rule = lock.read_with(guard); - children_layer_id = maybe_register_layers( + maybe_register_layers( self, layer_rule.name.as_ref(), - &mut current_layer, - &mut layer_names_to_pop, + containing_rule_state, ); }, CssRule::LayerStatement(ref lock) => { let layer_rule = lock.read_with(guard); for name in &*layer_rule.names { - let mut pushed = 0; - // There are no children, so we can ignore the - // return value. - maybe_register_layers(self, Some(name), &mut current_layer, &mut pushed); - for _ in 0..pushed { - current_layer.0.pop(); - } + maybe_register_layers(self, Some(name), containing_rule_state); + // Register each layer individually. + containing_rule_state.restore(&saved_containing_rule_state); } }, + CssRule::Container(ref lock) => { + let container_rule = lock.read_with(guard); + let id = ContainerConditionId(self.container_conditions.len() as u16); + self.container_conditions.push(ContainerConditionReference { + parent: containing_rule_state.container_condition_id, + condition: Some(container_rule.condition.clone()), + }); + containing_rule_state.container_condition_id = id; + }, // We don't care about any other rule. _ => {}, } @@ -2733,15 +2811,12 @@ impl CascadeData { stylesheet, guard, rebuild_kind, - current_layer, - children_layer_id, + containing_rule_state, precomputed_pseudo_element_decls.as_deref_mut(), )?; } - for _ in 0..layer_names_to_pop { - current_layer.0.pop(); - } + containing_rule_state.restore(&saved_containing_rule_state); } Ok(()) @@ -2770,7 +2845,7 @@ impl CascadeData { self.effective_media_query_results.saw_effective(contents); } - let mut current_layer = LayerName::new_empty(); + let mut state = ContainingRuleState::default(); self.add_rule_list( contents.rules(guard).iter(), device, @@ -2778,8 +2853,7 @@ impl CascadeData { stylesheet, guard, rebuild_kind, - &mut current_layer, - LayerId::root(), + &mut state, precomputed_pseudo_element_decls.as_deref_mut(), )?; @@ -2827,6 +2901,7 @@ impl CascadeData { CssRule::Style(..) | CssRule::Namespace(..) | CssRule::FontFace(..) | + CssRule::Container(..) | CssRule::CounterStyle(..) | CssRule::Supports(..) | CssRule::Keyframes(..) | @@ -2904,6 +2979,8 @@ impl CascadeData { self.layer_id.clear(); self.layers.clear(); self.layers.push(CascadeLayer::root()); + self.container_conditions.clear(); + self.container_conditions.push(ContainerConditionReference::none()); #[cfg(feature = "gecko")] { self.extra_data.clear(); @@ -2998,6 +3075,9 @@ pub struct Rule { /// The current layer id of this style rule. pub layer_id: LayerId, + /// The current @container rule id. + pub container_condition_id: ContainerConditionId, + /// The actual style rule. #[cfg_attr( feature = "gecko", @@ -3043,6 +3123,7 @@ impl Rule { style_rule: Arc>, source_order: u32, layer_id: LayerId, + container_condition_id: ContainerConditionId, ) -> Self { Rule { selector, @@ -3050,10 +3131,17 @@ impl Rule { style_rule, source_order, layer_id, + container_condition_id, } } } +// The size of this is critical to performance on the bloom-basic +// microbenchmark. +// When iterating over a large Rule array, we want to be able to fast-reject +// selectors (with the inline hashes) with as few cache misses as possible. +size_of_test!(Rule, 40); + /// A function to be able to test the revalidation stuff. pub fn needs_revalidation_for_testing(s: &Selector) -> bool { let mut attribute_dependencies = Default::default(); diff --git a/components/style/traversal.rs b/components/style/traversal.rs index bf0b963c709..fcaeb6e9c57 100644 --- a/components/style/traversal.rs +++ b/components/style/traversal.rs @@ -16,6 +16,13 @@ use crate::stylist::RuleInclusion; use crate::traversal_flags::TraversalFlags; use selectors::NthIndexCache; use smallvec::SmallVec; +use std::collections::HashMap; + +/// A cache from element reference to known-valid computed style. +pub type UndisplayedStyleCache = HashMap< + selectors::OpaqueElement, + servo_arc::Arc, +>; /// A per-traversal-level chunk of data. This is sent down by the traversal, and /// currently only holds the dom depth for the bloom filter. @@ -294,6 +301,7 @@ pub fn resolve_style( element: E, rule_inclusion: RuleInclusion, pseudo: Option<&PseudoElement>, + mut undisplayed_style_cache: Option<&mut UndisplayedStyleCache>, ) -> ElementStyles where E: TElement, @@ -304,6 +312,11 @@ where element.borrow_data().map_or(true, |d| !d.has_styles()), "Why are we here?" ); + debug_assert!( + rule_inclusion == RuleInclusion::All || undisplayed_style_cache.is_none(), + "can't use the cache for default styles only" + ); + let mut ancestors_requiring_style_resolution = SmallVec::<[E; 16]>::new(); // Clear the bloom filter, just in case the caller is reusing TLS. @@ -320,6 +333,12 @@ where } } } + if let Some(ref mut cache) = undisplayed_style_cache { + if let Some(s) = cache.get(¤t.opaque()) { + style = Some(s.clone()); + break; + } + } ancestors_requiring_style_resolution.push(current); ancestor = current.traversal_parent(); } @@ -337,7 +356,9 @@ where } ancestor = ancestor.unwrap().traversal_parent(); - layout_parent_style = ancestor.map(|a| a.borrow_data().unwrap().styles.primary().clone()); + layout_parent_style = ancestor.and_then(|a| { + a.borrow_data().map(|data| data.styles.primary().clone()) + }); } for ancestor in ancestors_requiring_style_resolution.iter().rev() { @@ -360,18 +381,27 @@ where layout_parent_style = style.clone(); } + if let Some(ref mut cache) = undisplayed_style_cache { + cache.insert(ancestor.opaque(), style.clone().unwrap()); + } context.thread_local.bloom_filter.push(*ancestor); } context.thread_local.bloom_filter.assert_complete(element); - StyleResolverForElement::new( + let styles: ElementStyles = StyleResolverForElement::new( element, context, rule_inclusion, PseudoElementResolution::Force, ) .resolve_style(style.as_deref(), layout_parent_style.as_deref()) - .into() + .into(); + + if let Some(ref mut cache) = undisplayed_style_cache { + cache.insert(element.opaque(), styles.primary().clone()); + } + + styles } /// Calculates the style for a single node. diff --git a/components/style/values/animated/color.rs b/components/style/values/animated/color.rs index f99e344a57a..cdfa45dc4e7 100644 --- a/components/style/values/animated/color.rs +++ b/components/style/values/animated/color.rs @@ -5,16 +5,20 @@ //! Animated types for CSS colors. use crate::values::animated::{Animate, Procedure, ToAnimatedZero}; +use crate::values::computed::Percentage; use crate::values::distance::{ComputeSquaredDistance, SquaredDistance}; -use crate::values::generics::color::{Color as GenericColor, ComplexColorRatios}; -use crate::values::specified::color::{ColorSpaceKind, HueAdjuster}; +use crate::values::generics::color::{ + GenericColor, GenericColorMix, ColorInterpolationMethod, ColorSpace, HueInterpolationMethod, +}; use euclid::default::{Transform3D, Vector3D}; +use std::f32::consts::PI; /// An animated RGBA color. /// /// Unlike in computed values, each component value may exceed the /// range `[0.0, 1.0]`. -#[derive(Clone, Copy, Debug, MallocSizeOf, PartialEq, ToAnimatedZero)] +#[derive(Clone, Copy, Debug, MallocSizeOf, PartialEq, ToAnimatedZero, ToAnimatedValue)] +#[repr(C)] pub struct RGBA { /// The red component. pub red: f32, @@ -26,6 +30,9 @@ pub struct RGBA { pub alpha: f32, } +const RAD_PER_DEG: f32 = PI / 180.0; +const DEG_PER_RAD: f32 = 180.0 / PI; + impl RGBA { /// Returns a transparent color. #[inline] @@ -43,44 +50,20 @@ impl RGBA { alpha, } } - - /// Returns whether or not the colour is in gamut for sRGB. - pub fn in_gamut(&self) -> bool { - 0. <= self.red && - self.red <= 1. && - 0. <= self.green && - self.green <= 1. && - 0. <= self.blue && - self.blue <= 1. - } - - /// Returns the colour with coordinates clamped to the sRGB range. - pub fn clamp(&self) -> Self { - Self { - red: self.red.max(0.).min(1.), - green: self.green.max(0.).min(1.), - blue: self.blue.max(0.).min(1.), - alpha: self.alpha, - } - } } impl Animate for RGBA { #[inline] fn animate(&self, other: &Self, procedure: Procedure) -> Result { - let mut alpha = self.alpha.animate(&other.alpha, procedure)?; - if alpha <= 0. { - // Ideally we should return color value that only alpha component is - // 0, but this is what current gecko does. - return Ok(RGBA::transparent()); - } - - alpha = alpha.min(1.); - let red = (self.red * self.alpha).animate(&(other.red * other.alpha), procedure)?; - let green = (self.green * self.alpha).animate(&(other.green * other.alpha), procedure)?; - let blue = (self.blue * self.alpha).animate(&(other.blue * other.alpha), procedure)?; - let inv = 1. / alpha; - Ok(RGBA::new(red * inv, green * inv, blue * inv, alpha)) + let (left_weight, right_weight) = procedure.weights(); + Ok(Color::mix( + &ColorInterpolationMethod::srgb(), + self, + left_weight as f32, + other, + right_weight as f32, + /* normalize_weights = */ false, + )) } } @@ -108,253 +91,122 @@ impl ComputeSquaredDistance for RGBA { } /// An animated value for ``. -pub type Color = GenericColor; +pub type Color = GenericColor; + +/// An animated value for ``. +pub type ColorMix = GenericColorMix; impl Color { - fn effective_intermediate_rgba(&self) -> RGBA { - if self.ratios.bg == 0. { - return RGBA::transparent(); - } - - if self.ratios.bg == 1. { - return self.color; - } - - RGBA { - alpha: self.color.alpha * self.ratios.bg, - ..self.color - } + fn to_rgba(&self, current_color: RGBA) -> RGBA { + let mut clone = self.clone(); + clone.simplify(Some(¤t_color)); + *clone.as_numeric().unwrap() } /// Mix two colors into one. pub fn mix( - color_space: ColorSpaceKind, - left_color: &Color, - left_weight: f32, - right_color: &Color, - right_weight: f32, - hue_adjuster: HueAdjuster, - ) -> Self { - match color_space { - ColorSpaceKind::Srgb => Self::mix_in::( - left_color, - left_weight, - right_color, - right_weight, - hue_adjuster, - ), - ColorSpaceKind::Xyz => Self::mix_in::( - left_color, - left_weight, - right_color, - right_weight, - hue_adjuster, - ), - ColorSpaceKind::Lab => Self::mix_in::( - left_color, - left_weight, - right_color, - right_weight, - hue_adjuster, - ), - ColorSpaceKind::Lch => Self::mix_in::( - left_color, - left_weight, - right_color, - right_weight, - hue_adjuster, - ), + interpolation: &ColorInterpolationMethod, + left_color: &RGBA, + mut left_weight: f32, + right_color: &RGBA, + mut right_weight: f32, + normalize_weights: bool, + ) -> RGBA { + // https://drafts.csswg.org/css-color-5/#color-mix-percent-norm + let mut alpha_multiplier = 1.0; + if normalize_weights { + let sum = left_weight + right_weight; + if sum != 1.0 { + let scale = 1.0 / sum; + left_weight *= scale; + right_weight *= scale; + if sum < 1.0 { + alpha_multiplier = sum; + } + } } + + let mix_function = match interpolation.space { + ColorSpace::Srgb => Self::mix_in::, + ColorSpace::LinearSrgb => Self::mix_in::, + ColorSpace::Xyz => Self::mix_in::, + ColorSpace::XyzD50 => Self::mix_in::, + ColorSpace::Lab => Self::mix_in::, + ColorSpace::Hwb => Self::mix_in::, + ColorSpace::Hsl => Self::mix_in::, + ColorSpace::Lch => Self::mix_in::, + }; + mix_function( + left_color, + left_weight, + right_color, + right_weight, + interpolation.hue, + alpha_multiplier, + ) } fn mix_in( - left_color: &Color, + left_color: &RGBA, left_weight: f32, - right_color: &Color, + right_color: &RGBA, right_weight: f32, - hue_adjuster: HueAdjuster, - ) -> Self + hue_interpolation: HueInterpolationMethod, + alpha_multiplier: f32, + ) -> RGBA where S: ModelledColor, { - let left_bg = S::from(left_color.scaled_rgba()); - let right_bg = S::from(right_color.scaled_rgba()); + let left = S::from(*left_color); + let right = S::from(*right_color); - let color = S::lerp(left_bg, left_weight, right_bg, right_weight, hue_adjuster); - let rgba: RGBA = color.into(); - let rgba = if !rgba.in_gamut() { - // TODO: Better gamut mapping. - rgba.clamp() - } else { - rgba - }; - - let fg = left_color.ratios.fg * left_weight + right_color.ratios.fg * right_weight; - Self::new(rgba, ComplexColorRatios { bg: 1., fg }) - } - - fn scaled_rgba(&self) -> RGBA { - if self.ratios.bg == 0. { - return RGBA::transparent(); + let color = S::lerp( + &left, + left_weight, + &right, + right_weight, + hue_interpolation, + ); + let mut rgba = RGBA::from(color.into()); + if alpha_multiplier != 1.0 { + rgba.alpha *= alpha_multiplier; } - if self.ratios.bg == 1. { - return self.color; - } - - RGBA { - red: self.color.red * self.ratios.bg, - green: self.color.green * self.ratios.bg, - blue: self.color.blue * self.ratios.bg, - alpha: self.color.alpha * self.ratios.bg, - } + rgba } } impl Animate for Color { #[inline] fn animate(&self, other: &Self, procedure: Procedure) -> Result { - let self_numeric = self.is_numeric(); - let other_numeric = other.is_numeric(); - - if self_numeric && other_numeric { - return Ok(Self::rgba(self.color.animate(&other.color, procedure)?)); - } - - let self_currentcolor = self.is_currentcolor(); - let other_currentcolor = other.is_currentcolor(); - - if self_currentcolor && other_currentcolor { - let (self_weight, other_weight) = procedure.weights(); - return Ok(Self::new( - RGBA::transparent(), - ComplexColorRatios { - bg: 0., - fg: (self_weight + other_weight) as f32, - }, - )); - } - - // FIXME(emilio): Without these special cases tests fail, looks fairly - // sketchy! - if (self_currentcolor && other_numeric) || (self_numeric && other_currentcolor) { - let (self_weight, other_weight) = procedure.weights(); - return Ok(if self_numeric { - Self::new( - self.color, - ComplexColorRatios { - bg: self_weight as f32, - fg: other_weight as f32, - }, - ) - } else { - Self::new( - other.color, - ComplexColorRatios { - bg: other_weight as f32, - fg: self_weight as f32, - }, - ) - }); - } - - // Compute the "scaled" contribution for `color`. - // Each `Color`, represents a complex combination of foreground color and - // background color where fg and bg represent the overall - // contributions. ie: - // - // color = { bg * mColor, fg * foreground } - // = { bg_color , fg_color } - // = bg_color + fg_color - // - // where `foreground` is `currentcolor`, and `bg_color`, - // `fg_color` are the scaled background and foreground - // contributions. - // - // Each operation, lerp, addition, or accumulate, can be - // represented as a scaled-addition each complex color. ie: - // - // p * col1 + q * col2 - // - // where p = (1 - a), q = a for lerp(a), p = 1, q = 1 for - // addition, etc. - // - // Therefore: - // - // col1 op col2 - // = p * col1 + q * col2 - // = p * { bg_color1, fg_color1 } + q * { bg_color2, fg_color2 } - // = p * (bg_color1 + fg_color1) + q * (bg_color2 + fg_color2) - // = p * bg_color1 + p * fg_color1 + q * bg_color2 + p * fg_color2 - // = (p * bg_color1 + q * bg_color2) + (p * fg_color1 + q * fg_color2) - // = (bg_color1 op bg_color2) + (fg_color1 op fg_color2) - // - // fg_color1 op fg_color2 is equivalent to (fg1 op fg2) * foreground, - // so the final color is: - // - // = { bg_color, fg_color } - // = { 1 * (bg_color1 op bg_color2), (fg1 op fg2) * foreground } - // - // To perform the operation on two complex colors, we need to - // generate the scaled contributions of each background color - // component. - let bg_color1 = self.scaled_rgba(); - let bg_color2 = other.scaled_rgba(); - - // Perform bg_color1 op bg_color2 - let bg_color = bg_color1.animate(&bg_color2, procedure)?; - - // Calculate the final foreground color ratios; perform - // animation on effective fg ratios. - let fg = self.ratios.fg.animate(&other.ratios.fg, procedure)?; - - Ok(Self::new(bg_color, ComplexColorRatios { bg: 1., fg })) + let (left_weight, right_weight) = procedure.weights(); + let mut color = Color::ColorMix(Box::new(ColorMix { + interpolation: ColorInterpolationMethod::srgb(), + left: self.clone(), + left_percentage: Percentage(left_weight as f32), + right: other.clone(), + right_percentage: Percentage(right_weight as f32), + // See https://github.com/w3c/csswg-drafts/issues/7324 + normalize_weights: false, + })); + color.simplify(None); + Ok(color) } } impl ComputeSquaredDistance for Color { #[inline] fn compute_squared_distance(&self, other: &Self) -> Result { - // All comments from the Animate impl also apply here. - let self_numeric = self.is_numeric(); - let other_numeric = other.is_numeric(); - - if self_numeric && other_numeric { - return self.color.compute_squared_distance(&other.color); - } - - let self_currentcolor = self.is_currentcolor(); - let other_currentcolor = other.is_currentcolor(); - if self_currentcolor && other_currentcolor { - return Ok(SquaredDistance::from_sqrt(0.)); - } - - if (self_currentcolor && other_numeric) || (self_numeric && other_currentcolor) { - let color = if self_numeric { - &self.color - } else { - &other.color - }; - // `computed_squared_distance` is symmetric. - return Ok(color.compute_squared_distance(&RGBA::transparent())? + - SquaredDistance::from_sqrt(1.)); - } - - let self_color = self.effective_intermediate_rgba(); - let other_color = other.effective_intermediate_rgba(); - let self_ratios = self.ratios; - let other_ratios = other.ratios; - - Ok(self_color.compute_squared_distance(&other_color)? + - self_ratios.bg.compute_squared_distance(&other_ratios.bg)? + - self_ratios.fg.compute_squared_distance(&other_ratios.fg)?) + let current_color = RGBA::transparent(); + self.to_rgba(current_color) + .compute_squared_distance(&other.to_rgba(current_color)) } } impl ToAnimatedZero for Color { #[inline] fn to_animated_zero(&self) -> Result { - Ok(RGBA::transparent().into()) + Ok(Color::rgba(RGBA::transparent())) } } @@ -365,327 +217,347 @@ impl ToAnimatedZero for Color { trait ModelledColor: Clone + Copy + From + Into { /// Linearly interpolate between the left and right colors. /// - /// The HueAdjuster parameter is only for color spaces where the hue is + /// The HueInterpolationMethod parameter is only for color spaces where the hue is /// represented as an angle (e.g., CIE LCH). fn lerp( - left_bg: Self, + left_bg: &Self, left_weight: f32, - right_bg: Self, + right_bg: &Self, right_weight: f32, - hue_adjuster: HueAdjuster, + hue_interpolation: HueInterpolationMethod, ) -> Self; } -impl ModelledColor for RGBA { - fn lerp( - left_bg: Self, - left_weight: f32, - right_bg: Self, - right_weight: f32, - _: HueAdjuster, - ) -> Self { - // Interpolation with alpha, as per - // https://drafts.csswg.org/css-color/#interpolation-alpha. - let mut red = 0.; - let mut green = 0.; - let mut blue = 0.; +fn interpolate_premultiplied_component( + left: f32, + left_weight: f32, + left_alpha: f32, + right: f32, + right_weight: f32, + right_alpha: f32, + inverse_of_result_alpha: f32, +) -> f32 { + (left * left_weight * left_alpha + right * right_weight * right_alpha) * inverse_of_result_alpha +} - // sRGB is a rectangular othogonal color space, so all component values - // are multiplied by the alpha value. - for &(bg, weight) in &[(left_bg, left_weight), (right_bg, right_weight)] { - red += bg.red * bg.alpha * weight; - green += bg.green * bg.alpha * weight; - blue += bg.blue * bg.alpha * weight; - } +// Normalize hue into [0, 360) +fn normalize_hue(v: f32) -> f32 { + v - 360. * (v / 360.).floor() +} - let alpha = (left_bg.alpha * left_weight + right_bg.alpha * right_weight).min(1.); - if alpha <= 0. { - RGBA::transparent() +fn adjust_hue(left: &mut f32, right: &mut f32, hue_interpolation: HueInterpolationMethod) { + // Adjust the hue angle as per + // https://drafts.csswg.org/css-color/#hue-interpolation. + // + // If both hue angles are NAN, they should be set to 0. Otherwise, if a + // single hue angle is NAN, it should use the other hue angle. + if left.is_nan() { + if right.is_nan() { + *left = 0.; + *right = 0.; } else { - let inv = 1. / alpha; - RGBA::new(red * inv, green * inv, blue * inv, alpha) + *left = *right; } + } else if right.is_nan() { + *right = *left; + } + + if hue_interpolation == HueInterpolationMethod::Specified { + // Angles are not adjusted. They are interpolated like any other + // component. + return; + } + + *left = normalize_hue(*left); + *right = normalize_hue(*right); + + match hue_interpolation { + // https://drafts.csswg.org/css-color/#shorter + HueInterpolationMethod::Shorter => { + let delta = *right - *left; + + if delta > 180. { + *left += 360.; + } else if delta < -180. { + *right += 360.; + } + }, + // https://drafts.csswg.org/css-color/#longer + HueInterpolationMethod::Longer => { + let delta = *right - *left; + if 0. < delta && delta < 180. { + *left += 360.; + } else if -180. < delta && delta < 0. { + *right += 360.; + } + }, + // https://drafts.csswg.org/css-color/#increasing + HueInterpolationMethod::Increasing => { + if *right < *left { + *right += 360.; + } + }, + // https://drafts.csswg.org/css-color/#decreasing + HueInterpolationMethod::Decreasing => { + if *left < *right { + *left += 360.; + } + }, + HueInterpolationMethod::Specified => unreachable!("Handled above"), } } -/// An animated XYZA colour. -#[derive(Clone, Copy, Debug)] -pub struct XYZA { - /// The x component. - pub x: f32, - /// The y component. - pub y: f32, - /// The z component. - pub z: f32, - /// The alpha component. - pub alpha: f32, +fn interpolate_hue( + mut left: f32, + left_weight: f32, + mut right: f32, + right_weight: f32, + hue_interpolation: HueInterpolationMethod, +) -> f32 { + adjust_hue(&mut left, &mut right, hue_interpolation); + left * left_weight + right * right_weight } -impl XYZA { - /// Returns a transparent color. - #[inline] - pub fn transparent() -> Self { - Self { - x: 0., - y: 0., - z: 0., - alpha: 0., - } +fn interpolate_premultiplied( + left: &[f32; 4], + left_weight: f32, + right: &[f32; 4], + right_weight: f32, + hue_index: Option, + hue_interpolation: HueInterpolationMethod, +) -> [f32; 4] { + let left_alpha = left[3]; + let right_alpha = right[3]; + let result_alpha = (left_alpha * left_weight + right_alpha * right_weight).min(1.); + let mut result = [0.; 4]; + if result_alpha <= 0. { + return result; } + + let inverse_of_result_alpha = 1. / result_alpha; + for i in 0..3 { + let is_hue = hue_index == Some(i); + result[i] = if is_hue { + interpolate_hue( + left[i], + left_weight, + right[i], + right_weight, + hue_interpolation, + ) + } else { + interpolate_premultiplied_component( + left[i], + left_weight, + left_alpha, + right[i], + right_weight, + right_alpha, + inverse_of_result_alpha, + ) + }; + } + result[3] = result_alpha; + + result } -impl ModelledColor for XYZA { - fn lerp( - left_bg: Self, - left_weight: f32, - right_bg: Self, - right_weight: f32, - _: HueAdjuster, - ) -> Self { - // Interpolation with alpha, as per - // https://drafts.csswg.org/css-color/#interpolation-alpha. - let mut x = 0.; - let mut y = 0.; - let mut z = 0.; - - // CIE XYZ is a rectangular othogonal color space, so all component - // values are multiplied by the alpha value. - for &(bg, weight) in &[(left_bg, left_weight), (right_bg, right_weight)] { - x += bg.x * bg.alpha * weight; - y += bg.y * bg.alpha * weight; - z += bg.z * bg.alpha * weight; - } - - let alpha = (left_bg.alpha * left_weight + right_bg.alpha * right_weight).min(1.); - if alpha <= 0. { - Self::transparent() - } else { - let inv = 1. / alpha; - Self { - x: x * inv, - y: y * inv, - z: z * inv, - alpha, +macro_rules! impl_lerp { + ($ty:ident, $hue_index:expr) => { + // These ensure the transmutes below are sound. + const_assert_eq!(std::mem::size_of::<$ty>(), std::mem::size_of::() * 4); + const_assert_eq!(std::mem::align_of::<$ty>(), std::mem::align_of::()); + impl ModelledColor for $ty { + fn lerp( + left: &Self, + left_weight: f32, + right: &Self, + right_weight: f32, + hue_interpolation: HueInterpolationMethod, + ) -> Self { + use std::mem::transmute; + unsafe { + transmute::<[f32; 4], Self>(interpolate_premultiplied( + transmute::<&Self, &[f32; 4]>(left), + left_weight, + transmute::<&Self, &[f32; 4]>(right), + right_weight, + $hue_index, + hue_interpolation, + )) + } } } - } + }; } -/// An animated LABA colour. +impl_lerp!(RGBA, None); + #[derive(Clone, Copy, Debug)] -pub struct LABA { - /// The lightness component. - pub lightness: f32, - /// The a component. - pub a: f32, - /// The b component. - pub b: f32, - /// The alpha component. - pub alpha: f32, +#[repr(C)] +struct LinearRGBA { + red: f32, + green: f32, + blue: f32, + alpha: f32, } -impl LABA { - /// Returns a transparent color. - #[inline] - pub fn transparent() -> Self { - Self { - lightness: 0., - a: 0., - b: 0., - alpha: 0., - } - } +impl_lerp!(LinearRGBA, None); + +/// An animated XYZ D65 colour. +#[derive(Clone, Copy, Debug)] +#[repr(C)] +struct XYZD65A { + x: f32, + y: f32, + z: f32, + alpha: f32, } -impl ModelledColor for LABA { - fn lerp( - left_bg: Self, - left_weight: f32, - right_bg: Self, - right_weight: f32, - _: HueAdjuster, - ) -> Self { - // Interpolation with alpha, as per - // https://drafts.csswg.org/css-color/#interpolation-alpha. - let mut lightness = 0.; - let mut a = 0.; - let mut b = 0.; +impl_lerp!(XYZD65A, None); - // CIE LAB is a rectangular othogonal color space, so all component - // values are multiplied by the alpha value. - for &(bg, weight) in &[(left_bg, left_weight), (right_bg, right_weight)] { - lightness += bg.lightness * bg.alpha * weight; - a += bg.a * bg.alpha * weight; - b += bg.b * bg.alpha * weight; - } - - let alpha = (left_bg.alpha * left_weight + right_bg.alpha * right_weight).min(1.); - if alpha <= 0. { - Self::transparent() - } else { - let inv = 1. / alpha; - Self { - lightness: lightness * inv, - a: a * inv, - b: b * inv, - alpha, - } - } - } +/// An animated XYZ D50 colour. +#[derive(Clone, Copy, Debug)] +#[repr(C)] +struct XYZD50A { + x: f32, + y: f32, + z: f32, + alpha: f32, } +impl_lerp!(XYZD50A, None); + +#[derive(Clone, Copy, Debug)] +#[repr(C)] +struct LABA { + lightness: f32, + a: f32, + b: f32, + alpha: f32, +} + +impl_lerp!(LABA, None); + /// An animated LCHA colour. #[derive(Clone, Copy, Debug)] -pub struct LCHA { - /// The lightness component. - pub lightness: f32, - /// The chroma component. - pub chroma: f32, - /// The hua component. - pub hue: f32, - /// The alpha component. - pub alpha: f32, +#[repr(C)] +struct LCHA { + lightness: f32, + chroma: f32, + hue: f32, + alpha: f32, } -impl LCHA { - /// Returns a transparent color. - #[inline] - pub fn transparent() -> Self { - Self { - lightness: 0., - chroma: 0., - hue: 0., - alpha: 0., - } - } +impl_lerp!(LCHA, Some(2)); + +/// An animated hwb() color. +#[derive(Clone, Copy, Debug)] +#[repr(C)] +struct HWBA { + hue: f32, + white: f32, + black: f32, + alpha: f32, } -impl LCHA { - fn adjust(left_bg: Self, right_bg: Self, hue_adjuster: HueAdjuster) -> (Self, Self) { - use std::f32::consts::{PI, TAU}; +impl_lerp!(HWBA, Some(0)); - let mut left_bg = left_bg; - let mut right_bg = right_bg; - - // Adjust the hue angle as per - // https://drafts.csswg.org/css-color/#hue-interpolation. - // - // If both hue angles are NAN, they should be set to 0. Otherwise, if a - // single hue angle is NAN, it should use the other hue angle. - if left_bg.hue.is_nan() || right_bg.hue.is_nan() { - if left_bg.hue.is_nan() && right_bg.hue.is_nan() { - left_bg.hue = 0.; - right_bg.hue = 0.; - } else if left_bg.hue.is_nan() { - left_bg.hue = right_bg.hue; - } else if right_bg.hue.is_nan() { - right_bg.hue = left_bg.hue; - } - } - - if hue_adjuster != HueAdjuster::Specified { - // Normalize hue into [0, 2 * PI) - while left_bg.hue < 0. { - left_bg.hue += TAU; - } - while left_bg.hue > TAU { - left_bg.hue -= TAU; - } - - while right_bg.hue < 0. { - right_bg.hue += TAU; - } - while right_bg.hue >= TAU { - right_bg.hue -= TAU; - } - } - - match hue_adjuster { - HueAdjuster::Shorter => { - let delta = right_bg.hue - left_bg.hue; - - if delta > PI { - left_bg.hue += PI; - } else if delta < -1. * PI { - right_bg.hue += PI; - } - }, - - HueAdjuster::Longer => { - let delta = right_bg.hue - left_bg.hue; - if 0. < delta && delta < PI { - left_bg.hue += TAU; - } else if -1. * PI < delta && delta < 0. { - right_bg.hue += TAU; - } - }, - - HueAdjuster::Increasing => { - if right_bg.hue < left_bg.hue { - right_bg.hue += TAU; - } - }, - - HueAdjuster::Decreasing => { - if left_bg.hue < right_bg.hue { - left_bg.hue += TAU; - } - }, - - //Angles are not adjusted. They are interpolated like any other - //component. - HueAdjuster::Specified => {}, - } - - (left_bg, right_bg) - } +#[derive(Clone, Copy, Debug)] +#[repr(C)] +struct HSLA { + hue: f32, + sat: f32, + light: f32, + alpha: f32, } -impl ModelledColor for LCHA { - fn lerp( - left_bg: Self, - left_weight: f32, - right_bg: Self, - right_weight: f32, - hue_adjuster: HueAdjuster, - ) -> Self { - // Interpolation with alpha, as per - // https://drafts.csswg.org/css-color/#interpolation-alpha. - let (left_bg, right_bg) = Self::adjust(left_bg, right_bg, hue_adjuster); +impl_lerp!(HSLA, Some(0)); - let mut lightness = 0.; - let mut chroma = 0.; - let mut hue = 0.; +// https://drafts.csswg.org/css-color/#rgb-to-hsl +// +// We also return min/max for the hwb conversion. +fn rgb_to_hsl(rgba: RGBA) -> (HSLA, f32, f32) { + let RGBA { + red, + green, + blue, + alpha, + } = rgba; + let max = red.max(green).max(blue); + let min = red.min(green).min(blue); + let mut hue = std::f32::NAN; + let mut sat = 0.; + let light = (min + max) / 2.; + let d = max - min; - // CIE LCH is a cylindical polar color space, so all component values - // are multiplied by the alpha value. - for &(bg, weight) in &[(left_bg, left_weight), (right_bg, right_weight)] { - lightness += bg.lightness * bg.alpha * weight; - chroma += bg.chroma * bg.alpha * weight; - // LCHA is a cylindrical color space so the hue coordinate is not - // pre-multipled by the alpha component when interpolating. - hue += bg.hue * weight; - } - - let alpha = (left_bg.alpha * left_weight + right_bg.alpha * right_weight).min(1.); - if alpha <= 0. { - Self::transparent() + if d != 0. { + sat = if light == 0.0 || light == 1.0 { + 0. } else { - let inv = 1. / alpha; - Self { - lightness: lightness * inv, - chroma: chroma * inv, - hue, - alpha, - } + (max - light) / light.min(1. - light) + }; + + if max == red { + hue = (green - blue) / d + if green < blue { 6. } else { 0. } + } else if max == green { + hue = (blue - red) / d + 2.; + } else { + hue = (red - green) / d + 4.; + } + + hue *= 60.; + } + + ( + HSLA { + hue, + sat, + light, + alpha, + }, + min, + max, + ) +} + +impl From for HSLA { + fn from(rgba: RGBA) -> Self { + rgb_to_hsl(rgba).0 + } +} + +impl From for RGBA { + fn from(hsla: HSLA) -> Self { + // cssparser expects hue in the 0..1 range. + let hue_normalized = normalize_hue(hsla.hue) / 360.; + let (r, g, b) = cssparser::hsl_to_rgb(hue_normalized, hsla.sat, hsla.light); + RGBA::new(r, g, b, hsla.alpha) + } +} + +impl From for HWBA { + // https://drafts.csswg.org/css-color/#rgb-to-hwb + fn from(rgba: RGBA) -> Self { + let (hsl, min, max) = rgb_to_hsl(rgba); + Self { + hue: hsl.hue, + white: min, + black: 1. - max, + alpha: rgba.alpha, } } } -impl From for XYZA { - /// Convert an RGBA colour to XYZ as specified in [1]. - /// - /// [1]: https://drafts.csswg.org/css-color/#rgb-to-lab +impl From for RGBA { + fn from(hwba: HWBA) -> Self { + let hue_normalized = normalize_hue(hwba.hue) / 360.; + let (r, g, b) = cssparser::hwb_to_rgb(hue_normalized, hwba.white, hwba.black); + RGBA::new(r, g, b, hwba.alpha) + } +} + +impl From for LinearRGBA { fn from(rgba: RGBA) -> Self { fn linearize(value: f32) -> f32 { let sign = if value < 0. { -1. } else { 1. }; @@ -696,15 +568,39 @@ impl From for XYZA { sign * ((abs + 0.055) / 1.055).powf(2.4) } + Self { + red: linearize(rgba.red), + green: linearize(rgba.green), + blue: linearize(rgba.blue), + alpha: rgba.alpha, + } + } +} - #[cfg_attr(rustfmt, rustfmt_skip)] - const SRGB_TO_XYZ: Transform3D = Transform3D::new( - 0.41239079926595934, 0.21263900587151027, 0.01933081871559182, 0., - 0.357584339383878, 0.715168678767756, 0.11919477979462598, 0., - 0.1804807884018343, 0.07219231536073371, 0.9505321522496607, 0., - 0., 0., 0., 1., - ); +impl From for RGBA { + fn from(lrgba: LinearRGBA) -> Self { + fn delinearize(value: f32) -> f32 { + let sign = if value < 0. { -1. } else { 1. }; + let abs = value.abs(); + if abs > 0.0031308 { + sign * (1.055 * abs.powf(1. / 2.4) - 0.055) + } else { + 12.92 * value + } + } + Self { + red: delinearize(lrgba.red), + green: delinearize(lrgba.green), + blue: delinearize(lrgba.blue), + alpha: lrgba.alpha, + } + } +} + +impl From for XYZD50A { + fn from(d65: XYZD65A) -> Self { + // https://drafts.csswg.org/css-color-4/#color-conversion-code #[cfg_attr(rustfmt, rustfmt_skip)] const BRADFORD: Transform3D = Transform3D::new( 1.0479298208405488, 0.029627815688159344, -0.009243058152591178, 0., @@ -712,34 +608,100 @@ impl From for XYZA { -0.05019222954313557, -0.01707382502938514, 0.7518742899580008, 0., 0., 0., 0., 1., ); - - // 1. Convert from sRGB to linear-light sRGB (undo gamma encoding). - let rgb = Vector3D::new( - linearize(rgba.red), - linearize(rgba.green), - linearize(rgba.blue), - ); - - // 2. Convert from linear sRGB to CIE XYZ. - // 3. Convert from a D65 whitepoint (used by sRGB) to the D50 whitepoint used in XYZ - // with the Bradford transform. - let xyz = SRGB_TO_XYZ.then(&BRADFORD).transform_vector3d(rgb); - - XYZA { - x: xyz.x, - y: xyz.y, - z: xyz.z, - alpha: rgba.alpha, + let d50 = BRADFORD.transform_vector3d(Vector3D::new(d65.x, d65.y, d65.z)); + Self { + x: d50.x, + y: d50.y, + z: d50.z, + alpha: d65.alpha, } } } -impl From for LABA { +impl From for XYZD65A { + fn from(d50: XYZD50A) -> Self { + // https://drafts.csswg.org/css-color-4/#color-conversion-code + #[cfg_attr(rustfmt, rustfmt_skip)] + const BRADFORD_INVERSE: Transform3D = Transform3D::new( + 0.9554734527042182, -0.028369706963208136, 0.012314001688319899, 0., + -0.023098536874261423, 1.0099954580058226, -0.020507696433477912, 0., + 0.0632593086610217, 0.021041398966943008, 1.3303659366080753, 0., + 0., 0., 0., 1., + ); + let d65 = BRADFORD_INVERSE.transform_vector3d(Vector3D::new(d50.x, d50.y, d50.z)); + Self { + x: d65.x, + y: d65.y, + z: d65.z, + alpha: d50.alpha, + } + } +} + +impl From for XYZD65A { + fn from(lrgba: LinearRGBA) -> Self { + // https://drafts.csswg.org/css-color-4/#color-conversion-code + #[cfg_attr(rustfmt, rustfmt_skip)] + const LSRGB_TO_XYZ: Transform3D = Transform3D::new( + 0.41239079926595934, 0.21263900587151027, 0.01933081871559182, 0., + 0.357584339383878, 0.715168678767756, 0.11919477979462598, 0., + 0.1804807884018343, 0.07219231536073371, 0.9505321522496607, 0., + 0., 0., 0., 1., + ); + let linear_rgb = Vector3D::new(lrgba.red, lrgba.green, lrgba.blue); + let xyz = LSRGB_TO_XYZ.transform_vector3d(linear_rgb); + Self { + x: xyz.x, + y: xyz.y, + z: xyz.z, + alpha: lrgba.alpha, + } + } +} + +impl From for LinearRGBA { + fn from(d65: XYZD65A) -> Self { + // https://drafts.csswg.org/css-color-4/#color-conversion-code + #[cfg_attr(rustfmt, rustfmt_skip)] + const XYZ_TO_LSRGB: Transform3D = Transform3D::new( + 3.2409699419045226, -0.9692436362808796, 0.05563007969699366, 0., + -1.537383177570094, 1.8759675015077202, -0.20397695888897652, 0., + -0.4986107602930034, 0.04155505740717559, 1.0569715142428786, 0., + 0., 0., 0., 1., + ); + + let xyz = Vector3D::new(d65.x, d65.y, d65.z); + let rgb = XYZ_TO_LSRGB.transform_vector3d(xyz); + Self { + red: rgb.x, + green: rgb.y, + blue: rgb.z, + alpha: d65.alpha, + } + } +} + +impl From for RGBA { + fn from(d65: XYZD65A) -> Self { + Self::from(LinearRGBA::from(d65)) + } +} + +impl From for XYZD65A { + /// Convert an RGBA colour to XYZ as specified in [1]. + /// + /// [1]: https://drafts.csswg.org/css-color/#rgb-to-lab + fn from(rgba: RGBA) -> Self { + Self::from(LinearRGBA::from(rgba)) + } +} + +impl From for LABA { /// Convert an XYZ colour to LAB as specified in [1] and [2]. /// /// [1]: https://drafts.csswg.org/css-color/#rgb-to-lab /// [2]: https://drafts.csswg.org/css-color/#color-conversion-code - fn from(xyza: XYZA) -> Self { + fn from(xyza: XYZD50A) -> Self { const WHITE: [f32; 3] = [0.96422, 1., 0.82521]; fn compute_f(value: f32) -> f32 { @@ -778,7 +740,7 @@ impl From for LCHA { /// /// [1]: https://drafts.csswg.org/css-color/#color-conversion-code fn from(laba: LABA) -> Self { - let hue = laba.b.atan2(laba.a); + let hue = laba.b.atan2(laba.a) * DEG_PER_RAD; let chroma = (laba.a * laba.a + laba.b * laba.b).sqrt(); LCHA { lightness: laba.lightness, @@ -794,8 +756,9 @@ impl From for LABA { /// /// [1]: https://drafts.csswg.org/css-color/#color-conversion-code fn from(lcha: LCHA) -> Self { - let a = lcha.chroma * lcha.hue.cos(); - let b = lcha.chroma * lcha.hue.sin(); + let hue_radians = lcha.hue * RAD_PER_DEG; + let a = lcha.chroma * hue_radians.cos(); + let b = lcha.chroma * hue_radians.sin(); LABA { lightness: lcha.lightness, a, @@ -805,7 +768,7 @@ impl From for LABA { } } -impl From for XYZA { +impl From for XYZD50A { /// Convert a CIELAB color to XYZ as specified in [1] and [2]. /// /// [1]: https://drafts.csswg.org/css-color/#lab-to-predefined @@ -836,7 +799,7 @@ impl From for XYZA { (116. * f2 - 16.) / KAPPA }; - XYZA { + Self { x: x * WHITE[0], y: y * WHITE[1], z: z * WHITE[2], @@ -845,85 +808,38 @@ impl From for XYZA { } } -impl From for RGBA { - /// Convert an XYZ color to sRGB as specified in [1] and [2]. - /// - /// [1]: https://www.w3.org/TR/css-color-4/#lab-to-predefined - /// [2]: https://www.w3.org/TR/css-color-4/#color-conversion-code - fn from(xyza: XYZA) -> Self { - #[cfg_attr(rustfmt, rustfmt_skip)] - const BRADFORD_INVERSE: Transform3D = Transform3D::new( - 0.9554734527042182, -0.028369706963208136, 0.012314001688319899, 0., - -0.023098536874261423, 1.0099954580058226, -0.020507696433477912, 0., - 0.0632593086610217, 0.021041398966943008, 1.3303659366080753, 0., - 0., 0., 0., 1., - ); +impl From for RGBA { + fn from(d50: XYZD50A) -> Self { + Self::from(XYZD65A::from(d50)) + } +} - #[cfg_attr(rustfmt, rustfmt_skip)] - const XYZ_TO_SRGB: Transform3D = Transform3D::new( - 3.2409699419045226, -0.9692436362808796, 0.05563007969699366, 0., - -1.537383177570094, 1.8759675015077202, -0.20397695888897652, 0., - -0.4986107602930034, 0.04155505740717559, 1.0569715142428786, 0., - 0., 0., 0., 1., - ); - - // 2. Convert from a D50 whitepoint (used by Lab) to the D65 whitepoint - // used in sRGB, with the Bradford transform. - // 3. Convert from (D65-adapted) CIE XYZ to linear-light srgb - let xyz = Vector3D::new(xyza.x, xyza.y, xyza.z); - let linear_rgb = BRADFORD_INVERSE.then(&XYZ_TO_SRGB).transform_vector3d(xyz); - - // 4. Convert from linear-light srgb to srgb (do gamma encoding). - fn delinearize(value: f32) -> f32 { - let sign = if value < 0. { -1. } else { 1. }; - let abs = value.abs(); - - if abs > 0.0031308 { - sign * (1.055 * abs.powf(1. / 2.4) - 0.055) - } else { - 12.92 * value - } - } - - let red = delinearize(linear_rgb.x); - let green = delinearize(linear_rgb.y); - let blue = delinearize(linear_rgb.z); - - RGBA { - red, - green, - blue, - alpha: xyza.alpha, - } +impl From for XYZD50A { + fn from(rgba: RGBA) -> Self { + Self::from(XYZD65A::from(rgba)) } } impl From for LABA { fn from(rgba: RGBA) -> Self { - let xyza: XYZA = rgba.into(); - xyza.into() + Self::from(XYZD50A::from(rgba)) } } impl From for RGBA { fn from(laba: LABA) -> Self { - let xyza: XYZA = laba.into(); - xyza.into() + Self::from(XYZD50A::from(laba)) } } impl From for LCHA { fn from(rgba: RGBA) -> Self { - let xyza: XYZA = rgba.into(); - let laba: LABA = xyza.into(); - laba.into() + Self::from(LABA::from(rgba)) } } impl From for RGBA { fn from(lcha: LCHA) -> Self { - let laba: LABA = lcha.into(); - let xyza: XYZA = laba.into(); - xyza.into() + Self::from(LABA::from(lcha)) } } diff --git a/components/style/values/computed/box.rs b/components/style/values/computed/box.rs index dd1e4900672..f05870d6ead 100644 --- a/components/style/values/computed/box.rs +++ b/components/style/values/computed/box.rs @@ -13,9 +13,11 @@ use crate::values::specified::box_ as specified; pub use crate::values::specified::box_::{ AnimationName, AnimationTimeline, Appearance, BreakBetween, BreakWithin, - Clear as SpecifiedClear, Contain, ContentVisibility, Display, Float as SpecifiedFloat, Overflow, - OverflowAnchor, OverflowClipBox, OverscrollBehavior, ScrollSnapAlign, ScrollSnapAxis, - ScrollSnapStrictness, ScrollSnapType, ScrollbarGutter, TouchAction, TransitionProperty, WillChange, + Clear as SpecifiedClear, Contain, ContainerName, ContainerType, ContentVisibility, Display, + Float as SpecifiedFloat, Overflow, OverflowAnchor, OverflowClipBox, + OverscrollBehavior, ScrollSnapAlign, ScrollSnapAxis, ScrollSnapStop, + ScrollSnapStrictness, ScrollSnapType, ScrollbarGutter, TouchAction, + TransitionProperty, WillChange, }; /// A computed value for the `vertical-align` property. diff --git a/components/style/values/computed/color.rs b/components/style/values/computed/color.rs index 7610bfbba3b..573cb6fe500 100644 --- a/components/style/values/computed/color.rs +++ b/components/style/values/computed/color.rs @@ -7,6 +7,7 @@ use crate::values::animated::color::RGBA as AnimatedRGBA; use crate::values::animated::ToAnimatedValue; use crate::values::generics::color::{GenericCaretColor, GenericColor, GenericColorOrAuto}; +use crate::values::computed::percentage::Percentage; use cssparser::{Color as CSSParserColor, RGBA}; use std::fmt; use style_traits::{CssWriter, ToCss}; @@ -20,7 +21,20 @@ pub type ColorPropertyValue = RGBA; pub type MozFontSmoothingBackgroundColor = RGBA; /// A computed value for ``. -pub type Color = GenericColor; +pub type Color = GenericColor; + +impl ToCss for Color { + fn to_css(&self, dest: &mut CssWriter) -> fmt::Result + where + W: fmt::Write, + { + match *self { + Self::Numeric(ref c) => c.to_css(dest), + Self::CurrentColor => CSSParserColor::CurrentColor.to_css(dest), + Self::ColorMix(ref m) => m.to_css(dest), + } + } +} impl Color { /// Returns a complex color value representing transparent. @@ -28,67 +42,21 @@ impl Color { Color::rgba(RGBA::transparent()) } - /// Combine this complex color with the given foreground color into - /// a numeric RGBA color. It currently uses linear blending. - pub fn to_rgba(&self, fg_color: RGBA) -> RGBA { - // Common cases that the complex color is either pure numeric color or - // pure currentcolor. - if self.is_numeric() { - return self.color; - } - - if self.is_currentcolor() { - return fg_color; - } - - let ratios = &self.ratios; - let color = &self.color; - - // For the more complicated case that the alpha value differs, - // we use the following formula to compute the components: - // alpha = self_alpha * bg_ratio + fg_alpha * fg_ratio - // color = (self_color * self_alpha * bg_ratio + - // fg_color * fg_alpha * fg_ratio) / alpha - - let p1 = ratios.bg; - let a1 = color.alpha_f32(); - let r1 = a1 * color.red_f32(); - let g1 = a1 * color.green_f32(); - let b1 = a1 * color.blue_f32(); - - let p2 = ratios.fg; - let a2 = fg_color.alpha_f32(); - let r2 = a2 * fg_color.red_f32(); - let g2 = a2 * fg_color.green_f32(); - let b2 = a2 * fg_color.blue_f32(); - - let a = p1 * a1 + p2 * a2; - if a <= 0. { - return RGBA::transparent(); - } - let a = a.min(1.); - - let inv = 1. / a; - - let r = (p1 * r1 + p2 * r2) * inv; - let g = (p1 * g1 + p2 * g2) * inv; - let b = (p1 * b1 + p2 * b2) * inv; - RGBA::from_floats(r, g, b, a) + /// Returns opaque black. + pub fn black() -> Color { + Color::rgba(RGBA::new(0, 0, 0, 255)) } -} -impl ToCss for Color { - fn to_css(&self, dest: &mut CssWriter) -> fmt::Result - where - W: fmt::Write, - { - if self.is_currentcolor() { - return CSSParserColor::CurrentColor.to_css(dest); - } - if self.is_numeric() { - return self.color.to_css(dest); - } - Ok(()) + /// Returns opaque white. + pub fn white() -> Color { + Color::rgba(RGBA::new(255, 255, 255, 255)) + } + + /// Combine this complex color with the given foreground color into + /// a numeric RGBA color. + pub fn into_rgba(mut self, current_color: RGBA) -> RGBA { + self.simplify(Some(¤t_color)); + *self.as_numeric().unwrap() } } diff --git a/components/style/values/computed/image.rs b/components/style/values/computed/image.rs index 980017b2822..5f650631c89 100644 --- a/components/style/values/computed/image.rs +++ b/components/style/values/computed/image.rs @@ -31,6 +31,9 @@ pub use specified::ImageRendering; pub type Image = generic::GenericImage; +// Images should remain small, see https://github.com/servo/servo/pull/18430 +size_of_test!(Image, 40); + /// Computed values for a CSS gradient. /// pub type Gradient = generic::GenericGradient< @@ -47,8 +50,6 @@ pub type Gradient = generic::GenericGradient< /// Computed values for CSS cross-fade /// pub type CrossFade = generic::CrossFade; -/// A computed percentage or nothing. -pub type PercentOrNone = generic::PercentOrNone; /// A computed radial gradient ending shape. pub type EndingShape = generic::GenericEndingShape; diff --git a/components/style/values/computed/length.rs b/components/style/values/computed/length.rs index b8ff80587e6..896001cbcd8 100644 --- a/components/style/values/computed/length.rs +++ b/components/style/values/computed/length.rs @@ -40,7 +40,7 @@ impl ToComputedValue for specified::NoCalcLength { context .builder .add_flags(ComputedValueFlags::USES_VIEWPORT_UNITS); - length.to_computed_value(context.viewport_size_for_viewport_unit_resolution()) + length.to_computed_value(context) }, specified::NoCalcLength::ServoCharacterWidth(length) => { length.to_computed_value(context.style().get_font().clone_font_size().size()) diff --git a/components/style/values/computed/mod.rs b/components/style/values/computed/mod.rs index 68194eb733a..68256cdd20f 100644 --- a/components/style/values/computed/mod.rs +++ b/components/style/values/computed/mod.rs @@ -16,6 +16,7 @@ use super::specified; use super::{CSSFloat, CSSInteger}; use crate::computed_value_flags::ComputedValueFlags; use crate::context::QuirksMode; +use crate::stylesheets::container_rule::ContainerInfo; use crate::font_metrics::{FontMetrics, FontMetricsOrientation}; use crate::media_queries::Device; #[cfg(feature = "gecko")] @@ -44,11 +45,12 @@ pub use self::basic_shape::FillRule; pub use self::border::{BorderCornerRadius, BorderRadius, BorderSpacing}; pub use self::border::{BorderImageRepeat, BorderImageSideWidth}; pub use self::border::{BorderImageSlice, BorderImageWidth}; -pub use self::box_::{AnimationIterationCount, AnimationName, AnimationTimeline, Contain}; +pub use self::box_::{AnimationIterationCount, AnimationName, AnimationTimeline, Contain, ContainerName, ContainerType}; pub use self::box_::{Appearance, BreakBetween, BreakWithin, Clear, ContentVisibility, Float}; pub use self::box_::{Display, Overflow, OverflowAnchor, TransitionProperty}; pub use self::box_::{OverflowClipBox, OverscrollBehavior, Perspective, Resize, ScrollbarGutter}; -pub use self::box_::{ScrollSnapAlign, ScrollSnapAxis, ScrollSnapStrictness, ScrollSnapType}; +pub use self::box_::{ScrollSnapAlign, ScrollSnapAxis, ScrollSnapStop}; +pub use self::box_::{ScrollSnapStrictness, ScrollSnapType}; pub use self::box_::{TouchAction, VerticalAlign, WillChange}; pub use self::color::{Color, ColorOrAuto, ColorPropertyValue, ColorScheme, PrintColorAdjust}; pub use self::column::ColumnCount; @@ -72,7 +74,7 @@ pub use self::list::ListStyleType; pub use self::list::Quotes; pub use self::motion::{OffsetPath, OffsetRotate}; pub use self::outline::OutlineStyle; -pub use self::page::{Orientation, PageName, PageSize, PaperSize}; +pub use self::page::{PageOrientation, PageName, PageSize, PaperSize}; pub use self::percentage::{NonNegativePercentage, Percentage}; pub use self::position::AspectRatio; pub use self::position::{ @@ -97,6 +99,7 @@ pub use self::transform::{TransformOrigin, TransformStyle, Translate}; pub use self::ui::CursorImage; pub use self::ui::{Cursor, MozForceBrokenImageIcon, UserSelect}; pub use super::specified::TextTransform; +pub use super::specified::ViewportVariant; pub use super::specified::{BorderStyle, TextDecorationLine}; pub use super::{Auto, Either, None_}; pub use app_units::Au; @@ -169,6 +172,9 @@ pub struct Context<'a> { /// values, which SMIL allows. pub for_smil_animation: bool, + /// Returns the container information to evaluate a given container query. + pub container_info: Option, + /// The property we are computing a value for, if it is a non-inherited /// property. None if we are computed a value for an inherited property /// or not computing for a property at all (e.g. in a media query @@ -197,6 +203,40 @@ impl<'a> Context<'a> { in_media_query: true, quirks_mode, for_smil_animation: false, + container_info: None, + for_non_inherited_property: None, + rule_cache_conditions: RefCell::new(&mut conditions), + }; + + f(&context) + } + + /// Creates a suitable context for container query evaluation for the style + /// specified. + pub fn for_container_query_evaluation( + device: &Device, + container_info_and_style: Option<(ContainerInfo, Arc)>, + f: F, + ) -> R + where + F: FnOnce(&Context) -> R, + { + let mut conditions = RuleCacheConditions::default(); + + let (container_info, style) = match container_info_and_style { + Some((ci, s)) => (Some(ci), Some(s)), + None => (None, None), + }; + + let style = style.as_ref().map(|s| &**s); + let quirks_mode = device.quirks_mode(); + let context = Context { + builder: StyleBuilder::for_inheritance(device, style, None), + cached_system_font: None, + in_media_query: true, + quirks_mode, + for_smil_animation: false, + container_info, for_non_inherited_property: None, rule_cache_conditions: RefCell::new(&mut conditions), }; @@ -253,10 +293,13 @@ impl<'a> Context<'a> { } /// The current viewport size, used to resolve viewport units. - pub fn viewport_size_for_viewport_unit_resolution(&self) -> default::Size2D { + pub fn viewport_size_for_viewport_unit_resolution( + &self, + variant: ViewportVariant, + ) -> default::Size2D { self.builder .device - .au_viewport_size_for_viewport_unit_resolution() + .au_viewport_size_for_viewport_unit_resolution(variant) } /// The default computed style we're getting our reset style from. @@ -300,8 +343,8 @@ impl<'a, 'cx, 'cx_a: 'cx, S: ToComputedValue + 'a> ComputedVecIter<'a, 'cx, 'cx_ /// Construct an iterator from a slice of specified values and a context pub fn new(cx: &'cx Context<'cx_a>, values: &'a [S]) -> Self { ComputedVecIter { - cx: cx, - values: values, + cx, + values, } } } diff --git a/components/style/values/computed/page.rs b/components/style/values/computed/page.rs index 080681e008f..5daf6bbcde8 100644 --- a/components/style/values/computed/page.rs +++ b/components/style/values/computed/page.rs @@ -11,7 +11,7 @@ use crate::values::generics::size::Size2D; use crate::values::specified::page as specified; pub use generics::page::GenericPageSize; -pub use generics::page::Orientation; +pub use generics::page::PageOrientation; pub use generics::page::PaperSize; pub use specified::PageName; @@ -26,7 +26,7 @@ pub enum PageSize { /// Specified size, paper size, or paper size and orientation. Size(Size2D), /// `landscape` or `portrait` value, no specified size. - Orientation(Orientation), + Orientation(PageOrientation), /// `auto` value Auto, } @@ -37,11 +37,11 @@ impl ToComputedValue for specified::PageSize { fn to_computed_value(&self, ctx: &Context) -> Self::ComputedValue { match &*self { Self::Size(s) => PageSize::Size(s.to_computed_value(ctx)), - Self::PaperSize(p, Orientation::Landscape) => PageSize::Size(Size2D { + Self::PaperSize(p, PageOrientation::Landscape) => PageSize::Size(Size2D { width: p.long_edge().to_computed_value(ctx), height: p.short_edge().to_computed_value(ctx), }), - Self::PaperSize(p, Orientation::Portrait) => PageSize::Size(Size2D { + Self::PaperSize(p, PageOrientation::Portrait) => PageSize::Size(Size2D { width: p.short_edge().to_computed_value(ctx), height: p.long_edge().to_computed_value(ctx), }), diff --git a/components/style/values/computed/percentage.rs b/components/style/values/computed/percentage.rs index 7430d82d471..4e9732ade2c 100644 --- a/components/style/values/computed/percentage.rs +++ b/components/style/values/computed/percentage.rs @@ -6,6 +6,7 @@ use crate::values::animated::ToAnimatedValue; use crate::values::generics::NonNegative; +use crate::values::specified::percentage::ToPercentage; use crate::values::{serialize_percentage, CSSFloat}; use crate::Zero; use std::fmt; @@ -64,6 +65,12 @@ impl Zero for Percentage { } } +impl ToPercentage for Percentage { + fn to_percentage(&self) -> CSSFloat { + self.0 + } +} + impl std::ops::AddAssign for Percentage { fn add_assign(&mut self, other: Self) { self.0 += other.0 diff --git a/components/style/values/computed/ratio.rs b/components/style/values/computed/ratio.rs index ba40039eae1..ae8997cfc06 100644 --- a/components/style/values/computed/ratio.rs +++ b/components/style/values/computed/ratio.rs @@ -72,6 +72,16 @@ impl ComputeSquaredDistance for Ratio { } } +impl Zero for Ratio { + fn zero() -> Self { + Self::new(Zero::zero(), One::one()) + } + + fn is_zero(&self) -> bool { + self.0.is_zero() + } +} + impl Ratio { /// Returns a new Ratio. #[inline] diff --git a/components/style/values/computed/svg.rs b/components/style/values/computed/svg.rs index 344f1d83518..640c3bfda70 100644 --- a/components/style/values/computed/svg.rs +++ b/components/style/values/computed/svg.rs @@ -8,7 +8,6 @@ use crate::values::computed::color::Color; use crate::values::computed::url::ComputedUrl; use crate::values::computed::{LengthPercentage, NonNegativeLengthPercentage, Opacity}; use crate::values::generics::svg as generic; -use crate::values::RGBA; use crate::Zero; pub use crate::values::specified::{DProperty, MozContextProperties, SVGPaintOrder}; @@ -22,9 +21,8 @@ pub type SVGPaintKind = generic::GenericSVGPaintKind; impl SVGPaint { /// Opaque black color pub fn black() -> Self { - let rgba = RGBA::from_floats(0., 0., 0., 1.).into(); SVGPaint { - kind: generic::SVGPaintKind::Color(rgba), + kind: generic::SVGPaintKind::Color(Color::black()), fallback: generic::SVGPaintFallback::Unset, } } diff --git a/components/style/values/generics/calc.rs b/components/style/values/generics/calc.rs index d9044bbb818..5d52cdf5f5f 100644 --- a/components/style/values/generics/calc.rs +++ b/components/style/values/generics/calc.rs @@ -45,13 +45,33 @@ pub enum SortKey { Cap, Ch, Deg, + Dvb, + Dvh, + Dvi, + Dvmax, + Dvmin, + Dvw, Em, Ex, Ic, + Lvb, + Lvh, + Lvi, + Lvmax, + Lvmin, + Lvw, Px, Rem, Sec, + Svb, + Svh, + Svi, + Svmax, + Svmin, + Svw, + Vb, Vh, + Vi, Vmax, Vmin, Vw, diff --git a/components/style/values/generics/color.rs b/components/style/values/generics/color.rs index 5b477dee60d..7ab3880bc1a 100644 --- a/components/style/values/generics/color.rs +++ b/components/style/values/generics/color.rs @@ -4,81 +4,268 @@ //! Generic types for color properties. -/// Ratios representing the contribution of color and currentcolor to -/// the final color value. -/// -/// NOTE(emilio): For animated colors, the sum of these two might be more than -/// one (because the background color would've been scaled down already). So -/// beware that it is not generally safe to assume that if bg is 1 then fg is 0, -/// for example. -#[derive(Clone, Copy, Debug, MallocSizeOf, PartialEq, ToAnimatedValue, ToShmem)] -#[repr(C)] -pub struct ComplexColorRatios { - /// Numeric color contribution. - pub bg: f32, - /// currentcolor contribution. - pub fg: f32, -} - -impl ComplexColorRatios { - /// Ratios representing a `Numeric` color. - pub const NUMERIC: ComplexColorRatios = ComplexColorRatios { bg: 1., fg: 0. }; - /// Ratios representing the `CurrentColor` color. - pub const CURRENT_COLOR: ComplexColorRatios = ComplexColorRatios { bg: 0., fg: 1. }; -} +use std::fmt::{self, Write}; +use style_traits::{CssWriter, ParseError, ToCss}; +use crate::values::{Parse, ParserContext, Parser}; +use crate::values::specified::percentage::ToPercentage; +use crate::values::animated::ToAnimatedValue; +use crate::values::animated::color::RGBA as AnimatedRGBA; /// This struct represents a combined color from a numeric color and /// the current foreground color (currentcolor keyword). -#[derive(Clone, Copy, Debug, MallocSizeOf, PartialEq, ToAnimatedValue, ToShmem)] +#[derive(Clone, Debug, MallocSizeOf, PartialEq, ToAnimatedValue, ToShmem)] #[repr(C)] -pub struct GenericColor { +pub enum GenericColor { /// The actual numeric color. - pub color: RGBA, - /// The ratios of mixing between numeric and currentcolor. - /// The formula is: `color * ratios.bg + currentcolor * ratios.fg`. - pub ratios: ComplexColorRatios, + Numeric(RGBA), + /// The `CurrentColor` keyword. + CurrentColor, + /// The color-mix() function. + ColorMix(Box>), +} + +/// A color space as defined in [1]. +/// +/// [1]: https://drafts.csswg.org/css-color-4/#typedef-color-space +#[derive(Clone, Copy, Debug, Eq, MallocSizeOf, Parse, PartialEq, ToAnimatedValue, ToComputedValue, ToCss, ToResolvedValue, ToShmem)] +#[repr(u8)] +pub enum ColorSpace { + /// The sRGB color space. + Srgb, + /// The linear-sRGB color space. + LinearSrgb, + /// The CIEXYZ color space. + #[parse(aliases = "xyz-d65")] + Xyz, + /// https://drafts.csswg.org/css-color-4/#valdef-color-xyz + XyzD50, + /// The CIELAB color space. + Lab, + /// https://drafts.csswg.org/css-color-4/#valdef-hsl-hsl + Hsl, + /// https://drafts.csswg.org/css-color-4/#valdef-hwb-hwb + Hwb, + /// The CIELAB color space, expressed in cylindrical coordinates. + Lch, + // TODO: Oklab, Lch +} + +impl ColorSpace { + /// Returns whether this is a ``. + pub fn is_polar(self) -> bool { + match self { + Self::Srgb | Self::LinearSrgb | Self::Xyz | Self::XyzD50 | Self::Lab => false, + Self::Hsl | Self::Hwb | Self::Lch => true, + } + } +} + +/// A hue-interpolation-method as defined in [1]. +/// +/// [1]: https://drafts.csswg.org/css-color-4/#typedef-hue-interpolation-method +#[derive(Clone, Copy, Debug, Eq, MallocSizeOf, Parse, PartialEq, ToAnimatedValue, ToComputedValue, ToCss, ToResolvedValue, ToShmem)] +#[repr(u8)] +pub enum HueInterpolationMethod { + /// https://drafts.csswg.org/css-color-4/#shorter + Shorter, + /// https://drafts.csswg.org/css-color-4/#longer + Longer, + /// https://drafts.csswg.org/css-color-4/#increasing + Increasing, + /// https://drafts.csswg.org/css-color-4/#decreasing + Decreasing, + /// https://drafts.csswg.org/css-color-4/#specified + Specified, +} + +/// https://drafts.csswg.org/css-color-4/#color-interpolation-method +#[derive(Clone, Copy, Debug, Eq, MallocSizeOf, PartialEq, ToShmem, ToAnimatedValue, ToComputedValue, ToResolvedValue)] +#[repr(C)] +pub struct ColorInterpolationMethod { + /// The color-space the interpolation should be done in. + pub space: ColorSpace, + /// The hue interpolation method. + pub hue: HueInterpolationMethod, +} + +impl ColorInterpolationMethod { + /// Returns the srgb interpolation method. + pub fn srgb() -> Self { + Self { + space: ColorSpace::Srgb, + hue: HueInterpolationMethod::Shorter, + } + } +} + +impl Parse for ColorInterpolationMethod { + fn parse<'i, 't>( + _: &ParserContext, + input: &mut Parser<'i, 't>, + ) -> Result> { + input.expect_ident_matching("in")?; + let space = ColorSpace::parse(input)?; + // https://drafts.csswg.org/css-color-4/#hue-interpolation + // Unless otherwise specified, if no specific hue interpolation + // algorithm is selected by the host syntax, the default is shorter. + let hue = if space.is_polar() { + input.try_parse(|input| -> Result<_, ParseError<'i>> { + let hue = HueInterpolationMethod::parse(input)?; + input.expect_ident_matching("hue")?; + Ok(hue) + }).unwrap_or(HueInterpolationMethod::Shorter) + } else { + HueInterpolationMethod::Shorter + }; + Ok(Self { space, hue }) + } +} + +impl ToCss for ColorInterpolationMethod { + fn to_css(&self, dest: &mut CssWriter) -> fmt::Result + where + W: Write, + { + dest.write_str("in ")?; + self.space.to_css(dest)?; + if self.hue != HueInterpolationMethod::Shorter { + dest.write_char(' ')?; + self.hue.to_css(dest)?; + dest.write_str(" hue")?; + } + Ok(()) + } +} + +/// A restricted version of the css `color-mix()` function, which only supports +/// percentages. +/// +/// https://drafts.csswg.org/css-color-5/#color-mix +#[derive(Clone, Debug, MallocSizeOf, PartialEq, ToAnimatedValue, ToComputedValue, ToResolvedValue, ToShmem)] +#[allow(missing_docs)] +#[repr(C)] +pub struct GenericColorMix { + pub interpolation: ColorInterpolationMethod, + pub left: Color, + pub left_percentage: Percentage, + pub right: Color, + pub right_percentage: Percentage, + pub normalize_weights: bool, +} + +pub use self::GenericColorMix as ColorMix; + +impl ToCss for ColorMix { + fn to_css(&self, dest: &mut CssWriter) -> fmt::Result + where + W: Write, + { + fn can_omit(percent: &Percentage, other: &Percentage, is_left: bool) -> bool { + if percent.is_calc() { + return false; + } + if percent.to_percentage() == 0.5 { + return other.to_percentage() == 0.5; + } + if is_left { + return false; + } + (1.0 - percent.to_percentage() - other.to_percentage()).abs() <= f32::EPSILON + } + + dest.write_str("color-mix(")?; + self.interpolation.to_css(dest)?; + dest.write_str(", ")?; + self.left.to_css(dest)?; + if !can_omit(&self.left_percentage, &self.right_percentage, true) { + dest.write_str(" ")?; + self.left_percentage.to_css(dest)?; + } + dest.write_str(", ")?; + self.right.to_css(dest)?; + if !can_omit(&self.right_percentage, &self.left_percentage, false) { + dest.write_str(" ")?; + self.right_percentage.to_css(dest)?; + } + dest.write_str(")") + } +} + +impl ColorMix, Percentage> { + fn to_rgba(&self) -> Option + where + RGBA: Clone + ToAnimatedValue, + Percentage: ToPercentage, + { + use crate::values::animated::color::Color as AnimatedColor; + let left = self.left.as_numeric()?.clone().to_animated_value(); + let right = self.right.as_numeric()?.clone().to_animated_value(); + Some(ToAnimatedValue::from_animated_value(AnimatedColor::mix( + &self.interpolation, + &left, + self.left_percentage.to_percentage(), + &right, + self.right_percentage.to_percentage(), + self.normalize_weights, + ))) + } } pub use self::GenericColor as Color; -impl Color { - /// Returns a color value representing currentcolor. - pub fn currentcolor() -> Self { - Color { - color: cssparser::RGBA::transparent(), - ratios: ComplexColorRatios::CURRENT_COLOR, +impl Color { + /// Returns the numeric rgba value if this color is numeric, or None + /// otherwise. + pub fn as_numeric(&self) -> Option<&RGBA> { + match *self { + Self::Numeric(ref rgba) => Some(rgba), + _ => None, } } -} -impl Color { - /// Create a color based upon the specified ratios. - pub fn new(color: RGBA, ratios: ComplexColorRatios) -> Self { - Self { color, ratios } + /// Simplifies the color-mix()es to the extent possible given a current + /// color (or not). + pub fn simplify(&mut self, current_color: Option<&RGBA>) + where + RGBA: Clone + ToAnimatedValue, + Percentage: ToPercentage, + { + match *self { + Self::Numeric(..) => {}, + Self::CurrentColor => { + if let Some(c) = current_color { + *self = Self::Numeric(c.clone()); + } + }, + Self::ColorMix(ref mut mix) => { + mix.left.simplify(current_color); + mix.right.simplify(current_color); + + if let Some(mix) = mix.to_rgba() { + *self = Self::Numeric(mix); + } + }, + } + } + + /// Returns a color value representing currentcolor. + pub fn currentcolor() -> Self { + Self::CurrentColor } /// Returns a numeric color representing the given RGBA value. pub fn rgba(color: RGBA) -> Self { - Self { - color, - ratios: ComplexColorRatios::NUMERIC, - } - } - - /// Whether it is a numeric color (no currentcolor component). - pub fn is_numeric(&self) -> bool { - self.ratios == ComplexColorRatios::NUMERIC + Self::Numeric(color) } /// Whether it is a currentcolor value (no numeric color component). pub fn is_currentcolor(&self) -> bool { - self.ratios == ComplexColorRatios::CURRENT_COLOR + matches!(*self, Self::CurrentColor) } -} -impl From for Color { - fn from(color: RGBA) -> Self { - Self::rgba(color) + /// Whether it is a numeric color (no currentcolor component). + pub fn is_numeric(&self) -> bool { + matches!(*self, Self::Numeric(..)) } } diff --git a/components/style/values/generics/image.rs b/components/style/values/generics/image.rs index dd504a07d24..d5e830810a8 100644 --- a/components/style/values/generics/image.rs +++ b/components/style/values/generics/image.rs @@ -8,6 +8,7 @@ use crate::custom_properties; use crate::values::generics::position::PositionComponent; +use crate::values::generics::Optional; use crate::values::serialize_atom_identifier; use crate::Atom; use crate::Zero; @@ -71,20 +72,6 @@ pub struct GenericCrossFade { pub elements: crate::OwnedSlice>, } -/// A ` | none` value. Represents optional percentage values -/// assosicated with cross-fade images. -#[derive( - Clone, Debug, MallocSizeOf, PartialEq, ToComputedValue, ToResolvedValue, ToShmem, ToCss, -)] -#[repr(C, u8)] -pub enum PercentOrNone { - /// `none` variant. - #[css(skip)] - None, - /// A percentage variant. - Percent(Percentage), -} - /// An optional percent and a cross fade image. #[derive( Clone, Debug, MallocSizeOf, PartialEq, ToComputedValue, ToResolvedValue, ToShmem, ToCss, @@ -92,7 +79,7 @@ pub enum PercentOrNone { #[repr(C)] pub struct GenericCrossFadeElement { /// The percent of the final image that `image` will be. - pub percent: PercentOrNone, + pub percent: Optional, /// A color or image that will be blended when cross-fade is /// evaluated. pub image: GenericCrossFadeImage, diff --git a/components/style/values/generics/mod.rs b/components/style/values/generics/mod.rs index b15d4f01887..8c10085af18 100644 --- a/components/style/values/generics/mod.rs +++ b/components/style/values/generics/mod.rs @@ -310,3 +310,74 @@ impl ClipRectOrAuto { } pub use page::PageSize; + +/// An optional value, much like `Option`, but with a defined struct layout +/// to be able to use it from C++ as well. +/// +/// Note that this is relatively inefficient, struct-layout-wise, as you have +/// one byte for the tag, but padding to the alignment of T. If you have +/// multiple optional values and care about struct compactness, you might be +/// better off "coalescing" the combinations into a parent enum. But that +/// shouldn't matter for most use cases. +#[allow(missing_docs)] +#[derive( + Animate, + Clone, + ComputeSquaredDistance, + Copy, + Debug, + MallocSizeOf, + Parse, + PartialEq, + SpecifiedValueInfo, + ToAnimatedValue, + ToAnimatedZero, + ToComputedValue, + ToCss, + ToResolvedValue, + ToShmem, +)] +#[repr(C, u8)] +pub enum Optional { + #[css(skip)] + None, + Some(T), +} + +impl Optional { + /// Returns whether this value is present. + pub fn is_some(&self) -> bool { + matches!(*self, Self::Some(..)) + } + + /// Returns whether this value is not present. + pub fn is_none(&self) -> bool { + matches!(*self, Self::None) + } + + /// Turns this Optional<> into a regular rust Option<>. + pub fn into_rust(self) -> Option { + match self { + Self::Some(v) => Some(v), + Self::None => None, + } + } + + /// Return a reference to the containing value, if any, as a plain rust + /// Option<>. + pub fn as_ref(&self) -> Option<&T> { + match *self { + Self::Some(ref v) => Some(v), + Self::None => None, + } + } +} + +impl From> for Optional { + fn from(rust: Option) -> Self { + match rust { + Some(t) => Self::Some(t), + None => Self::None, + } + } +} diff --git a/components/style/values/generics/page.rs b/components/style/values/generics/page.rs index 1de1a8e912c..efb67e0812d 100644 --- a/components/style/values/generics/page.rs +++ b/components/style/values/generics/page.rs @@ -87,7 +87,7 @@ impl PaperSize { ToShmem, )] #[repr(u8)] -pub enum Orientation { +pub enum PageOrientation { /// Portrait orientation Portrait, /// Landscape orientation @@ -95,8 +95,8 @@ pub enum Orientation { } #[inline] -fn is_portrait(orientation: &Orientation) -> bool { - *orientation == Orientation::Portrait +fn is_portrait(orientation: &PageOrientation) -> bool { + *orientation == PageOrientation::Portrait } /// Page size property @@ -110,9 +110,9 @@ pub enum GenericPageSize { /// Page dimensions. Size(S), /// An orientation with no size. - Orientation(Orientation), + Orientation(PageOrientation), /// Paper size by name - PaperSize(PaperSize, #[css(skip_if = "is_portrait")] Orientation), + PaperSize(PaperSize, #[css(skip_if = "is_portrait")] PageOrientation), } pub use self::GenericPageSize as PageSize; diff --git a/components/style/values/mod.rs b/components/style/values/mod.rs index 9d7b7d74acc..e221619c843 100644 --- a/components/style/values/mod.rs +++ b/components/style/values/mod.rs @@ -16,7 +16,6 @@ pub use cssparser::{SourceLocation, Token, RGBA}; use precomputed_hash::PrecomputedHash; use selectors::parser::SelectorParseErrorKind; use std::fmt::{self, Debug, Write}; -use std::hash; use style_traits::{CssWriter, ParseError, StyleParseErrorKind, ToCss}; use to_shmem::impl_trivial_to_shmem; @@ -458,28 +457,33 @@ impl CustomIdent { ident: &CowRcStr<'i>, excluding: &[&str], ) -> Result> { - use crate::properties::CSSWideKeyword; - // https://drafts.csswg.org/css-values-4/#custom-idents: - // - // The CSS-wide keywords are not valid s. The default - // keyword is reserved and is also not a valid . - // - if CSSWideKeyword::from_ident(ident).is_ok() || ident.eq_ignore_ascii_case("default") { + if !Self::is_valid(ident, excluding) { return Err( location.new_custom_error(SelectorParseErrorKind::UnexpectedIdent(ident.clone())) ); } - - // https://drafts.csswg.org/css-values-4/#custom-idents: - // - // Excluded keywords are excluded in all ASCII case permutations. - // if excluding.iter().any(|s| ident.eq_ignore_ascii_case(s)) { Err(location.new_custom_error(StyleParseErrorKind::UnspecifiedError)) } else { Ok(CustomIdent(Atom::from(ident.as_ref()))) } } + + fn is_valid(ident: &str, excluding: &[&str]) -> bool { + use crate::properties::CSSWideKeyword; + // https://drafts.csswg.org/css-values-4/#custom-idents: + // + // The CSS-wide keywords are not valid s. The default + // keyword is reserved and is also not a valid . + if CSSWideKeyword::from_ident(ident).is_ok() || ident.eq_ignore_ascii_case("default") { + return false; + } + + // https://drafts.csswg.org/css-values-4/#custom-idents: + // + // Excluded keywords are excluded in all ASCII case permutations. + !excluding.iter().any(|s| ident.eq_ignore_ascii_case(s)) + } } impl ToCss for CustomIdent { @@ -496,45 +500,72 @@ impl ToCss for CustomIdent { /// /// /// +/// +/// We use a single atom for these. Empty atom represents `none` animation. +#[repr(transparent)] #[derive( - Clone, Debug, MallocSizeOf, SpecifiedValueInfo, ToComputedValue, ToResolvedValue, ToShmem, + Clone, Debug, Hash, PartialEq, MallocSizeOf, SpecifiedValueInfo, ToComputedValue, ToResolvedValue, ToShmem, )] -#[repr(C, u8)] -pub enum TimelineOrKeyframesName { - /// - Ident(CustomIdent), - /// - QuotedString(Atom), -} +pub struct TimelineOrKeyframesName(Atom); impl TimelineOrKeyframesName { /// pub fn from_ident(value: &str) -> Self { - let location = SourceLocation { line: 0, column: 0 }; - let custom_ident = CustomIdent::from_ident(location, &value.into(), &["none"]).ok(); - match custom_ident { - Some(ident) => Self::Ident(ident), - None => Self::QuotedString(value.into()), - } + Self(Atom::from(value)) + } + + /// Returns the `none` value. + pub fn none() -> Self { + Self(atom!("")) + } + + /// Returns whether this is the special `none` value. + pub fn is_none(&self) -> bool { + self.0 == atom!("") } /// Create a new TimelineOrKeyframesName from Atom. #[cfg(feature = "gecko")] pub fn from_atom(atom: Atom) -> Self { - debug_assert_ne!(atom, atom!("")); - - // FIXME: We might want to preserve , but currently Gecko - // stores both of and into nsAtom, so - // we can't tell it. - Self::Ident(CustomIdent(atom)) + Self(atom) } /// The name as an Atom pub fn as_atom(&self) -> &Atom { - match *self { - Self::Ident(ref ident) => &ident.0, - Self::QuotedString(ref atom) => atom, + &self.0 + } + + fn parse<'i, 't>(input: &mut Parser<'i, 't>, invalid: &[&str]) -> Result> { + debug_assert!(invalid.contains(&"none")); + let location = input.current_source_location(); + Ok(match *input.next()? { + Token::Ident(ref s) => Self(CustomIdent::from_ident(location, s, invalid)?.0), + Token::QuotedString(ref s) => Self(Atom::from(s.as_ref())), + ref t => return Err(location.new_unexpected_token_error(t.clone())), + }) + } + + fn to_css(&self, dest: &mut CssWriter, invalid: &[&str]) -> fmt::Result + where + W: Write, + { + debug_assert!(invalid.contains(&"none")); + + if self.0 == atom!("") { + return dest.write_str("none") } + + let mut serialize = |s: &_| { + if CustomIdent::is_valid(s, invalid) { + serialize_identifier(s, dest) + } else { + s.to_css(dest) + } + }; + #[cfg(feature = "gecko")] + return self.0.with_str(|s| serialize(s)); + #[cfg(feature = "servo")] + return serialize(self.0.as_ref()); } } @@ -547,53 +578,70 @@ pub trait IsAuto { fn is_auto(&self) -> bool; } -impl PartialEq for TimelineOrKeyframesName { - fn eq(&self, other: &Self) -> bool { - self.as_atom() == other.as_atom() +/// The typedef of . +#[repr(transparent)] +#[derive( + Clone, Debug, Deref, Hash, Eq, PartialEq, MallocSizeOf, SpecifiedValueInfo, ToComputedValue, ToResolvedValue, ToShmem, +)] +pub struct TimelineName(TimelineOrKeyframesName); + +impl TimelineName { + /// Returns the `none` value. + pub fn none() -> Self { + Self(TimelineOrKeyframesName::none()) } } -impl hash::Hash for TimelineOrKeyframesName { - fn hash(&self, state: &mut H) - where - H: hash::Hasher, - { - self.as_atom().hash(state) +impl Parse for TimelineName { + fn parse<'i, 't>(_: &ParserContext, input: &mut Parser<'i, 't>) -> Result> { + Ok(Self(TimelineOrKeyframesName::parse(input, &["none", "auto"])?)) } } -impl Parse for TimelineOrKeyframesName { - fn parse<'i, 't>( - _context: &ParserContext, - input: &mut Parser<'i, 't>, - ) -> Result> { - let location = input.current_source_location(); - match *input.next()? { - Token::Ident(ref s) => Ok(Self::Ident(CustomIdent::from_ident( - location, - s, - &["none"], - )?)), - Token::QuotedString(ref s) => Ok(Self::QuotedString(Atom::from(s.as_ref()))), - ref t => Err(location.new_unexpected_token_error(t.clone())), - } - } -} - -impl ToCss for TimelineOrKeyframesName { +impl ToCss for TimelineName { fn to_css(&self, dest: &mut CssWriter) -> fmt::Result where W: Write, { - match *self { - Self::Ident(ref ident) => ident.to_css(dest), - Self::QuotedString(ref atom) => atom.to_string().to_css(dest), - } + self.0.to_css(dest, &["none", "auto"]) } } -/// The typedef of . -pub type TimelineName = TimelineOrKeyframesName; - /// The typedef of . -pub type KeyframesName = TimelineOrKeyframesName; +#[derive( + Clone, Debug, Deref, Hash, Eq, PartialEq, MallocSizeOf, SpecifiedValueInfo, ToComputedValue, ToResolvedValue, ToShmem, +)] +pub struct KeyframesName(TimelineOrKeyframesName); + +impl KeyframesName { + /// Create a new KeyframesName from Atom. + #[cfg(feature = "gecko")] + pub fn from_atom(atom: Atom) -> Self { + Self(TimelineOrKeyframesName::from_atom(atom)) + } + + /// + pub fn from_ident(value: &str) -> Self { + Self(TimelineOrKeyframesName::from_ident(value)) + } + + /// Returns the `none` value. + pub fn none() -> Self { + Self(TimelineOrKeyframesName::none()) + } +} + +impl Parse for KeyframesName { + fn parse<'i, 't>(_: &ParserContext, input: &mut Parser<'i, 't>) -> Result> { + Ok(Self(TimelineOrKeyframesName::parse(input, &["none"])?)) + } +} + +impl ToCss for KeyframesName { + fn to_css(&self, dest: &mut CssWriter) -> fmt::Result + where + W: Write, + { + self.0.to_css(dest, &["none"]) + } +} diff --git a/components/style/values/specified/box.rs b/components/style/values/specified/box.rs index 35b1b3ff648..3a6677de42a 100644 --- a/components/style/values/specified/box.rs +++ b/components/style/values/specified/box.rs @@ -17,7 +17,6 @@ use crate::values::{CustomIdent, KeyframesName, TimelineName}; use crate::Atom; use cssparser::Parser; use num_traits::FromPrimitive; -use selectors::parser::SelectorParseErrorKind; use std::fmt::{self, Debug, Formatter, Write}; use style_traits::{CssWriter, KeywordsCollectFn, ParseError}; use style_traits::{SpecifiedValueInfo, StyleParseErrorKind, ToCss}; @@ -680,33 +679,30 @@ impl AnimationIterationCount { PartialEq, SpecifiedValueInfo, ToComputedValue, + ToCss, ToResolvedValue, ToShmem, )] #[value_info(other_values = "none")] -pub struct AnimationName(pub Option); +pub struct AnimationName(pub KeyframesName); impl AnimationName { /// Get the name of the animation as an `Atom`. pub fn as_atom(&self) -> Option<&Atom> { - self.0.as_ref().map(|n| n.as_atom()) + if self.is_none() { + return None; + } + Some(self.0.as_atom()) } /// Returns the `none` value. pub fn none() -> Self { - AnimationName(None) + AnimationName(KeyframesName::none()) } -} -impl ToCss for AnimationName { - fn to_css(&self, dest: &mut CssWriter) -> fmt::Result - where - W: Write, - { - match self.0 { - Some(ref name) => name.to_css(dest), - None => dest.write_str("none"), - } + /// Returns whether this is the none value. + pub fn is_none(&self) -> bool { + self.0.is_none() } } @@ -716,14 +712,88 @@ impl Parse for AnimationName { input: &mut Parser<'i, 't>, ) -> Result> { if let Ok(name) = input.try_parse(|input| KeyframesName::parse(context, input)) { - return Ok(AnimationName(Some(name))); + return Ok(AnimationName(name)); } input.expect_ident_matching("none")?; - Ok(AnimationName(None)) + Ok(AnimationName(KeyframesName::none())) } } +/// A value for the used in scroll(). +/// +/// https://drafts.csswg.org/scroll-animations-1/rewrite#typedef-scroller +#[derive( + Clone, + Debug, + Eq, + Hash, + MallocSizeOf, + Parse, + PartialEq, + SpecifiedValueInfo, + ToComputedValue, + ToCss, + ToResolvedValue, + ToShmem, +)] +#[repr(u8)] +pub enum Scroller { + /// The nearest ancestor scroll container. (Default.) + Nearest, + /// The document viewport as the scroll container. + Root, + // FIXME: Bug 1764450: Once we support container-name CSS property (Bug 1744224), we may add + // here, based on the result of the spec issue: + // https://github.com/w3c/csswg-drafts/issues/7046 +} + +impl Default for Scroller { + fn default() -> Self { + Self::Nearest + } +} + +/// A value for the used in scroll(). +/// +/// https://drafts.csswg.org/scroll-animations-1/rewrite#typedef-axis +#[derive( + Clone, + Debug, + Eq, + Hash, + MallocSizeOf, + Parse, + PartialEq, + SpecifiedValueInfo, + ToComputedValue, + ToCss, + ToResolvedValue, + ToShmem, +)] +#[repr(u8)] +pub enum ScrollAxis { + /// The block axis of the scroll container. (Default.) + Block, + /// The inline axis of the scroll container. + Inline, + /// The vertical block axis of the scroll container. + Vertical, + /// The horizontal axis of the scroll container. + Horizontal, +} + +impl Default for ScrollAxis { + fn default() -> Self { + Self::Block + } +} + +#[inline] +fn is_default(value: &T) -> bool { + *value == Default::default() +} + /// A value for the . /// /// https://drafts.csswg.org/css-animations-2/#typedef-single-animation-timeline @@ -745,10 +815,15 @@ impl Parse for AnimationName { pub enum AnimationTimeline { /// Use default timeline. The animation’s timeline is a DocumentTimeline. Auto, - /// The animation is not associated with a timeline. - None, /// The scroll-timeline name Timeline(TimelineName), + /// The scroll() notation + /// https://drafts.csswg.org/scroll-animations-1/rewrite#scroll-notation + #[css(function)] + Scroll( + #[css(skip_if = "is_default")] ScrollAxis, + #[css(skip_if = "is_default")] Scroller, + ), } impl AnimationTimeline { @@ -769,16 +844,30 @@ impl Parse for AnimationTimeline { input: &mut Parser<'i, 't>, ) -> Result> { // We are using the same parser for TimelineName and KeyframesName, but animation-timeline - // accepts "auto", so need to manually parse this. (We can not derive Parse because - // TimelineName excludes only "none" keyword.) + // accepts "auto", so need to manually parse this. (We can not derive + // Parse because TimelineName excludes only the "none" keyword). + // // FIXME: Bug 1733260: we may drop None based on the spec issue: - // Note: https://github.com/w3c/csswg-drafts/issues/6674. + // https://github.com/w3c/csswg-drafts/issues/6674 + // + // If `none` is removed, then we could potentially shrink this the same + // way we deal with animation-name. if input.try_parse(|i| i.expect_ident_matching("auto")).is_ok() { return Ok(Self::Auto); } if input.try_parse(|i| i.expect_ident_matching("none")).is_ok() { - return Ok(Self::None); + return Ok(AnimationTimeline::Timeline(TimelineName::none())); + } + + // https://drafts.csswg.org/scroll-animations-1/rewrite#scroll-notation + if input.try_parse(|i| i.expect_function_matching("scroll")).is_ok() { + return input.parse_nested_block(|i| { + Ok(Self::Scroll( + i.try_parse(ScrollAxis::parse).unwrap_or(ScrollAxis::Block), + i.try_parse(Scroller::parse).unwrap_or(Scroller::Nearest), + )) + }); } TimelineName::parse(context, input).map(AnimationTimeline::Timeline) @@ -991,6 +1080,28 @@ impl ToCss for ScrollSnapAlign { } } +#[allow(missing_docs)] +#[cfg_attr(feature = "servo", derive(Deserialize, Serialize))] +#[derive( + Clone, + Copy, + Debug, + Eq, + MallocSizeOf, + Parse, + PartialEq, + SpecifiedValueInfo, + ToComputedValue, + ToCss, + ToResolvedValue, + ToShmem, +)] +#[repr(u8)] +pub enum ScrollSnapStop { + Normal, + Always, +} + #[allow(missing_docs)] #[cfg_attr(feature = "servo", derive(Deserialize, Serialize))] #[derive( @@ -1196,7 +1307,9 @@ impl Parse for WillChange { &["will-change", "none", "all", "auto"], )?; - if ident.0 == atom!("scroll-position") { + if context.in_ua_sheet() && ident.0 == atom!("-moz-fixed-pos-containing-block") { + bits |= WillChangeBits::FIXPOS_CB_NON_SVG; + } else if ident.0 == atom!("scroll-position") { bits |= WillChangeBits::SCROLL; } else { bits |= change_bits_for_maybe_property(&parser_ident, context); @@ -1213,9 +1326,8 @@ impl Parse for WillChange { bitflags! { /// Values for the `touch-action` property. - #[derive(MallocSizeOf, SpecifiedValueInfo, ToComputedValue, ToResolvedValue, ToShmem)] - /// These constants match Gecko's `NS_STYLE_TOUCH_ACTION_*` constants. - #[value_info(other_values = "auto,none,manipulation,pan-x,pan-y,pinch-zoom")] + #[derive(MallocSizeOf, SpecifiedValueInfo, ToComputedValue, ToCss, ToResolvedValue, ToShmem, Parse)] + #[css(bitflags(single = "none,auto,manipulation", mixed = "pan-x,pan-y,pinch-zoom"))] #[repr(C)] pub struct TouchAction: u8 { /// `none` variant @@ -1241,178 +1353,32 @@ impl TouchAction { } } -impl ToCss for TouchAction { - fn to_css(&self, dest: &mut CssWriter) -> fmt::Result - where - W: Write, - { - if self.contains(TouchAction::AUTO) { - return dest.write_str("auto"); - } - if self.contains(TouchAction::NONE) { - return dest.write_str("none"); - } - if self.contains(TouchAction::MANIPULATION) { - return dest.write_str("manipulation"); - } - - let mut has_any = false; - macro_rules! maybe_write_value { - ($ident:path => $str:expr) => { - if self.contains($ident) { - if has_any { - dest.write_str(" ")?; - } - has_any = true; - dest.write_str($str)?; - } - }; - } - maybe_write_value!(TouchAction::PAN_X => "pan-x"); - maybe_write_value!(TouchAction::PAN_Y => "pan-y"); - maybe_write_value!(TouchAction::PINCH_ZOOM => "pinch-zoom"); - - debug_assert!(has_any); - Ok(()) - } -} - -impl Parse for TouchAction { - /// auto | none | [ pan-x || pan-y || pinch-zoom ] | manipulation - fn parse<'i, 't>( - _context: &ParserContext, - input: &mut Parser<'i, 't>, - ) -> Result> { - let mut result = TouchAction::empty(); - while let Ok(name) = input.try_parse(|i| i.expect_ident_cloned()) { - let flag = match_ignore_ascii_case! { &name, - "pan-x" => Some(TouchAction::PAN_X), - "pan-y" => Some(TouchAction::PAN_Y), - "pinch-zoom" => Some(TouchAction::PINCH_ZOOM), - "none" if result.is_empty() => return Ok(TouchAction::NONE), - "manipulation" if result.is_empty() => return Ok(TouchAction::MANIPULATION), - "auto" if result.is_empty() => return Ok(TouchAction::AUTO), - _ => None - }; - - let flag = match flag { - Some(flag) if !result.contains(flag) => flag, - _ => { - return Err( - input.new_custom_error(SelectorParseErrorKind::UnexpectedIdent(name)) - ); - }, - }; - result.insert(flag); - } - - if !result.is_empty() { - Ok(result) - } else { - Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError)) - } - } -} - bitflags! { - #[derive(MallocSizeOf, SpecifiedValueInfo, ToComputedValue, ToResolvedValue, ToShmem)] - #[value_info(other_values = "none,strict,content,size,layout,paint")] + #[derive(MallocSizeOf, Parse, SpecifiedValueInfo, ToComputedValue, ToCss, ToResolvedValue, ToShmem)] + #[css(bitflags(single = "none,strict,content", mixed="size,layout,paint,inline-size", overlapping_bits))] #[repr(C)] /// Constants for contain: https://drafts.csswg.org/css-contain/#contain-property pub struct Contain: u8 { /// `none` variant, just for convenience. const NONE = 0; - /// 'size' variant, turns on size containment - const SIZE = 1 << 0; + /// `inline-size` variant, turns on single-axis inline size containment + const INLINE_SIZE = 1 << 0; + /// `block-size` variant, turns on single-axis block size containment, internal only + const BLOCK_SIZE = 1 << 1; /// `layout` variant, turns on layout containment - const LAYOUT = 1 << 1; + const LAYOUT = 1 << 2; /// `paint` variant, turns on paint containment - const PAINT = 1 << 2; + const PAINT = 1 << 3; + /// 'size' variant, turns on size containment + const SIZE = 1 << 4 | Contain::INLINE_SIZE.bits | Contain::BLOCK_SIZE.bits; + /// `content` variant, turns on layout and paint containment + const CONTENT = 1 << 5 | Contain::LAYOUT.bits | Contain::PAINT.bits; /// `strict` variant, turns on all types of containment - const STRICT = 1 << 3; - /// 'content' variant, turns on layout and paint containment - const CONTENT = 1 << 4; - /// variant with all the bits that contain: strict turns on - const STRICT_BITS = Contain::LAYOUT.bits | Contain::PAINT.bits | Contain::SIZE.bits; - /// variant with all the bits that contain: content turns on - const CONTENT_BITS = Contain::LAYOUT.bits | Contain::PAINT.bits; + const STRICT = 1 << 6 | Contain::LAYOUT.bits | Contain::PAINT.bits | Contain::SIZE.bits; } } -impl ToCss for Contain { - fn to_css(&self, dest: &mut CssWriter) -> fmt::Result - where - W: Write, - { - if self.is_empty() { - return dest.write_str("none"); - } - if self.contains(Contain::STRICT) { - return dest.write_str("strict"); - } - if self.contains(Contain::CONTENT) { - return dest.write_str("content"); - } - - let mut has_any = false; - macro_rules! maybe_write_value { - ($ident:path => $str:expr) => { - if self.contains($ident) { - if has_any { - dest.write_str(" ")?; - } - has_any = true; - dest.write_str($str)?; - } - }; - } - maybe_write_value!(Contain::SIZE => "size"); - maybe_write_value!(Contain::LAYOUT => "layout"); - maybe_write_value!(Contain::PAINT => "paint"); - - debug_assert!(has_any); - Ok(()) - } -} - -impl Parse for Contain { - /// none | strict | content | [ size || layout || paint ] - fn parse<'i, 't>( - _context: &ParserContext, - input: &mut Parser<'i, 't>, - ) -> Result> { - let mut result = Contain::empty(); - while let Ok(name) = input.try_parse(|i| i.expect_ident_cloned()) { - let flag = match_ignore_ascii_case! { &name, - "size" => Some(Contain::SIZE), - "layout" => Some(Contain::LAYOUT), - "paint" => Some(Contain::PAINT), - "strict" if result.is_empty() => return Ok(Contain::STRICT | Contain::STRICT_BITS), - "content" if result.is_empty() => return Ok(Contain::CONTENT | Contain::CONTENT_BITS), - "none" if result.is_empty() => return Ok(result), - _ => None - }; - - let flag = match flag { - Some(flag) if !result.contains(flag) => flag, - _ => { - return Err( - input.new_custom_error(SelectorParseErrorKind::UnexpectedIdent(name)) - ); - }, - }; - result.insert(flag); - } - - if !result.is_empty() { - Ok(result) - } else { - Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError)) - } - } -} - -#[allow(missing_docs)] +/// https://drafts.csswg.org/css-contain-2/#content-visibility #[cfg_attr(feature = "servo", derive(Deserialize, Serialize))] #[derive( Clone, @@ -1440,6 +1406,61 @@ pub enum ContentVisibility { Visible, } +bitflags! { + #[derive(MallocSizeOf, SpecifiedValueInfo, ToComputedValue, ToCss, Parse, ToResolvedValue, ToShmem)] + #[repr(C)] + #[allow(missing_docs)] + #[css(bitflags(single="none", mixed="style,size,inline-size", overlapping_bits))] + /// https://drafts.csswg.org/css-contain-3/#container-type + /// + /// TODO: block-size is on the spec but it seems it was removed? WPTs don't + /// support it, see https://github.com/w3c/csswg-drafts/issues/7179. + pub struct ContainerType: u8 { + /// The `none` variant. + const NONE = 0; + /// The `style` variant. + const STYLE = 1 << 0; + /// The `inline-size` variant. + const INLINE_SIZE = 1 << 1; + /// The `size` variant, exclusive with `inline-size` (they sharing bits + /// guarantees this). + const SIZE = 1 << 2 | Self::INLINE_SIZE.bits; + } +} + +/// https://drafts.csswg.org/css-contain-3/#container-name +#[repr(transparent)] +#[derive(Clone, Debug, MallocSizeOf, PartialEq, SpecifiedValueInfo, ToComputedValue, ToCss, ToResolvedValue, ToShmem)] +pub struct ContainerName(#[css(iterable, if_empty = "none")] pub crate::OwnedSlice); + +impl ContainerName { + /// Return the `none` value. + pub fn none() -> Self { + Self(Default::default()) + } + + /// Returns whether this is the `none` value. + pub fn is_none(&self) -> bool { + self.0.is_empty() + } +} + +impl Parse for ContainerName { + fn parse<'i, 't>( _: &ParserContext, input: &mut Parser<'i, 't>) -> Result> { + let mut idents = vec![]; + let location = input.current_source_location(); + let first = input.expect_ident()?; + if first.eq_ignore_ascii_case("none") { + return Ok(Self::none()) + } + idents.push(CustomIdent::from_ident(location, first, &["none"])?); + while let Ok(ident) = input.try_parse(|input| input.expect_ident_cloned()) { + idents.push(CustomIdent::from_ident(location, &ident, &["none"])?); + } + Ok(ContainerName(idents.into())) + } +} + /// A specified value for the `perspective` property. pub type Perspective = GenericPerspective; @@ -1890,10 +1911,6 @@ pub enum Appearance { MozMacSourceList, #[parse(condition = "ParserContext::in_ua_or_chrome_sheet")] MozMacSourceListSelection, - #[parse(condition = "ParserContext::in_ua_or_chrome_sheet")] - MozMacVibrantTitlebarDark, - #[parse(condition = "ParserContext::in_ua_or_chrome_sheet")] - MozMacVibrantTitlebarLight, /// A themed focus outline (for outline:auto). /// @@ -2101,9 +2118,9 @@ impl Overflow { } bitflags! { - #[derive(MallocSizeOf, SpecifiedValueInfo, ToComputedValue, ToResolvedValue, ToShmem)] - #[value_info(other_values = "auto,stable,both-edges")] + #[derive(MallocSizeOf, SpecifiedValueInfo, ToCss, ToComputedValue, ToResolvedValue, ToShmem, Parse)] #[repr(C)] + #[css(bitflags(single = "auto", mixed = "stable,both-edges", validate_mixed="Self::has_stable"))] /// Values for scrollbar-gutter: /// pub struct ScrollbarGutter: u8 { @@ -2116,56 +2133,9 @@ bitflags! { } } -impl ToCss for ScrollbarGutter { - fn to_css(&self, dest: &mut CssWriter) -> fmt::Result - where - W: Write, - { - if self.is_empty() { - return dest.write_str("auto"); - } - - debug_assert!( - self.contains(ScrollbarGutter::STABLE), - "We failed to parse the syntax!" - ); - dest.write_str("stable")?; - if self.contains(ScrollbarGutter::BOTH_EDGES) { - dest.write_str(" both-edges")?; - } - - Ok(()) - } -} - -impl Parse for ScrollbarGutter { - /// auto | stable && both-edges? - fn parse<'i, 't>( - _context: &ParserContext, - input: &mut Parser<'i, 't>, - ) -> Result> { - if input.try_parse(|i| i.expect_ident_matching("auto")).is_ok() { - return Ok(ScrollbarGutter::AUTO); - } - - let mut result = ScrollbarGutter::empty(); - while let Ok(ident) = input.try_parse(|i| i.expect_ident_cloned()) { - let flag = match_ignore_ascii_case! { &ident, - "stable" => Some(ScrollbarGutter::STABLE), - "both-edges" => Some(ScrollbarGutter::BOTH_EDGES), - _ => None - }; - - match flag { - Some(flag) if !result.contains(flag) => result.insert(flag), - _ => return Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError)), - } - } - - if result.contains(ScrollbarGutter::STABLE) { - Ok(result) - } else { - Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError)) - } +impl ScrollbarGutter { + #[inline] + fn has_stable(self) -> bool { + self.intersects(Self::STABLE) } } diff --git a/components/style/values/specified/calc.rs b/components/style/values/specified/calc.rs index f5448591f21..c3abb9170d5 100644 --- a/components/style/values/specified/calc.rs +++ b/components/style/values/specified/calc.rs @@ -190,9 +190,29 @@ impl generic::CalcNodeLeaf for Leaf { }, NoCalcLength::ViewportPercentage(ref vp) => match *vp { ViewportPercentageLength::Vh(..) => SortKey::Vh, + ViewportPercentageLength::Svh(..) => SortKey::Svh, + ViewportPercentageLength::Lvh(..) => SortKey::Lvh, + ViewportPercentageLength::Dvh(..) => SortKey::Dvh, ViewportPercentageLength::Vw(..) => SortKey::Vw, + ViewportPercentageLength::Svw(..) => SortKey::Svw, + ViewportPercentageLength::Lvw(..) => SortKey::Lvw, + ViewportPercentageLength::Dvw(..) => SortKey::Dvw, ViewportPercentageLength::Vmax(..) => SortKey::Vmax, + ViewportPercentageLength::Svmax(..) => SortKey::Svmax, + ViewportPercentageLength::Lvmax(..) => SortKey::Lvmax, + ViewportPercentageLength::Dvmax(..) => SortKey::Dvmax, ViewportPercentageLength::Vmin(..) => SortKey::Vmin, + ViewportPercentageLength::Svmin(..) => SortKey::Svmin, + ViewportPercentageLength::Lvmin(..) => SortKey::Lvmin, + ViewportPercentageLength::Dvmin(..) => SortKey::Dvmin, + ViewportPercentageLength::Vb(..) => SortKey::Vb, + ViewportPercentageLength::Svb(..) => SortKey::Svb, + ViewportPercentageLength::Lvb(..) => SortKey::Lvb, + ViewportPercentageLength::Dvb(..) => SortKey::Dvb, + ViewportPercentageLength::Vi(..) => SortKey::Vi, + ViewportPercentageLength::Svi(..) => SortKey::Svi, + ViewportPercentageLength::Lvi(..) => SortKey::Lvi, + ViewportPercentageLength::Dvi(..) => SortKey::Dvi, }, NoCalcLength::ServoCharacterWidth(..) => unreachable!(), }, diff --git a/components/style/values/specified/color.rs b/components/style/values/specified/color.rs index 4c0683dbce0..493c2cf8c85 100644 --- a/components/style/values/specified/color.rs +++ b/components/style/values/specified/color.rs @@ -7,7 +7,7 @@ use super::AllowQuirks; use crate::parser::{Parse, ParserContext}; use crate::values::computed::{Color as ComputedColor, Context, ToComputedValue}; -use crate::values::generics::color::{GenericCaretColor, GenericColorOrAuto}; +use crate::values::generics::color::{ColorInterpolationMethod, GenericColorMix, GenericCaretColor, GenericColorOrAuto}; use crate::values::specified::calc::CalcNode; use crate::values::specified::Percentage; use crate::values::CustomIdent; @@ -19,52 +19,8 @@ use std::io::Write as IoWrite; use style_traits::{CssType, CssWriter, KeywordsCollectFn, ParseError, StyleParseErrorKind}; use style_traits::{SpecifiedValueInfo, ToCss, ValueParseErrorKind}; -/// A color space as defined in [1]. -/// -/// [1]: https://drafts.csswg.org/css-color-5/#typedef-colorspace -#[derive(Clone, Copy, Debug, Eq, MallocSizeOf, Parse, PartialEq, ToCss, ToShmem)] -pub enum ColorSpaceKind { - /// The sRGB color space. - Srgb, - /// The CIEXYZ color space. - Xyz, - /// The CIELAB color space. - Lab, - /// The CIELAB color space, expressed in cylindrical coordinates. - Lch, -} - -/// A hue adjuster as defined in [1]. -/// -/// [1]: https://drafts.csswg.org/css-color-5/#typedef-hue-adjuster -#[derive(Clone, Copy, Debug, Eq, MallocSizeOf, Parse, PartialEq, ToCss, ToShmem)] -pub enum HueAdjuster { - /// The "shorter" angle adjustment. - Shorter, - /// The "longer" angle adjustment. - Longer, - /// The "increasing" angle adjustment. - Increasing, - /// The "decreasing" angle adjustment. - Decreasing, - /// The "specified" angle adjustment. - Specified, -} - -/// A restricted version of the css `color-mix()` function, which only supports -/// percentages. -/// -/// https://drafts.csswg.org/css-color-5/#color-mix -#[derive(Clone, Debug, MallocSizeOf, PartialEq, ToShmem)] -#[allow(missing_docs)] -pub struct ColorMix { - pub color_space: ColorSpaceKind, - pub left: Color, - pub left_percentage: Percentage, - pub right: Color, - pub right_percentage: Percentage, - pub hue_adjuster: HueAdjuster, -} +/// A specified color-mix(). +pub type ColorMix = GenericColorMix; #[inline] fn allow_color_mix() -> bool { @@ -74,18 +30,6 @@ fn allow_color_mix() -> bool { return false; } -#[inline] -fn allow_color_mix_color_spaces() -> bool { - #[cfg(feature = "gecko")] - return static_prefs::pref!("layout.css.color-mix.color-spaces.enabled"); - #[cfg(feature = "servo")] - return false; -} - -// NOTE(emilio): Syntax is still a bit in-flux, since [1] doesn't seem -// particularly complete, and disagrees with the examples. -// -// [1]: https://github.com/w3c/csswg-drafts/commit/a4316446112f9e814668c2caff7f826f512f8fed impl Parse for ColorMix { fn parse<'i, 't>( context: &ParserContext, @@ -98,100 +42,58 @@ impl Parse for ColorMix { return Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError)); } - let color_spaces_enabled = context.chrome_rules_enabled() || - allow_color_mix_color_spaces(); - input.expect_function_matching("color-mix")?; - // NOTE(emilio): This implements the syntax described here for now, - // might need to get updated in the future. - // - // https://github.com/w3c/csswg-drafts/issues/6066#issuecomment-789836765 input.parse_nested_block(|input| { - input.expect_ident_matching("in")?; - let color_space = if color_spaces_enabled { - ColorSpaceKind::parse(input)? - } else { - input.expect_ident_matching("srgb")?; - ColorSpaceKind::Srgb - }; + let interpolation = ColorInterpolationMethod::parse(context, input)?; input.expect_comma()?; + let try_parse_percentage = |input: &mut Parser| -> Option { + input + .try_parse(|input| Percentage::parse_zero_to_a_hundred(context, input)) + .ok() + }; + + let mut left_percentage = try_parse_percentage(input); + let left = Color::parse(context, input)?; - let left_percentage = input - .try_parse(|input| Percentage::parse(context, input)) - .ok(); + if left_percentage.is_none() { + left_percentage = try_parse_percentage(input); + } input.expect_comma()?; + let mut right_percentage = try_parse_percentage(input); + let right = Color::parse(context, input)?; - let right_percentage = input - .try_parse(|input| Percentage::parse(context, input)) - .unwrap_or_else(|_| { - Percentage::new(1.0 - left_percentage.map_or(0.5, |p| p.get())) - }); + + if right_percentage.is_none() { + right_percentage = try_parse_percentage(input); + } + + let right_percentage = right_percentage + .unwrap_or_else(|| Percentage::new(1.0 - left_percentage.map_or(0.5, |p| p.get()))); let left_percentage = left_percentage.unwrap_or_else(|| Percentage::new(1.0 - right_percentage.get())); - let hue_adjuster = input - .try_parse(|input| HueAdjuster::parse(input)) - .unwrap_or(HueAdjuster::Shorter); + if left_percentage.get() + right_percentage.get() <= 0.0 { + // If the percentages sum to zero, the function is invalid. + return Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError)); + } Ok(ColorMix { - color_space, + interpolation, left, left_percentage, right, right_percentage, - hue_adjuster, + normalize_weights: true, }) }) } } -impl ToCss for ColorMix { - fn to_css(&self, dest: &mut CssWriter) -> fmt::Result - where - W: Write, - { - fn can_omit(percent: &Percentage, other: &Percentage, is_left: bool) -> bool { - if percent.is_calc() { - return false; - } - if percent.get() == 0.5 { - return other.get() == 0.5; - } - if is_left { - return false; - } - (1.0 - percent.get() - other.get()).abs() <= f32::EPSILON - } - - dest.write_str("color-mix(in ")?; - self.color_space.to_css(dest)?; - dest.write_str(", ")?; - self.left.to_css(dest)?; - if !can_omit(&self.left_percentage, &self.right_percentage, true) { - dest.write_str(" ")?; - self.left_percentage.to_css(dest)?; - } - dest.write_str(", ")?; - self.right.to_css(dest)?; - if !can_omit(&self.right_percentage, &self.left_percentage, false) { - dest.write_str(" ")?; - self.right_percentage.to_css(dest)?; - } - - if self.hue_adjuster != HueAdjuster::Shorter { - dest.write_str(" ")?; - self.hue_adjuster.to_css(dest)?; - } - - dest.write_str(")") - } -} - /// Specified color value #[derive(Clone, Debug, MallocSizeOf, PartialEq, ToShmem)] pub enum Color { @@ -204,8 +106,6 @@ pub enum Color { /// Authored representation authored: Option>, }, - /// A complex color value from computed value - Complex(ComputedColor), /// A system color. #[cfg(feature = "gecko")] System(SystemColor), @@ -387,10 +287,6 @@ pub enum SystemColor { /// Inactive light hightlight MozMacSecondaryhighlight, - /// Font smoothing background colors needed by the Mac OS X theme, based on - /// -moz-appearance names. - MozMacVibrantTitlebarLight, - MozMacVibrantTitlebarDark, MozMacMenupopup, MozMacMenuitem, MozMacActiveMenuitem, @@ -597,8 +493,6 @@ impl ToCss for Color { Color::Numeric { parsed: ref rgba, .. } => rgba.to_css(dest), - // TODO: Could represent this as a color-mix() instead. - Color::Complex(_) => Ok(()), Color::ColorMix(ref mix) => mix.to_css(dest), #[cfg(feature = "gecko")] Color::System(system) => system.to_css(dest), @@ -744,23 +638,23 @@ impl Color { /// the context to resolve, then `None` is returned. pub fn to_computed_color(&self, context: Option<&Context>) -> Option { Some(match *self { - Color::CurrentColor => ComputedColor::currentcolor(), - Color::Numeric { ref parsed, .. } => ComputedColor::rgba(*parsed), - Color::Complex(ref complex) => *complex, + Color::CurrentColor => ComputedColor::CurrentColor, + Color::Numeric { ref parsed, .. } => ComputedColor::Numeric(*parsed), Color::ColorMix(ref mix) => { - use crate::values::animated::color::Color as AnimatedColor; - use crate::values::animated::ToAnimatedValue; + use crate::values::computed::percentage::Percentage; - let left = mix.left.to_computed_color(context)?.to_animated_value(); - let right = mix.right.to_computed_color(context)?.to_animated_value(); - ToAnimatedValue::from_animated_value(AnimatedColor::mix( - mix.color_space, - &left, - mix.left_percentage.get(), - &right, - mix.right_percentage.get(), - mix.hue_adjuster, - )) + let left = mix.left.to_computed_color(context)?; + let right = mix.right.to_computed_color(context)?; + let mut color = ComputedColor::ColorMix(Box::new(GenericColorMix { + interpolation: mix.interpolation, + left, + left_percentage: Percentage(mix.left_percentage.get()), + right, + right_percentage: Percentage(mix.right_percentage.get()), + normalize_weights: mix.normalize_weights, + })); + color.simplify(None); + color }, #[cfg(feature = "gecko")] Color::System(system) => system.compute(context?), @@ -778,13 +672,13 @@ impl ToComputedValue for Color { } fn from_computed_value(computed: &ComputedColor) -> Self { - if computed.is_numeric() { - return Color::rgba(computed.color); + match *computed { + ComputedColor::Numeric(ref color) => Color::rgba(*color), + ComputedColor::CurrentColor => Color::CurrentColor, + ComputedColor::ColorMix(ref mix) => { + Color::ColorMix(Box::new(ToComputedValue::from_computed_value(&**mix))) + }, } - if computed.is_currentcolor() { - return Color::currentcolor(); - } - Color::Complex(*computed) } } @@ -812,7 +706,7 @@ impl ToComputedValue for MozFontSmoothingBackgroundColor { fn to_computed_value(&self, context: &Context) -> RGBA { self.0 .to_computed_value(context) - .to_rgba(RGBA::transparent()) + .into_rgba(RGBA::transparent()) } fn from_computed_value(computed: &RGBA) -> Self { @@ -829,7 +723,15 @@ impl SpecifiedValueInfo for Color { // should probably be handled that way as well. // XXX `currentColor` should really be `currentcolor`. But let's // keep it consistent with the old system for now. - f(&["rgb", "rgba", "hsl", "hsla", "hwb", "currentColor", "transparent"]); + f(&[ + "rgb", + "rgba", + "hsl", + "hsla", + "hwb", + "currentColor", + "transparent", + ]); } } @@ -846,7 +748,7 @@ impl ToComputedValue for ColorPropertyValue { fn to_computed_value(&self, context: &Context) -> RGBA { self.0 .to_computed_value(context) - .to_rgba(context.builder.get_parent_inherited_text().clone_color()) + .into_rgba(context.builder.get_parent_inherited_text().clone_color()) } #[inline] @@ -1008,7 +910,19 @@ impl ToCss for ColorScheme { } /// https://drafts.csswg.org/css-color-adjust/#print-color-adjust -#[derive(Clone, Copy, Debug, MallocSizeOf, Parse, PartialEq, SpecifiedValueInfo, ToCss, ToComputedValue, ToResolvedValue, ToShmem)] +#[derive( + Clone, + Copy, + Debug, + MallocSizeOf, + Parse, + PartialEq, + SpecifiedValueInfo, + ToCss, + ToComputedValue, + ToResolvedValue, + ToShmem, +)] #[repr(u8)] pub enum PrintColorAdjust { /// Ignore backgrounds and darken text. diff --git a/components/style/values/specified/image.rs b/components/style/values/specified/image.rs index f4cf417a044..1c1d1c88725 100644 --- a/components/style/values/specified/image.rs +++ b/components/style/values/specified/image.rs @@ -39,6 +39,9 @@ use style_traits::{SpecifiedValueInfo, StyleParseErrorKind, ToCss}; pub type Image = generic::Image; +// Images should remain small, see https://github.com/servo/servo/pull/18430 +size_of_test!(Image, 40); + /// Specified values for a CSS gradient. /// pub type Gradient = generic::Gradient< @@ -60,8 +63,6 @@ pub type CrossFade = generic::CrossFade; pub type CrossFadeElement = generic::CrossFadeElement; /// CrossFadeImage = image | color pub type CrossFadeImage = generic::CrossFadeImage; -/// A specified percentage or nothing. -pub type PercentOrNone = generic::PercentOrNone; /// `image-set()` pub type ImageSet = generic::ImageSet; @@ -315,6 +316,16 @@ impl CrossFade { } impl CrossFadeElement { + fn parse_percentage<'i, 't>(context: &ParserContext, input: &mut Parser<'i, 't>) -> Option { + // We clamp our values here as this is the way that Safari and Chrome's + // implementation handle out-of-bounds percentages but whether or not + // this behavior follows the specification is still being discussed. + // See: + input.try_parse(|input| Percentage::parse_non_negative(context, input)) + .ok() + .map(|p| p.clamp_to_hundred()) + } + /// = ? && [ | ] fn parse<'i, 't>( context: &ParserContext, @@ -322,14 +333,17 @@ impl CrossFadeElement { cors_mode: CorsMode, ) -> Result> { // Try and parse a leading percent sign. - let mut percent = PercentOrNone::parse_or_none(context, input); + let mut percent = Self::parse_percentage(context, input); // Parse the image let image = CrossFadeImage::parse(context, input, cors_mode)?; // Try and parse a trailing percent sign. - if percent == PercentOrNone::None { - percent = PercentOrNone::parse_or_none(context, input); + if percent.is_none() { + percent = Self::parse_percentage(context, input); } - Ok(Self { percent, image }) + Ok(Self { + percent: percent.into(), + image, + }) } } @@ -351,22 +365,6 @@ impl CrossFadeImage { } } -impl PercentOrNone { - fn parse_or_none<'i, 't>(context: &ParserContext, input: &mut Parser<'i, 't>) -> Self { - // We clamp our values here as this is the way that Safari and - // Chrome's implementation handle out-of-bounds percentages - // but whether or not this behavior follows the specification - // is still being discussed. See: - // - if let Ok(percent) = input.try_parse(|input| Percentage::parse_non_negative(context, input)) - { - Self::Percent(percent.clamp_to_hundred()) - } else { - Self::None - } - } -} - impl ImageSet { fn parse<'i, 't>( context: &ParserContext, @@ -735,12 +733,12 @@ impl Gradient { if items.is_empty() { items = vec![ generic::GradientItem::ComplexColorStop { - color: Color::transparent().into(), - position: Percentage::zero().into(), + color: Color::transparent(), + position: LengthPercentage::zero_percent(), }, generic::GradientItem::ComplexColorStop { - color: Color::transparent().into(), - position: Percentage::hundred().into(), + color: Color::transparent(), + position: LengthPercentage::hundred_percent(), }, ]; } else if items.len() == 1 { diff --git a/components/style/values/specified/length.rs b/components/style/values/specified/length.rs index e092f254bd4..159eb996364 100644 --- a/components/style/values/specified/length.rs +++ b/components/style/values/specified/length.rs @@ -21,7 +21,6 @@ use crate::values::CSSFloat; use crate::Zero; use app_units::Au; use cssparser::{Parser, Token}; -use euclid::default::Size2D; use std::cmp; use std::ops::{Add, Mul}; use style_traits::values::specified::AllowedNumericType; @@ -278,45 +277,249 @@ impl FontRelativeLength { } } +/// https://drafts.csswg.org/css-values/#viewport-variants +pub enum ViewportVariant { + /// https://drafts.csswg.org/css-values/#ua-default-viewport-size + UADefault, + /// https://drafts.csswg.org/css-values/#small-viewport-percentage-units + Small, + /// https://drafts.csswg.org/css-values/#large-viewport-percentage-units + Large, + /// https://drafts.csswg.org/css-values/#dynamic-viewport-percentage-units + Dynamic, +} + +/// https://drafts.csswg.org/css-values/#viewport-relative-units +#[derive(PartialEq)] +enum ViewportUnit { + /// *vw units. + Vw, + /// *vh units. + Vh, + /// *vmin units. + Vmin, + /// *vmax units. + Vmax, + /// *vb units. + Vb, + /// *vi units. + Vi, +} + /// A viewport-relative length. /// /// #[derive(Clone, Copy, Debug, MallocSizeOf, PartialEq, ToCss, ToShmem)] pub enum ViewportPercentageLength { - /// A vw unit: https://drafts.csswg.org/css-values/#vw + /// #[css(dimension)] Vw(CSSFloat), - /// A vh unit: https://drafts.csswg.org/css-values/#vh + /// + #[css(dimension)] + Svw(CSSFloat), + /// + #[css(dimension)] + Lvw(CSSFloat), + /// + #[css(dimension)] + Dvw(CSSFloat), + /// #[css(dimension)] Vh(CSSFloat), - /// + /// + #[css(dimension)] + Svh(CSSFloat), + /// + #[css(dimension)] + Lvh(CSSFloat), + /// + #[css(dimension)] + Dvh(CSSFloat), + /// #[css(dimension)] Vmin(CSSFloat), - /// + /// + #[css(dimension)] + Svmin(CSSFloat), + /// + #[css(dimension)] + Lvmin(CSSFloat), + /// + #[css(dimension)] + Dvmin(CSSFloat), + /// #[css(dimension)] Vmax(CSSFloat), + /// + #[css(dimension)] + Svmax(CSSFloat), + /// + #[css(dimension)] + Lvmax(CSSFloat), + /// + #[css(dimension)] + Dvmax(CSSFloat), + /// + #[css(dimension)] + Vb(CSSFloat), + /// + #[css(dimension)] + Svb(CSSFloat), + /// + #[css(dimension)] + Lvb(CSSFloat), + /// + #[css(dimension)] + Dvb(CSSFloat), + /// + #[css(dimension)] + Vi(CSSFloat), + /// + #[css(dimension)] + Svi(CSSFloat), + /// + #[css(dimension)] + Lvi(CSSFloat), + /// + #[css(dimension)] + Dvi(CSSFloat), } impl ViewportPercentageLength { /// Return true if this is a zero value. fn is_zero(&self) -> bool { - match *self { - ViewportPercentageLength::Vw(v) | - ViewportPercentageLength::Vh(v) | - ViewportPercentageLength::Vmin(v) | - ViewportPercentageLength::Vmax(v) => v == 0., - } + let (_, _, v) = self.unpack(); + v == 0. } fn is_negative(&self) -> bool { - match *self { - ViewportPercentageLength::Vw(v) | - ViewportPercentageLength::Vh(v) | - ViewportPercentageLength::Vmin(v) | - ViewportPercentageLength::Vmax(v) => v < 0., - } + let (_, _, v) = self.unpack(); + v < 0. } + fn unpack(&self) -> (ViewportVariant, ViewportUnit, CSSFloat) { + match *self { + ViewportPercentageLength::Vw(v) => ( + ViewportVariant::UADefault, + ViewportUnit::Vw, + v, + ), + ViewportPercentageLength::Svw(v) => ( + ViewportVariant::Small, + ViewportUnit::Vw, + v, + ), + ViewportPercentageLength::Lvw(v) => ( + ViewportVariant::Large, + ViewportUnit::Vw, + v, + ), + ViewportPercentageLength::Dvw(v) => ( + ViewportVariant::Dynamic, + ViewportUnit::Vw, + v, + ), + ViewportPercentageLength::Vh(v) => ( + ViewportVariant::UADefault, + ViewportUnit::Vh, + v, + ), + ViewportPercentageLength::Svh(v) => ( + ViewportVariant::Small, + ViewportUnit::Vh, + v, + ), + ViewportPercentageLength::Lvh(v) => ( + ViewportVariant::Large, + ViewportUnit::Vh, + v), + ViewportPercentageLength::Dvh(v) => ( + ViewportVariant::Dynamic, + ViewportUnit::Vh, + v, + ), + ViewportPercentageLength::Vmin(v) => ( + ViewportVariant::UADefault, + ViewportUnit::Vmin, + v, + ), + ViewportPercentageLength::Svmin(v) => ( + ViewportVariant::Small, + ViewportUnit::Vmin, + v, + ), + ViewportPercentageLength::Lvmin(v) => ( + ViewportVariant::Large, + ViewportUnit::Vmin, + v, + ), + ViewportPercentageLength::Dvmin(v) => ( + ViewportVariant::Dynamic, + ViewportUnit::Vmin, + v, + ), + ViewportPercentageLength::Vmax(v) => ( + ViewportVariant::UADefault, + ViewportUnit::Vmax, + v, + ), + ViewportPercentageLength::Svmax(v) => ( + ViewportVariant::Small, + ViewportUnit::Vmax, + v, + ), + ViewportPercentageLength::Lvmax(v) => ( + ViewportVariant::Large, + ViewportUnit::Vmax, + v, + ), + ViewportPercentageLength::Dvmax(v) => ( + ViewportVariant::Dynamic, + ViewportUnit::Vmax, + v, + ), + ViewportPercentageLength::Vb(v) => ( + ViewportVariant::UADefault, + ViewportUnit::Vb, + v, + ), + ViewportPercentageLength::Svb(v) => ( + ViewportVariant::Small, + ViewportUnit::Vb, + v, + ), + ViewportPercentageLength::Lvb(v) => ( + ViewportVariant::Large, + ViewportUnit::Vb, + v, + ), + ViewportPercentageLength::Dvb(v) => ( + ViewportVariant::Dynamic, + ViewportUnit::Vb, + v, + ), + ViewportPercentageLength::Vi(v) => ( + ViewportVariant::UADefault, + ViewportUnit::Vi, + v, + ), + ViewportPercentageLength::Svi(v) => ( + ViewportVariant::Small, + ViewportUnit::Vi, + v, + ), + ViewportPercentageLength::Lvi(v) => ( + ViewportVariant::Large, + ViewportUnit::Vi, + v, + ), + ViewportPercentageLength::Dvi(v) => ( + ViewportVariant::Dynamic, + ViewportUnit::Vi, + v, + ), + } + } fn try_sum(&self, other: &Self) -> Result { use self::ViewportPercentageLength::*; @@ -326,14 +529,39 @@ impl ViewportPercentageLength { Ok(match (self, other) { (&Vw(one), &Vw(other)) => Vw(one + other), + (&Svw(one), &Svw(other)) => Svw(one + other), + (&Lvw(one), &Lvw(other)) => Lvw(one + other), + (&Dvw(one), &Dvw(other)) => Dvw(one + other), (&Vh(one), &Vh(other)) => Vh(one + other), + (&Svh(one), &Svh(other)) => Svh(one + other), + (&Lvh(one), &Lvh(other)) => Lvh(one + other), + (&Dvh(one), &Dvh(other)) => Dvh(one + other), (&Vmin(one), &Vmin(other)) => Vmin(one + other), + (&Svmin(one), &Svmin(other)) => Svmin(one + other), + (&Lvmin(one), &Lvmin(other)) => Lvmin(one + other), + (&Dvmin(one), &Dvmin(other)) => Dvmin(one + other), (&Vmax(one), &Vmax(other)) => Vmax(one + other), + (&Svmax(one), &Svmax(other)) => Svmax(one + other), + (&Lvmax(one), &Lvmax(other)) => Lvmax(one + other), + (&Dvmax(one), &Dvmax(other)) => Dvmax(one + other), + (&Vb(one), &Vb(other)) => Vb(one + other), + (&Svb(one), &Svb(other)) => Svb(one + other), + (&Lvb(one), &Lvb(other)) => Lvb(one + other), + (&Dvb(one), &Dvb(other)) => Dvb(one + other), + (&Vi(one), &Vi(other)) => Vi(one + other), + (&Svi(one), &Svi(other)) => Svi(one + other), + (&Lvi(one), &Lvi(other)) => Lvi(one + other), + (&Dvi(one), &Dvi(other)) => Dvi(one + other), // See https://github.com/rust-lang/rust/issues/68867. rustc isn't // able to figure it own on its own so we help. _ => unsafe { match *self { - Vw(..) | Vh(..) | Vmin(..) | Vmax(..) => {}, + Vw(..) | Svw(..) | Lvw(..) | Dvw(..) | + Vh(..) | Svh(..) | Lvh(..) | Dvh(..) | + Vmin(..) | Svmin(..) | Lvmin(..) | Dvmin(..) | + Vmax(..) | Svmax(..) | Lvmax(..) | Dvmax(..) | + Vb(..) | Svb(..) | Lvb(..) | Dvb(..) | + Vi(..) | Svi(..) | Lvi(..) | Dvi(..) => {}, } debug_unreachable!("Forgot to handle unit in try_sum()") }, @@ -341,15 +569,24 @@ impl ViewportPercentageLength { } /// Computes the given viewport-relative length for the given viewport size. - pub fn to_computed_value(&self, viewport_size: Size2D) -> CSSPixelLength { - let (factor, length) = match *self { - ViewportPercentageLength::Vw(length) => (length, viewport_size.width), - ViewportPercentageLength::Vh(length) => (length, viewport_size.height), - ViewportPercentageLength::Vmin(length) => { - (length, cmp::min(viewport_size.width, viewport_size.height)) - }, - ViewportPercentageLength::Vmax(length) => { - (length, cmp::max(viewport_size.width, viewport_size.height)) + pub fn to_computed_value(&self, context: &Context) -> CSSPixelLength { + let (variant, unit, factor) = self.unpack(); + let size = context.viewport_size_for_viewport_unit_resolution(variant); + let length = match unit { + ViewportUnit::Vw => size.width, + ViewportUnit::Vh => size.height, + ViewportUnit::Vmin => cmp::min(size.width, size.height), + ViewportUnit::Vmax => cmp::max(size.width, size.height), + ViewportUnit::Vi | ViewportUnit::Vb => { + context + .rule_cache_conditions + .borrow_mut() + .set_writing_mode_dependency(context.builder.writing_mode); + if (unit == ViewportUnit::Vb) == context.style().writing_mode.is_vertical() { + size.width + } else { + size.height + } }, }; @@ -578,15 +815,75 @@ impl NoCalcLength { "vw" if !context.in_page_rule() => { NoCalcLength::ViewportPercentage(ViewportPercentageLength::Vw(value)) }, + "svw" if !context.in_page_rule() => { + NoCalcLength::ViewportPercentage(ViewportPercentageLength::Svw(value)) + }, + "lvw" if !context.in_page_rule() => { + NoCalcLength::ViewportPercentage(ViewportPercentageLength::Lvw(value)) + }, + "dvw" if !context.in_page_rule() => { + NoCalcLength::ViewportPercentage(ViewportPercentageLength::Dvw(value)) + }, "vh" if !context.in_page_rule() => { NoCalcLength::ViewportPercentage(ViewportPercentageLength::Vh(value)) }, + "svh" if !context.in_page_rule() => { + NoCalcLength::ViewportPercentage(ViewportPercentageLength::Svh(value)) + }, + "lvh" if !context.in_page_rule() => { + NoCalcLength::ViewportPercentage(ViewportPercentageLength::Lvh(value)) + }, + "dvh" if !context.in_page_rule() => { + NoCalcLength::ViewportPercentage(ViewportPercentageLength::Dvh(value)) + }, "vmin" if !context.in_page_rule() => { NoCalcLength::ViewportPercentage(ViewportPercentageLength::Vmin(value)) }, + "svmin" if !context.in_page_rule() => { + NoCalcLength::ViewportPercentage(ViewportPercentageLength::Svmin(value)) + }, + "lvmin" if !context.in_page_rule() => { + NoCalcLength::ViewportPercentage(ViewportPercentageLength::Lvmin(value)) + }, + "dvmin" if !context.in_page_rule() => { + NoCalcLength::ViewportPercentage(ViewportPercentageLength::Dvmin(value)) + }, "vmax" if !context.in_page_rule() => { NoCalcLength::ViewportPercentage(ViewportPercentageLength::Vmax(value)) }, + "svmax" if !context.in_page_rule() => { + NoCalcLength::ViewportPercentage(ViewportPercentageLength::Svmax(value)) + }, + "lvmax" if !context.in_page_rule() => { + NoCalcLength::ViewportPercentage(ViewportPercentageLength::Lvmax(value)) + }, + "dvmax" if !context.in_page_rule() => { + NoCalcLength::ViewportPercentage(ViewportPercentageLength::Dvmax(value)) + }, + "vb" if !context.in_page_rule() => { + NoCalcLength::ViewportPercentage(ViewportPercentageLength::Vb(value)) + }, + "svb" if !context.in_page_rule() => { + NoCalcLength::ViewportPercentage(ViewportPercentageLength::Svb(value)) + }, + "lvb" if !context.in_page_rule() => { + NoCalcLength::ViewportPercentage(ViewportPercentageLength::Lvb(value)) + }, + "dvb" if !context.in_page_rule() => { + NoCalcLength::ViewportPercentage(ViewportPercentageLength::Dvb(value)) + }, + "vi" if !context.in_page_rule() => { + NoCalcLength::ViewportPercentage(ViewportPercentageLength::Vi(value)) + }, + "svi" if !context.in_page_rule() => { + NoCalcLength::ViewportPercentage(ViewportPercentageLength::Svi(value)) + }, + "lvi" if !context.in_page_rule() => { + NoCalcLength::ViewportPercentage(ViewportPercentageLength::Lvi(value)) + }, + "dvi" if !context.in_page_rule() => { + NoCalcLength::ViewportPercentage(ViewportPercentageLength::Dvi(value)) + }, _ => return Err(()), }) } @@ -770,9 +1067,29 @@ impl Mul for ViewportPercentageLength { fn mul(self, scalar: CSSFloat) -> ViewportPercentageLength { match self { ViewportPercentageLength::Vw(v) => ViewportPercentageLength::Vw(v * scalar), + ViewportPercentageLength::Svw(v) => ViewportPercentageLength::Svw(v * scalar), + ViewportPercentageLength::Lvw(v) => ViewportPercentageLength::Lvw(v * scalar), + ViewportPercentageLength::Dvw(v) => ViewportPercentageLength::Dvw(v * scalar), ViewportPercentageLength::Vh(v) => ViewportPercentageLength::Vh(v * scalar), + ViewportPercentageLength::Svh(v) => ViewportPercentageLength::Svh(v * scalar), + ViewportPercentageLength::Lvh(v) => ViewportPercentageLength::Lvh(v * scalar), + ViewportPercentageLength::Dvh(v) => ViewportPercentageLength::Dvh(v * scalar), ViewportPercentageLength::Vmin(v) => ViewportPercentageLength::Vmin(v * scalar), + ViewportPercentageLength::Svmin(v) => ViewportPercentageLength::Svmin(v * scalar), + ViewportPercentageLength::Lvmin(v) => ViewportPercentageLength::Lvmin(v * scalar), + ViewportPercentageLength::Dvmin(v) => ViewportPercentageLength::Dvmin(v * scalar), ViewportPercentageLength::Vmax(v) => ViewportPercentageLength::Vmax(v * scalar), + ViewportPercentageLength::Svmax(v) => ViewportPercentageLength::Svmax(v * scalar), + ViewportPercentageLength::Lvmax(v) => ViewportPercentageLength::Lvmax(v * scalar), + ViewportPercentageLength::Dvmax(v) => ViewportPercentageLength::Dvmax(v * scalar), + ViewportPercentageLength::Vb(v) => ViewportPercentageLength::Vb(v * scalar), + ViewportPercentageLength::Svb(v) => ViewportPercentageLength::Svb(v * scalar), + ViewportPercentageLength::Lvb(v) => ViewportPercentageLength::Lvb(v * scalar), + ViewportPercentageLength::Dvb(v) => ViewportPercentageLength::Dvb(v * scalar), + ViewportPercentageLength::Vi(v) => ViewportPercentageLength::Vi(v * scalar), + ViewportPercentageLength::Svi(v) => ViewportPercentageLength::Svi(v * scalar), + ViewportPercentageLength::Lvi(v) => ViewportPercentageLength::Lvi(v * scalar), + ViewportPercentageLength::Dvi(v) => ViewportPercentageLength::Dvi(v * scalar), } } } @@ -787,14 +1104,39 @@ impl PartialOrd for ViewportPercentageLength { match (self, other) { (&Vw(ref one), &Vw(ref other)) => one.partial_cmp(other), + (&Svw(ref one), &Svw(ref other)) => one.partial_cmp(other), + (&Lvw(ref one), &Lvw(ref other)) => one.partial_cmp(other), + (&Dvw(ref one), &Dvw(ref other)) => one.partial_cmp(other), (&Vh(ref one), &Vh(ref other)) => one.partial_cmp(other), + (&Svh(ref one), &Svh(ref other)) => one.partial_cmp(other), + (&Lvh(ref one), &Lvh(ref other)) => one.partial_cmp(other), + (&Dvh(ref one), &Dvh(ref other)) => one.partial_cmp(other), (&Vmin(ref one), &Vmin(ref other)) => one.partial_cmp(other), + (&Svmin(ref one), &Svmin(ref other)) => one.partial_cmp(other), + (&Lvmin(ref one), &Lvmin(ref other)) => one.partial_cmp(other), + (&Dvmin(ref one), &Dvmin(ref other)) => one.partial_cmp(other), (&Vmax(ref one), &Vmax(ref other)) => one.partial_cmp(other), + (&Svmax(ref one), &Svmax(ref other)) => one.partial_cmp(other), + (&Lvmax(ref one), &Lvmax(ref other)) => one.partial_cmp(other), + (&Dvmax(ref one), &Dvmax(ref other)) => one.partial_cmp(other), + (&Vb(ref one), &Vb(ref other)) => one.partial_cmp(other), + (&Svb(ref one), &Svb(ref other)) => one.partial_cmp(other), + (&Lvb(ref one), &Lvb(ref other)) => one.partial_cmp(other), + (&Dvb(ref one), &Dvb(ref other)) => one.partial_cmp(other), + (&Vi(ref one), &Vi(ref other)) => one.partial_cmp(other), + (&Svi(ref one), &Svi(ref other)) => one.partial_cmp(other), + (&Lvi(ref one), &Lvi(ref other)) => one.partial_cmp(other), + (&Dvi(ref one), &Dvi(ref other)) => one.partial_cmp(other), // See https://github.com/rust-lang/rust/issues/68867. rustc isn't // able to figure it own on its own so we help. _ => unsafe { match *self { - Vw(..) | Vh(..) | Vmin(..) | Vmax(..) => {}, + Vw(..) | Svw(..) | Lvw(..) | Dvw(..) | + Vh(..) | Svh(..) | Lvh(..) | Dvh(..) | + Vmin(..) | Svmin(..) | Lvmin(..) | Dvmin(..) | + Vmax(..) | Svmax(..) | Lvmax(..) | Dvmax(..) | + Vb(..) | Svb(..) | Lvb(..) | Dvb(..) | + Vi(..) | Svi(..) | Lvi(..) | Dvi(..) => {}, } debug_unreachable!("Forgot an arm in partial_cmp?") }, @@ -986,10 +1328,9 @@ impl From for LengthPercentage { impl From for LengthPercentage { #[inline] fn from(pc: Percentage) -> Self { - if pc.is_calc() { - // FIXME(emilio): Hard-coding the clamping mode is suspect. + if let Some(clamping_mode) = pc.calc_clamping_mode() { LengthPercentage::Calc(Box::new(CalcLengthPercentage { - clamping_mode: AllowedNumericType::All, + clamping_mode, node: CalcNode::Leaf(calc::Leaf::Percentage(pc.get())), })) } else { @@ -1022,6 +1363,12 @@ impl LengthPercentage { LengthPercentage::Percentage(computed::Percentage::zero()) } + #[inline] + /// Returns a `100%` value. + pub fn hundred_percent() -> LengthPercentage { + LengthPercentage::Percentage(computed::Percentage::hundred()) + } + fn parse_internal<'i, 't>( context: &ParserContext, input: &mut Parser<'i, 't>, diff --git a/components/style/values/specified/mod.rs b/components/style/values/specified/mod.rs index ac622961642..c06386ef97d 100644 --- a/components/style/values/specified/mod.rs +++ b/components/style/values/specified/mod.rs @@ -37,10 +37,11 @@ pub use self::border::{BorderCornerRadius, BorderImageSlice, BorderImageWidth}; pub use self::border::{BorderImageRepeat, BorderImageSideWidth}; pub use self::border::{BorderRadius, BorderSideWidth, BorderSpacing, BorderStyle}; pub use self::box_::{AnimationIterationCount, AnimationName, AnimationTimeline, Contain, Display}; -pub use self::box_::{Appearance, BreakBetween, BreakWithin}; +pub use self::box_::{Appearance, BreakBetween, BreakWithin, ContainerName, ContainerType}; pub use self::box_::{Clear, ContentVisibility, Float, Overflow, OverflowAnchor}; pub use self::box_::{OverflowClipBox, OverscrollBehavior, Perspective, Resize, ScrollbarGutter}; -pub use self::box_::{ScrollSnapAlign, ScrollSnapAxis, ScrollSnapStrictness, ScrollSnapType}; +pub use self::box_::{ScrollSnapAlign, ScrollSnapAxis, ScrollSnapStop}; +pub use self::box_::{ScrollSnapStrictness, ScrollSnapType}; pub use self::box_::{TouchAction, TransitionProperty, VerticalAlign, WillChange}; pub use self::color::{Color, ColorOrAuto, ColorPropertyValue, ColorScheme, PrintColorAdjust}; pub use self::column::ColumnCount; @@ -60,7 +61,7 @@ pub use self::length::{AbsoluteLength, CalcLengthPercentage, CharacterWidth}; pub use self::length::{FontRelativeLength, Length, LengthOrNumber, NonNegativeLengthOrNumber}; pub use self::length::{LengthOrAuto, LengthPercentage, LengthPercentageOrAuto}; pub use self::length::{MaxSize, Size}; -pub use self::length::{NoCalcLength, ViewportPercentageLength}; +pub use self::length::{NoCalcLength, ViewportPercentageLength, ViewportVariant}; pub use self::length::{ NonNegativeLength, NonNegativeLengthPercentage, NonNegativeLengthPercentageOrAuto, }; @@ -69,7 +70,7 @@ pub use self::list::ListStyleType; pub use self::list::Quotes; pub use self::motion::{OffsetPath, OffsetRotate}; pub use self::outline::OutlineStyle; -pub use self::page::{Orientation, PageName, PageSize, PaperSize}; +pub use self::page::{PageOrientation, PageName, PageSize, PaperSize}; pub use self::percentage::{NonNegativePercentage, Percentage}; pub use self::position::AspectRatio; pub use self::position::{ diff --git a/components/style/values/specified/page.rs b/components/style/values/specified/page.rs index 883f529d867..e660b435327 100644 --- a/components/style/values/specified/page.rs +++ b/components/style/values/specified/page.rs @@ -11,7 +11,7 @@ use crate::values::{generics, CustomIdent}; use cssparser::Parser; use style_traits::ParseError; -pub use generics::page::Orientation; +pub use generics::page::PageOrientation; pub use generics::page::PaperSize; /// Specified value of the @page size descriptor pub type PageSize = generics::page::PageSize>; @@ -24,12 +24,12 @@ impl Parse for PageSize { // Try to parse as [ ] if let Ok(paper_size) = input.try_parse(PaperSize::parse) { let orientation = input - .try_parse(Orientation::parse) - .unwrap_or(Orientation::Portrait); + .try_parse(PageOrientation::parse) + .unwrap_or(PageOrientation::Portrait); return Ok(PageSize::PaperSize(paper_size, orientation)); } // Try to parse as [ ] - if let Ok(orientation) = input.try_parse(Orientation::parse) { + if let Ok(orientation) = input.try_parse(PageOrientation::parse) { if let Ok(paper_size) = input.try_parse(PaperSize::parse) { return Ok(PageSize::PaperSize(paper_size, orientation)); } diff --git a/components/style/values/specified/percentage.rs b/components/style/values/specified/percentage.rs index a152f7d8dc0..1f92c6c3e6a 100644 --- a/components/style/values/specified/percentage.rs +++ b/components/style/values/specified/percentage.rs @@ -92,9 +92,9 @@ impl Percentage { Number::new_with_clamping_mode(self.value, self.calc_clamping_mode) } - /// Returns whether this percentage is a `calc()` value. - pub fn is_calc(&self) -> bool { - self.calc_clamping_mode.is_some() + /// Returns the calc() clamping mode for this percentage. + pub fn calc_clamping_mode(&self) -> Option { + self.calc_clamping_mode } /// Reverses this percentage, preserving calc-ness. @@ -138,6 +138,15 @@ impl Percentage { Self::parse_with_clamping_mode(context, input, AllowedNumericType::NonNegative) } + /// Parses a percentage token, but rejects it if it's negative or more than + /// 100%. + pub fn parse_zero_to_a_hundred<'i, 't>( + context: &ParserContext, + input: &mut Parser<'i, 't>, + ) -> Result> { + Self::parse_with_clamping_mode(context, input, AllowedNumericType::ZeroToOne) + } + /// Clamp to 100% if the value is over 100%. #[inline] pub fn clamp_to_hundred(self) -> Self { @@ -174,6 +183,24 @@ impl ToComputedValue for Percentage { impl SpecifiedValueInfo for Percentage {} +/// Turns the percentage into a plain float. +pub trait ToPercentage { + /// Returns whether this percentage used to be a calc(). + fn is_calc(&self) -> bool { false } + /// Turns the percentage into a plain float. + fn to_percentage(&self) -> CSSFloat; +} + +impl ToPercentage for Percentage { + fn is_calc(&self) -> bool { + self.calc_clamping_mode.is_some() + } + + fn to_percentage(&self) -> CSSFloat { + self.get() + } +} + /// A wrapper of Percentage, whose value must be >= 0. pub type NonNegativePercentage = NonNegative; diff --git a/components/style/values/specified/position.rs b/components/style/values/specified/position.rs index 26359c155c6..b40460cdcd7 100644 --- a/components/style/values/specified/position.rs +++ b/components/style/values/specified/position.rs @@ -556,6 +556,7 @@ impl From for u8 { } } +// TODO: Can be derived with some care. impl Parse for GridAutoFlow { /// [ row | column ] || dense fn parse<'i, 't>( diff --git a/components/style/values/specified/source_size_list.rs b/components/style/values/specified/source_size_list.rs index edb2c09f73e..4023467d1a8 100644 --- a/components/style/values/specified/source_size_list.rs +++ b/components/style/values/specified/source_size_list.rs @@ -6,8 +6,9 @@ #[cfg(feature = "gecko")] use crate::gecko_bindings::sugar::ownership::{HasBoxFFI, HasFFI, HasSimpleFFI}; -use crate::media_queries::{Device, MediaCondition}; +use crate::media_queries::Device; use crate::parser::{Parse, ParserContext}; +use crate::queries::{QueryCondition, FeatureType}; use crate::values::computed::{self, ToComputedValue}; use crate::values::specified::{Length, NoCalcLength, ViewportPercentageLength}; use app_units::Au; @@ -20,7 +21,7 @@ use style_traits::ParseError; /// https://html.spec.whatwg.org/multipage/#source-size #[derive(Debug)] pub struct SourceSize { - condition: MediaCondition, + condition: QueryCondition, value: Length, } @@ -29,9 +30,8 @@ impl Parse for SourceSize { context: &ParserContext, input: &mut Parser<'i, 't>, ) -> Result> { - let condition = MediaCondition::parse(context, input)?; + let condition = QueryCondition::parse(context, input, FeatureType::Media)?; let value = Length::parse_non_negative(context, input)?; - Ok(Self { condition, value }) } } @@ -56,12 +56,12 @@ impl SourceSizeList { /// Evaluate this to get the final viewport length. pub fn evaluate(&self, device: &Device, quirks_mode: QuirksMode) -> Au { - let matching_source_size = self - .source_sizes - .iter() - .find(|source_size| source_size.condition.matches(device, quirks_mode)); - computed::Context::for_media_query_evaluation(device, quirks_mode, |context| { + let matching_source_size = self + .source_sizes + .iter() + .find(|source_size| source_size.condition.matches(context)); + match matching_source_size { Some(source_size) => source_size.value.to_computed_value(context), None => match self.value { diff --git a/components/style/values/specified/text.rs b/components/style/values/specified/text.rs index 2d561e04dbd..9c58b6a4572 100644 --- a/components/style/values/specified/text.rs +++ b/components/style/values/specified/text.rs @@ -232,8 +232,8 @@ impl ToComputedValue for TextOverflow { } bitflags! { - #[derive(MallocSizeOf, Serialize, SpecifiedValueInfo, ToComputedValue, ToResolvedValue, ToShmem)] - #[value_info(other_values = "none,underline,overline,line-through,blink")] + #[derive(MallocSizeOf, Parse, Serialize, SpecifiedValueInfo, ToCss, ToComputedValue, ToResolvedValue, ToShmem)] + #[css(bitflags(single = "none", mixed = "underline,overline,line-through,blink"))] #[repr(C)] /// Specified keyword values for the text-decoration-line property. pub struct TextDecorationLine: u8 { @@ -265,94 +265,6 @@ impl Default for TextDecorationLine { } } -impl Parse for TextDecorationLine { - /// none | [ underline || overline || line-through || blink ] - fn parse<'i, 't>( - _context: &ParserContext, - input: &mut Parser<'i, 't>, - ) -> Result> { - let mut result = TextDecorationLine::empty(); - - // NOTE(emilio): this loop has this weird structure because we run this - // code to parse the text-decoration shorthand as well, so we need to - // ensure we don't return an error if we don't consume the whole thing - // because we find an invalid identifier or other kind of token. - loop { - let flag: Result<_, ParseError<'i>> = input.try_parse(|input| { - let flag = try_match_ident_ignore_ascii_case! { input, - "none" if result.is_empty() => TextDecorationLine::NONE, - "underline" => TextDecorationLine::UNDERLINE, - "overline" => TextDecorationLine::OVERLINE, - "line-through" => TextDecorationLine::LINE_THROUGH, - "blink" => TextDecorationLine::BLINK, - }; - - Ok(flag) - }); - - let flag = match flag { - Ok(flag) => flag, - Err(..) => break, - }; - - if flag.is_empty() { - return Ok(TextDecorationLine::NONE); - } - - if result.contains(flag) { - return Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError)); - } - - result.insert(flag) - } - - if !result.is_empty() { - Ok(result) - } else { - Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError)) - } - } -} - -impl ToCss for TextDecorationLine { - fn to_css(&self, dest: &mut CssWriter) -> fmt::Result - where - W: Write, - { - if self.is_empty() { - return dest.write_str("none"); - } - - #[cfg(feature = "gecko")] - { - if *self == TextDecorationLine::COLOR_OVERRIDE { - return Ok(()); - } - } - - let mut writer = SequenceWriter::new(dest, " "); - let mut any = false; - - macro_rules! maybe_write { - ($ident:ident => $str:expr) => { - if self.contains(TextDecorationLine::$ident) { - any = true; - writer.raw_item($str)?; - } - }; - } - - maybe_write!(UNDERLINE => "underline"); - maybe_write!(OVERLINE => "overline"); - maybe_write!(LINE_THROUGH => "line-through"); - maybe_write!(BLINK => "blink"); - - debug_assert!(any); - - Ok(()) - } -} - impl TextDecorationLine { #[inline] /// Returns the initial value of text-decoration-line @@ -399,6 +311,7 @@ impl TextTransform { } } +// TODO: This can be simplified by deriving it. impl Parse for TextTransform { fn parse<'i, 't>( _context: &ParserContext, @@ -615,11 +528,20 @@ pub enum TextAlign { /// unlike other keywords. #[cfg(feature = "gecko")] MatchParent, - /// `MozCenterOrInherit` value of text-align property. It cannot be parsed, - /// only set directly on the elements and it has a different handling - /// unlike other values. + /// This is how we implement the following HTML behavior from + /// https://html.spec.whatwg.org/#tables-2: + /// + /// User agents are expected to have a rule in their user agent style sheet + /// that matches th elements that have a parent node whose computed value + /// for the 'text-align' property is its initial value, whose declaration + /// block consists of just a single declaration that sets the 'text-align' + /// property to the value 'center'. + /// + /// Since selectors can't depend on the ancestor styles, we implement it with a + /// magic value that computes to the right thing. Since this is an + /// implementation detail, it shouldn't be exposed to web content. #[cfg(feature = "gecko")] - #[css(skip)] + #[parse(condition = "ParserContext::in_ua_or_chrome_sheet")] MozCenterOrInherit, } @@ -1194,6 +1116,7 @@ bitflags! { } } +// TODO: This can be derived with some care. impl Parse for TextUnderlinePosition { fn parse<'i, 't>( _context: &ParserContext, diff --git a/components/style_derive/parse.rs b/components/style_derive/parse.rs index 18ff31c0526..6c1b144066b 100644 --- a/components/style_derive/parse.rs +++ b/components/style_derive/parse.rs @@ -2,14 +2,14 @@ * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ -use crate::to_css::CssVariantAttrs; +use crate::to_css::{CssBitflagAttrs, CssVariantAttrs}; use darling::FromField; use darling::FromVariant; use derive_common::cg; -use proc_macro2::TokenStream; -use quote::quote; +use proc_macro2::{TokenStream, Span}; +use quote::{quote, TokenStreamExt}; use syn::parse_quote; -use syn::{self, DeriveInput, Path}; +use syn::{self, Ident, DeriveInput, Path}; use synstructure::{Structure, VariantInfo}; #[derive(Default, FromVariant)] @@ -25,6 +25,70 @@ pub struct ParseFieldAttrs { field_bound: bool, } +fn parse_bitflags(bitflags: &CssBitflagAttrs) -> TokenStream { + let mut match_arms = TokenStream::new(); + for (rust_name, css_name) in bitflags.single_flags() { + let rust_ident = Ident::new(&rust_name, Span::call_site()); + match_arms.append_all(quote! { + #css_name if result.is_empty() => { + single_flag = true; + Self::#rust_ident + }, + }); + } + + for (rust_name, css_name) in bitflags.mixed_flags() { + let rust_ident = Ident::new(&rust_name, Span::call_site()); + match_arms.append_all(quote! { + #css_name => Self::#rust_ident, + }); + } + + let mut validate_condition = quote! { !result.is_empty() }; + if let Some(ref function) = bitflags.validate_mixed { + validate_condition.append_all(quote! { + && #function(result) + }); + } + + // NOTE(emilio): this loop has this weird structure because we run this code + // to parse stuff like text-decoration-line in the text-decoration + // shorthand, so we need to be a bit careful that we don't error if we don't + // consume the whole thing because we find an invalid identifier or other + // kind of token. Instead, we should leave it unconsumed. + quote! { + let mut result = Self::empty(); + loop { + let mut single_flag = false; + let flag: Result<_, style_traits::ParseError<'i>> = input.try_parse(|input| { + Ok(try_match_ident_ignore_ascii_case! { input, + #match_arms + }) + }); + + let flag = match flag { + Ok(flag) => flag, + Err(..) => break, + }; + + if single_flag { + return Ok(flag); + } + + if result.intersects(flag) { + return Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError)); + } + + result.insert(flag); + } + if #validate_condition { + Ok(result) + } else { + Err(input.new_custom_error(style_traits::StyleParseErrorKind::UnspecifiedError)) + } + } +} + fn parse_non_keyword_variant( where_clause: &mut Option, name: &syn::Ident, @@ -46,6 +110,13 @@ fn parse_non_keyword_variant( let binding_ast = &bindings[0].ast(); let ty = &binding_ast.ty; + if let Some(ref bitflags) = variant_attrs.bitflags { + assert!(skip_try, "Should be the only variant"); + assert!(parse_attrs.condition.is_none(), "Should be the only variant"); + assert!(where_clause.is_none(), "Generic bitflags?"); + return parse_bitflags(bitflags) + } + let field_attrs = cg::parse_field_attrs::(binding_ast); if field_attrs.field_bound { cg::add_predicate(where_clause, parse_quote!(#ty: crate::parser::Parse)); diff --git a/components/style_derive/rustfmt.toml b/components/style_derive/rustfmt.toml new file mode 100644 index 00000000000..c7ad93bafe3 --- /dev/null +++ b/components/style_derive/rustfmt.toml @@ -0,0 +1 @@ +disable_all_formatting = true diff --git a/components/style_derive/specified_value_info.rs b/components/style_derive/specified_value_info.rs index d198990cca4..5d14aaa05ac 100644 --- a/components/style_derive/specified_value_info.rs +++ b/components/style_derive/specified_value_info.rs @@ -71,7 +71,14 @@ pub fn derive(mut input: DeriveInput) -> TokenStream { } }, Data::Struct(ref s) => { - if !derive_struct_fields(&s.fields, &mut types, &mut values) { + if let Some(ref bitflags) = css_attrs.bitflags { + for (_rust_name, css_name) in bitflags.single_flags() { + values.push(css_name) + } + for (_rust_name, css_name) in bitflags.mixed_flags() { + values.push(css_name) + } + } else if !derive_struct_fields(&s.fields, &mut types, &mut values) { values.push(input_name()); } }, diff --git a/components/style_derive/to_css.rs b/components/style_derive/to_css.rs index 40e219c374b..ac0bf05b900 100644 --- a/components/style_derive/to_css.rs +++ b/components/style_derive/to_css.rs @@ -5,15 +5,84 @@ use darling::util::Override; use darling::FromDeriveInput; use darling::FromField; +use darling::FromMeta; use darling::FromVariant; use derive_common::cg; -use proc_macro2::TokenStream; +use proc_macro2::{Span, TokenStream}; use quote::quote; use quote::{ToTokens, TokenStreamExt}; use syn::parse_quote; -use syn::{self, Data, Path, WhereClause}; +use syn::{self, Data, Ident, Path, WhereClause}; use synstructure::{BindingInfo, Structure, VariantInfo}; +fn derive_bitflags(input: &syn::DeriveInput, bitflags: &CssBitflagAttrs) -> TokenStream { + let name = &input.ident; + let mut body = TokenStream::new(); + for (rust_name, css_name) in bitflags.single_flags() { + let rust_ident = Ident::new(&rust_name, Span::call_site()); + body.append_all(quote! { + if *self == Self::#rust_ident { + return dest.write_str(#css_name); + } + }); + } + + body.append_all(quote! { + let mut has_any = false; + }); + + if bitflags.overlapping_bits { + body.append_all(quote! { + let mut serialized = Self::empty(); + }); + } + + for (rust_name, css_name) in bitflags.mixed_flags() { + let rust_ident = Ident::new(&rust_name, Span::call_site()); + let serialize = quote! { + if has_any { + dest.write_char(' ')?; + } + has_any = true; + dest.write_str(#css_name)?; + }; + if bitflags.overlapping_bits { + body.append_all(quote! { + if self.contains(Self::#rust_ident) && !serialized.intersects(Self::#rust_ident) { + #serialize + serialized.insert(Self::#rust_ident); + } + }); + } else { + body.append_all(quote! { + if self.intersects(Self::#rust_ident) { + #serialize + } + }); + } + } + + body.append_all(quote! { + Ok(()) + }); + + quote! { + impl style_traits::ToCss for #name { + #[allow(unused_variables)] + #[inline] + fn to_css( + &self, + dest: &mut style_traits::CssWriter, + ) -> std::fmt::Result + where + W: std::fmt::Write, + { + #body + } + } + } +} + pub fn derive(mut input: syn::DeriveInput) -> TokenStream { let mut where_clause = input.generics.where_clause.take(); for param in input.generics.type_params() { @@ -21,12 +90,21 @@ pub fn derive(mut input: syn::DeriveInput) -> TokenStream { } let input_attrs = cg::parse_input_attrs::(&input); - if let Data::Enum(_) = input.data { + if matches!(input.data, Data::Enum(..)) || input_attrs.bitflags.is_some() { assert!( input_attrs.function.is_none(), - "#[css(function)] is not allowed on enums" + "#[css(function)] is not allowed on enums or bitflags" ); - assert!(!input_attrs.comma, "#[css(comma)] is not allowed on enums"); + assert!( + !input_attrs.comma, + "#[css(comma)] is not allowed on enums or bitflags" + ); + } + + if let Some(ref bitflags) = input_attrs.bitflags { + assert!(!input_attrs.derive_debug, "Bitflags can derive debug on their own"); + assert!(where_clause.is_none(), "Generic bitflags?"); + return derive_bitflags(&input, bitflags); } let match_body = { @@ -249,6 +327,36 @@ fn derive_single_field_expr( expr } +#[derive(Default, FromMeta)] +pub struct CssBitflagAttrs { + /// Flags that can only go on their own, comma-separated. + pub single: String, + /// Flags that can go mixed with each other, comma-separated. + pub mixed: String, + /// Extra validation of the resulting mixed flags. + #[darling(default)] + pub validate_mixed: Option, + /// Whether there are overlapping bits we need to take care of when + /// serializing. + #[darling(default)] + pub overlapping_bits: bool, +} + +impl CssBitflagAttrs { + /// Returns a vector of (rust_name, css_name) of a given flag list. + fn names(s: &str) -> Vec<(String, String)> { + s.split(',').map(|css_name| (cg::to_scream_case(css_name), css_name.to_owned())).collect() + } + + pub fn single_flags(&self) -> Vec<(String, String)> { + Self::names(&self.single) + } + + pub fn mixed_flags(&self) -> Vec<(String, String)> { + Self::names(&self.mixed) + } +} + #[derive(Default, FromDeriveInput)] #[darling(attributes(css), default)] pub struct CssInputAttrs { @@ -257,6 +365,7 @@ pub struct CssInputAttrs { pub function: Option>, // Here because structs variants are also their whole type definition. pub comma: bool, + pub bitflags: Option, } #[derive(Default, FromVariant)] @@ -266,6 +375,7 @@ pub struct CssVariantAttrs { // Here because structs variants are also their whole type definition. pub derive_debug: bool, pub comma: bool, + pub bitflags: Option, pub dimension: bool, pub keyword: Option, pub skip: bool, diff --git a/components/style_traits/Cargo.toml b/components/style_traits/Cargo.toml index 779bb5487b5..8a135354cf8 100644 --- a/components/style_traits/Cargo.toml +++ b/components/style_traits/Cargo.toml @@ -27,6 +27,7 @@ serde = "1.0" servo_arc = { path = "../servo_arc" } servo_atoms = { path = "../atoms", optional = true } servo_url = { path = "../url", optional = true } +size_of_test = { path = "../size_of_test" } to_shmem = { path = "../to_shmem" } to_shmem_derive = { path = "../to_shmem_derive" } webrender_api = { workspace = true, optional = true } diff --git a/components/style_traits/lib.rs b/components/style_traits/lib.rs index 28260272d66..d7c7d8ad33d 100644 --- a/components/style_traits/lib.rs +++ b/components/style_traits/lib.rs @@ -10,6 +10,9 @@ #![crate_type = "rlib"] #![deny(unsafe_code, missing_docs)] +#[macro_use] +extern crate size_of_test; + use bitflags::bitflags; use malloc_size_of_derive::MallocSizeOf; use serde::{Deserialize, Serialize}; @@ -81,9 +84,11 @@ pub use crate::values::{ /// The error type for all CSS parsing routines. pub type ParseError<'i> = cssparser::ParseError<'i, StyleParseErrorKind<'i>>; +size_of_test!(ParseError, 64); /// Error in property value parsing pub type ValueParseError<'i> = cssparser::ParseError<'i, ValueParseErrorKind<'i>>; +size_of_test!(ValueParseError, 48); #[derive(Clone, Debug, PartialEq)] /// Errors that can be encountered while parsing CSS values. @@ -148,6 +153,7 @@ pub enum StyleParseErrorKind<'i> { /// The property is not allowed within a page rule. NotAllowedInPageRule, } +size_of_test!(StyleParseErrorKind, 56); impl<'i> From> for StyleParseErrorKind<'i> { fn from(this: ValueParseErrorKind<'i>) -> Self { @@ -169,6 +175,7 @@ pub enum ValueParseErrorKind<'i> { /// An invalid filter value was encountered. InvalidFilter(Token<'i>), } +size_of_test!(ValueParseErrorKind, 40); impl<'i> StyleParseErrorKind<'i> { /// Create an InvalidValue parse error diff --git a/components/style_traits/values.rs b/components/style_traits/values.rs index 124626bc973..3bae5d2553b 100644 --- a/components/style_traits/values.rs +++ b/components/style_traits/values.rs @@ -44,6 +44,36 @@ use std::fmt::{self, Write}; /// * `#[css(represents_keyword)]` can be used on bool fields in order to /// serialize the field name if the field is true, or nothing otherwise. It /// also collects those keywords for `SpecifiedValueInfo`. +/// * `#[css(bitflags(single="", mixed="", validate="", overlapping_bits)]` can +/// be used to derive parse / serialize / etc on bitflags. The rules for parsing +/// bitflags are the following: +/// +/// * `single` flags can only appear on their own. It's common that bitflags +/// properties at least have one such value like `none` or `auto`. +/// * `mixed` properties can appear mixed together, but not along any other +/// flag that shares a bit with itself. For example, if you have three +/// bitflags like: +/// +/// FOO = 1 << 0; +/// BAR = 1 << 1; +/// BAZ = 1 << 2; +/// BAZZ = BAR | BAZ; +/// +/// Then the following combinations won't be valid: +/// +/// * foo foo: (every flag shares a bit with itself) +/// * bar bazz: (bazz shares a bit with bar) +/// +/// But `bar baz` will be valid, as they don't share bits, and so would +/// `foo` with any other flag, or `bazz` on its own. +/// * `overlapping_bits` enables some tracking during serialization of mixed +/// flags to avoid serializing variants that can subsume other variants. +/// In the example above, you could do: +/// mixed="foo,bazz,bar,baz", overlapping_bits +/// to ensure that if bazz is serialized, bar and baz aren't, even though +/// their bits are set. Note that the serialization order is canonical, +/// and thus depends on the order you specify the flags in. +/// /// * finally, one can put `#[css(derive_debug)]` on the whole type, to /// implement `Debug` by a single call to `ToCss::to_css`. pub trait ToCss { @@ -557,6 +587,8 @@ pub mod specified { NonNegative, /// Allow only numeric values greater or equal to 1.0. AtLeastOne, + /// Allow only numeric values from 0 to 1.0. + ZeroToOne, } impl Default for AllowedNumericType { @@ -577,6 +609,7 @@ pub mod specified { AllowedNumericType::All => true, AllowedNumericType::NonNegative => val >= 0.0, AllowedNumericType::AtLeastOne => val >= 1.0, + AllowedNumericType::ZeroToOne => val >= 0.0 && val <= 1.0, } } @@ -584,9 +617,10 @@ pub mod specified { #[inline] pub fn clamp(&self, val: f32) -> f32 { match *self { - AllowedNumericType::NonNegative if val < 0. => 0., - AllowedNumericType::AtLeastOne if val < 1. => 1., - _ => val, + AllowedNumericType::All => val, + AllowedNumericType::NonNegative => val.max(0.), + AllowedNumericType::AtLeastOne => val.max(1.), + AllowedNumericType::ZeroToOne => val.max(0.).min(1.), } } } diff --git a/servo-tidy.toml b/servo-tidy.toml index a2d4f10c724..0560ae5afd8 100644 --- a/servo-tidy.toml +++ b/servo-tidy.toml @@ -86,32 +86,9 @@ packages = [ # Files that are ignored for all tidy and lint checks. files = [ "./components/net/tests/parsable_mime/text", - # These are ignored to avoid diverging from Gecko - "./components/style/counter_style/mod.rs", - "./components/style/properties/declaration_block.rs", - "./components/style/properties/helpers.mako.rs", - "./components/style/rule_collector.rs", - "./components/style/selector_map.rs", - "./components/style/stylesheets/import_rule.rs", - "./components/style/stylesheets/layer_rule.rs", - "./components/style/stylesheets/origin.rs", - "./components/style/stylesheets/page_rule.rs", - "./components/style/stylesheets/rule_parser.rs", - "./components/style/stylesheets/scroll_timeline_rule.rs", - "./components/style/stylist.rs", - "./components/style/values/animated/transform.rs", - "./components/style/values/computed/font.rs", - "./components/style/values/computed/image.rs", - "./components/style/values/specified/box.rs", - "./components/style/values/specified/color.rs", - "./components/style/values/specified/transform.rs", - # Mako does not lend itself easily to splitting long lines - "./components/style/properties/helpers/animated_properties.mako.rs", - "./components/style/properties/shorthands/text.mako.rs", - # Long regexes are long. - "./components/style/gecko/regen_atoms.py", - # Helper macro where actually a pseudo-element per line makes sense. - "./components/style/gecko/non_ts_pseudo_class_list.rs", + # Ignore style files to avoid diverging too much from upstream Gecko + "./components/style/", + "./components/style_derive/parse.rs", "./resources/hsts_preload.json", "./tests/wpt/meta/MANIFEST.json", "./tests/wpt/meta-legacy-layout/MANIFEST.json", diff --git a/tests/unit/style/Cargo.toml b/tests/unit/style/Cargo.toml index c9f17dcf973..38439c1f280 100644 --- a/tests/unit/style/Cargo.toml +++ b/tests/unit/style/Cargo.toml @@ -21,7 +21,6 @@ servo_arc = {path = "../../../components/servo_arc"} servo_atoms = {path = "../../../components/atoms"} servo_config = {path = "../../../components/config"} servo_url = {path = "../../../components/url"} -size_of_test = {path = "../../../components/size_of_test"} style = {path = "../../../components/style", features = ["servo"]} style_traits = {path = "../../../components/style_traits"} std_test_override = { path = "../../../components/std_test_override" } diff --git a/tests/unit/style/lib.rs b/tests/unit/style/lib.rs index c87c8fbc370..3ccb818dfcb 100644 --- a/tests/unit/style/lib.rs +++ b/tests/unit/style/lib.rs @@ -16,9 +16,6 @@ extern crate serde_json; extern crate servo_arc; extern crate servo_atoms; extern crate servo_url; -#[macro_use] -extern crate size_of_test; -#[macro_use] extern crate style; extern crate style_traits; extern crate test; @@ -30,8 +27,6 @@ mod logical_geometry; mod parsing; mod properties; mod rule_tree; -mod size_of; -mod specified_values; mod str; mod stylesheets; mod stylist; diff --git a/tests/unit/style/size_of.rs b/tests/unit/style/size_of.rs deleted file mode 100644 index cb64495141b..00000000000 --- a/tests/unit/style/size_of.rs +++ /dev/null @@ -1,51 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ - -use selectors::parser::{SelectorParseError, SelectorParseErrorKind}; -use style::invalidation::element::invalidation_map::Dependency; -use style::properties; - -size_of_test!(test_size_of_dependency, Dependency, 24); - -size_of_test!( - test_size_of_property_declaration, - properties::PropertyDeclaration, - 32 -); - -// This is huge, but we allocate it on the stack and then never move it, -// we only pass `&mut SourcePropertyDeclaration` references around. -size_of_test!( - test_size_of_parsed_declaration, - properties::SourcePropertyDeclaration, - 568 -); - -size_of_test!( - test_size_of_selector_parse_error_kind, - SelectorParseErrorKind, - 40 -); -size_of_test!( - test_size_of_style_parse_error_kind, - ::style_traits::StyleParseErrorKind, - 56 -); -size_of_test!( - test_size_of_value_parse_error_kind, - ::style_traits::ValueParseErrorKind, - 40 -); - -size_of_test!(test_size_of_selector_parse_error, SelectorParseError, 48); -size_of_test!( - test_size_of_style_traits_parse_error, - ::style_traits::ParseError, - 64 -); -size_of_test!( - test_size_of_value_parse_error, - ::style_traits::ValueParseError, - 48 -); diff --git a/tests/unit/style/specified_values.rs b/tests/unit/style/specified_values.rs deleted file mode 100644 index d5c061ab502..00000000000 --- a/tests/unit/style/specified_values.rs +++ /dev/null @@ -1,50 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ - -use style; - -#[cfg(all(test, target_pointer_width = "64"))] -#[test] -fn size_of_specified_values() { - use std::mem::size_of; - let threshold = 24; - - let mut bad_properties = vec![]; - - macro_rules! check_property { - ( $( { $name: ident, $boxed: expr } )+ ) => { - $( - let size = size_of::(); - let is_boxed = $boxed; - if (!is_boxed && size > threshold) || (is_boxed && size <= threshold) { - bad_properties.push((stringify!($name), size, is_boxed)); - } - )+ - } - } - - longhand_properties_idents!(check_property); - - let mut failing_messages = vec![]; - - for bad_prop in bad_properties { - if !bad_prop.2 { - failing_messages.push( - format!("Your changes have increased the size of {} SpecifiedValue to {}. The threshold is \ - currently {}. SpecifiedValues affect size of PropertyDeclaration enum and \ - increasing the size may negative affect style system performance. Please consider \ - using `boxed=\"True\"` in this longhand.", - bad_prop.0, bad_prop.1, threshold)); - } else if bad_prop.2 { - failing_messages.push( - format!("Your changes have decreased the size of {} SpecifiedValue to {}. Good work! \ - The threshold is currently {}. Please consider removing `boxed=\"True\"` from this longhand.", - bad_prop.0, bad_prop.1, threshold)); - } - } - - if !failing_messages.is_empty() { - panic!("{}", failing_messages.join("\n\n")); - } -} diff --git a/tests/unit/style/stylist.rs b/tests/unit/style/stylist.rs index 46d83fca317..b680d8188a2 100644 --- a/tests/unit/style/stylist.rs +++ b/tests/unit/style/stylist.rs @@ -16,10 +16,9 @@ use style::properties::{PropertyDeclaration, PropertyDeclarationBlock}; use style::selector_map::SelectorMap; use style::selector_parser::{SelectorImpl, SelectorParser}; use style::shared_lock::SharedRwLock; -use style::stylesheets::layer_rule::LayerId; use style::stylesheets::StyleRule; use style::stylist::needs_revalidation_for_testing; -use style::stylist::{Rule, Stylist}; +use style::stylist::{ContainerConditionId, LayerId, Rule, Stylist}; use style::thread_state::{self, ThreadState}; /// Helper method to get some Rules from selector strings. @@ -56,6 +55,7 @@ fn get_mock_rules(css_selectors: &[&str]) -> (Vec>, SharedRwLock) { locked.clone(), i as u32, LayerId::root(), + ContainerConditionId::none(), ) }) .collect() diff --git a/tests/wpt/meta-legacy-layout/css/css-values/viewport-units-compute.html.ini b/tests/wpt/meta-legacy-layout/css/css-values/viewport-units-compute.html.ini deleted file mode 100644 index 06a82c1aaba..00000000000 --- a/tests/wpt/meta-legacy-layout/css/css-values/viewport-units-compute.html.ini +++ /dev/null @@ -1,90 +0,0 @@ -[viewport-units-compute.html] - [100vi computes to 200px] - expected: FAIL - - [100svw computes to 200px] - expected: FAIL - - [100svi computes to 200px] - expected: FAIL - - [100svmax computes to 200px] - expected: FAIL - - [100lvw computes to 200px] - expected: FAIL - - [100lvi computes to 200px] - expected: FAIL - - [100lvmax computes to 200px] - expected: FAIL - - [100dvw computes to 200px] - expected: FAIL - - [100dvi computes to 200px] - expected: FAIL - - [100dvmax computes to 200px] - expected: FAIL - - [100vb computes to 100px] - expected: FAIL - - [100svh computes to 100px] - expected: FAIL - - [100svb computes to 100px] - expected: FAIL - - [100svmin computes to 100px] - expected: FAIL - - [100lvh computes to 100px] - expected: FAIL - - [100lvb computes to 100px] - expected: FAIL - - [100lvmin computes to 100px] - expected: FAIL - - [100dvh computes to 100px] - expected: FAIL - - [100dvb computes to 100px] - expected: FAIL - - [100dvmin computes to 100px] - expected: FAIL - - [1dvw computes to 2px] - expected: FAIL - - [10dvw computes to 20px] - expected: FAIL - - [1dvh computes to 1px] - expected: FAIL - - [10dvh computes to 10px] - expected: FAIL - - [calc(1dvw + 1dvw) computes to 4px] - expected: FAIL - - [calc(1dvw + 1dvh) computes to 3px] - expected: FAIL - - [calc(1dvw + 100px) computes to 102px] - expected: FAIL - - [max(1svw, 1svh) computes to 2px] - expected: FAIL - - [min(1lvw, 1lvh) computes to 1px] - expected: FAIL - - [calc(1dvw + 10%) computes to 12px] - expected: FAIL diff --git a/tests/wpt/meta-legacy-layout/css/css-values/viewport-units-keyframes.html.ini b/tests/wpt/meta-legacy-layout/css/css-values/viewport-units-keyframes.html.ini deleted file mode 100644 index 804ae41be0b..00000000000 --- a/tests/wpt/meta-legacy-layout/css/css-values/viewport-units-keyframes.html.ini +++ /dev/null @@ -1,60 +0,0 @@ -[viewport-units-keyframes.html] - [Interpolation from 0px to 100vi is 100px at 50%] - expected: FAIL - - [Interpolation from 0px to 100svw is 100px at 50%] - expected: FAIL - - [Interpolation from 0px to 100svi is 100px at 50%] - expected: FAIL - - [Interpolation from 0px to 100svmax is 100px at 50%] - expected: FAIL - - [Interpolation from 0px to 100lvw is 100px at 50%] - expected: FAIL - - [Interpolation from 0px to 100lvi is 100px at 50%] - expected: FAIL - - [Interpolation from 0px to 100lvmax is 100px at 50%] - expected: FAIL - - [Interpolation from 0px to 100dvw is 100px at 50%] - expected: FAIL - - [Interpolation from 0px to 100dvi is 100px at 50%] - expected: FAIL - - [Interpolation from 0px to 100dvmax is 100px at 50%] - expected: FAIL - - [Interpolation from 0px to 100vb is 50px at 50%] - expected: FAIL - - [Interpolation from 0px to 100svh is 50px at 50%] - expected: FAIL - - [Interpolation from 0px to 100svb is 50px at 50%] - expected: FAIL - - [Interpolation from 0px to 100svmin is 50px at 50%] - expected: FAIL - - [Interpolation from 0px to 100lvh is 50px at 50%] - expected: FAIL - - [Interpolation from 0px to 100lvb is 50px at 50%] - expected: FAIL - - [Interpolation from 0px to 100lvmin is 50px at 50%] - expected: FAIL - - [Interpolation from 0px to 100dvh is 50px at 50%] - expected: FAIL - - [Interpolation from 0px to 100dvb is 50px at 50%] - expected: FAIL - - [Interpolation from 0px to 100dvmin is 50px at 50%] - expected: FAIL diff --git a/tests/wpt/meta-legacy-layout/css/css-values/viewport-units-media-queries.html.ini b/tests/wpt/meta-legacy-layout/css/css-values/viewport-units-media-queries.html.ini index 3ab74d58d8e..f35893c7154 100644 --- a/tests/wpt/meta-legacy-layout/css/css-values/viewport-units-media-queries.html.ini +++ b/tests/wpt/meta-legacy-layout/css/css-values/viewport-units-media-queries.html.ini @@ -1,34 +1,4 @@ [viewport-units-media-queries.html] - [@media(width:100vi) applies] - expected: FAIL - - [@media(width:100svw) applies] - expected: FAIL - - [@media(width:100svi) applies] - expected: FAIL - - [@media(width:100svmax) applies] - expected: FAIL - - [@media(width:100lvw) applies] - expected: FAIL - - [@media(width:100lvi) applies] - expected: FAIL - - [@media(width:100lvmax) applies] - expected: FAIL - - [@media(width:100dvw) applies] - expected: FAIL - - [@media(width:100dvi) applies] - expected: FAIL - - [@media(width:100dvmax) applies] - expected: FAIL - [@media(height:100vh) applies] expected: FAIL diff --git a/tests/wpt/meta-legacy-layout/css/css-values/viewport-units-parsing.html.ini b/tests/wpt/meta-legacy-layout/css/css-values/viewport-units-parsing.html.ini deleted file mode 100644 index cefd79faca5..00000000000 --- a/tests/wpt/meta-legacy-layout/css/css-values/viewport-units-parsing.html.ini +++ /dev/null @@ -1,60 +0,0 @@ -[viewport-units-parsing.html] - [e.style['width'\] = "1vi" should set the property value] - expected: FAIL - - [e.style['width'\] = "1vb" should set the property value] - expected: FAIL - - [e.style['width'\] = "1svw" should set the property value] - expected: FAIL - - [e.style['width'\] = "1svh" should set the property value] - expected: FAIL - - [e.style['width'\] = "1svi" should set the property value] - expected: FAIL - - [e.style['width'\] = "1svb" should set the property value] - expected: FAIL - - [e.style['width'\] = "1svmin" should set the property value] - expected: FAIL - - [e.style['width'\] = "1svmax" should set the property value] - expected: FAIL - - [e.style['width'\] = "1lvw" should set the property value] - expected: FAIL - - [e.style['width'\] = "1lvh" should set the property value] - expected: FAIL - - [e.style['width'\] = "1lvi" should set the property value] - expected: FAIL - - [e.style['width'\] = "1lvb" should set the property value] - expected: FAIL - - [e.style['width'\] = "1lvmin" should set the property value] - expected: FAIL - - [e.style['width'\] = "1lvmax" should set the property value] - expected: FAIL - - [e.style['width'\] = "1dvw" should set the property value] - expected: FAIL - - [e.style['width'\] = "1dvh" should set the property value] - expected: FAIL - - [e.style['width'\] = "1dvi" should set the property value] - expected: FAIL - - [e.style['width'\] = "1dvb" should set the property value] - expected: FAIL - - [e.style['width'\] = "1dvmin" should set the property value] - expected: FAIL - - [e.style['width'\] = "1dvmax" should set the property value] - expected: FAIL diff --git a/tests/wpt/meta/css/css-values/viewport-units-media-queries.html.ini b/tests/wpt/meta/css/css-values/viewport-units-media-queries.html.ini index adcbd9435b3..9fbce555a33 100644 --- a/tests/wpt/meta/css/css-values/viewport-units-media-queries.html.ini +++ b/tests/wpt/meta/css/css-values/viewport-units-media-queries.html.ini @@ -1,34 +1,4 @@ [viewport-units-media-queries.html] - [@media(width:100vi) applies] - expected: FAIL - - [@media(width:100svw) applies] - expected: FAIL - - [@media(width:100svi) applies] - expected: FAIL - - [@media(width:100svmax) applies] - expected: FAIL - - [@media(width:100lvw) applies] - expected: FAIL - - [@media(width:100lvi) applies] - expected: FAIL - - [@media(width:100lvmax) applies] - expected: FAIL - - [@media(width:100dvw) applies] - expected: FAIL - - [@media(width:100dvi) applies] - expected: FAIL - - [@media(width:100dvmax) applies] - expected: FAIL - [@media(height:100vh) applies] expected: FAIL diff --git a/tests/wpt/meta/css/css-values/viewport-units-parsing.html.ini b/tests/wpt/meta/css/css-values/viewport-units-parsing.html.ini deleted file mode 100644 index cefd79faca5..00000000000 --- a/tests/wpt/meta/css/css-values/viewport-units-parsing.html.ini +++ /dev/null @@ -1,60 +0,0 @@ -[viewport-units-parsing.html] - [e.style['width'\] = "1vi" should set the property value] - expected: FAIL - - [e.style['width'\] = "1vb" should set the property value] - expected: FAIL - - [e.style['width'\] = "1svw" should set the property value] - expected: FAIL - - [e.style['width'\] = "1svh" should set the property value] - expected: FAIL - - [e.style['width'\] = "1svi" should set the property value] - expected: FAIL - - [e.style['width'\] = "1svb" should set the property value] - expected: FAIL - - [e.style['width'\] = "1svmin" should set the property value] - expected: FAIL - - [e.style['width'\] = "1svmax" should set the property value] - expected: FAIL - - [e.style['width'\] = "1lvw" should set the property value] - expected: FAIL - - [e.style['width'\] = "1lvh" should set the property value] - expected: FAIL - - [e.style['width'\] = "1lvi" should set the property value] - expected: FAIL - - [e.style['width'\] = "1lvb" should set the property value] - expected: FAIL - - [e.style['width'\] = "1lvmin" should set the property value] - expected: FAIL - - [e.style['width'\] = "1lvmax" should set the property value] - expected: FAIL - - [e.style['width'\] = "1dvw" should set the property value] - expected: FAIL - - [e.style['width'\] = "1dvh" should set the property value] - expected: FAIL - - [e.style['width'\] = "1dvi" should set the property value] - expected: FAIL - - [e.style['width'\] = "1dvb" should set the property value] - expected: FAIL - - [e.style['width'\] = "1dvmin" should set the property value] - expected: FAIL - - [e.style['width'\] = "1dvmax" should set the property value] - expected: FAIL