mirror of
https://github.com/servo/servo.git
synced 2025-06-06 16:45:39 +00:00
Backport several style changes from Gecko (5) (#30099)
* 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 `<th>` `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 <browser> 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 <ident> 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<T> 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: <length> 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 <emilio@crisal.io> Co-authored-by: Brad Werth <bwerth@mozilla.com> Co-authored-by: David Shin <dshin@mozilla.com> Co-authored-by: Hiroyuki Ikezoe <hikezoe.birchill@mozilla.com> Co-authored-by: Nika Layzell <nika@thelayzells.com> Co-authored-by: Boris Chiou <boris.chiou@gmail.com> Co-authored-by: Autumn on Tape <autumn@cyfox.net> Co-authored-by: Mike Hommey <mh+mozilla@glandium.org>
This commit is contained in:
parent
8255e8e318
commit
8e15389cae
152 changed files with 5857 additions and 4617 deletions
17
Cargo.lock
generated
17
Cargo.lock
generated
|
@ -5372,6 +5372,7 @@ dependencies = [
|
||||||
"phf_codegen",
|
"phf_codegen",
|
||||||
"precomputed-hash",
|
"precomputed-hash",
|
||||||
"servo_arc",
|
"servo_arc",
|
||||||
|
"size_of_test",
|
||||||
"smallvec",
|
"smallvec",
|
||||||
"to_shmem",
|
"to_shmem",
|
||||||
"to_shmem_derive",
|
"to_shmem_derive",
|
||||||
|
@ -5915,6 +5916,9 @@ checksum = "fa8f3741c7372e75519bd9346068370c9cdaabcc1f9599cbcf2a2719352286b7"
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "size_of_test"
|
name = "size_of_test"
|
||||||
version = "0.0.1"
|
version = "0.0.1"
|
||||||
|
dependencies = [
|
||||||
|
"static_assertions",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "slab"
|
name = "slab"
|
||||||
|
@ -6042,6 +6046,12 @@ version = "1.1.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "dba1a27d3efae4351c8051072d619e3ade2820635c3958d826bfea39d59b54c8"
|
checksum = "dba1a27d3efae4351c8051072d619e3ade2820635c3958d826bfea39d59b54c8"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "static_assertions"
|
||||||
|
version = "1.1.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "std_test_override"
|
name = "std_test_override"
|
||||||
version = "0.0.1"
|
version = "0.0.1"
|
||||||
|
@ -6145,6 +6155,7 @@ dependencies = [
|
||||||
"servo_url",
|
"servo_url",
|
||||||
"smallbitvec",
|
"smallbitvec",
|
||||||
"smallvec",
|
"smallvec",
|
||||||
|
"static_assertions",
|
||||||
"string_cache",
|
"string_cache",
|
||||||
"style_derive",
|
"style_derive",
|
||||||
"style_traits",
|
"style_traits",
|
||||||
|
@ -6186,7 +6197,6 @@ dependencies = [
|
||||||
"servo_atoms",
|
"servo_atoms",
|
||||||
"servo_config",
|
"servo_config",
|
||||||
"servo_url",
|
"servo_url",
|
||||||
"size_of_test",
|
|
||||||
"std_test_override",
|
"std_test_override",
|
||||||
"style",
|
"style",
|
||||||
"style_traits",
|
"style_traits",
|
||||||
|
@ -6208,6 +6218,7 @@ dependencies = [
|
||||||
"servo_arc",
|
"servo_arc",
|
||||||
"servo_atoms",
|
"servo_atoms",
|
||||||
"servo_url",
|
"servo_url",
|
||||||
|
"size_of_test",
|
||||||
"to_shmem",
|
"to_shmem",
|
||||||
"to_shmem_derive",
|
"to_shmem_derive",
|
||||||
"webrender_api",
|
"webrender_api",
|
||||||
|
@ -6691,9 +6702,9 @@ checksum = "9e79c4d996edb816c91e4308506774452e55e95c3c9de07b6729e17e15a5ef81"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "uluru"
|
name = "uluru"
|
||||||
version = "2.2.0"
|
version = "3.0.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "308dcc9d947b227796f851adb99936fb060681a89c002c9c1928404a3b6c2d72"
|
checksum = "794a32261a1f5eb6a4462c81b59cec87b5c27d5deea7dd1ac8fc781c41d226db"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"arrayvec 0.7.1",
|
"arrayvec 0.7.1",
|
||||||
]
|
]
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
-moz-content-preferred-color-scheme
|
||||||
-moz-gtk-csd-close-button-position
|
-moz-gtk-csd-close-button-position
|
||||||
-moz-gtk-csd-maximize-button-position
|
-moz-gtk-csd-maximize-button-position
|
||||||
-moz-gtk-csd-menu-radius
|
-moz-gtk-csd-menu-radius
|
||||||
|
@ -12,7 +13,9 @@ animationcancel
|
||||||
animationend
|
animationend
|
||||||
animationiteration
|
animationiteration
|
||||||
animationstart
|
animationstart
|
||||||
|
aspect-ratio
|
||||||
beforeunload
|
beforeunload
|
||||||
|
block-size
|
||||||
button
|
button
|
||||||
canplay
|
canplay
|
||||||
canplaythrough
|
canplaythrough
|
||||||
|
@ -50,11 +53,13 @@ fullscreenchange
|
||||||
fullscreenerror
|
fullscreenerror
|
||||||
gattserverdisconnected
|
gattserverdisconnected
|
||||||
hashchange
|
hashchange
|
||||||
|
height
|
||||||
hidden
|
hidden
|
||||||
icecandidate
|
icecandidate
|
||||||
iceconnectionstatechange
|
iceconnectionstatechange
|
||||||
icegatheringstatechange
|
icegatheringstatechange
|
||||||
image
|
image
|
||||||
|
inline-size
|
||||||
input
|
input
|
||||||
inputsourceschange
|
inputsourceschange
|
||||||
invalid
|
invalid
|
||||||
|
@ -82,6 +87,7 @@ none
|
||||||
number
|
number
|
||||||
onchange
|
onchange
|
||||||
open
|
open
|
||||||
|
orientation
|
||||||
pagehide
|
pagehide
|
||||||
pageshow
|
pageshow
|
||||||
password
|
password
|
||||||
|
|
|
@ -373,6 +373,11 @@ pub fn to_css_identifier(mut camel_case: &str) -> String {
|
||||||
result
|
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".
|
/// Given "FooBar", returns "Foo" and sets `camel_case` to "Bar".
|
||||||
fn split_camel_segment<'input>(camel_case: &mut &'input str) -> Option<&'input str> {
|
fn split_camel_segment<'input>(camel_case: &mut &'input str) -> Option<&'input str> {
|
||||||
let index = match camel_case.chars().next() {
|
let index = match camel_case.chars().next() {
|
||||||
|
|
|
@ -44,6 +44,7 @@ servo_atoms = { path = "../atoms" }
|
||||||
servo_config = { path = "../config" }
|
servo_config = { path = "../config" }
|
||||||
servo_geometry = { path = "../geometry" }
|
servo_geometry = { path = "../geometry" }
|
||||||
servo_url = { path = "../url" }
|
servo_url = { path = "../url" }
|
||||||
|
size_of_test = { path = "../size_of_test" }
|
||||||
smallvec = { workspace = true, features = ["union"] }
|
smallvec = { workspace = true, features = ["union"] }
|
||||||
style = { path = "../style", features = ["servo"] }
|
style = { path = "../style", features = ["servo"] }
|
||||||
style_traits = { path = "../style_traits" }
|
style_traits = { path = "../style_traits" }
|
||||||
|
@ -52,6 +53,3 @@ unicode-script = { workspace = true }
|
||||||
webrender_api = { workspace = true }
|
webrender_api = { workspace = true }
|
||||||
xi-unicode = { workspace = true }
|
xi-unicode = { workspace = true }
|
||||||
|
|
||||||
[dev-dependencies]
|
|
||||||
size_of_test = { path = "../size_of_test" }
|
|
||||||
|
|
||||||
|
|
|
@ -653,7 +653,7 @@ impl Fragment {
|
||||||
absolute_bounds: Rect<Au>,
|
absolute_bounds: Rect<Au>,
|
||||||
) {
|
) {
|
||||||
let background = style.get_background();
|
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
|
// XXXManishearth the below method should ideally use an iterator over
|
||||||
// backgrounds
|
// backgrounds
|
||||||
self.build_display_list_for_background_if_applicable_with_background(
|
self.build_display_list_for_background_if_applicable_with_background(
|
||||||
|
@ -1037,7 +1037,9 @@ impl Fragment {
|
||||||
webrender_api::BoxShadowDisplayItem {
|
webrender_api::BoxShadowDisplayItem {
|
||||||
common: items::empty_common_item_properties(),
|
common: items::empty_common_item_properties(),
|
||||||
box_bounds: absolute_bounds.to_layout(),
|
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(
|
offset: LayoutVector2D::new(
|
||||||
box_shadow.base.horizontal.px(),
|
box_shadow.base.horizontal.px(),
|
||||||
box_shadow.base.vertical.px(),
|
box_shadow.base.vertical.px(),
|
||||||
|
@ -1083,10 +1085,10 @@ impl Fragment {
|
||||||
|
|
||||||
let border_style_struct = style.get_border();
|
let border_style_struct = style.get_border();
|
||||||
let mut colors = SideOffsets2D::new(
|
let mut colors = SideOffsets2D::new(
|
||||||
border_style_struct.border_top_color,
|
border_style_struct.border_top_color.clone(),
|
||||||
border_style_struct.border_right_color,
|
border_style_struct.border_right_color.clone(),
|
||||||
border_style_struct.border_bottom_color,
|
border_style_struct.border_bottom_color.clone(),
|
||||||
border_style_struct.border_left_color,
|
border_style_struct.border_left_color.clone(),
|
||||||
);
|
);
|
||||||
let mut border_style = SideOffsets2D::new(
|
let mut border_style = SideOffsets2D::new(
|
||||||
border_style_struct.border_top_style,
|
border_style_struct.border_top_style,
|
||||||
|
@ -1316,7 +1318,7 @@ impl Fragment {
|
||||||
|
|
||||||
// Append the outline to the display list.
|
// Append the outline to the display list.
|
||||||
let color = style
|
let color = style
|
||||||
.resolve_color(style.get_outline().outline_color)
|
.resolve_color(style.get_outline().outline_color.clone())
|
||||||
.to_layout();
|
.to_layout();
|
||||||
let base = state.create_base_display_item(
|
let base = state.create_base_display_item(
|
||||||
clip,
|
clip,
|
||||||
|
@ -1451,7 +1453,8 @@ impl Fragment {
|
||||||
// TODO: Allow non-text fragments to be selected too.
|
// TODO: Allow non-text fragments to be selected too.
|
||||||
if scanned_text_fragment_info.selected() {
|
if scanned_text_fragment_info.selected() {
|
||||||
let style = self.selected_style();
|
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(
|
let base = state.create_base_display_item(
|
||||||
stacking_relative_border_box,
|
stacking_relative_border_box,
|
||||||
self.node,
|
self.node,
|
||||||
|
@ -2055,7 +2058,7 @@ impl Fragment {
|
||||||
base: base.clone(),
|
base: base.clone(),
|
||||||
shadow: webrender_api::Shadow {
|
shadow: webrender_api::Shadow {
|
||||||
offset: LayoutVector2D::new(shadow.horizontal.px(), shadow.vertical.px()),
|
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(),
|
blur_radius: shadow.blur.px(),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
|
@ -86,7 +86,7 @@ fn convert_gradient_stops(
|
||||||
// Only keep the color stops, discard the color interpolation hints.
|
// Only keep the color stops, discard the color interpolation hints.
|
||||||
let mut stop_items = gradient_items
|
let mut stop_items = gradient_items
|
||||||
.iter()
|
.iter()
|
||||||
.filter_map(|item| match *item {
|
.filter_map(|item| match item {
|
||||||
GradientItem::SimpleColorStop(color) => Some(ColorStop {
|
GradientItem::SimpleColorStop(color) => Some(ColorStop {
|
||||||
color,
|
color,
|
||||||
position: None,
|
position: None,
|
||||||
|
@ -191,7 +191,7 @@ fn convert_gradient_stops(
|
||||||
assert!(offset.is_finite());
|
assert!(offset.is_finite());
|
||||||
stops.push(GradientStop {
|
stops.push(GradientStop {
|
||||||
offset: offset,
|
offset: offset,
|
||||||
color: style.resolve_color(stop.color).to_layout(),
|
color: style.resolve_color(stop.color.clone()).to_layout(),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
stops
|
stops
|
||||||
|
|
|
@ -158,6 +158,11 @@ pub struct Fragment {
|
||||||
pub established_reference_frame: Option<ClipScrollNodeIndex>,
|
pub established_reference_frame: Option<ClipScrollNodeIndex>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(debug_assertions)]
|
||||||
|
size_of_test!(Fragment, 176);
|
||||||
|
#[cfg(not(debug_assertions))]
|
||||||
|
size_of_test!(Fragment, 152);
|
||||||
|
|
||||||
impl Serialize for Fragment {
|
impl Serialize for Fragment {
|
||||||
fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
|
fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
|
||||||
let mut serializer = serializer.serialize_struct("fragment", 3)?;
|
let mut serializer = serializer.serialize_struct("fragment", 3)?;
|
||||||
|
@ -212,6 +217,8 @@ pub enum SpecificFragmentInfo {
|
||||||
TruncatedFragment(Box<TruncatedFragmentInfo>),
|
TruncatedFragment(Box<TruncatedFragmentInfo>),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
size_of_test!(SpecificFragmentInfo, 24);
|
||||||
|
|
||||||
impl SpecificFragmentInfo {
|
impl SpecificFragmentInfo {
|
||||||
fn restyle_damage(&self) -> RestyleDamage {
|
fn restyle_damage(&self) -> RestyleDamage {
|
||||||
let flow = match *self {
|
let flow = match *self {
|
||||||
|
|
|
@ -16,6 +16,8 @@ extern crate log;
|
||||||
extern crate range;
|
extern crate range;
|
||||||
#[macro_use]
|
#[macro_use]
|
||||||
extern crate serde;
|
extern crate serde;
|
||||||
|
#[macro_use]
|
||||||
|
extern crate size_of_test;
|
||||||
|
|
||||||
#[macro_use]
|
#[macro_use]
|
||||||
pub mod layout_debug;
|
pub mod layout_debug;
|
||||||
|
|
|
@ -860,7 +860,7 @@ where
|
||||||
shared: &context.style_context,
|
shared: &context.style_context,
|
||||||
thread_local: &mut tlc,
|
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()
|
styles.primary().clone()
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
@ -916,7 +916,13 @@ pub fn process_resolved_style_request<'dom>(
|
||||||
thread_local: &mut tlc,
|
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 style = styles.primary();
|
||||||
let longhand_id = match *property {
|
let longhand_id = match *property {
|
||||||
PropertyId::LonghandAlias(id, _) | PropertyId::Longhand(id) => id,
|
PropertyId::LonghandAlias(id, _) | PropertyId::Longhand(id) => id,
|
||||||
|
|
|
@ -794,7 +794,7 @@ fn perform_border_collapse_for_row(
|
||||||
child_table_row
|
child_table_row
|
||||||
.final_collapsed_borders
|
.final_collapsed_borders
|
||||||
.inline
|
.inline
|
||||||
.push_or_set(i, *this_inline_border);
|
.push_or_set(i, this_inline_border.clone());
|
||||||
if i == 0 {
|
if i == 0 {
|
||||||
child_table_row.final_collapsed_borders.inline[i].combine(&table_inline_borders.start);
|
child_table_row.final_collapsed_borders.inline[i].combine(&table_inline_borders.start);
|
||||||
} else if i + 1 == number_of_borders_inline_direction {
|
} 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]);
|
this_border.combine(&previous_block_borders[i]);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
PreviousBlockCollapsedBorders::FromTable(table_border) => {
|
PreviousBlockCollapsedBorders::FromTable(ref table_border) => {
|
||||||
this_border.combine(&table_border);
|
this_border.combine(&table_border);
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
@ -837,7 +837,7 @@ fn perform_border_collapse_for_row(
|
||||||
.iter()
|
.iter()
|
||||||
.enumerate()
|
.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 {
|
match next_block_borders {
|
||||||
NextBlockCollapsedBorders::FromNextRow(next_block_borders) => {
|
NextBlockCollapsedBorders::FromNextRow(next_block_borders) => {
|
||||||
if next_block_borders.len() > i {
|
if next_block_borders.len() > i {
|
||||||
|
@ -1358,7 +1358,7 @@ impl<'table> TableCellStyleInfo<'table> {
|
||||||
if background as *const Background == initial.get_background() as *const _ {
|
if background as *const Background == initial.get_background() as *const _ {
|
||||||
return;
|
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(
|
cell_flow.build_display_list_for_background_if_applicable_with_background(
|
||||||
state,
|
state,
|
||||||
background,
|
background,
|
||||||
|
|
|
@ -375,7 +375,7 @@ impl fmt::Debug for TableCellFlow {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Copy, Debug, Serialize)]
|
#[derive(Clone, Debug, Serialize)]
|
||||||
pub struct CollapsedBordersForCell {
|
pub struct CollapsedBordersForCell {
|
||||||
pub inline_start_border: CollapsedBorder,
|
pub inline_start_border: CollapsedBorder,
|
||||||
pub inline_end_border: CollapsedBorder,
|
pub inline_end_border: CollapsedBorder,
|
||||||
|
@ -494,10 +494,10 @@ impl CollapsedBordersForCell {
|
||||||
) {
|
) {
|
||||||
let logical_border_colors = LogicalMargin::new(
|
let logical_border_colors = LogicalMargin::new(
|
||||||
writing_mode,
|
writing_mode,
|
||||||
self.block_start_border.color,
|
self.block_start_border.color.clone(),
|
||||||
self.inline_end_border.color,
|
self.inline_end_border.color.clone(),
|
||||||
self.block_end_border.color,
|
self.block_end_border.color.clone(),
|
||||||
self.inline_start_border.color,
|
self.inline_start_border.color.clone(),
|
||||||
);
|
);
|
||||||
*border_colors = logical_border_colors.to_physical(writing_mode);
|
*border_colors = logical_border_colors.to_physical(writing_mode);
|
||||||
|
|
||||||
|
|
|
@ -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.
|
/// 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 {
|
pub struct CollapsedBorder {
|
||||||
/// The style of the border.
|
/// The style of the border.
|
||||||
pub style: BorderStyle,
|
pub style: BorderStyle,
|
||||||
|
@ -771,7 +771,7 @@ impl CollapsedBorder {
|
||||||
CollapsedBorder {
|
CollapsedBorder {
|
||||||
style: css_style.get_border().border_top_style,
|
style: css_style.get_border().border_top_style,
|
||||||
width: Au::from(css_style.get_border().border_top_width),
|
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,
|
provenance: provenance,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -782,7 +782,7 @@ impl CollapsedBorder {
|
||||||
CollapsedBorder {
|
CollapsedBorder {
|
||||||
style: css_style.get_border().border_right_style,
|
style: css_style.get_border().border_right_style,
|
||||||
width: Au::from(css_style.get_border().border_right_width),
|
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,
|
provenance: provenance,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -796,7 +796,7 @@ impl CollapsedBorder {
|
||||||
CollapsedBorder {
|
CollapsedBorder {
|
||||||
style: css_style.get_border().border_bottom_style,
|
style: css_style.get_border().border_bottom_style,
|
||||||
width: Au::from(css_style.get_border().border_bottom_width),
|
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,
|
provenance: provenance,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -807,7 +807,7 @@ impl CollapsedBorder {
|
||||||
CollapsedBorder {
|
CollapsedBorder {
|
||||||
style: css_style.get_border().border_left_style,
|
style: css_style.get_border().border_left_style,
|
||||||
width: Au::from(css_style.get_border().border_left_width),
|
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,
|
provenance: provenance,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -883,18 +883,18 @@ impl CollapsedBorder {
|
||||||
match (self.style, other.style) {
|
match (self.style, other.style) {
|
||||||
// Step 1.
|
// Step 1.
|
||||||
(BorderStyle::Hidden, _) => {},
|
(BorderStyle::Hidden, _) => {},
|
||||||
(_, BorderStyle::Hidden) => *self = *other,
|
(_, BorderStyle::Hidden) => *self = other.clone(),
|
||||||
// Step 2.
|
// Step 2.
|
||||||
(BorderStyle::None, _) => *self = *other,
|
(BorderStyle::None, _) => *self = other.clone(),
|
||||||
(_, BorderStyle::None) => {},
|
(_, BorderStyle::None) => {},
|
||||||
// Step 3.
|
// Step 3.
|
||||||
_ if self.width > other.width => {},
|
_ 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 => {},
|
||||||
(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.
|
// Step 4.
|
||||||
_ if (self.provenance as i8) >= other.provenance as i8 => {},
|
_ 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
|
.collapsed_borders_for_row
|
||||||
.inline
|
.inline
|
||||||
.get(child_index)
|
.get(child_index)
|
||||||
.map_or(CollapsedBorder::new(), |x| *x),
|
.map_or(CollapsedBorder::new(), |x| x.clone()),
|
||||||
inline_end_border: border_collapse_info
|
inline_end_border: border_collapse_info
|
||||||
.collapsed_borders_for_row
|
.collapsed_borders_for_row
|
||||||
.inline
|
.inline
|
||||||
.get(child_index + 1)
|
.get(child_index + 1)
|
||||||
.map_or(CollapsedBorder::new(), |x| *x),
|
.map_or(CollapsedBorder::new(), |x| x.clone()),
|
||||||
block_start_border: border_collapse_info
|
block_start_border: border_collapse_info
|
||||||
.collapsed_borders_for_row
|
.collapsed_borders_for_row
|
||||||
.block_start
|
.block_start
|
||||||
.get(child_index)
|
.get(child_index)
|
||||||
.map_or(CollapsedBorder::new(), |x| *x),
|
.map_or(CollapsedBorder::new(), |x| x.clone()),
|
||||||
block_end_border: border_collapse_info
|
block_end_border: border_collapse_info
|
||||||
.collapsed_borders_for_row
|
.collapsed_borders_for_row
|
||||||
.block_end
|
.block_end
|
||||||
.get(child_index)
|
.get(child_index)
|
||||||
.map_or(CollapsedBorder::new(), |x| *x),
|
.map_or(CollapsedBorder::new(), |x| x.clone()),
|
||||||
inline_start_width: border_collapse_info
|
inline_start_width: border_collapse_info
|
||||||
.collapsed_border_spacing_for_row
|
.collapsed_border_spacing_for_row
|
||||||
.inline
|
.inline
|
||||||
|
|
|
@ -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
|
|
||||||
);
|
|
|
@ -275,11 +275,11 @@ fn fixup_stops(
|
||||||
for item in items {
|
for item in items {
|
||||||
match item {
|
match item {
|
||||||
GradientItem::SimpleColorStop(color) => stops.push(ColorStop {
|
GradientItem::SimpleColorStop(color) => stops.push(ColorStop {
|
||||||
color: super::rgba(style.resolve_color(*color)),
|
color: super::rgba(style.resolve_color(color.clone())),
|
||||||
position: None,
|
position: None,
|
||||||
}),
|
}),
|
||||||
GradientItem::ComplexColorStop { color, position } => stops.push(ColorStop {
|
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. {
|
position: Some(if gradient_line_length.px() == 0. {
|
||||||
0.
|
0.
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -352,7 +352,7 @@ impl Fragment {
|
||||||
let text_decoration_color = fragment
|
let text_decoration_color = fragment
|
||||||
.parent_style
|
.parent_style
|
||||||
.clone_text_decoration_color()
|
.clone_text_decoration_color()
|
||||||
.to_rgba(color);
|
.into_rgba(color);
|
||||||
let text_decoration_style = fragment.parent_style.clone_text_decoration_style();
|
let text_decoration_style = fragment.parent_style.clone_text_decoration_style();
|
||||||
if text_decoration_style == ComputedTextDecorationStyle::MozNone {
|
if text_decoration_style == ComputedTextDecorationStyle::MozNone {
|
||||||
return;
|
return;
|
||||||
|
@ -535,7 +535,7 @@ impl<'a> BuilderForBoxFragment<'a> {
|
||||||
let source = background::Source::Fragment;
|
let source = background::Source::Fragment;
|
||||||
let style = &self.fragment.style;
|
let style = &self.fragment.style;
|
||||||
let b = style.get_background();
|
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 {
|
if background_color.alpha > 0 {
|
||||||
// https://drafts.csswg.org/css-backgrounds/#background-color
|
// https://drafts.csswg.org/css-backgrounds/#background-color
|
||||||
// “The background color is clipped according to the background-clip
|
// “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 common = builder.common_properties(self.border_rect, &self.fragment.style);
|
||||||
let details = wr::BorderDetails::Normal(wr::NormalBorder {
|
let details = wr::BorderDetails::Normal(wr::NormalBorder {
|
||||||
top: self.build_border_side(border.border_top_style, border.border_top_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),
|
right: self
|
||||||
bottom: self.build_border_side(border.border_bottom_style, border.border_bottom_color),
|
.build_border_side(border.border_right_style, border.border_right_color.clone()),
|
||||||
left: self.build_border_side(border.border_left_style, border.border_left_color),
|
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,
|
radius: self.border_radius,
|
||||||
do_aa: true,
|
do_aa: true,
|
||||||
});
|
});
|
||||||
|
@ -718,7 +723,7 @@ impl<'a> BuilderForBoxFragment<'a> {
|
||||||
OutlineStyle::Auto => BorderStyle::Solid,
|
OutlineStyle::Auto => BorderStyle::Solid,
|
||||||
OutlineStyle::BorderStyle(s) => s,
|
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 {
|
let details = wr::BorderDetails::Normal(wr::NormalBorder {
|
||||||
top: side,
|
top: side,
|
||||||
right: side,
|
right: side,
|
||||||
|
|
|
@ -404,7 +404,7 @@ impl StackingContext {
|
||||||
.union(&fragment_tree.scrollable_overflow)
|
.union(&fragment_tree.scrollable_overflow)
|
||||||
.to_webrender();
|
.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 {
|
if background_color.alpha > 0 {
|
||||||
let common = builder.common_properties(painting_area, &style);
|
let common = builder.common_properties(painting_area, &style);
|
||||||
let color = super::rgba(background_color);
|
let color = super::rgba(background_color);
|
||||||
|
|
|
@ -361,7 +361,13 @@ pub fn process_resolved_style_request_for_unstyled_node<'dom>(
|
||||||
};
|
};
|
||||||
|
|
||||||
let element = node.as_element().unwrap();
|
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 style = styles.primary();
|
||||||
let longhand_id = match *property {
|
let longhand_id = match *property {
|
||||||
PropertyId::LonghandAlias(id, _) | PropertyId::Longhand(id) => id,
|
PropertyId::LonghandAlias(id, _) | PropertyId::Longhand(id) => id,
|
||||||
|
|
|
@ -465,7 +465,7 @@ impl ComputedValuesExt for ComputedValues {
|
||||||
/// Whether or not this style specifies a non-transparent background.
|
/// Whether or not this style specifies a non-transparent background.
|
||||||
fn background_is_transparent(&self) -> bool {
|
fn background_is_transparent(&self) -> bool {
|
||||||
let background = self.get_background();
|
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 &&
|
color.alpha == 0 &&
|
||||||
background
|
background
|
||||||
.background_image
|
.background_image
|
||||||
|
|
|
@ -1796,7 +1796,8 @@ fn get_root_flow_background_color(flow: &mut dyn Flow) -> ColorF {
|
||||||
.fragment
|
.fragment
|
||||||
.style
|
.style
|
||||||
.get_background()
|
.get_background()
|
||||||
.background_color,
|
.background_color
|
||||||
|
.clone(),
|
||||||
);
|
);
|
||||||
ColorF::new(
|
ColorF::new(
|
||||||
color.red_f32(),
|
color.red_f32(),
|
||||||
|
|
|
@ -19,7 +19,5 @@ malloc_size_of = { path = "../malloc_size_of" }
|
||||||
malloc_size_of_derive = { workspace = true }
|
malloc_size_of_derive = { workspace = true }
|
||||||
parking_lot = { workspace = true }
|
parking_lot = { workspace = true }
|
||||||
serde = { workspace = true }
|
serde = { workspace = true }
|
||||||
webrender_api = { workspace = true }
|
|
||||||
|
|
||||||
[dev-dependencies]
|
|
||||||
size_of_test = { path = "../size_of_test" }
|
size_of_test = { path = "../size_of_test" }
|
||||||
|
webrender_api = { workspace = true }
|
||||||
|
|
|
@ -198,6 +198,9 @@ pub struct PipelineNamespaceId(pub u32);
|
||||||
|
|
||||||
namespace_id! {PipelineId, PipelineIndex}
|
namespace_id! {PipelineId, PipelineIndex}
|
||||||
|
|
||||||
|
size_of_test!(PipelineId, 8);
|
||||||
|
size_of_test!(Option<PipelineId>, 8);
|
||||||
|
|
||||||
impl PipelineId {
|
impl PipelineId {
|
||||||
pub fn new() -> PipelineId {
|
pub fn new() -> PipelineId {
|
||||||
PIPELINE_NAMESPACE.with(|tls| {
|
PIPELINE_NAMESPACE.with(|tls| {
|
||||||
|
@ -240,6 +243,9 @@ impl fmt::Display for PipelineId {
|
||||||
|
|
||||||
namespace_id! {BrowsingContextId, BrowsingContextIndex}
|
namespace_id! {BrowsingContextId, BrowsingContextIndex}
|
||||||
|
|
||||||
|
size_of_test!(BrowsingContextId, 8);
|
||||||
|
size_of_test!(Option<BrowsingContextId>, 8);
|
||||||
|
|
||||||
impl BrowsingContextId {
|
impl BrowsingContextId {
|
||||||
pub fn new() -> BrowsingContextId {
|
pub fn new() -> BrowsingContextId {
|
||||||
PIPELINE_NAMESPACE.with(|tls| {
|
PIPELINE_NAMESPACE.with(|tls| {
|
||||||
|
@ -269,6 +275,9 @@ thread_local!(pub static TOP_LEVEL_BROWSING_CONTEXT_ID: Cell<Option<TopLevelBrow
|
||||||
)]
|
)]
|
||||||
pub struct TopLevelBrowsingContextId(pub BrowsingContextId);
|
pub struct TopLevelBrowsingContextId(pub BrowsingContextId);
|
||||||
|
|
||||||
|
size_of_test!(TopLevelBrowsingContextId, 8);
|
||||||
|
size_of_test!(Option<TopLevelBrowsingContextId>, 8);
|
||||||
|
|
||||||
impl TopLevelBrowsingContextId {
|
impl TopLevelBrowsingContextId {
|
||||||
pub fn new() -> TopLevelBrowsingContextId {
|
pub fn new() -> TopLevelBrowsingContextId {
|
||||||
TopLevelBrowsingContextId(BrowsingContextId::new())
|
TopLevelBrowsingContextId(BrowsingContextId::new())
|
||||||
|
|
|
@ -12,5 +12,7 @@ extern crate malloc_size_of;
|
||||||
extern crate malloc_size_of_derive;
|
extern crate malloc_size_of_derive;
|
||||||
#[macro_use]
|
#[macro_use]
|
||||||
extern crate serde;
|
extern crate serde;
|
||||||
|
#[macro_use]
|
||||||
|
extern crate size_of_test;
|
||||||
|
|
||||||
pub mod constellation_msg;
|
pub mod constellation_msg;
|
||||||
|
|
|
@ -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<PipelineId>, 8);
|
|
||||||
size_of_test!(test_size_of_browsing_context_id, BrowsingContextId, 8);
|
|
||||||
size_of_test!(
|
|
||||||
test_size_of_optional_browsing_context_id,
|
|
||||||
Option<BrowsingContextId>,
|
|
||||||
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<TopLevelBrowsingContextId>,
|
|
||||||
8
|
|
||||||
);
|
|
|
@ -98,8 +98,9 @@ impl CSSRule {
|
||||||
DomRoot::upcast(CSSSupportsRule::new(window, parent_stylesheet, s))
|
DomRoot::upcast(CSSSupportsRule::new(window, parent_stylesheet, s))
|
||||||
},
|
},
|
||||||
StyleCssRule::Page(_) => unreachable!(),
|
StyleCssRule::Page(_) => unreachable!(),
|
||||||
StyleCssRule::Document(_) => unimplemented!(), // TODO
|
StyleCssRule::Container(_) => unimplemented!(), // TODO
|
||||||
StyleCssRule::Viewport(_) => unimplemented!(), // TODO
|
StyleCssRule::Document(_) => unimplemented!(), // TODO
|
||||||
|
StyleCssRule::Viewport(_) => unimplemented!(), // TODO
|
||||||
StyleCssRule::LayerBlock(_) => unimplemented!(), // TODO
|
StyleCssRule::LayerBlock(_) => unimplemented!(), // TODO
|
||||||
StyleCssRule::LayerStatement(_) => unimplemented!(), // TODO
|
StyleCssRule::LayerStatement(_) => unimplemented!(), // TODO
|
||||||
StyleCssRule::ScrollTimeline(_) => unimplemented!(), // TODO
|
StyleCssRule::ScrollTimeline(_) => unimplemented!(), // TODO
|
||||||
|
|
|
@ -111,6 +111,7 @@ use std::cell::Cell;
|
||||||
use std::default::Default;
|
use std::default::Default;
|
||||||
use std::fmt;
|
use std::fmt;
|
||||||
use std::mem;
|
use std::mem;
|
||||||
|
use std::ops::Deref;
|
||||||
use std::rc::Rc;
|
use std::rc::Rc;
|
||||||
use std::str::FromStr;
|
use std::str::FromStr;
|
||||||
use style::applicable_declarations::ApplicableDeclarationBlock;
|
use style::applicable_declarations::ApplicableDeclarationBlock;
|
||||||
|
@ -3218,15 +3219,11 @@ impl<'a> SelectorsElement for DomRoot<Element> {
|
||||||
Element::namespace(self) == Element::namespace(other)
|
Element::namespace(self) == Element::namespace(other)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn match_non_ts_pseudo_class<F>(
|
fn match_non_ts_pseudo_class(
|
||||||
&self,
|
&self,
|
||||||
pseudo_class: &NonTSPseudoClass,
|
pseudo_class: &NonTSPseudoClass,
|
||||||
_: &mut MatchingContext<Self::Impl>,
|
_: &mut MatchingContext<Self::Impl>,
|
||||||
_: &mut F,
|
) -> bool {
|
||||||
) -> bool
|
|
||||||
where
|
|
||||||
F: FnMut(&Self, ElementSelectorFlags),
|
|
||||||
{
|
|
||||||
match *pseudo_class {
|
match *pseudo_class {
|
||||||
// https://github.com/servo/servo/issues/8718
|
// https://github.com/servo/servo/issues/8718
|
||||||
NonTSPseudoClass::Link | NonTSPseudoClass::AnyLink => self.is_link(),
|
NonTSPseudoClass::Link | NonTSPseudoClass::AnyLink => self.is_link(),
|
||||||
|
@ -3307,6 +3304,15 @@ impl<'a> SelectorsElement for DomRoot<Element> {
|
||||||
fn is_html_slot_element(&self) -> bool {
|
fn is_html_slot_element(&self) -> bool {
|
||||||
self.is_html_element() && self.local_name() == &local_name!("slot")
|
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 {
|
impl Element {
|
||||||
|
|
|
@ -79,7 +79,9 @@ use script_layout_interface::{HTMLCanvasData, HTMLMediaData, LayoutElementType,
|
||||||
use script_layout_interface::{SVGSVGData, StyleAndOpaqueLayoutData, TrustedNodeAddress};
|
use script_layout_interface::{SVGSVGData, StyleAndOpaqueLayoutData, TrustedNodeAddress};
|
||||||
use script_traits::DocumentActivity;
|
use script_traits::DocumentActivity;
|
||||||
use script_traits::UntrustedNodeAddress;
|
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 selectors::parser::SelectorList;
|
||||||
use servo_arc::Arc;
|
use servo_arc::Arc;
|
||||||
use servo_url::ServoUrl;
|
use servo_url::ServoUrl;
|
||||||
|
@ -473,6 +475,7 @@ impl<'a> Iterator for QuerySelectorIterator {
|
||||||
None,
|
None,
|
||||||
None,
|
None,
|
||||||
node.owner_doc().quirks_mode(),
|
node.owner_doc().quirks_mode(),
|
||||||
|
NeedsSelectorFlags::No,
|
||||||
);
|
);
|
||||||
if let Some(element) = DomRoot::downcast(node) {
|
if let Some(element) = DomRoot::downcast(node) {
|
||||||
if matches_selector_list(selectors, &element, &mut ctx) {
|
if matches_selector_list(selectors, &element, &mut ctx) {
|
||||||
|
@ -956,8 +959,13 @@ impl Node {
|
||||||
// Step 3.
|
// Step 3.
|
||||||
Ok(selectors) => {
|
Ok(selectors) => {
|
||||||
// FIXME(bholley): Consider an nth-index cache here.
|
// FIXME(bholley): Consider an nth-index cache here.
|
||||||
let mut ctx =
|
let mut ctx = MatchingContext::new(
|
||||||
MatchingContext::new(MatchingMode::Normal, None, None, doc.quirks_mode());
|
MatchingMode::Normal,
|
||||||
|
None,
|
||||||
|
None,
|
||||||
|
doc.quirks_mode(),
|
||||||
|
NeedsSelectorFlags::No,
|
||||||
|
);
|
||||||
Ok(self
|
Ok(self
|
||||||
.traverse_preorder(ShadowIncluding::No)
|
.traverse_preorder(ShadowIncluding::No)
|
||||||
.filter_map(DomRoot::downcast)
|
.filter_map(DomRoot::downcast)
|
||||||
|
|
|
@ -358,14 +358,6 @@ impl<'dom, LayoutDataType: LayoutDataTrait> style::dom::TElement
|
||||||
false
|
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 {
|
fn has_animations(&self, context: &SharedStyleContext) -> bool {
|
||||||
// This is not used for pseudo elements currently so we can pass None.
|
// This is not used for pseudo elements currently so we can pass None.
|
||||||
return self.has_css_animations(context, /* pseudo_element = */ 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 {
|
fn namespace(&self) -> &Namespace {
|
||||||
self.element.namespace()
|
self.element.namespace()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn primary_box_size(&self) -> euclid::default::Size2D<app_units::Au> {
|
||||||
|
todo!();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'dom, LayoutDataType: LayoutDataTrait> ::selectors::Element
|
impl<'dom, LayoutDataType: LayoutDataTrait> ::selectors::Element
|
||||||
|
@ -573,15 +569,11 @@ impl<'dom, LayoutDataType: LayoutDataTrait> ::selectors::Element
|
||||||
false
|
false
|
||||||
}
|
}
|
||||||
|
|
||||||
fn match_non_ts_pseudo_class<F>(
|
fn match_non_ts_pseudo_class(
|
||||||
&self,
|
&self,
|
||||||
pseudo_class: &NonTSPseudoClass,
|
pseudo_class: &NonTSPseudoClass,
|
||||||
_: &mut MatchingContext<Self::Impl>,
|
_: &mut MatchingContext<Self::Impl>,
|
||||||
_: &mut F,
|
) -> bool {
|
||||||
) -> bool
|
|
||||||
where
|
|
||||||
F: FnMut(&Self, ElementSelectorFlags),
|
|
||||||
{
|
|
||||||
match *pseudo_class {
|
match *pseudo_class {
|
||||||
// https://github.com/servo/servo/issues/8718
|
// https://github.com/servo/servo/issues/8718
|
||||||
NonTSPseudoClass::Link | NonTSPseudoClass::AnyLink => self.is_link(),
|
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 {
|
fn is_html_element_in_html_document(&self) -> bool {
|
||||||
self.element.is_html_element() && self.as_node().owner_doc().is_html_document()
|
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
|
/// 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<F>(
|
fn match_non_ts_pseudo_class(
|
||||||
&self,
|
&self,
|
||||||
_: &NonTSPseudoClass,
|
_: &NonTSPseudoClass,
|
||||||
_: &mut MatchingContext<Self::Impl>,
|
_: &mut MatchingContext<Self::Impl>,
|
||||||
_: &mut F,
|
) -> bool {
|
||||||
) -> bool
|
|
||||||
where
|
|
||||||
F: FnMut(&Self, ElementSelectorFlags),
|
|
||||||
{
|
|
||||||
// NB: This could maybe be implemented
|
// NB: This could maybe be implemented
|
||||||
warn!("ServoThreadSafeLayoutElement::match_non_ts_pseudo_class called");
|
warn!("ServoThreadSafeLayoutElement::match_non_ts_pseudo_class called");
|
||||||
false
|
false
|
||||||
|
@ -907,6 +899,10 @@ impl<'dom, LayoutDataType: LayoutDataTrait> ::selectors::Element
|
||||||
warn!("ServoThreadSafeLayoutElement::is_root called");
|
warn!("ServoThreadSafeLayoutElement::is_root called");
|
||||||
false
|
false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn set_selector_flags(&self, flags: ElementSelectorFlags) {
|
||||||
|
self.element.element.insert_selector_flags(flags);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'dom, LayoutDataType: LayoutDataTrait> GetStyleAndOpaqueLayoutData<'dom>
|
impl<'dom, LayoutDataType: LayoutDataTrait> GetStyleAndOpaqueLayoutData<'dom>
|
||||||
|
|
|
@ -28,6 +28,7 @@ log = "0.4"
|
||||||
phf = "0.10"
|
phf = "0.10"
|
||||||
precomputed-hash = "0.1"
|
precomputed-hash = "0.1"
|
||||||
servo_arc = { version = "0.2", path = "../servo_arc" }
|
servo_arc = { version = "0.2", path = "../servo_arc" }
|
||||||
|
size_of_test = { path = "../size_of_test" }
|
||||||
smallvec = "1.0"
|
smallvec = "1.0"
|
||||||
to_shmem = { version = "0.0.0", path = "../to_shmem", optional = true }
|
to_shmem = { version = "0.0.0", path = "../to_shmem", optional = true }
|
||||||
to_shmem_derive = { version = "0.0.0", path = "../to_shmem_derive", optional = true }
|
to_shmem_derive = { version = "0.0.0", path = "../to_shmem_derive", optional = true }
|
||||||
|
|
|
@ -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.
|
/// Which quirks mode is this document in.
|
||||||
///
|
///
|
||||||
/// See: https://quirks.spec.whatwg.org/
|
/// See: https://quirks.spec.whatwg.org/
|
||||||
|
@ -140,6 +148,7 @@ where
|
||||||
pub extra_data: Impl::ExtraMatchingData,
|
pub extra_data: Impl::ExtraMatchingData,
|
||||||
|
|
||||||
quirks_mode: QuirksMode,
|
quirks_mode: QuirksMode,
|
||||||
|
needs_selector_flags: NeedsSelectorFlags,
|
||||||
classes_and_ids_case_sensitivity: CaseSensitivity,
|
classes_and_ids_case_sensitivity: CaseSensitivity,
|
||||||
_impl: ::std::marker::PhantomData<Impl>,
|
_impl: ::std::marker::PhantomData<Impl>,
|
||||||
}
|
}
|
||||||
|
@ -154,6 +163,7 @@ where
|
||||||
bloom_filter: Option<&'a BloomFilter>,
|
bloom_filter: Option<&'a BloomFilter>,
|
||||||
nth_index_cache: Option<&'a mut NthIndexCache>,
|
nth_index_cache: Option<&'a mut NthIndexCache>,
|
||||||
quirks_mode: QuirksMode,
|
quirks_mode: QuirksMode,
|
||||||
|
needs_selector_flags: NeedsSelectorFlags,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
Self::new_for_visited(
|
Self::new_for_visited(
|
||||||
matching_mode,
|
matching_mode,
|
||||||
|
@ -161,6 +171,7 @@ where
|
||||||
nth_index_cache,
|
nth_index_cache,
|
||||||
VisitedHandlingMode::AllLinksUnvisited,
|
VisitedHandlingMode::AllLinksUnvisited,
|
||||||
quirks_mode,
|
quirks_mode,
|
||||||
|
needs_selector_flags,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -171,6 +182,7 @@ where
|
||||||
nth_index_cache: Option<&'a mut NthIndexCache>,
|
nth_index_cache: Option<&'a mut NthIndexCache>,
|
||||||
visited_handling: VisitedHandlingMode,
|
visited_handling: VisitedHandlingMode,
|
||||||
quirks_mode: QuirksMode,
|
quirks_mode: QuirksMode,
|
||||||
|
needs_selector_flags: NeedsSelectorFlags,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
Self {
|
Self {
|
||||||
matching_mode,
|
matching_mode,
|
||||||
|
@ -179,6 +191,7 @@ where
|
||||||
nth_index_cache,
|
nth_index_cache,
|
||||||
quirks_mode,
|
quirks_mode,
|
||||||
classes_and_ids_case_sensitivity: quirks_mode.classes_and_ids_case_sensitivity(),
|
classes_and_ids_case_sensitivity: quirks_mode.classes_and_ids_case_sensitivity(),
|
||||||
|
needs_selector_flags,
|
||||||
scope_element: None,
|
scope_element: None,
|
||||||
current_host: None,
|
current_host: None,
|
||||||
nesting_level: 0,
|
nesting_level: 0,
|
||||||
|
@ -213,6 +226,12 @@ where
|
||||||
self.matching_mode
|
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
|
/// The case-sensitivity for class and ID selectors
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn classes_and_ids_case_sensitivity(&self) -> CaseSensitivity {
|
pub fn classes_and_ids_case_sensitivity(&self) -> CaseSensitivity {
|
||||||
|
|
|
@ -5,6 +5,9 @@
|
||||||
// Make |cargo bench| work.
|
// Make |cargo bench| work.
|
||||||
#![cfg_attr(feature = "bench", feature(test))]
|
#![cfg_attr(feature = "bench", feature(test))]
|
||||||
|
|
||||||
|
#[macro_use]
|
||||||
|
extern crate size_of_test;
|
||||||
|
|
||||||
pub mod attr;
|
pub mod attr;
|
||||||
pub mod bloom;
|
pub mod bloom;
|
||||||
mod builder;
|
mod builder;
|
||||||
|
|
|
@ -78,8 +78,7 @@ where
|
||||||
// This is pretty much any(..) but manually inlined because the compiler
|
// This is pretty much any(..) but manually inlined because the compiler
|
||||||
// refuses to do so from querySelector / querySelectorAll.
|
// refuses to do so from querySelector / querySelectorAll.
|
||||||
for selector in &selector_list.0 {
|
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 {
|
if matches {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
@ -184,17 +183,15 @@ enum MatchesHoverAndActiveQuirk {
|
||||||
/// unncessary cache miss for cases when we can fast-reject with AncestorHashes
|
/// unncessary cache miss for cases when we can fast-reject with AncestorHashes
|
||||||
/// (which the caller can store inline with the selector pointer).
|
/// (which the caller can store inline with the selector pointer).
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
pub fn matches_selector<E, F>(
|
pub fn matches_selector<E>(
|
||||||
selector: &Selector<E::Impl>,
|
selector: &Selector<E::Impl>,
|
||||||
offset: usize,
|
offset: usize,
|
||||||
hashes: Option<&AncestorHashes>,
|
hashes: Option<&AncestorHashes>,
|
||||||
element: &E,
|
element: &E,
|
||||||
context: &mut MatchingContext<E::Impl>,
|
context: &mut MatchingContext<E::Impl>,
|
||||||
flags_setter: &mut F,
|
|
||||||
) -> bool
|
) -> bool
|
||||||
where
|
where
|
||||||
E: Element,
|
E: Element,
|
||||||
F: FnMut(&E, ElementSelectorFlags),
|
|
||||||
{
|
{
|
||||||
// Use the bloom filter to fast-reject.
|
// Use the bloom filter to fast-reject.
|
||||||
if let Some(hashes) = hashes {
|
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
|
/// Whether a compound selector matched, and whether it was the rightmost
|
||||||
|
@ -277,7 +274,7 @@ where
|
||||||
);
|
);
|
||||||
|
|
||||||
for component in iter {
|
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;
|
return CompoundSelectorMatchingResult::NotMatched;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -293,15 +290,13 @@ where
|
||||||
|
|
||||||
/// Matches a complex selector.
|
/// Matches a complex selector.
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
pub fn matches_complex_selector<E, F>(
|
pub fn matches_complex_selector<E>(
|
||||||
mut iter: SelectorIter<E::Impl>,
|
mut iter: SelectorIter<E::Impl>,
|
||||||
element: &E,
|
element: &E,
|
||||||
context: &mut MatchingContext<E::Impl>,
|
context: &mut MatchingContext<E::Impl>,
|
||||||
flags_setter: &mut F,
|
|
||||||
) -> bool
|
) -> bool
|
||||||
where
|
where
|
||||||
E: Element,
|
E: Element,
|
||||||
F: FnMut(&E, ElementSelectorFlags),
|
|
||||||
{
|
{
|
||||||
// If this is the special pseudo-element mode, consume the ::pseudo-element
|
// If this is the special pseudo-element mode, consume the ::pseudo-element
|
||||||
// before proceeding, since the caller has already handled that part.
|
// before proceeding, since the caller has already handled that part.
|
||||||
|
@ -334,7 +329,7 @@ where
|
||||||
}
|
}
|
||||||
|
|
||||||
let result =
|
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)
|
matches!(result, SelectorMatchingResult::Matched)
|
||||||
}
|
}
|
||||||
|
@ -458,16 +453,14 @@ where
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn matches_complex_selector_internal<E, F>(
|
fn matches_complex_selector_internal<E>(
|
||||||
mut selector_iter: SelectorIter<E::Impl>,
|
mut selector_iter: SelectorIter<E::Impl>,
|
||||||
element: &E,
|
element: &E,
|
||||||
context: &mut MatchingContext<E::Impl>,
|
context: &mut MatchingContext<E::Impl>,
|
||||||
flags_setter: &mut F,
|
|
||||||
rightmost: Rightmost,
|
rightmost: Rightmost,
|
||||||
) -> SelectorMatchingResult
|
) -> SelectorMatchingResult
|
||||||
where
|
where
|
||||||
E: Element,
|
E: Element,
|
||||||
F: FnMut(&E, ElementSelectorFlags),
|
|
||||||
{
|
{
|
||||||
debug!(
|
debug!(
|
||||||
"Matching complex selector {:?} for {:?}",
|
"Matching complex selector {:?} for {:?}",
|
||||||
|
@ -478,16 +471,16 @@ where
|
||||||
&mut selector_iter,
|
&mut selector_iter,
|
||||||
element,
|
element,
|
||||||
context,
|
context,
|
||||||
flags_setter,
|
|
||||||
rightmost,
|
rightmost,
|
||||||
);
|
);
|
||||||
|
|
||||||
let combinator = selector_iter.next_sequence();
|
let combinator = selector_iter.next_sequence();
|
||||||
if combinator.map_or(false, |c| c.is_sibling()) {
|
if combinator.map_or(false, |c| c.is_sibling()) {
|
||||||
flags_setter(
|
if context.needs_selector_flags() {
|
||||||
element,
|
element.apply_selector_flags(
|
||||||
ElementSelectorFlags::HAS_SLOW_SELECTOR_LATER_SIBLINGS,
|
ElementSelectorFlags::HAS_SLOW_SELECTOR_LATER_SIBLINGS
|
||||||
);
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if !matches_compound_selector {
|
if !matches_compound_selector {
|
||||||
|
@ -532,7 +525,6 @@ where
|
||||||
selector_iter.clone(),
|
selector_iter.clone(),
|
||||||
&element,
|
&element,
|
||||||
context,
|
context,
|
||||||
flags_setter,
|
|
||||||
Rightmost::No,
|
Rightmost::No,
|
||||||
)
|
)
|
||||||
});
|
});
|
||||||
|
@ -595,16 +587,14 @@ where
|
||||||
|
|
||||||
/// Determines whether the given element matches the given compound selector.
|
/// Determines whether the given element matches the given compound selector.
|
||||||
#[inline]
|
#[inline]
|
||||||
fn matches_compound_selector<E, F>(
|
fn matches_compound_selector<E>(
|
||||||
selector_iter: &mut SelectorIter<E::Impl>,
|
selector_iter: &mut SelectorIter<E::Impl>,
|
||||||
element: &E,
|
element: &E,
|
||||||
context: &mut MatchingContext<E::Impl>,
|
context: &mut MatchingContext<E::Impl>,
|
||||||
flags_setter: &mut F,
|
|
||||||
rightmost: Rightmost,
|
rightmost: Rightmost,
|
||||||
) -> bool
|
) -> bool
|
||||||
where
|
where
|
||||||
E: Element,
|
E: Element,
|
||||||
F: FnMut(&E, ElementSelectorFlags),
|
|
||||||
{
|
{
|
||||||
let matches_hover_and_active_quirk =
|
let matches_hover_and_active_quirk =
|
||||||
matches_hover_and_active_quirk(&selector_iter, context, rightmost);
|
matches_hover_and_active_quirk(&selector_iter, context, rightmost);
|
||||||
|
@ -643,19 +633,17 @@ where
|
||||||
};
|
};
|
||||||
iter::once(selector)
|
iter::once(selector)
|
||||||
.chain(selector_iter)
|
.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.
|
/// Determines whether the given element matches the given single selector.
|
||||||
fn matches_simple_selector<E, F>(
|
fn matches_simple_selector<E>(
|
||||||
selector: &Component<E::Impl>,
|
selector: &Component<E::Impl>,
|
||||||
element: &E,
|
element: &E,
|
||||||
context: &mut LocalMatchingContext<E::Impl>,
|
context: &mut LocalMatchingContext<E::Impl>,
|
||||||
flags_setter: &mut F,
|
|
||||||
) -> bool
|
) -> bool
|
||||||
where
|
where
|
||||||
E: Element,
|
E: Element,
|
||||||
F: FnMut(&E, ElementSelectorFlags),
|
|
||||||
{
|
{
|
||||||
debug_assert!(context.shared.is_nested() || !context.shared.in_negation());
|
debug_assert!(context.shared.is_nested() || !context.shared.in_negation());
|
||||||
|
|
||||||
|
@ -703,7 +691,7 @@ where
|
||||||
// <slots> are never flattened tree slottables.
|
// <slots> are never flattened tree slottables.
|
||||||
!element.is_html_slot_element() &&
|
!element.is_html_slot_element() &&
|
||||||
context.shared.nest(|context| {
|
context.shared.nest(|context| {
|
||||||
matches_complex_selector(selector.iter(), element, context, flags_setter)
|
matches_complex_selector(selector.iter(), element, context)
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
Component::PseudoElement(ref pseudo) => {
|
Component::PseudoElement(ref pseudo) => {
|
||||||
|
@ -795,16 +783,18 @@ where
|
||||||
return false;
|
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::FirstChild => matches_first_child(element, context.shared),
|
||||||
Component::LastChild => matches_last_child(element, flags_setter),
|
Component::LastChild => matches_last_child(element, context.shared),
|
||||||
Component::OnlyChild => {
|
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::Root => element.is_root(),
|
||||||
Component::Empty => {
|
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()
|
element.is_empty()
|
||||||
},
|
},
|
||||||
Component::Host(ref selector) => {
|
Component::Host(ref selector) => {
|
||||||
|
@ -814,7 +804,7 @@ where
|
||||||
.map_or(false, |host| host == element.opaque()) &&
|
.map_or(false, |host| host == element.opaque()) &&
|
||||||
selector.as_ref().map_or(true, |selector| {
|
selector.as_ref().map_or(true, |selector| {
|
||||||
context.shared.nest(|context| {
|
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(),
|
None => element.is_root(),
|
||||||
},
|
},
|
||||||
Component::NthChild(a, b) => {
|
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) => {
|
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) => {
|
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) => {
|
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 => {
|
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 => {
|
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 => {
|
Component::OnlyOfType => {
|
||||||
matches_generic_nth_child(element, context, 0, 1, true, false, flags_setter) &&
|
matches_generic_nth_child(element, context.shared, 0, 1, true, false) &&
|
||||||
matches_generic_nth_child(element, context, 0, 1, true, true, flags_setter)
|
matches_generic_nth_child(element, context.shared, 0, 1, true, true)
|
||||||
},
|
},
|
||||||
Component::Is(ref list) | Component::Where(ref list) => context.shared.nest(|context| {
|
Component::Is(ref list) | Component::Where(ref list) => context.shared.nest(|context| {
|
||||||
for selector in &**list {
|
for selector in &**list {
|
||||||
if matches_complex_selector(selector.iter(), element, context, flags_setter) {
|
if matches_complex_selector(selector.iter(), element, context) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -854,7 +844,7 @@ where
|
||||||
}),
|
}),
|
||||||
Component::Negation(ref list) => context.shared.nest_for_negation(|context| {
|
Component::Negation(ref list) => context.shared.nest_for_negation(|context| {
|
||||||
for selector in &**list {
|
for selector in &**list {
|
||||||
if matches_complex_selector(selector.iter(), element, context, flags_setter) {
|
if matches_complex_selector(selector.iter(), element, context) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -873,35 +863,31 @@ fn select_name<'a, T>(is_html: bool, local_name: &'a T, local_name_lower: &'a T)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
fn matches_generic_nth_child<E, F>(
|
fn matches_generic_nth_child<E>(
|
||||||
element: &E,
|
element: &E,
|
||||||
context: &mut LocalMatchingContext<E::Impl>,
|
context: &mut MatchingContext<E::Impl>,
|
||||||
a: i32,
|
a: i32,
|
||||||
b: i32,
|
b: i32,
|
||||||
is_of_type: bool,
|
is_of_type: bool,
|
||||||
is_from_end: bool,
|
is_from_end: bool,
|
||||||
flags_setter: &mut F,
|
|
||||||
) -> bool
|
) -> bool
|
||||||
where
|
where
|
||||||
E: Element,
|
E: Element,
|
||||||
F: FnMut(&E, ElementSelectorFlags),
|
|
||||||
{
|
{
|
||||||
if element.ignores_nth_child_selectors() {
|
if element.ignores_nth_child_selectors() {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
flags_setter(
|
if context.needs_selector_flags() {
|
||||||
element,
|
element.apply_selector_flags(if is_from_end {
|
||||||
if is_from_end {
|
|
||||||
ElementSelectorFlags::HAS_SLOW_SELECTOR
|
ElementSelectorFlags::HAS_SLOW_SELECTOR
|
||||||
} else {
|
} else {
|
||||||
ElementSelectorFlags::HAS_SLOW_SELECTOR_LATER_SIBLINGS
|
ElementSelectorFlags::HAS_SLOW_SELECTOR_LATER_SIBLINGS
|
||||||
},
|
});
|
||||||
);
|
}
|
||||||
|
|
||||||
// Grab a reference to the appropriate cache.
|
// Grab a reference to the appropriate cache.
|
||||||
let mut cache = context
|
let mut cache = context
|
||||||
.shared
|
|
||||||
.nth_index_cache
|
.nth_index_cache
|
||||||
.as_mut()
|
.as_mut()
|
||||||
.map(|c| c.get(is_of_type, is_from_end));
|
.map(|c| c.get(is_of_type, is_from_end));
|
||||||
|
@ -992,21 +978,23 @@ where
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
fn matches_first_child<E, F>(element: &E, flags_setter: &mut F) -> bool
|
fn matches_first_child<E>(element: &E, context: &MatchingContext<E::Impl>) -> bool
|
||||||
where
|
where
|
||||||
E: Element,
|
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()
|
element.prev_sibling_element().is_none()
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
fn matches_last_child<E, F>(element: &E, flags_setter: &mut F) -> bool
|
fn matches_last_child<E>(element: &E, context: &MatchingContext<E::Impl>) -> bool
|
||||||
where
|
where
|
||||||
E: Element,
|
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()
|
element.next_sibling_element().is_none()
|
||||||
}
|
}
|
||||||
|
|
|
@ -160,6 +160,8 @@ impl SelectorParsingState {
|
||||||
|
|
||||||
pub type SelectorParseError<'i> = ParseError<'i, SelectorParseErrorKind<'i>>;
|
pub type SelectorParseError<'i> = ParseError<'i, SelectorParseErrorKind<'i>>;
|
||||||
|
|
||||||
|
size_of_test!(SelectorParseError, 48);
|
||||||
|
|
||||||
#[derive(Clone, Debug, PartialEq)]
|
#[derive(Clone, Debug, PartialEq)]
|
||||||
pub enum SelectorParseErrorKind<'i> {
|
pub enum SelectorParseErrorKind<'i> {
|
||||||
NoQualifiedNameInAttributeSelector(Token<'i>),
|
NoQualifiedNameInAttributeSelector(Token<'i>),
|
||||||
|
@ -184,6 +186,8 @@ pub enum SelectorParseErrorKind<'i> {
|
||||||
ClassNeedsIdent(Token<'i>),
|
ClassNeedsIdent(Token<'i>),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
size_of_test!(SelectorParseErrorKind, 40);
|
||||||
|
|
||||||
macro_rules! with_all_bounds {
|
macro_rules! with_all_bounds {
|
||||||
(
|
(
|
||||||
[ $( $InSelector: tt )* ]
|
[ $( $InSelector: tt )* ]
|
||||||
|
|
1
components/selectors/rustfmt.toml
Normal file
1
components/selectors/rustfmt.toml
Normal file
|
@ -0,0 +1 @@
|
||||||
|
disable_all_formatting = true
|
|
@ -77,14 +77,11 @@ pub trait Element: Sized + Clone + Debug {
|
||||||
operation: &AttrSelectorOperation<&<Self::Impl as SelectorImpl>::AttrValue>,
|
operation: &AttrSelectorOperation<&<Self::Impl as SelectorImpl>::AttrValue>,
|
||||||
) -> bool;
|
) -> bool;
|
||||||
|
|
||||||
fn match_non_ts_pseudo_class<F>(
|
fn match_non_ts_pseudo_class(
|
||||||
&self,
|
&self,
|
||||||
pc: &<Self::Impl as SelectorImpl>::NonTSPseudoClass,
|
pc: &<Self::Impl as SelectorImpl>::NonTSPseudoClass,
|
||||||
context: &mut MatchingContext<Self::Impl>,
|
context: &mut MatchingContext<Self::Impl>,
|
||||||
flags_setter: &mut F,
|
) -> bool;
|
||||||
) -> bool
|
|
||||||
where
|
|
||||||
F: FnMut(&Self, ElementSelectorFlags);
|
|
||||||
|
|
||||||
fn match_pseudo_element(
|
fn match_pseudo_element(
|
||||||
&self,
|
&self,
|
||||||
|
@ -92,6 +89,30 @@ pub trait Element: Sized + Clone + Debug {
|
||||||
context: &mut MatchingContext<Self::Impl>,
|
context: &mut MatchingContext<Self::Impl>,
|
||||||
) -> bool;
|
) -> 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`.
|
/// Whether this element is a `link`.
|
||||||
fn is_link(&self) -> bool;
|
fn is_link(&self) -> bool;
|
||||||
|
|
||||||
|
|
|
@ -8,3 +8,6 @@ publish = false
|
||||||
|
|
||||||
[lib]
|
[lib]
|
||||||
path = "lib.rs"
|
path = "lib.rs"
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
static_assertions = "1.1"
|
||||||
|
|
|
@ -2,33 +2,13 @@
|
||||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
* 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/. */
|
* 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_export]
|
||||||
macro_rules! size_of_test {
|
macro_rules! size_of_test {
|
||||||
($testname: ident, $t: ty, $expected_size: expr) => {
|
($t: ty, $expected_size: expr) => {
|
||||||
#[test]
|
#[cfg(target_pointer_width = "64")]
|
||||||
fn $testname() {
|
$crate::const_assert_eq!(std::mem::size_of::<$t>(), $expected_size);
|
||||||
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!()
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
@ -62,13 +62,14 @@ servo_config = { path = "../config", optional = true }
|
||||||
servo_url = { path = "../url", optional = true }
|
servo_url = { path = "../url", optional = true }
|
||||||
smallbitvec = "2.3.0"
|
smallbitvec = "2.3.0"
|
||||||
smallvec = "1.0"
|
smallvec = "1.0"
|
||||||
|
static_assertions = "1.1"
|
||||||
string_cache = { version = "0.8", optional = true }
|
string_cache = { version = "0.8", optional = true }
|
||||||
style_derive = { path = "../style_derive" }
|
style_derive = { path = "../style_derive" }
|
||||||
style_traits = { path = "../style_traits" }
|
style_traits = { path = "../style_traits" }
|
||||||
time = "0.1"
|
time = "0.1"
|
||||||
to_shmem = { path = "../to_shmem" }
|
to_shmem = { path = "../to_shmem" }
|
||||||
to_shmem_derive = { path = "../to_shmem_derive" }
|
to_shmem_derive = { path = "../to_shmem_derive" }
|
||||||
uluru = "2"
|
uluru = "3.0"
|
||||||
unicode-bidi = "0.3"
|
unicode-bidi = "0.3"
|
||||||
unicode-segmentation = "1.0"
|
unicode-segmentation = "1.0"
|
||||||
void = "1.0.2"
|
void = "1.0.2"
|
||||||
|
|
|
@ -455,8 +455,8 @@ pub struct Animation {
|
||||||
impl Animation {
|
impl Animation {
|
||||||
/// Whether or not this animation is cancelled by changes from a new style.
|
/// Whether or not this animation is cancelled by changes from a new style.
|
||||||
fn is_cancelled_in_new_style(&self, new_style: &Arc<ComputedValues>) -> bool {
|
fn is_cancelled_in_new_style(&self, new_style: &Arc<ComputedValues>) -> bool {
|
||||||
let index = new_style
|
let new_ui = new_style.get_ui();
|
||||||
.get_box()
|
let index = new_ui
|
||||||
.animation_name_iter()
|
.animation_name_iter()
|
||||||
.position(|animation_name| Some(&self.name) == animation_name.as_atom());
|
.position(|animation_name| Some(&self.name) == animation_name.as_atom());
|
||||||
let index = match index {
|
let index = match index {
|
||||||
|
@ -464,7 +464,7 @@ impl Animation {
|
||||||
None => return true,
|
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,
|
/// Given the current time, advances this animation to the next iteration,
|
||||||
|
@ -1073,10 +1073,10 @@ impl ElementAnimationSet {
|
||||||
old_style: &ComputedValues,
|
old_style: &ComputedValues,
|
||||||
new_style: &Arc<ComputedValues>,
|
new_style: &Arc<ComputedValues>,
|
||||||
) {
|
) {
|
||||||
let box_style = new_style.get_box();
|
let style = new_style.get_ui();
|
||||||
let timing_function = box_style.transition_timing_function_mod(index);
|
let timing_function = style.transition_timing_function_mod(index);
|
||||||
let duration = box_style.transition_duration_mod(index);
|
let duration = style.transition_duration_mod(index);
|
||||||
let delay = box_style.transition_delay_mod(index).seconds() as f64;
|
let delay = style.transition_delay_mod(index).seconds() as f64;
|
||||||
let now = context.current_time_for_animations;
|
let now = context.current_time_for_animations;
|
||||||
|
|
||||||
// Only start a new transition if the style actually changes between
|
// Only start a new transition if the style actually changes between
|
||||||
|
@ -1344,15 +1344,15 @@ pub fn maybe_start_animations<E>(
|
||||||
) where
|
) where
|
||||||
E: TElement,
|
E: TElement,
|
||||||
{
|
{
|
||||||
let box_style = new_style.get_box();
|
let style = new_style.get_ui();
|
||||||
for (i, name) in box_style.animation_name_iter().enumerate() {
|
for (i, name) in style.animation_name_iter().enumerate() {
|
||||||
let name = match name.as_atom() {
|
let name = match name.as_atom() {
|
||||||
Some(atom) => atom,
|
Some(atom) => atom,
|
||||||
None => continue,
|
None => continue,
|
||||||
};
|
};
|
||||||
|
|
||||||
debug!("maybe_start_animations: name={}", name);
|
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. {
|
if duration == 0. {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
@ -1375,14 +1375,14 @@ pub fn maybe_start_animations<E>(
|
||||||
// NB: This delay may be negative, meaning that the animation may be created
|
// 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
|
// in a state where we have advanced one or more iterations or even that the
|
||||||
// animation begins in a finished state.
|
// 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::Infinite => KeyframesIterationState::Infinite(0.0),
|
||||||
AnimationIterationCount::Number(n) => KeyframesIterationState::Finite(0.0, n.into()),
|
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 {
|
let initial_direction = match animation_direction {
|
||||||
AnimationDirection::Normal | AnimationDirection::Alternate => {
|
AnimationDirection::Normal | AnimationDirection::Alternate => {
|
||||||
|
@ -1396,7 +1396,7 @@ pub fn maybe_start_animations<E>(
|
||||||
let now = context.current_time_for_animations;
|
let now = context.current_time_for_animations;
|
||||||
let started_at = now + delay as f64;
|
let started_at = now + delay as f64;
|
||||||
let mut starting_progress = (now - started_at) / duration;
|
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::Paused => AnimationState::Paused(starting_progress),
|
||||||
AnimationPlayState::Running => AnimationState::Pending,
|
AnimationPlayState::Running => AnimationState::Pending,
|
||||||
};
|
};
|
||||||
|
@ -1406,7 +1406,7 @@ pub fn maybe_start_animations<E>(
|
||||||
&keyframe_animation,
|
&keyframe_animation,
|
||||||
context,
|
context,
|
||||||
new_style,
|
new_style,
|
||||||
new_style.get_box().animation_timing_function_mod(i),
|
style.animation_timing_function_mod(i),
|
||||||
resolver,
|
resolver,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -1416,7 +1416,7 @@ pub fn maybe_start_animations<E>(
|
||||||
computed_steps,
|
computed_steps,
|
||||||
started_at,
|
started_at,
|
||||||
duration,
|
duration,
|
||||||
fill_mode: box_style.animation_fill_mode_mod(i),
|
fill_mode: style.animation_fill_mode_mod(i),
|
||||||
delay: delay as f64,
|
delay: delay as f64,
|
||||||
iteration_state,
|
iteration_state,
|
||||||
state,
|
state,
|
||||||
|
|
|
@ -42,13 +42,7 @@ pub struct CascadePriority {
|
||||||
layer_order: LayerOrder,
|
layer_order: LayerOrder,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[allow(dead_code)]
|
const_assert_eq!(std::mem::size_of::<CascadePriority>(), std::mem::size_of::<u32>());
|
||||||
fn size_assert() {
|
|
||||||
#[allow(unsafe_code)]
|
|
||||||
unsafe {
|
|
||||||
std::mem::transmute::<u32, CascadePriority>(0u32)
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
impl PartialOrd for CascadePriority {
|
impl PartialOrd for CascadePriority {
|
||||||
#[inline]
|
#[inline]
|
||||||
|
@ -208,3 +202,6 @@ impl ApplicableDeclarationBlock {
|
||||||
(self.source, self.cascade_priority)
|
(self.source, self.cascade_priority)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Size of this struct determines sorting and selector-matching performance.
|
||||||
|
size_of_test!(ApplicableDeclarationBlock, 24);
|
||||||
|
|
|
@ -285,11 +285,11 @@ fn generate_structs() {
|
||||||
let mut fixups = vec![];
|
let mut fixups = vec![];
|
||||||
let builder = BuilderWithConfig::new(builder, CONFIG["structs"].as_table().unwrap())
|
let builder = BuilderWithConfig::new(builder, CONFIG["structs"].as_table().unwrap())
|
||||||
.handle_common(&mut fixups)
|
.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("bitfield-enums", |b, item| b.bitfield_enum(item))
|
||||||
.handle_str_items("rusty-enums", |b, item| b.rustified_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("allowlist-vars", |b, item| b.allowlist_var(item))
|
||||||
.handle_str_items("whitelist-types", |b, item| b.allowlist_type(item))
|
.handle_str_items("allowlist-types", |b, item| b.allowlist_type(item))
|
||||||
.handle_str_items("opaque-types", |b, item| b.opaque_type(item))
|
.handle_str_items("opaque-types", |b, item| b.opaque_type(item))
|
||||||
.handle_table_items("cbindgen-types", |b, item| {
|
.handle_table_items("cbindgen-types", |b, item| {
|
||||||
let gecko = item["gecko"].as_str().unwrap();
|
let gecko = item["gecko"].as_str().unwrap();
|
||||||
|
|
|
@ -27,8 +27,8 @@ use crate::traversal_flags::TraversalFlags;
|
||||||
use app_units::Au;
|
use app_units::Au;
|
||||||
use euclid::default::Size2D;
|
use euclid::default::Size2D;
|
||||||
use euclid::Scale;
|
use euclid::Scale;
|
||||||
|
#[cfg(feature = "servo")]
|
||||||
use fxhash::FxHashMap;
|
use fxhash::FxHashMap;
|
||||||
use selectors::matching::ElementSelectorFlags;
|
|
||||||
use selectors::NthIndexCache;
|
use selectors::NthIndexCache;
|
||||||
#[cfg(feature = "gecko")]
|
#[cfg(feature = "gecko")]
|
||||||
use servo_arc::Arc;
|
use servo_arc::Arc;
|
||||||
|
@ -41,7 +41,6 @@ use style_traits::DevicePixel;
|
||||||
#[cfg(feature = "servo")]
|
#[cfg(feature = "servo")]
|
||||||
use style_traits::SpeculativePainter;
|
use style_traits::SpeculativePainter;
|
||||||
use time;
|
use time;
|
||||||
use uluru::LRUCache;
|
|
||||||
|
|
||||||
pub use selectors::matching::QuirksMode;
|
pub use selectors::matching::QuirksMode;
|
||||||
|
|
||||||
|
@ -517,65 +516,6 @@ impl<E: TElement> SequentialTask<E> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
type CacheItem<E> = (SendElement<E>, ElementSelectorFlags);
|
|
||||||
|
|
||||||
/// Map from Elements to ElementSelectorFlags. Used to defer applying selector
|
|
||||||
/// flags until after the traversal.
|
|
||||||
pub struct SelectorFlagsMap<E: TElement> {
|
|
||||||
/// The hashmap storing the flags to apply.
|
|
||||||
map: FxHashMap<SendElement<E>, ElementSelectorFlags>,
|
|
||||||
/// An LRU cache to avoid hashmap lookups, which can be slow if the map
|
|
||||||
/// gets big.
|
|
||||||
cache: LRUCache<CacheItem<E>, { 4 + 1 }>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(debug_assertions)]
|
|
||||||
impl<E: TElement> Drop for SelectorFlagsMap<E> {
|
|
||||||
fn drop(&mut self) {
|
|
||||||
debug_assert!(self.map.is_empty());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<E: TElement> SelectorFlagsMap<E> {
|
|
||||||
/// Creates a new empty SelectorFlagsMap.
|
|
||||||
pub fn new() -> Self {
|
|
||||||
SelectorFlagsMap {
|
|
||||||
map: FxHashMap::default(),
|
|
||||||
cache: LRUCache::default(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Inserts some flags into the map for a given element.
|
|
||||||
pub fn insert_flags(&mut self, element: E, flags: ElementSelectorFlags) {
|
|
||||||
let el = unsafe { SendElement::new(element) };
|
|
||||||
// Check the cache. If the flags have already been noted, we're done.
|
|
||||||
if let Some(item) = self.cache.find(|x| x.0 == el) {
|
|
||||||
if !item.1.contains(flags) {
|
|
||||||
item.1.insert(flags);
|
|
||||||
self.map.get_mut(&el).unwrap().insert(flags);
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
let f = self.map.entry(el).or_insert(ElementSelectorFlags::empty());
|
|
||||||
*f |= flags;
|
|
||||||
|
|
||||||
self.cache
|
|
||||||
.insert((unsafe { SendElement::new(element) }, *f))
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Applies the flags. Must be called on the main thread.
|
|
||||||
fn apply_flags(&mut self) {
|
|
||||||
debug_assert_eq!(thread_state::get(), ThreadState::LAYOUT);
|
|
||||||
self.cache.clear();
|
|
||||||
for (el, flags) in self.map.drain() {
|
|
||||||
unsafe {
|
|
||||||
el.set_selector_flags(flags);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// A list of SequentialTasks that get executed on Drop.
|
/// A list of SequentialTasks that get executed on Drop.
|
||||||
pub struct SequentialTaskList<E>(Vec<SequentialTask<E>>)
|
pub struct SequentialTaskList<E>(Vec<SequentialTask<E>>)
|
||||||
where
|
where
|
||||||
|
@ -697,11 +637,6 @@ pub struct ThreadLocalStyleContext<E: TElement> {
|
||||||
/// filter, to ensure they're dropped before we execute the tasks, which
|
/// filter, to ensure they're dropped before we execute the tasks, which
|
||||||
/// could create another ThreadLocalStyleContext for style computation.
|
/// could create another ThreadLocalStyleContext for style computation.
|
||||||
pub tasks: SequentialTaskList<E>,
|
pub tasks: SequentialTaskList<E>,
|
||||||
/// ElementSelectorFlags that need to be applied after the traversal is
|
|
||||||
/// complete. This map is used in cases where the matching algorithm needs
|
|
||||||
/// to set flags on elements it doesn't have exclusive access to (i.e. other
|
|
||||||
/// than the current element).
|
|
||||||
pub selector_flags: SelectorFlagsMap<E>,
|
|
||||||
/// Statistics about the traversal.
|
/// Statistics about the traversal.
|
||||||
pub statistics: PerThreadTraversalStatistics,
|
pub statistics: PerThreadTraversalStatistics,
|
||||||
/// A checker used to ensure that parallel.rs does not recurse indefinitely
|
/// A checker used to ensure that parallel.rs does not recurse indefinitely
|
||||||
|
@ -719,7 +654,6 @@ impl<E: TElement> ThreadLocalStyleContext<E> {
|
||||||
rule_cache: RuleCache::new(),
|
rule_cache: RuleCache::new(),
|
||||||
bloom_filter: StyleBloom::new(),
|
bloom_filter: StyleBloom::new(),
|
||||||
tasks: SequentialTaskList(Vec::new()),
|
tasks: SequentialTaskList(Vec::new()),
|
||||||
selector_flags: SelectorFlagsMap::new(),
|
|
||||||
statistics: PerThreadTraversalStatistics::default(),
|
statistics: PerThreadTraversalStatistics::default(),
|
||||||
stack_limit_checker: StackLimitChecker::new(
|
stack_limit_checker: StackLimitChecker::new(
|
||||||
(STYLE_THREAD_STACK_SIZE_KB - STACK_SAFETY_MARGIN_KB) * 1024,
|
(STYLE_THREAD_STACK_SIZE_KB - STACK_SAFETY_MARGIN_KB) * 1024,
|
||||||
|
@ -729,15 +663,6 @@ impl<E: TElement> ThreadLocalStyleContext<E> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<E: TElement> Drop for ThreadLocalStyleContext<E> {
|
|
||||||
fn drop(&mut self) {
|
|
||||||
debug_assert_eq!(thread_state::get(), ThreadState::LAYOUT);
|
|
||||||
|
|
||||||
// Apply any slow selector flags that need to be set on parents.
|
|
||||||
self.selector_flags.apply_flags();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// A `StyleContext` is just a simple container for a immutable reference to a
|
/// A `StyleContext` is just a simple container for a immutable reference to a
|
||||||
/// shared style context, and a mutable reference to a local one.
|
/// shared style context, and a mutable reference to a local one.
|
||||||
pub struct StyleContext<'a, E: TElement + 'a> {
|
pub struct StyleContext<'a, E: TElement + 'a> {
|
||||||
|
|
|
@ -64,6 +64,27 @@ fn get_safearea_inset_right(device: &Device) -> VariableValue {
|
||||||
VariableValue::pixels(device.safe_area_insets().right)
|
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] = [
|
static ENVIRONMENT_VARIABLES: [EnvironmentVariable; 4] = [
|
||||||
make_variable!(atom!("safe-area-inset-top"), get_safearea_inset_top),
|
make_variable!(atom!("safe-area-inset-top"), get_safearea_inset_top),
|
||||||
make_variable!(atom!("safe-area-inset-bottom"), get_safearea_inset_bottom),
|
make_variable!(atom!("safe-area-inset-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!(
|
lnf_int_variable!(
|
||||||
atom!("-moz-gtk-csd-titlebar-radius"),
|
atom!("-moz-gtk-csd-titlebar-radius"),
|
||||||
TitlebarRadius,
|
TitlebarRadius,
|
||||||
|
@ -121,6 +142,7 @@ static CHROME_ENVIRONMENT_VARIABLES: [EnvironmentVariable; 5] = [
|
||||||
GTKCSDMaximizeButtonPosition,
|
GTKCSDMaximizeButtonPosition,
|
||||||
integer
|
integer
|
||||||
),
|
),
|
||||||
|
make_variable!(atom!("-moz-content-preferred-color-scheme"), get_content_preferred_color_scheme),
|
||||||
];
|
];
|
||||||
|
|
||||||
impl CssEnvironment {
|
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.
|
/// Create VariableValue from a float amount of CSS pixels.
|
||||||
fn pixels(number: f32) -> Self {
|
fn pixels(number: f32) -> Self {
|
||||||
// FIXME (https://github.com/servo/rust-cssparser/issues/266):
|
// FIXME (https://github.com/servo/rust-cssparser/issues/266):
|
||||||
|
|
|
@ -159,6 +159,9 @@ pub struct ElementStyles {
|
||||||
pub pseudos: EagerPseudoStyles,
|
pub pseudos: EagerPseudoStyles,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// There's one of these per rendered elements so it better be small.
|
||||||
|
size_of_test!(ElementStyles, 16);
|
||||||
|
|
||||||
impl ElementStyles {
|
impl ElementStyles {
|
||||||
/// Returns the primary style.
|
/// Returns the primary style.
|
||||||
pub fn get_primary(&self) -> Option<&Arc<ComputedValues>> {
|
pub fn get_primary(&self) -> Option<&Arc<ComputedValues>> {
|
||||||
|
@ -249,6 +252,9 @@ pub struct ElementData {
|
||||||
pub flags: ElementDataFlags,
|
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.
|
/// The kind of restyle that a single element should do.
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub enum RestyleKind {
|
pub enum RestyleKind {
|
||||||
|
|
|
@ -22,7 +22,7 @@ use crate::traversal_flags::TraversalFlags;
|
||||||
use crate::values::AtomIdent;
|
use crate::values::AtomIdent;
|
||||||
use crate::{LocalName, Namespace, WeakAtom};
|
use crate::{LocalName, Namespace, WeakAtom};
|
||||||
use atomic_refcell::{AtomicRef, AtomicRefMut};
|
use atomic_refcell::{AtomicRef, AtomicRefMut};
|
||||||
use selectors::matching::{ElementSelectorFlags, QuirksMode, VisitedHandlingMode};
|
use selectors::matching::{QuirksMode, VisitedHandlingMode};
|
||||||
use selectors::sink::Push;
|
use selectors::sink::Push;
|
||||||
use selectors::Element as SelectorsElement;
|
use selectors::Element as SelectorsElement;
|
||||||
use servo_arc::{Arc, ArcBorrow};
|
use servo_arc::{Arc, ArcBorrow};
|
||||||
|
@ -734,19 +734,6 @@ pub trait TElement:
|
||||||
/// native anonymous content can opt out of this style fixup.)
|
/// native anonymous content can opt out of this style fixup.)
|
||||||
fn skip_item_display_fixup(&self) -> bool;
|
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
|
/// In Gecko, element has a flag that represents the element may have
|
||||||
/// any type of animations or not to bail out animation stuff early.
|
/// any type of animations or not to bail out animation stuff early.
|
||||||
/// Whereas Servo doesn't have such flag.
|
/// Whereas Servo doesn't have such flag.
|
||||||
|
@ -941,6 +928,9 @@ pub trait TElement:
|
||||||
/// Returns element's namespace.
|
/// Returns element's namespace.
|
||||||
fn namespace(&self)
|
fn namespace(&self)
|
||||||
-> &<SelectorImpl as selectors::parser::SelectorImpl>::BorrowedNamespaceUrl;
|
-> &<SelectorImpl as selectors::parser::SelectorImpl>::BorrowedNamespaceUrl;
|
||||||
|
|
||||||
|
/// Returns the size of the primary box of the element.
|
||||||
|
fn primary_box_size(&self) -> euclid::default::Size2D<app_units::Au>;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// TNode and TElement aren't Send because we want to be careful and explicit
|
/// TNode and TElement aren't Send because we want to be careful and explicit
|
||||||
|
|
|
@ -12,7 +12,7 @@ use crate::invalidation::element::invalidator::{DescendantInvalidationLists, Inv
|
||||||
use crate::invalidation::element::invalidator::{InvalidationProcessor, InvalidationVector};
|
use crate::invalidation::element::invalidator::{InvalidationProcessor, InvalidationVector};
|
||||||
use crate::values::AtomIdent;
|
use crate::values::AtomIdent;
|
||||||
use selectors::attr::CaseSensitivity;
|
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::parser::{Combinator, Component, LocalName, SelectorImpl};
|
||||||
use selectors::{Element, NthIndexCache, SelectorList};
|
use selectors::{Element, NthIndexCache, SelectorList};
|
||||||
use smallvec::SmallVec;
|
use smallvec::SmallVec;
|
||||||
|
@ -26,7 +26,13 @@ pub fn element_matches<E>(
|
||||||
where
|
where
|
||||||
E: Element,
|
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.scope_element = Some(element.opaque());
|
||||||
context.current_host = element.containing_shadow_host().map(|e| e.opaque());
|
context.current_host = element.containing_shadow_host().map(|e| e.opaque());
|
||||||
matching::matches_selector_list(selector_list, element, &mut context)
|
matching::matches_selector_list(selector_list, element, &mut context)
|
||||||
|
@ -48,6 +54,7 @@ where
|
||||||
None,
|
None,
|
||||||
Some(&mut nth_index_cache),
|
Some(&mut nth_index_cache),
|
||||||
quirks_mode,
|
quirks_mode,
|
||||||
|
NeedsSelectorFlags::No,
|
||||||
);
|
);
|
||||||
context.scope_element = Some(element.opaque());
|
context.scope_element = Some(element.opaque());
|
||||||
context.current_host = element.containing_shadow_host().map(|e| e.opaque());
|
context.current_host = element.containing_shadow_host().map(|e| e.opaque());
|
||||||
|
@ -618,8 +625,8 @@ pub fn query_selector<E, Q>(
|
||||||
None,
|
None,
|
||||||
Some(&mut nth_index_cache),
|
Some(&mut nth_index_cache),
|
||||||
quirks_mode,
|
quirks_mode,
|
||||||
|
NeedsSelectorFlags::No,
|
||||||
);
|
);
|
||||||
|
|
||||||
let root_element = root.as_element();
|
let root_element = root.as_element();
|
||||||
matching_context.scope_element = root_element.map(|e| e.opaque());
|
matching_context.scope_element = root_element.map(|e| e.opaque());
|
||||||
matching_context.current_host = match root_element {
|
matching_context.current_host = match root_element {
|
||||||
|
|
|
@ -118,8 +118,8 @@ bitflags! {
|
||||||
const IN_MODAL_DIALOG_STATE = 1 << 42;
|
const IN_MODAL_DIALOG_STATE = 1 << 42;
|
||||||
/// <https://html.spec.whatwg.org/multipage/#inert-subtrees>
|
/// <https://html.spec.whatwg.org/multipage/#inert-subtrees>
|
||||||
const IN_MOZINERT_STATE = 1 << 43;
|
const IN_MOZINERT_STATE = 1 << 43;
|
||||||
/// State for the topmost dialog element in top layer
|
/// State for the topmost modal element in top layer
|
||||||
const IN_TOPMOST_MODAL_DIALOG_STATE = 1 << 44;
|
const IN_TOPMOST_MODAL_TOP_LAYER_STATE = 1 << 44;
|
||||||
/// Initially used for the devtools highlighter, but now somehow only
|
/// Initially used for the devtools highlighter, but now somehow only
|
||||||
/// used for the devtools accessibility inspector.
|
/// used for the devtools accessibility inspector.
|
||||||
const IN_DEVTOOLS_HIGHLIGHTED_STATE = 1 << 45;
|
const IN_DEVTOOLS_HIGHLIGHTED_STATE = 1 << 45;
|
||||||
|
@ -148,9 +148,5 @@ bitflags! {
|
||||||
const LTR_LOCALE = 1 << 2;
|
const LTR_LOCALE = 1 << 2;
|
||||||
/// LWTheme status
|
/// LWTheme status
|
||||||
const LWTHEME = 1 << 3;
|
const LWTHEME = 1 << 3;
|
||||||
/// LWTheme status
|
|
||||||
const LWTHEME_BRIGHTTEXT = 1 << 4;
|
|
||||||
/// LWTheme status
|
|
||||||
const LWTHEME_DARKTEXT = 1 << 5;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -15,7 +15,7 @@ use crate::gecko_bindings::structs::{
|
||||||
RawServoKeyframesRule, RawServoLayerBlockRule, RawServoLayerStatementRule, RawServoMediaList,
|
RawServoKeyframesRule, RawServoLayerBlockRule, RawServoLayerStatementRule, RawServoMediaList,
|
||||||
RawServoMediaRule, RawServoMozDocumentRule, RawServoNamespaceRule, RawServoPageRule,
|
RawServoMediaRule, RawServoMozDocumentRule, RawServoNamespaceRule, RawServoPageRule,
|
||||||
RawServoScrollTimelineRule, RawServoStyleRule, RawServoStyleSheetContents,
|
RawServoScrollTimelineRule, RawServoStyleRule, RawServoStyleSheetContents,
|
||||||
RawServoSupportsRule, ServoCssRules,
|
RawServoSupportsRule, RawServoContainerRule, ServoCssRules,
|
||||||
};
|
};
|
||||||
use crate::gecko_bindings::sugar::ownership::{HasArcFFI, HasFFI, Strong};
|
use crate::gecko_bindings::sugar::ownership::{HasArcFFI, HasFFI, Strong};
|
||||||
use crate::media_queries::MediaList;
|
use crate::media_queries::MediaList;
|
||||||
|
@ -26,7 +26,7 @@ use crate::stylesheets::keyframes_rule::Keyframe;
|
||||||
use crate::stylesheets::{
|
use crate::stylesheets::{
|
||||||
CounterStyleRule, CssRules, DocumentRule, FontFaceRule, FontFeatureValuesRule, ImportRule,
|
CounterStyleRule, CssRules, DocumentRule, FontFaceRule, FontFeatureValuesRule, ImportRule,
|
||||||
KeyframesRule, LayerBlockRule, LayerStatementRule, MediaRule, NamespaceRule, PageRule,
|
KeyframesRule, LayerBlockRule, LayerStatementRule, MediaRule, NamespaceRule, PageRule,
|
||||||
ScrollTimelineRule, StyleRule, StylesheetContents, SupportsRule,
|
ScrollTimelineRule, StyleRule, StylesheetContents, SupportsRule, ContainerRule,
|
||||||
};
|
};
|
||||||
use servo_arc::{Arc, ArcBorrow};
|
use servo_arc::{Arc, ArcBorrow};
|
||||||
use std::{mem, ptr};
|
use std::{mem, ptr};
|
||||||
|
@ -98,6 +98,9 @@ impl_arc_ffi!(Locked<ScrollTimelineRule> => RawServoScrollTimelineRule
|
||||||
impl_arc_ffi!(Locked<SupportsRule> => RawServoSupportsRule
|
impl_arc_ffi!(Locked<SupportsRule> => RawServoSupportsRule
|
||||||
[Servo_SupportsRule_AddRef, Servo_SupportsRule_Release]);
|
[Servo_SupportsRule_AddRef, Servo_SupportsRule_Release]);
|
||||||
|
|
||||||
|
impl_arc_ffi!(Locked<ContainerRule> => RawServoContainerRule
|
||||||
|
[Servo_ContainerRule_AddRef, Servo_ContainerRule_Release]);
|
||||||
|
|
||||||
impl_arc_ffi!(Locked<DocumentRule> => RawServoMozDocumentRule
|
impl_arc_ffi!(Locked<DocumentRule> => RawServoMozDocumentRule
|
||||||
[Servo_DocumentRule_AddRef, Servo_DocumentRule_Release]);
|
[Servo_DocumentRule_AddRef, Servo_DocumentRule_Release]);
|
||||||
|
|
||||||
|
|
|
@ -129,6 +129,12 @@ impl StylesheetInDocument for GeckoStyleSheet {
|
||||||
pub struct PerDocumentStyleDataImpl {
|
pub struct PerDocumentStyleDataImpl {
|
||||||
/// Rule processor.
|
/// Rule processor.
|
||||||
pub stylist: Stylist,
|
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
|
/// The data itself is an `AtomicRefCell`, which guarantees the proper semantics
|
||||||
|
@ -143,6 +149,8 @@ impl PerDocumentStyleData {
|
||||||
|
|
||||||
PerDocumentStyleData(AtomicRefCell::new(PerDocumentStyleDataImpl {
|
PerDocumentStyleData(AtomicRefCell::new(PerDocumentStyleDataImpl {
|
||||||
stylist: Stylist::new(device, quirks_mode.into()),
|
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()
|
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.
|
/// Measure heap usage.
|
||||||
pub fn add_size_of(&self, ops: &mut MallocSizeOfOps, sizes: &mut ServoStyleSetSizes) {
|
pub fn add_size_of(&self, ops: &mut MallocSizeOfOps, sizes: &mut ServoStyleSetSizes) {
|
||||||
self.stylist.add_size_of(ops, sizes);
|
self.stylist.add_size_of(ops, sizes);
|
||||||
|
|
|
@ -6,13 +6,10 @@
|
||||||
|
|
||||||
use crate::gecko_bindings::bindings;
|
use crate::gecko_bindings::bindings;
|
||||||
use crate::gecko_bindings::structs;
|
use crate::gecko_bindings::structs;
|
||||||
use crate::media_queries::media_feature::{AllowsRanges, ParsingRequirements};
|
use crate::queries::feature::{AllowsRanges, Evaluator, FeatureFlags, QueryFeatureDescription};
|
||||||
use crate::media_queries::media_feature::{Evaluator, MediaFeatureDescription};
|
use crate::queries::values::Orientation;
|
||||||
use crate::media_queries::media_feature_expression::RangeOrOperator;
|
|
||||||
use crate::media_queries::{Device, MediaType};
|
use crate::media_queries::{Device, MediaType};
|
||||||
use crate::values::computed::CSSPixelLength;
|
use crate::values::computed::{Context, CSSPixelLength, Ratio, Resolution};
|
||||||
use crate::values::computed::Ratio;
|
|
||||||
use crate::values::computed::Resolution;
|
|
||||||
use app_units::Au;
|
use app_units::Au;
|
||||||
use euclid::default::Size2D;
|
use euclid::default::Size2D;
|
||||||
|
|
||||||
|
@ -26,150 +23,56 @@ fn device_size(device: &Device) -> Size2D<Au> {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// https://drafts.csswg.org/mediaqueries-4/#width
|
/// https://drafts.csswg.org/mediaqueries-4/#width
|
||||||
fn eval_width(
|
fn eval_width(context: &Context) -> CSSPixelLength {
|
||||||
device: &Device,
|
CSSPixelLength::new(context.device().au_viewport_size().width.to_f32_px())
|
||||||
value: Option<CSSPixelLength>,
|
|
||||||
range_or_operator: Option<RangeOrOperator>,
|
|
||||||
) -> bool {
|
|
||||||
RangeOrOperator::evaluate(
|
|
||||||
range_or_operator,
|
|
||||||
value.map(Au::from),
|
|
||||||
device.au_viewport_size().width,
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// https://drafts.csswg.org/mediaqueries-4/#device-width
|
/// https://drafts.csswg.org/mediaqueries-4/#device-width
|
||||||
fn eval_device_width(
|
fn eval_device_width(context: &Context) -> CSSPixelLength {
|
||||||
device: &Device,
|
CSSPixelLength::new(device_size(context.device()).width.to_f32_px())
|
||||||
value: Option<CSSPixelLength>,
|
|
||||||
range_or_operator: Option<RangeOrOperator>,
|
|
||||||
) -> bool {
|
|
||||||
RangeOrOperator::evaluate(
|
|
||||||
range_or_operator,
|
|
||||||
value.map(Au::from),
|
|
||||||
device_size(device).width,
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// https://drafts.csswg.org/mediaqueries-4/#height
|
/// https://drafts.csswg.org/mediaqueries-4/#height
|
||||||
fn eval_height(
|
fn eval_height(context: &Context) -> CSSPixelLength {
|
||||||
device: &Device,
|
CSSPixelLength::new(context.device().au_viewport_size().height.to_f32_px())
|
||||||
value: Option<CSSPixelLength>,
|
|
||||||
range_or_operator: Option<RangeOrOperator>,
|
|
||||||
) -> bool {
|
|
||||||
RangeOrOperator::evaluate(
|
|
||||||
range_or_operator,
|
|
||||||
value.map(Au::from),
|
|
||||||
device.au_viewport_size().height,
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// https://drafts.csswg.org/mediaqueries-4/#device-height
|
/// https://drafts.csswg.org/mediaqueries-4/#device-height
|
||||||
fn eval_device_height(
|
fn eval_device_height(context: &Context) -> CSSPixelLength {
|
||||||
device: &Device,
|
CSSPixelLength::new(device_size(context.device()).height.to_f32_px())
|
||||||
value: Option<CSSPixelLength>,
|
|
||||||
range_or_operator: Option<RangeOrOperator>,
|
|
||||||
) -> bool {
|
|
||||||
RangeOrOperator::evaluate(
|
|
||||||
range_or_operator,
|
|
||||||
value.map(Au::from),
|
|
||||||
device_size(device).height,
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn eval_aspect_ratio_for<F>(
|
fn eval_aspect_ratio_for<F>(context: &Context, get_size: F) -> Ratio
|
||||||
device: &Device,
|
|
||||||
query_value: Option<Ratio>,
|
|
||||||
range_or_operator: Option<RangeOrOperator>,
|
|
||||||
get_size: F,
|
|
||||||
) -> bool
|
|
||||||
where
|
where
|
||||||
F: FnOnce(&Device) -> Size2D<Au>,
|
F: FnOnce(&Device) -> Size2D<Au>,
|
||||||
{
|
{
|
||||||
// A ratio of 0/0 behaves as the ratio 1/0, so we need to call used_value()
|
let size = get_size(context.device());
|
||||||
// to convert it if necessary.
|
Ratio::new(size.width.0 as f32, size.height.0 as f32)
|
||||||
// FIXME: we may need to update here once
|
|
||||||
// https://github.com/w3c/csswg-drafts/issues/4954 got resolved.
|
|
||||||
let query_value = match query_value {
|
|
||||||
Some(v) => v.used_value(),
|
|
||||||
None => return true,
|
|
||||||
};
|
|
||||||
|
|
||||||
let size = get_size(device);
|
|
||||||
let value = Ratio::new(size.width.0 as f32, size.height.0 as f32);
|
|
||||||
RangeOrOperator::evaluate_with_query_value(range_or_operator, query_value, value)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// https://drafts.csswg.org/mediaqueries-4/#aspect-ratio
|
/// https://drafts.csswg.org/mediaqueries-4/#aspect-ratio
|
||||||
fn eval_aspect_ratio(
|
fn eval_aspect_ratio(context: &Context) -> Ratio {
|
||||||
device: &Device,
|
eval_aspect_ratio_for(context, Device::au_viewport_size)
|
||||||
query_value: Option<Ratio>,
|
|
||||||
range_or_operator: Option<RangeOrOperator>,
|
|
||||||
) -> bool {
|
|
||||||
eval_aspect_ratio_for(
|
|
||||||
device,
|
|
||||||
query_value,
|
|
||||||
range_or_operator,
|
|
||||||
Device::au_viewport_size,
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// https://drafts.csswg.org/mediaqueries-4/#device-aspect-ratio
|
/// https://drafts.csswg.org/mediaqueries-4/#device-aspect-ratio
|
||||||
fn eval_device_aspect_ratio(
|
fn eval_device_aspect_ratio(context: &Context) -> Ratio {
|
||||||
device: &Device,
|
eval_aspect_ratio_for(context, device_size)
|
||||||
query_value: Option<Ratio>,
|
|
||||||
range_or_operator: Option<RangeOrOperator>,
|
|
||||||
) -> bool {
|
|
||||||
eval_aspect_ratio_for(device, query_value, range_or_operator, device_size)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// https://compat.spec.whatwg.org/#css-media-queries-webkit-device-pixel-ratio
|
/// https://compat.spec.whatwg.org/#css-media-queries-webkit-device-pixel-ratio
|
||||||
fn eval_device_pixel_ratio(
|
fn eval_device_pixel_ratio(context: &Context) -> f32 {
|
||||||
device: &Device,
|
eval_resolution(context).dppx()
|
||||||
query_value: Option<f32>,
|
|
||||||
range_or_operator: Option<RangeOrOperator>,
|
|
||||||
) -> bool {
|
|
||||||
eval_resolution(
|
|
||||||
device,
|
|
||||||
query_value.map(Resolution::from_dppx),
|
|
||||||
range_or_operator,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, Copy, Debug, FromPrimitive, Parse, ToCss)]
|
|
||||||
#[repr(u8)]
|
|
||||||
enum Orientation {
|
|
||||||
Landscape,
|
|
||||||
Portrait,
|
|
||||||
}
|
|
||||||
|
|
||||||
fn eval_orientation_for<F>(device: &Device, value: Option<Orientation>, get_size: F) -> bool
|
|
||||||
where
|
|
||||||
F: FnOnce(&Device) -> Size2D<Au>,
|
|
||||||
{
|
|
||||||
let query_orientation = match value {
|
|
||||||
Some(v) => v,
|
|
||||||
None => return true,
|
|
||||||
};
|
|
||||||
|
|
||||||
let size = get_size(device);
|
|
||||||
|
|
||||||
// Per spec, square viewports should be 'portrait'
|
|
||||||
let is_landscape = size.width > size.height;
|
|
||||||
match query_orientation {
|
|
||||||
Orientation::Landscape => is_landscape,
|
|
||||||
Orientation::Portrait => !is_landscape,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// https://drafts.csswg.org/mediaqueries-4/#orientation
|
/// https://drafts.csswg.org/mediaqueries-4/#orientation
|
||||||
fn eval_orientation(device: &Device, value: Option<Orientation>) -> bool {
|
fn eval_orientation(context: &Context, value: Option<Orientation>) -> bool {
|
||||||
eval_orientation_for(device, value, Device::au_viewport_size)
|
Orientation::eval(context.device().au_viewport_size(), value)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// FIXME: There's no spec for `-moz-device-orientation`.
|
/// FIXME: There's no spec for `-moz-device-orientation`.
|
||||||
fn eval_device_orientation(device: &Device, value: Option<Orientation>) -> bool {
|
fn eval_device_orientation(context: &Context, value: Option<Orientation>) -> bool {
|
||||||
eval_orientation_for(device, value, device_size)
|
Orientation::eval(device_size(context.device()), value)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Values for the display-mode media feature.
|
/// Values for the display-mode media feature.
|
||||||
|
@ -184,25 +87,23 @@ pub enum DisplayMode {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// https://w3c.github.io/manifest/#the-display-mode-media-feature
|
/// https://w3c.github.io/manifest/#the-display-mode-media-feature
|
||||||
fn eval_display_mode(device: &Device, query_value: Option<DisplayMode>) -> bool {
|
fn eval_display_mode(context: &Context, query_value: Option<DisplayMode>) -> bool {
|
||||||
match query_value {
|
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,
|
None => true,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// https://drafts.csswg.org/mediaqueries-4/#grid
|
/// https://drafts.csswg.org/mediaqueries-4/#grid
|
||||||
fn eval_grid(_: &Device, query_value: Option<bool>, _: Option<RangeOrOperator>) -> bool {
|
fn eval_grid(_: &Context) -> bool {
|
||||||
// Gecko doesn't support grid devices (e.g., ttys), so the 'grid' feature
|
// Gecko doesn't support grid devices (e.g., ttys), so the 'grid' feature
|
||||||
// is always 0.
|
// is always 0.
|
||||||
let supports_grid = false;
|
false
|
||||||
query_value.map_or(supports_grid, |v| v == supports_grid)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// https://compat.spec.whatwg.org/#css-media-queries-webkit-transform-3d
|
/// https://compat.spec.whatwg.org/#css-media-queries-webkit-transform-3d
|
||||||
fn eval_transform_3d(_: &Device, query_value: Option<bool>, _: Option<RangeOrOperator>) -> bool {
|
fn eval_transform_3d(_: &Context) -> bool {
|
||||||
let supports_transforms = true;
|
true
|
||||||
query_value.map_or(supports_transforms, |v| v == supports_transforms)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Copy, Debug, FromPrimitive, Parse, ToCss)]
|
#[derive(Clone, Copy, Debug, FromPrimitive, Parse, ToCss)]
|
||||||
|
@ -213,58 +114,33 @@ enum Scan {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// https://drafts.csswg.org/mediaqueries-4/#scan
|
/// https://drafts.csswg.org/mediaqueries-4/#scan
|
||||||
fn eval_scan(_: &Device, _: Option<Scan>) -> bool {
|
fn eval_scan(_: &Context, _: Option<Scan>) -> bool {
|
||||||
// Since Gecko doesn't support the 'tv' media type, the 'scan' feature never
|
// Since Gecko doesn't support the 'tv' media type, the 'scan' feature never
|
||||||
// matches.
|
// matches.
|
||||||
false
|
false
|
||||||
}
|
}
|
||||||
|
|
||||||
/// https://drafts.csswg.org/mediaqueries-4/#color
|
/// https://drafts.csswg.org/mediaqueries-4/#color
|
||||||
fn eval_color(
|
fn eval_color(context: &Context) -> u32 {
|
||||||
device: &Device,
|
unsafe { bindings::Gecko_MediaFeatures_GetColorDepth(context.device().document()) }
|
||||||
query_value: Option<u32>,
|
|
||||||
range_or_operator: Option<RangeOrOperator>,
|
|
||||||
) -> bool {
|
|
||||||
let color_bits_per_channel =
|
|
||||||
unsafe { bindings::Gecko_MediaFeatures_GetColorDepth(device.document()) };
|
|
||||||
RangeOrOperator::evaluate(range_or_operator, query_value, color_bits_per_channel)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// https://drafts.csswg.org/mediaqueries-4/#color-index
|
/// https://drafts.csswg.org/mediaqueries-4/#color-index
|
||||||
fn eval_color_index(
|
fn eval_color_index(_: &Context) -> u32 {
|
||||||
_: &Device,
|
|
||||||
query_value: Option<u32>,
|
|
||||||
range_or_operator: Option<RangeOrOperator>,
|
|
||||||
) -> bool {
|
|
||||||
// We should return zero if the device does not use a color lookup table.
|
// We should return zero if the device does not use a color lookup table.
|
||||||
let index = 0;
|
0
|
||||||
RangeOrOperator::evaluate(range_or_operator, query_value, index)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// https://drafts.csswg.org/mediaqueries-4/#monochrome
|
/// https://drafts.csswg.org/mediaqueries-4/#monochrome
|
||||||
fn eval_monochrome(
|
fn eval_monochrome(context: &Context) -> u32 {
|
||||||
device: &Device,
|
|
||||||
query_value: Option<u32>,
|
|
||||||
range_or_operator: Option<RangeOrOperator>,
|
|
||||||
) -> bool {
|
|
||||||
// For color devices we should return 0.
|
// For color devices we should return 0.
|
||||||
let depth =
|
unsafe { bindings::Gecko_MediaFeatures_GetMonochromeBitsPerPixel(context.device().document()) }
|
||||||
unsafe { bindings::Gecko_MediaFeatures_GetMonochromeBitsPerPixel(device.document()) };
|
|
||||||
RangeOrOperator::evaluate(range_or_operator, query_value, depth)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// https://drafts.csswg.org/mediaqueries-4/#resolution
|
/// https://drafts.csswg.org/mediaqueries-4/#resolution
|
||||||
fn eval_resolution(
|
fn eval_resolution(context: &Context) -> Resolution {
|
||||||
device: &Device,
|
let resolution_dppx = unsafe { bindings::Gecko_MediaFeatures_GetResolution(context.device().document()) };
|
||||||
query_value: Option<Resolution>,
|
Resolution::from_dppx(resolution_dppx)
|
||||||
range_or_operator: Option<RangeOrOperator>,
|
|
||||||
) -> bool {
|
|
||||||
let resolution_dppx = unsafe { bindings::Gecko_MediaFeatures_GetResolution(device.document()) };
|
|
||||||
RangeOrOperator::evaluate(
|
|
||||||
range_or_operator,
|
|
||||||
query_value.map(|r| r.dppx()),
|
|
||||||
resolution_dppx,
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Copy, Debug, FromPrimitive, Parse, ToCss)]
|
#[derive(Clone, Copy, Debug, FromPrimitive, Parse, ToCss)]
|
||||||
|
@ -283,10 +159,22 @@ pub enum PrefersColorScheme {
|
||||||
Dark,
|
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
|
/// https://drafts.csswg.org/mediaqueries-5/#prefers-reduced-motion
|
||||||
fn eval_prefers_reduced_motion(device: &Device, query_value: Option<PrefersReducedMotion>) -> bool {
|
fn eval_prefers_reduced_motion(context: &Context, query_value: Option<PrefersReducedMotion>) -> bool {
|
||||||
let prefers_reduced =
|
let prefers_reduced =
|
||||||
unsafe { bindings::Gecko_MediaFeatures_PrefersReducedMotion(device.document()) };
|
unsafe { bindings::Gecko_MediaFeatures_PrefersReducedMotion(context.device().document()) };
|
||||||
let query_value = match query_value {
|
let query_value = match query_value {
|
||||||
Some(v) => v,
|
Some(v) => v,
|
||||||
None => return prefers_reduced,
|
None => return prefers_reduced,
|
||||||
|
@ -303,19 +191,20 @@ fn eval_prefers_reduced_motion(device: &Device, query_value: Option<PrefersReduc
|
||||||
#[derive(Clone, Copy, Debug, FromPrimitive, Parse, PartialEq, ToCss)]
|
#[derive(Clone, Copy, Debug, FromPrimitive, Parse, PartialEq, ToCss)]
|
||||||
#[repr(u8)]
|
#[repr(u8)]
|
||||||
pub enum PrefersContrast {
|
pub enum PrefersContrast {
|
||||||
/// More contrast is preferred. Corresponds to an accessibility theme
|
/// More contrast is preferred.
|
||||||
/// being enabled or Firefox forcing high contrast colors.
|
|
||||||
More,
|
More,
|
||||||
/// Low contrast is preferred.
|
/// Low contrast is preferred.
|
||||||
Less,
|
Less,
|
||||||
|
/// Custom (not more, not less).
|
||||||
|
Custom,
|
||||||
/// The default value if neither high or low contrast is enabled.
|
/// The default value if neither high or low contrast is enabled.
|
||||||
NoPreference,
|
NoPreference,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// https://drafts.csswg.org/mediaqueries-5/#prefers-contrast
|
/// https://drafts.csswg.org/mediaqueries-5/#prefers-contrast
|
||||||
fn eval_prefers_contrast(device: &Device, query_value: Option<PrefersContrast>) -> bool {
|
fn eval_prefers_contrast(context: &Context, query_value: Option<PrefersContrast>) -> bool {
|
||||||
let prefers_contrast =
|
let prefers_contrast =
|
||||||
unsafe { bindings::Gecko_MediaFeatures_PrefersContrast(device.document()) };
|
unsafe { bindings::Gecko_MediaFeatures_PrefersContrast(context.device().document()) };
|
||||||
match query_value {
|
match query_value {
|
||||||
Some(v) => v == prefers_contrast,
|
Some(v) => v == prefers_contrast,
|
||||||
None => prefers_contrast != PrefersContrast::NoPreference,
|
None => prefers_contrast != PrefersContrast::NoPreference,
|
||||||
|
@ -334,8 +223,8 @@ pub enum ForcedColors {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// https://drafts.csswg.org/mediaqueries-5/#forced-colors
|
/// https://drafts.csswg.org/mediaqueries-5/#forced-colors
|
||||||
fn eval_forced_colors(device: &Device, query_value: Option<ForcedColors>) -> bool {
|
fn eval_forced_colors(context: &Context, query_value: Option<ForcedColors>) -> bool {
|
||||||
let forced = !device.use_document_colors();
|
let forced = !context.device().use_document_colors();
|
||||||
match query_value {
|
match query_value {
|
||||||
Some(query_value) => forced == (query_value == ForcedColors::Active),
|
Some(query_value) => forced == (query_value == ForcedColors::Active),
|
||||||
None => forced,
|
None => forced,
|
||||||
|
@ -352,7 +241,7 @@ enum OverflowBlock {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// https://drafts.csswg.org/mediaqueries-4/#mf-overflow-block
|
/// https://drafts.csswg.org/mediaqueries-4/#mf-overflow-block
|
||||||
fn eval_overflow_block(device: &Device, query_value: Option<OverflowBlock>) -> bool {
|
fn eval_overflow_block(context: &Context, query_value: Option<OverflowBlock>) -> bool {
|
||||||
// For the time being, assume that printing (including previews)
|
// For the time being, assume that printing (including previews)
|
||||||
// is the only time when we paginate, and we are otherwise always
|
// is the only time when we paginate, and we are otherwise always
|
||||||
// scrolling. This is true at the moment in Firefox, but may need
|
// 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<OverflowBlock>) -> b
|
||||||
// billboard mode that doesn't support overflow at all).
|
// billboard mode that doesn't support overflow at all).
|
||||||
//
|
//
|
||||||
// If this ever changes, don't forget to change eval_overflow_inline too.
|
// 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 {
|
let query_value = match query_value {
|
||||||
Some(v) => v,
|
Some(v) => v,
|
||||||
None => return true,
|
None => return true,
|
||||||
|
@ -381,9 +270,9 @@ enum OverflowInline {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// https://drafts.csswg.org/mediaqueries-4/#mf-overflow-inline
|
/// https://drafts.csswg.org/mediaqueries-4/#mf-overflow-inline
|
||||||
fn eval_overflow_inline(device: &Device, query_value: Option<OverflowInline>) -> bool {
|
fn eval_overflow_inline(context: &Context, query_value: Option<OverflowInline>) -> bool {
|
||||||
// See the note in eval_overflow_block.
|
// 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 {
|
let query_value = match query_value {
|
||||||
Some(v) => v,
|
Some(v) => v,
|
||||||
None => return scrolling,
|
None => return scrolling,
|
||||||
|
@ -395,13 +284,41 @@ fn eval_overflow_inline(device: &Device, query_value: Option<OverflowInline>) ->
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Copy, Debug, FromPrimitive, Parse, ToCss)]
|
||||||
|
#[repr(u8)]
|
||||||
|
enum Update {
|
||||||
|
None,
|
||||||
|
Slow,
|
||||||
|
Fast,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// https://drafts.csswg.org/mediaqueries-4/#update
|
||||||
|
fn eval_update(context: &Context, query_value: Option<Update>) -> bool {
|
||||||
|
// This has similar caveats to those described in eval_overflow_block.
|
||||||
|
// For now, we report that print (incl. print media simulation,
|
||||||
|
// which can in fact update but is limited to the developer tools)
|
||||||
|
// is `update: none` and that all other contexts are `update: fast`,
|
||||||
|
// which may not be true for future platforms, like e-ink devices.
|
||||||
|
let can_update = context.device().media_type() != MediaType::print();
|
||||||
|
let query_value = match query_value {
|
||||||
|
Some(v) => v,
|
||||||
|
None => return can_update,
|
||||||
|
};
|
||||||
|
|
||||||
|
match query_value {
|
||||||
|
Update::None => !can_update,
|
||||||
|
Update::Slow => false,
|
||||||
|
Update::Fast => can_update,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn do_eval_prefers_color_scheme(
|
fn do_eval_prefers_color_scheme(
|
||||||
device: &Device,
|
context: &Context,
|
||||||
use_content: bool,
|
use_content: bool,
|
||||||
query_value: Option<PrefersColorScheme>,
|
query_value: Option<PrefersColorScheme>,
|
||||||
) -> bool {
|
) -> bool {
|
||||||
let prefers_color_scheme =
|
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 {
|
match query_value {
|
||||||
Some(v) => prefers_color_scheme == v,
|
Some(v) => prefers_color_scheme == v,
|
||||||
None => true,
|
None => true,
|
||||||
|
@ -409,15 +326,34 @@ fn do_eval_prefers_color_scheme(
|
||||||
}
|
}
|
||||||
|
|
||||||
/// https://drafts.csswg.org/mediaqueries-5/#prefers-color-scheme
|
/// https://drafts.csswg.org/mediaqueries-5/#prefers-color-scheme
|
||||||
fn eval_prefers_color_scheme(device: &Device, query_value: Option<PrefersColorScheme>) -> bool {
|
fn eval_prefers_color_scheme(context: &Context, query_value: Option<PrefersColorScheme>) -> bool {
|
||||||
do_eval_prefers_color_scheme(device, /* use_content = */ false, query_value)
|
do_eval_prefers_color_scheme(context, /* use_content = */ false, query_value)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn eval_content_prefers_color_scheme(
|
fn eval_content_prefers_color_scheme(
|
||||||
device: &Device,
|
context: &Context,
|
||||||
query_value: Option<PrefersColorScheme>,
|
query_value: Option<PrefersColorScheme>,
|
||||||
) -> bool {
|
) -> 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<DynamicRange>) -> bool {
|
||||||
|
let dynamic_range =
|
||||||
|
unsafe { bindings::Gecko_MediaFeatures_DynamicRange(context.device().document()) };
|
||||||
|
match query_value {
|
||||||
|
Some(v) => dynamic_range >= v,
|
||||||
|
None => false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/// https://drafts.csswg.org/mediaqueries-5/#video-dynamic-range
|
||||||
|
fn eval_video_dynamic_range(context: &Context, query_value: Option<DynamicRange>) -> bool {
|
||||||
|
let dynamic_range =
|
||||||
|
unsafe { bindings::Gecko_MediaFeatures_VideoDynamicRange(context.device().document()) };
|
||||||
|
match query_value {
|
||||||
|
Some(v) => dynamic_range >= v,
|
||||||
|
None => false,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
bitflags! {
|
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 {
|
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 {
|
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
|
/// https://drafts.csswg.org/mediaqueries-4/#pointer
|
||||||
fn eval_pointer(device: &Device, query_value: Option<Pointer>) -> bool {
|
fn eval_pointer(context: &Context, query_value: Option<Pointer>) -> bool {
|
||||||
eval_pointer_capabilities(query_value, primary_pointer_capabilities(device))
|
eval_pointer_capabilities(query_value, primary_pointer_capabilities(context))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// https://drafts.csswg.org/mediaqueries-4/#descdef-media-any-pointer
|
/// https://drafts.csswg.org/mediaqueries-4/#descdef-media-any-pointer
|
||||||
fn eval_any_pointer(device: &Device, query_value: Option<Pointer>) -> bool {
|
fn eval_any_pointer(context: &Context, query_value: Option<Pointer>) -> bool {
|
||||||
eval_pointer_capabilities(query_value, all_pointer_capabilities(device))
|
eval_pointer_capabilities(query_value, all_pointer_capabilities(context))
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Copy, Debug, FromPrimitive, Parse, ToCss)]
|
#[derive(Clone, Copy, Debug, FromPrimitive, Parse, ToCss)]
|
||||||
|
@ -499,54 +435,33 @@ fn eval_hover_capabilities(
|
||||||
}
|
}
|
||||||
|
|
||||||
/// https://drafts.csswg.org/mediaqueries-4/#hover
|
/// https://drafts.csswg.org/mediaqueries-4/#hover
|
||||||
fn eval_hover(device: &Device, query_value: Option<Hover>) -> bool {
|
fn eval_hover(context: &Context, query_value: Option<Hover>) -> bool {
|
||||||
eval_hover_capabilities(query_value, primary_pointer_capabilities(device))
|
eval_hover_capabilities(query_value, primary_pointer_capabilities(context))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// https://drafts.csswg.org/mediaqueries-4/#descdef-media-any-hover
|
/// https://drafts.csswg.org/mediaqueries-4/#descdef-media-any-hover
|
||||||
fn eval_any_hover(device: &Device, query_value: Option<Hover>) -> bool {
|
fn eval_any_hover(context: &Context, query_value: Option<Hover>) -> bool {
|
||||||
eval_hover_capabilities(query_value, all_pointer_capabilities(device))
|
eval_hover_capabilities(query_value, all_pointer_capabilities(context))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn eval_moz_is_glyph(
|
fn eval_moz_is_glyph(context: &Context) -> bool {
|
||||||
device: &Device,
|
context.device().document().mIsSVGGlyphsDocument()
|
||||||
query_value: Option<bool>,
|
|
||||||
_: Option<RangeOrOperator>,
|
|
||||||
) -> bool {
|
|
||||||
let is_glyph = device.document().mIsSVGGlyphsDocument();
|
|
||||||
query_value.map_or(is_glyph, |v| v == is_glyph)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn eval_moz_print_preview(
|
fn eval_moz_print_preview(context: &Context) -> bool {
|
||||||
device: &Device,
|
let is_print_preview = context.device().is_print_preview();
|
||||||
query_value: Option<bool>,
|
|
||||||
_: Option<RangeOrOperator>,
|
|
||||||
) -> bool {
|
|
||||||
let is_print_preview = device.is_print_preview();
|
|
||||||
if 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(
|
fn eval_moz_non_native_content_theme(context: &Context) -> bool {
|
||||||
device: &Device,
|
unsafe { bindings::Gecko_MediaFeatures_ShouldAvoidNativeTheme(context.device().document()) }
|
||||||
query_value: Option<bool>,
|
|
||||||
_: Option<RangeOrOperator>,
|
|
||||||
) -> bool {
|
|
||||||
let non_native_theme =
|
|
||||||
unsafe { bindings::Gecko_MediaFeatures_ShouldAvoidNativeTheme(device.document()) };
|
|
||||||
query_value.map_or(non_native_theme, |v| v == non_native_theme)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn eval_moz_is_resource_document(
|
fn eval_moz_is_resource_document(context: &Context) -> bool {
|
||||||
device: &Device,
|
unsafe { bindings::Gecko_MediaFeatures_IsResourceDocument(context.device().document()) }
|
||||||
query_value: Option<bool>,
|
|
||||||
_: Option<RangeOrOperator>,
|
|
||||||
) -> bool {
|
|
||||||
let is_resource_doc =
|
|
||||||
unsafe { bindings::Gecko_MediaFeatures_IsResourceDocument(device.document()) };
|
|
||||||
query_value.map_or(is_resource_doc, |v| v == is_resource_doc)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Allows front-end CSS to discern platform via media queries.
|
/// Allows front-end CSS to discern platform via media queries.
|
||||||
|
@ -572,7 +487,7 @@ pub enum Platform {
|
||||||
WindowsWin10,
|
WindowsWin10,
|
||||||
}
|
}
|
||||||
|
|
||||||
fn eval_moz_platform(_: &Device, query_value: Option<Platform>) -> bool {
|
fn eval_moz_platform(_: &Context, query_value: Option<Platform>) -> bool {
|
||||||
let query_value = match query_value {
|
let query_value = match query_value {
|
||||||
Some(v) => v,
|
Some(v) => v,
|
||||||
None => return false,
|
None => return false,
|
||||||
|
@ -581,32 +496,22 @@ fn eval_moz_platform(_: &Device, query_value: Option<Platform>) -> bool {
|
||||||
unsafe { bindings::Gecko_MediaFeatures_MatchesPlatform(query_value) }
|
unsafe { bindings::Gecko_MediaFeatures_MatchesPlatform(query_value) }
|
||||||
}
|
}
|
||||||
|
|
||||||
fn eval_moz_windows_non_native_menus(
|
fn eval_moz_windows_non_native_menus(context: &Context) -> bool {
|
||||||
device: &Device,
|
|
||||||
query_value: Option<bool>,
|
|
||||||
_: Option<RangeOrOperator>,
|
|
||||||
) -> bool {
|
|
||||||
let use_non_native_menus = match static_prefs::pref!("browser.display.windows.non_native_menus")
|
let use_non_native_menus = match static_prefs::pref!("browser.display.windows.non_native_menus")
|
||||||
{
|
{
|
||||||
0 => false,
|
0 => false,
|
||||||
1 => true,
|
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)
|
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(
|
fn eval_moz_overlay_scrollbars(context: &Context) -> bool {
|
||||||
device: &Device,
|
unsafe { bindings::Gecko_MediaFeatures_UseOverlayScrollbars(context.device().document()) }
|
||||||
query_value: Option<bool>,
|
|
||||||
_: Option<RangeOrOperator>,
|
|
||||||
) -> bool {
|
|
||||||
let use_overlay =
|
|
||||||
unsafe { bindings::Gecko_MediaFeatures_UseOverlayScrollbars(device.document()) };
|
|
||||||
query_value.map_or(use_overlay, |v| v == use_overlay)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_lnf_int(int_id: i32) -> i32 {
|
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 {
|
macro_rules! lnf_int_feature {
|
||||||
($feature_name:expr, $int_id:ident, $get_value:ident) => {{
|
($feature_name:expr, $int_id:ident, $get_value:ident) => {{
|
||||||
fn __eval(_: &Device, query_value: Option<bool>, _: Option<RangeOrOperator>) -> bool {
|
fn __eval(_: &Context) -> bool {
|
||||||
let value = $get_value(bindings::LookAndFeel_IntID::$int_id as i32);
|
$get_value(bindings::LookAndFeel_IntID::$int_id as i32)
|
||||||
query_value.map_or(value, |v| v == value)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
feature!(
|
feature!(
|
||||||
$feature_name,
|
$feature_name,
|
||||||
AllowsRanges::No,
|
AllowsRanges::No,
|
||||||
Evaluator::BoolInteger(__eval),
|
Evaluator::BoolInteger(__eval),
|
||||||
ParsingRequirements::CHROME_AND_UA_ONLY,
|
FeatureFlags::CHROME_AND_UA_ONLY,
|
||||||
)
|
)
|
||||||
}};
|
}};
|
||||||
($feature_name:expr, $int_id:ident) => {{
|
($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
|
/// pref, with `rust: true`. The feature name needs to be defined in
|
||||||
/// `StaticAtoms.py` just like the others. In order to support dynamic changes,
|
/// `StaticAtoms.py` just like the others. In order to support dynamic changes,
|
||||||
/// you also need to add them to kMediaQueryPrefs in nsXPLookAndFeel.cpp
|
/// you also need to add them to kMediaQueryPrefs in nsXPLookAndFeel.cpp
|
||||||
|
#[allow(unused)]
|
||||||
macro_rules! bool_pref_feature {
|
macro_rules! bool_pref_feature {
|
||||||
($feature_name:expr, $pref:tt) => {{
|
($feature_name:expr, $pref:tt) => {{
|
||||||
fn __eval(_: &Device, query_value: Option<bool>, _: Option<RangeOrOperator>) -> bool {
|
fn __eval(_: &Context) -> bool {
|
||||||
let value = static_prefs::pref!($pref);
|
static_prefs::pref!($pref)
|
||||||
query_value.map_or(value, |v| v == value)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
feature!(
|
feature!(
|
||||||
$feature_name,
|
$feature_name,
|
||||||
AllowsRanges::No,
|
AllowsRanges::No,
|
||||||
Evaluator::BoolInteger(__eval),
|
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
|
/// to support new types in these entries and (2) ensuring that either
|
||||||
/// nsPresContext::MediaFeatureValuesChanged is called when the value that
|
/// nsPresContext::MediaFeatureValuesChanged is called when the value that
|
||||||
/// would be returned by the evaluator function could change.
|
/// would be returned by the evaluator function could change.
|
||||||
pub static MEDIA_FEATURES: [MediaFeatureDescription; 58] = [
|
pub static MEDIA_FEATURES: [QueryFeatureDescription; 59] = [
|
||||||
feature!(
|
feature!(
|
||||||
atom!("width"),
|
atom!("width"),
|
||||||
AllowsRanges::Yes,
|
AllowsRanges::Yes,
|
||||||
Evaluator::Length(eval_width),
|
Evaluator::Length(eval_width),
|
||||||
ParsingRequirements::empty(),
|
FeatureFlags::empty(),
|
||||||
),
|
),
|
||||||
feature!(
|
feature!(
|
||||||
atom!("height"),
|
atom!("height"),
|
||||||
AllowsRanges::Yes,
|
AllowsRanges::Yes,
|
||||||
Evaluator::Length(eval_height),
|
Evaluator::Length(eval_height),
|
||||||
ParsingRequirements::empty(),
|
FeatureFlags::empty(),
|
||||||
),
|
),
|
||||||
feature!(
|
feature!(
|
||||||
atom!("aspect-ratio"),
|
atom!("aspect-ratio"),
|
||||||
AllowsRanges::Yes,
|
AllowsRanges::Yes,
|
||||||
Evaluator::NumberRatio(eval_aspect_ratio),
|
Evaluator::NumberRatio(eval_aspect_ratio),
|
||||||
ParsingRequirements::empty(),
|
FeatureFlags::empty(),
|
||||||
),
|
),
|
||||||
feature!(
|
feature!(
|
||||||
atom!("orientation"),
|
atom!("orientation"),
|
||||||
AllowsRanges::No,
|
AllowsRanges::No,
|
||||||
keyword_evaluator!(eval_orientation, Orientation),
|
keyword_evaluator!(eval_orientation, Orientation),
|
||||||
ParsingRequirements::empty(),
|
FeatureFlags::empty(),
|
||||||
),
|
),
|
||||||
feature!(
|
feature!(
|
||||||
atom!("device-width"),
|
atom!("device-width"),
|
||||||
AllowsRanges::Yes,
|
AllowsRanges::Yes,
|
||||||
Evaluator::Length(eval_device_width),
|
Evaluator::Length(eval_device_width),
|
||||||
ParsingRequirements::empty(),
|
FeatureFlags::empty(),
|
||||||
),
|
),
|
||||||
feature!(
|
feature!(
|
||||||
atom!("device-height"),
|
atom!("device-height"),
|
||||||
AllowsRanges::Yes,
|
AllowsRanges::Yes,
|
||||||
Evaluator::Length(eval_device_height),
|
Evaluator::Length(eval_device_height),
|
||||||
ParsingRequirements::empty(),
|
FeatureFlags::empty(),
|
||||||
),
|
),
|
||||||
feature!(
|
feature!(
|
||||||
atom!("device-aspect-ratio"),
|
atom!("device-aspect-ratio"),
|
||||||
AllowsRanges::Yes,
|
AllowsRanges::Yes,
|
||||||
Evaluator::NumberRatio(eval_device_aspect_ratio),
|
Evaluator::NumberRatio(eval_device_aspect_ratio),
|
||||||
ParsingRequirements::empty(),
|
FeatureFlags::empty(),
|
||||||
),
|
),
|
||||||
feature!(
|
feature!(
|
||||||
atom!("-moz-device-orientation"),
|
atom!("-moz-device-orientation"),
|
||||||
AllowsRanges::No,
|
AllowsRanges::No,
|
||||||
keyword_evaluator!(eval_device_orientation, Orientation),
|
keyword_evaluator!(eval_device_orientation, Orientation),
|
||||||
ParsingRequirements::empty(),
|
FeatureFlags::empty(),
|
||||||
),
|
),
|
||||||
// Webkit extensions that we support for de-facto web compatibility.
|
// Webkit extensions that we support for de-facto web compatibility.
|
||||||
// -webkit-{min|max}-device-pixel-ratio (controlled with its own pref):
|
// -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"),
|
atom!("device-pixel-ratio"),
|
||||||
AllowsRanges::Yes,
|
AllowsRanges::Yes,
|
||||||
Evaluator::Float(eval_device_pixel_ratio),
|
Evaluator::Float(eval_device_pixel_ratio),
|
||||||
ParsingRequirements::WEBKIT_PREFIX,
|
FeatureFlags::WEBKIT_PREFIX,
|
||||||
),
|
),
|
||||||
// -webkit-transform-3d.
|
// -webkit-transform-3d.
|
||||||
feature!(
|
feature!(
|
||||||
atom!("transform-3d"),
|
atom!("transform-3d"),
|
||||||
AllowsRanges::No,
|
AllowsRanges::No,
|
||||||
Evaluator::BoolInteger(eval_transform_3d),
|
Evaluator::BoolInteger(eval_transform_3d),
|
||||||
ParsingRequirements::WEBKIT_PREFIX,
|
FeatureFlags::WEBKIT_PREFIX,
|
||||||
),
|
),
|
||||||
feature!(
|
feature!(
|
||||||
atom!("-moz-device-pixel-ratio"),
|
atom!("-moz-device-pixel-ratio"),
|
||||||
AllowsRanges::Yes,
|
AllowsRanges::Yes,
|
||||||
Evaluator::Float(eval_device_pixel_ratio),
|
Evaluator::Float(eval_device_pixel_ratio),
|
||||||
ParsingRequirements::empty(),
|
FeatureFlags::empty(),
|
||||||
),
|
),
|
||||||
feature!(
|
feature!(
|
||||||
atom!("resolution"),
|
atom!("resolution"),
|
||||||
AllowsRanges::Yes,
|
AllowsRanges::Yes,
|
||||||
Evaluator::Resolution(eval_resolution),
|
Evaluator::Resolution(eval_resolution),
|
||||||
ParsingRequirements::empty(),
|
FeatureFlags::empty(),
|
||||||
),
|
),
|
||||||
feature!(
|
feature!(
|
||||||
atom!("display-mode"),
|
atom!("display-mode"),
|
||||||
AllowsRanges::No,
|
AllowsRanges::No,
|
||||||
keyword_evaluator!(eval_display_mode, DisplayMode),
|
keyword_evaluator!(eval_display_mode, DisplayMode),
|
||||||
ParsingRequirements::empty(),
|
FeatureFlags::empty(),
|
||||||
),
|
),
|
||||||
feature!(
|
feature!(
|
||||||
atom!("grid"),
|
atom!("grid"),
|
||||||
AllowsRanges::No,
|
AllowsRanges::No,
|
||||||
Evaluator::BoolInteger(eval_grid),
|
Evaluator::BoolInteger(eval_grid),
|
||||||
ParsingRequirements::empty(),
|
FeatureFlags::empty(),
|
||||||
),
|
),
|
||||||
feature!(
|
feature!(
|
||||||
atom!("scan"),
|
atom!("scan"),
|
||||||
AllowsRanges::No,
|
AllowsRanges::No,
|
||||||
keyword_evaluator!(eval_scan, Scan),
|
keyword_evaluator!(eval_scan, Scan),
|
||||||
ParsingRequirements::empty(),
|
FeatureFlags::empty(),
|
||||||
),
|
),
|
||||||
feature!(
|
feature!(
|
||||||
atom!("color"),
|
atom!("color"),
|
||||||
AllowsRanges::Yes,
|
AllowsRanges::Yes,
|
||||||
Evaluator::Integer(eval_color),
|
Evaluator::Integer(eval_color),
|
||||||
ParsingRequirements::empty(),
|
FeatureFlags::empty(),
|
||||||
),
|
),
|
||||||
feature!(
|
feature!(
|
||||||
atom!("color-index"),
|
atom!("color-index"),
|
||||||
AllowsRanges::Yes,
|
AllowsRanges::Yes,
|
||||||
Evaluator::Integer(eval_color_index),
|
Evaluator::Integer(eval_color_index),
|
||||||
ParsingRequirements::empty(),
|
FeatureFlags::empty(),
|
||||||
),
|
),
|
||||||
feature!(
|
feature!(
|
||||||
atom!("monochrome"),
|
atom!("monochrome"),
|
||||||
AllowsRanges::Yes,
|
AllowsRanges::Yes,
|
||||||
Evaluator::Integer(eval_monochrome),
|
Evaluator::Integer(eval_monochrome),
|
||||||
ParsingRequirements::empty(),
|
FeatureFlags::empty(),
|
||||||
),
|
),
|
||||||
feature!(
|
feature!(
|
||||||
atom!("prefers-reduced-motion"),
|
atom!("prefers-reduced-motion"),
|
||||||
AllowsRanges::No,
|
AllowsRanges::No,
|
||||||
keyword_evaluator!(eval_prefers_reduced_motion, PrefersReducedMotion),
|
keyword_evaluator!(eval_prefers_reduced_motion, PrefersReducedMotion),
|
||||||
ParsingRequirements::empty(),
|
FeatureFlags::empty(),
|
||||||
),
|
),
|
||||||
feature!(
|
feature!(
|
||||||
atom!("prefers-contrast"),
|
atom!("prefers-contrast"),
|
||||||
|
@ -809,31 +713,49 @@ pub static MEDIA_FEATURES: [MediaFeatureDescription; 58] = [
|
||||||
// layout.css.prefers-contrast.enabled preference. See
|
// layout.css.prefers-contrast.enabled preference. See
|
||||||
// disabed_by_pref in media_feature_expression.rs for how that
|
// disabed_by_pref in media_feature_expression.rs for how that
|
||||||
// is done.
|
// is done.
|
||||||
ParsingRequirements::empty(),
|
FeatureFlags::empty(),
|
||||||
),
|
),
|
||||||
feature!(
|
feature!(
|
||||||
atom!("forced-colors"),
|
atom!("forced-colors"),
|
||||||
AllowsRanges::No,
|
AllowsRanges::No,
|
||||||
keyword_evaluator!(eval_forced_colors, ForcedColors),
|
keyword_evaluator!(eval_forced_colors, ForcedColors),
|
||||||
ParsingRequirements::empty(),
|
FeatureFlags::empty(),
|
||||||
),
|
),
|
||||||
feature!(
|
feature!(
|
||||||
atom!("overflow-block"),
|
atom!("overflow-block"),
|
||||||
AllowsRanges::No,
|
AllowsRanges::No,
|
||||||
keyword_evaluator!(eval_overflow_block, OverflowBlock),
|
keyword_evaluator!(eval_overflow_block, OverflowBlock),
|
||||||
ParsingRequirements::empty(),
|
FeatureFlags::empty(),
|
||||||
),
|
),
|
||||||
feature!(
|
feature!(
|
||||||
atom!("overflow-inline"),
|
atom!("overflow-inline"),
|
||||||
AllowsRanges::No,
|
AllowsRanges::No,
|
||||||
keyword_evaluator!(eval_overflow_inline, OverflowInline),
|
keyword_evaluator!(eval_overflow_inline, OverflowInline),
|
||||||
ParsingRequirements::empty(),
|
FeatureFlags::empty(),
|
||||||
|
),
|
||||||
|
feature!(
|
||||||
|
atom!("update"),
|
||||||
|
AllowsRanges::No,
|
||||||
|
keyword_evaluator!(eval_update, Update),
|
||||||
|
FeatureFlags::empty(),
|
||||||
),
|
),
|
||||||
feature!(
|
feature!(
|
||||||
atom!("prefers-color-scheme"),
|
atom!("prefers-color-scheme"),
|
||||||
AllowsRanges::No,
|
AllowsRanges::No,
|
||||||
keyword_evaluator!(eval_prefers_color_scheme, PrefersColorScheme),
|
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
|
// Evaluates to the preferred color scheme for content. Only useful in
|
||||||
// chrome context, where the chrome color-scheme and the content
|
// 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"),
|
atom!("-moz-content-prefers-color-scheme"),
|
||||||
AllowsRanges::No,
|
AllowsRanges::No,
|
||||||
keyword_evaluator!(eval_content_prefers_color_scheme, PrefersColorScheme),
|
keyword_evaluator!(eval_content_prefers_color_scheme, PrefersColorScheme),
|
||||||
ParsingRequirements::CHROME_AND_UA_ONLY,
|
FeatureFlags::CHROME_AND_UA_ONLY,
|
||||||
),
|
),
|
||||||
feature!(
|
feature!(
|
||||||
atom!("pointer"),
|
atom!("pointer"),
|
||||||
AllowsRanges::No,
|
AllowsRanges::No,
|
||||||
keyword_evaluator!(eval_pointer, Pointer),
|
keyword_evaluator!(eval_pointer, Pointer),
|
||||||
ParsingRequirements::empty(),
|
FeatureFlags::empty(),
|
||||||
),
|
),
|
||||||
feature!(
|
feature!(
|
||||||
atom!("any-pointer"),
|
atom!("any-pointer"),
|
||||||
AllowsRanges::No,
|
AllowsRanges::No,
|
||||||
keyword_evaluator!(eval_any_pointer, Pointer),
|
keyword_evaluator!(eval_any_pointer, Pointer),
|
||||||
ParsingRequirements::empty(),
|
FeatureFlags::empty(),
|
||||||
),
|
),
|
||||||
feature!(
|
feature!(
|
||||||
atom!("hover"),
|
atom!("hover"),
|
||||||
AllowsRanges::No,
|
AllowsRanges::No,
|
||||||
keyword_evaluator!(eval_hover, Hover),
|
keyword_evaluator!(eval_hover, Hover),
|
||||||
ParsingRequirements::empty(),
|
FeatureFlags::empty(),
|
||||||
),
|
),
|
||||||
feature!(
|
feature!(
|
||||||
atom!("any-hover"),
|
atom!("any-hover"),
|
||||||
AllowsRanges::No,
|
AllowsRanges::No,
|
||||||
keyword_evaluator!(eval_any_hover, Hover),
|
keyword_evaluator!(eval_any_hover, Hover),
|
||||||
ParsingRequirements::empty(),
|
FeatureFlags::empty(),
|
||||||
),
|
),
|
||||||
// Internal -moz-is-glyph media feature: applies only inside SVG glyphs.
|
// Internal -moz-is-glyph media feature: applies only inside SVG glyphs.
|
||||||
// Internal because it is really only useful in the user agent anyway
|
// 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"),
|
atom!("-moz-is-glyph"),
|
||||||
AllowsRanges::No,
|
AllowsRanges::No,
|
||||||
Evaluator::BoolInteger(eval_moz_is_glyph),
|
Evaluator::BoolInteger(eval_moz_is_glyph),
|
||||||
ParsingRequirements::CHROME_AND_UA_ONLY,
|
FeatureFlags::CHROME_AND_UA_ONLY,
|
||||||
),
|
),
|
||||||
feature!(
|
feature!(
|
||||||
atom!("-moz-is-resource-document"),
|
atom!("-moz-is-resource-document"),
|
||||||
AllowsRanges::No,
|
AllowsRanges::No,
|
||||||
Evaluator::BoolInteger(eval_moz_is_resource_document),
|
Evaluator::BoolInteger(eval_moz_is_resource_document),
|
||||||
ParsingRequirements::CHROME_AND_UA_ONLY,
|
FeatureFlags::CHROME_AND_UA_ONLY,
|
||||||
),
|
),
|
||||||
feature!(
|
feature!(
|
||||||
atom!("-moz-platform"),
|
atom!("-moz-platform"),
|
||||||
AllowsRanges::No,
|
AllowsRanges::No,
|
||||||
keyword_evaluator!(eval_moz_platform, Platform),
|
keyword_evaluator!(eval_moz_platform, Platform),
|
||||||
ParsingRequirements::CHROME_AND_UA_ONLY,
|
FeatureFlags::CHROME_AND_UA_ONLY,
|
||||||
),
|
),
|
||||||
feature!(
|
feature!(
|
||||||
atom!("-moz-print-preview"),
|
atom!("-moz-print-preview"),
|
||||||
AllowsRanges::No,
|
AllowsRanges::No,
|
||||||
Evaluator::BoolInteger(eval_moz_print_preview),
|
Evaluator::BoolInteger(eval_moz_print_preview),
|
||||||
ParsingRequirements::CHROME_AND_UA_ONLY,
|
FeatureFlags::CHROME_AND_UA_ONLY,
|
||||||
),
|
),
|
||||||
feature!(
|
feature!(
|
||||||
atom!("-moz-non-native-content-theme"),
|
atom!("-moz-non-native-content-theme"),
|
||||||
AllowsRanges::No,
|
AllowsRanges::No,
|
||||||
Evaluator::BoolInteger(eval_moz_non_native_content_theme),
|
Evaluator::BoolInteger(eval_moz_non_native_content_theme),
|
||||||
ParsingRequirements::CHROME_AND_UA_ONLY,
|
FeatureFlags::CHROME_AND_UA_ONLY,
|
||||||
),
|
),
|
||||||
feature!(
|
feature!(
|
||||||
atom!("-moz-windows-non-native-menus"),
|
atom!("-moz-windows-non-native-menus"),
|
||||||
AllowsRanges::No,
|
AllowsRanges::No,
|
||||||
Evaluator::BoolInteger(eval_moz_windows_non_native_menus),
|
Evaluator::BoolInteger(eval_moz_windows_non_native_menus),
|
||||||
ParsingRequirements::CHROME_AND_UA_ONLY,
|
FeatureFlags::CHROME_AND_UA_ONLY,
|
||||||
),
|
),
|
||||||
feature!(
|
feature!(
|
||||||
atom!("-moz-overlay-scrollbars"),
|
atom!("-moz-overlay-scrollbars"),
|
||||||
AllowsRanges::No,
|
AllowsRanges::No,
|
||||||
Evaluator::BoolInteger(eval_moz_overlay_scrollbars),
|
Evaluator::BoolInteger(eval_moz_overlay_scrollbars),
|
||||||
ParsingRequirements::CHROME_AND_UA_ONLY,
|
FeatureFlags::CHROME_AND_UA_ONLY,
|
||||||
),
|
),
|
||||||
lnf_int_feature!(
|
lnf_int_feature!(
|
||||||
atom!("-moz-scrollbar-start-backward"),
|
atom!("-moz-scrollbar-start-backward"),
|
||||||
|
@ -933,10 +855,6 @@ pub static MEDIA_FEATURES: [MediaFeatureDescription; 58] = [
|
||||||
ScrollArrowStyle,
|
ScrollArrowStyle,
|
||||||
get_scrollbar_end_forward
|
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-menubar-drag"), MenuBarDrag),
|
||||||
lnf_int_feature!(atom!("-moz-windows-default-theme"), WindowsDefaultTheme),
|
lnf_int_feature!(atom!("-moz-windows-default-theme"), WindowsDefaultTheme),
|
||||||
lnf_int_feature!(atom!("-moz-mac-graphite-theme"), MacGraphiteTheme),
|
lnf_int_feature!(atom!("-moz-mac-graphite-theme"), MacGraphiteTheme),
|
||||||
|
@ -959,8 +877,4 @@ pub static MEDIA_FEATURES: [MediaFeatureDescription; 58] = [
|
||||||
GTKCSDReversedPlacement
|
GTKCSDReversedPlacement
|
||||||
),
|
),
|
||||||
lnf_int_feature!(atom!("-moz-system-dark-theme"), SystemUsesDarkTheme),
|
lnf_int_feature!(atom!("-moz-system-dark-theme"), SystemUsesDarkTheme),
|
||||||
bool_pref_feature!(
|
|
||||||
atom!("-moz-proton-places-tooltip"),
|
|
||||||
"browser.proton.places-tooltip.enabled"
|
|
||||||
),
|
|
||||||
];
|
];
|
||||||
|
|
|
@ -17,6 +17,7 @@ use crate::values::computed::font::GenericFontFamily;
|
||||||
use crate::values::computed::{ColorScheme, Length};
|
use crate::values::computed::{ColorScheme, Length};
|
||||||
use crate::values::specified::color::SystemColor;
|
use crate::values::specified::color::SystemColor;
|
||||||
use crate::values::specified::font::FONT_MEDIUM_PX;
|
use crate::values::specified::font::FONT_MEDIUM_PX;
|
||||||
|
use crate::values::specified::ViewportVariant;
|
||||||
use crate::values::{CustomIdent, KeyframesName};
|
use crate::values::{CustomIdent, KeyframesName};
|
||||||
use app_units::{Au, AU_PER_PX};
|
use app_units::{Au, AU_PER_PX};
|
||||||
use cssparser::RGBA;
|
use cssparser::RGBA;
|
||||||
|
@ -58,6 +59,9 @@ pub struct Device {
|
||||||
/// Whether any styles computed in the document relied on the viewport size
|
/// Whether any styles computed in the document relied on the viewport size
|
||||||
/// by using vw/vh/vmin/vmax units.
|
/// by using vw/vh/vmin/vmax units.
|
||||||
used_viewport_size: AtomicBool,
|
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
|
/// The CssEnvironment object responsible of getting CSS environment
|
||||||
/// variables.
|
/// variables.
|
||||||
environment: CssEnvironment,
|
environment: CssEnvironment,
|
||||||
|
@ -100,6 +104,7 @@ impl Device {
|
||||||
used_root_font_size: AtomicBool::new(false),
|
used_root_font_size: AtomicBool::new(false),
|
||||||
used_font_metrics: AtomicBool::new(false),
|
used_font_metrics: AtomicBool::new(false),
|
||||||
used_viewport_size: AtomicBool::new(false),
|
used_viewport_size: AtomicBool::new(false),
|
||||||
|
used_dynamic_viewport_size: AtomicBool::new(false),
|
||||||
environment: CssEnvironment,
|
environment: CssEnvironment,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -267,6 +272,8 @@ impl Device {
|
||||||
self.used_root_font_size.store(false, Ordering::Relaxed);
|
self.used_root_font_size.store(false, Ordering::Relaxed);
|
||||||
self.used_font_metrics.store(false, Ordering::Relaxed);
|
self.used_font_metrics.store(false, Ordering::Relaxed);
|
||||||
self.used_viewport_size.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.
|
/// 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
|
/// Returns the current viewport size in app units, recording that it's been
|
||||||
/// used for viewport unit resolution.
|
/// used for viewport unit resolution.
|
||||||
pub fn au_viewport_size_for_viewport_unit_resolution(&self) -> Size2D<Au> {
|
pub fn au_viewport_size_for_viewport_unit_resolution(
|
||||||
|
&self,
|
||||||
|
variant: ViewportVariant,
|
||||||
|
) -> Size2D<Au> {
|
||||||
self.used_viewport_size.store(true, Ordering::Relaxed);
|
self.used_viewport_size.store(true, Ordering::Relaxed);
|
||||||
let pc = match self.pres_context() {
|
let pc = match self.pres_context() {
|
||||||
Some(pc) => pc,
|
Some(pc) => pc,
|
||||||
|
@ -348,8 +358,42 @@ impl Device {
|
||||||
return self.page_size_minus_default_margin(pc);
|
return self.page_size_minus_default_margin(pc);
|
||||||
}
|
}
|
||||||
|
|
||||||
let size = &pc.mSizeForViewportUnits;
|
match variant {
|
||||||
Size2D::new(Au(size.width), Au(size.height))
|
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.
|
/// 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)
|
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.
|
/// Returns whether font metrics have been queried.
|
||||||
pub fn used_font_metrics(&self) -> bool {
|
pub fn used_font_metrics(&self) -> bool {
|
||||||
self.used_font_metrics.load(Ordering::Relaxed)
|
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.
|
/// Returns the device pixel ratio.
|
||||||
pub fn device_pixel_ratio(&self) -> Scale<f32, CSSPixel, DevicePixel> {
|
pub fn device_pixel_ratio(&self) -> Scale<f32, CSSPixel, DevicePixel> {
|
||||||
let pc = match self.pres_context() {
|
let pc = match self.pres_context() {
|
||||||
|
|
|
@ -54,7 +54,7 @@ macro_rules! apply_non_ts_list {
|
||||||
("-moz-styleeditor-transitioning", MozStyleeditorTransitioning, IN_STYLEEDITOR_TRANSITIONING_STATE, PSEUDO_CLASS_ENABLED_IN_UA_SHEETS),
|
("-moz-styleeditor-transitioning", MozStyleeditorTransitioning, IN_STYLEEDITOR_TRANSITIONING_STATE, PSEUDO_CLASS_ENABLED_IN_UA_SHEETS),
|
||||||
("fullscreen", Fullscreen, IN_FULLSCREEN_STATE, _),
|
("fullscreen", Fullscreen, IN_FULLSCREEN_STATE, _),
|
||||||
("-moz-modal-dialog", MozModalDialog, IN_MODAL_DIALOG_STATE, PSEUDO_CLASS_ENABLED_IN_UA_SHEETS),
|
("-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-broken", MozBroken, IN_BROKEN_STATE, _),
|
||||||
("-moz-loading", MozLoading, IN_LOADING_STATE, _),
|
("-moz-loading", MozLoading, IN_LOADING_STATE, _),
|
||||||
("-moz-has-dir-attr", MozHasDirAttr, IN_HAS_DIR_ATTR_STATE, PSEUDO_CLASS_ENABLED_IN_UA_SHEETS),
|
("-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-is-html", MozIsHTML, _, _),
|
||||||
("-moz-placeholder", MozPlaceholder, _, _),
|
("-moz-placeholder", MozPlaceholder, _, _),
|
||||||
("-moz-lwtheme", MozLWTheme, _, PSEUDO_CLASS_ENABLED_IN_UA_SHEETS_AND_CHROME),
|
("-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, _, _),
|
("-moz-window-inactive", MozWindowInactive, _, _),
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
|
@ -139,15 +139,6 @@ impl NonTSPseudoClass {
|
||||||
/// Returns whether the pseudo-class is enabled in content sheets.
|
/// Returns whether the pseudo-class is enabled in content sheets.
|
||||||
#[inline]
|
#[inline]
|
||||||
fn is_enabled_in_content(&self) -> bool {
|
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)
|
!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::MozWindowInactive => DocumentState::WINDOW_INACTIVE,
|
||||||
NonTSPseudoClass::MozLWTheme => DocumentState::LWTHEME,
|
NonTSPseudoClass::MozLWTheme => DocumentState::LWTHEME,
|
||||||
NonTSPseudoClass::MozLWThemeBrightText => DocumentState::LWTHEME_BRIGHTTEXT,
|
|
||||||
NonTSPseudoClass::MozLWThemeDarkText => DocumentState::LWTHEME_DARKTEXT,
|
|
||||||
_ => DocumentState::empty(),
|
_ => DocumentState::empty(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -208,15 +197,13 @@ impl NonTSPseudoClass {
|
||||||
NonTSPseudoClass::MozNativeAnonymous |
|
NonTSPseudoClass::MozNativeAnonymous |
|
||||||
// :-moz-placeholder is parsed but never matches.
|
// :-moz-placeholder is parsed but never matches.
|
||||||
NonTSPseudoClass::MozPlaceholder |
|
NonTSPseudoClass::MozPlaceholder |
|
||||||
// :-moz-locale-dir and :-moz-window-inactive depend only on
|
// :-moz-lwtheme, :-moz-locale-dir and
|
||||||
// the state of the document, which is invariant across all
|
// :-moz-window-inactive depend only on the state of the
|
||||||
// the elements involved in a given style cache.
|
// document, which is invariant across all the elements
|
||||||
NonTSPseudoClass::MozLocaleDir(_) |
|
// involved in a given style cache.
|
||||||
NonTSPseudoClass::MozWindowInactive |
|
|
||||||
// Similar for the document themes.
|
|
||||||
NonTSPseudoClass::MozLWTheme |
|
NonTSPseudoClass::MozLWTheme |
|
||||||
NonTSPseudoClass::MozLWThemeBrightText |
|
NonTSPseudoClass::MozLocaleDir(_) |
|
||||||
NonTSPseudoClass::MozLWThemeDarkText
|
NonTSPseudoClass::MozWindowInactive
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -458,3 +445,9 @@ unsafe impl HasFFI for SelectorList<SelectorImpl> {
|
||||||
}
|
}
|
||||||
unsafe impl HasSimpleFFI for SelectorList<SelectorImpl> {}
|
unsafe impl HasSimpleFFI for SelectorList<SelectorImpl> {}
|
||||||
unsafe impl HasBoxFFI for SelectorList<SelectorImpl> {}
|
unsafe impl HasBoxFFI for SelectorList<SelectorImpl> {}
|
||||||
|
|
||||||
|
// Selector and component sizes are important for matching performance.
|
||||||
|
size_of_test!(selectors::parser::Selector<SelectorImpl>, 8);
|
||||||
|
size_of_test!(selectors::parser::Component<SelectorImpl>, 24);
|
||||||
|
size_of_test!(PseudoElement, 16);
|
||||||
|
size_of_test!(NonTSPseudoClass, 16);
|
||||||
|
|
|
@ -44,14 +44,14 @@ unsafe fn get_class_or_part_from_attr(attr: &structs::nsAttrValue) -> Class {
|
||||||
structs::nsAttrValue_ValueType_eAtomArray
|
structs::nsAttrValue_ValueType_eAtomArray
|
||||||
);
|
);
|
||||||
// NOTE: Bindgen doesn't deal with AutoTArray, so cast it below.
|
// NOTE: Bindgen doesn't deal with AutoTArray, so cast it below.
|
||||||
let array: *mut u8 = *(*container)
|
let attr_array: *mut _ = *(*container)
|
||||||
.__bindgen_anon_1
|
.__bindgen_anon_1
|
||||||
.mValue
|
.mValue
|
||||||
.as_ref()
|
.as_ref()
|
||||||
.__bindgen_anon_1
|
.__bindgen_anon_1
|
||||||
.mAtomArray
|
.mAtomArray
|
||||||
.as_ref();
|
.as_ref();
|
||||||
let array = array as *const structs::nsTArray<structs::RefPtr<nsAtom>>;
|
let array = (*attr_array).mArray.as_ptr() as *const structs::nsTArray<structs::RefPtr<nsAtom>>;
|
||||||
return Class::More(&**array);
|
return Class::More(&**array);
|
||||||
}
|
}
|
||||||
debug_assert_eq!(base_type, structs::nsAttrValue_ValueBaseType_eStringBase);
|
debug_assert_eq!(base_type, structs::nsAttrValue_ValueBaseType_eStringBase);
|
||||||
|
|
|
@ -66,6 +66,8 @@ use crate::stylist::CascadeData;
|
||||||
use crate::values::{AtomIdent, AtomString};
|
use crate::values::{AtomIdent, AtomString};
|
||||||
use crate::CaseSensitivityExt;
|
use crate::CaseSensitivityExt;
|
||||||
use crate::LocalName;
|
use crate::LocalName;
|
||||||
|
use app_units::Au;
|
||||||
|
use euclid::default::Size2D;
|
||||||
use atomic_refcell::{AtomicRef, AtomicRefCell, AtomicRefMut};
|
use atomic_refcell::{AtomicRef, AtomicRefCell, AtomicRefMut};
|
||||||
use fxhash::FxHashMap;
|
use fxhash::FxHashMap;
|
||||||
use selectors::attr::{AttrSelectorOperation, AttrSelectorOperator};
|
use selectors::attr::{AttrSelectorOperation, AttrSelectorOperator};
|
||||||
|
@ -321,6 +323,11 @@ impl<'ln> GeckoNode<'ln> {
|
||||||
self.flags() & (NODE_IS_IN_SHADOW_TREE as u32) != 0
|
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.
|
/// WARNING: This logic is duplicated in Gecko's FlattenedTreeParentIsParent.
|
||||||
/// Make sure to mirror any modifications in both places.
|
/// Make sure to mirror any modifications in both places.
|
||||||
#[inline]
|
#[inline]
|
||||||
|
@ -1030,6 +1037,21 @@ impl<'le> TElement for GeckoElement<'le> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn primary_box_size(&self) -> Size2D<Au> {
|
||||||
|
if !self.as_node().is_connected() {
|
||||||
|
return Size2D::zero();
|
||||||
|
}
|
||||||
|
|
||||||
|
unsafe {
|
||||||
|
let frame = self.0._base._base._base.__bindgen_anon_1.mPrimaryFrame.as_ref();
|
||||||
|
if frame.is_null() {
|
||||||
|
return Size2D::zero();
|
||||||
|
}
|
||||||
|
Size2D::new(Au((**frame).mRect.width), Au((**frame).mRect.height))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Return the list of slotted nodes of this node.
|
/// Return the list of slotted nodes of this node.
|
||||||
#[inline]
|
#[inline]
|
||||||
fn slotted_nodes(&self) -> &[Self::ConcreteNode] {
|
fn slotted_nodes(&self) -> &[Self::ConcreteNode] {
|
||||||
|
@ -1393,16 +1415,6 @@ impl<'le> TElement for GeckoElement<'le> {
|
||||||
self.is_root_of_native_anonymous_subtree()
|
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]
|
#[inline]
|
||||||
fn may_have_animations(&self) -> bool {
|
fn may_have_animations(&self) -> bool {
|
||||||
if let Some(pseudo) = self.implemented_pseudo_element() {
|
if let Some(pseudo) = self.implemented_pseudo_element() {
|
||||||
|
@ -1501,7 +1513,7 @@ impl<'le> TElement for GeckoElement<'le> {
|
||||||
) -> bool {
|
) -> bool {
|
||||||
use crate::properties::LonghandIdSet;
|
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 existing_transitions = self.css_transitions_info();
|
||||||
let mut transitions_to_keep = LonghandIdSet::new();
|
let mut transitions_to_keep = LonghandIdSet::new();
|
||||||
for transition_property in after_change_style.transition_properties() {
|
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);
|
transitions_to_keep.insert(physical_longhand);
|
||||||
if self.needs_transitions_update_per_property(
|
if self.needs_transitions_update_per_property(
|
||||||
physical_longhand,
|
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,
|
before_change_style,
|
||||||
after_change_style,
|
after_change_style,
|
||||||
&existing_transitions,
|
&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_lang::SpecifiedValue as SpecifiedLang;
|
||||||
use crate::properties::longhands::_x_text_zoom::SpecifiedValue as SpecifiedZoom;
|
use crate::properties::longhands::_x_text_zoom::SpecifiedValue as SpecifiedZoom;
|
||||||
use crate::properties::longhands::color::SpecifiedValue as SpecifiedColor;
|
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::stylesheets::layer_rule::LayerOrder;
|
||||||
use crate::values::specified::color::Color;
|
use crate::values::specified::color::Color;
|
||||||
lazy_static! {
|
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 = {
|
static ref TABLE_COLOR_RULE: ApplicableDeclarationBlock = {
|
||||||
let global_style_data = &*GLOBAL_STYLE_DATA;
|
let global_style_data = &*GLOBAL_STYLE_DATA;
|
||||||
let pdb = PropertyDeclarationBlock::with_one(
|
let pdb = PropertyDeclarationBlock::with_one(
|
||||||
|
@ -1654,9 +1652,7 @@ impl<'le> TElement for GeckoElement<'le> {
|
||||||
let ns = self.namespace_id();
|
let ns = self.namespace_id();
|
||||||
// <th> elements get a default MozCenterOrInherit which may get overridden
|
// <th> elements get a default MozCenterOrInherit which may get overridden
|
||||||
if ns == structs::kNameSpaceID_XHTML as i32 {
|
if ns == structs::kNameSpaceID_XHTML as i32 {
|
||||||
if self.local_name().as_ptr() == atom!("th").as_ptr() {
|
if self.local_name().as_ptr() == atom!("table").as_ptr() &&
|
||||||
hints.push(TH_RULE.clone());
|
|
||||||
} else if self.local_name().as_ptr() == atom!("table").as_ptr() &&
|
|
||||||
self.as_node().owner_doc().quirks_mode() == QuirksMode::Quirks
|
self.as_node().owner_doc().quirks_mode() == QuirksMode::Quirks
|
||||||
{
|
{
|
||||||
hints.push(TABLE_COLOR_RULE.clone());
|
hints.push(TABLE_COLOR_RULE.clone());
|
||||||
|
@ -1848,6 +1844,11 @@ impl<'le> ::selectors::Element for GeckoElement<'le> {
|
||||||
None
|
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(
|
fn attr_matches(
|
||||||
&self,
|
&self,
|
||||||
ns: &NamespaceConstraint<&Namespace>,
|
ns: &NamespaceConstraint<&Namespace>,
|
||||||
|
@ -1960,15 +1961,11 @@ impl<'le> ::selectors::Element for GeckoElement<'le> {
|
||||||
self.local_name() == other.local_name() && self.namespace() == other.namespace()
|
self.local_name() == other.local_name() && self.namespace() == other.namespace()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn match_non_ts_pseudo_class<F>(
|
fn match_non_ts_pseudo_class(
|
||||||
&self,
|
&self,
|
||||||
pseudo_class: &NonTSPseudoClass,
|
pseudo_class: &NonTSPseudoClass,
|
||||||
context: &mut MatchingContext<Self::Impl>,
|
context: &mut MatchingContext<Self::Impl>,
|
||||||
flags_setter: &mut F,
|
) -> bool {
|
||||||
) -> bool
|
|
||||||
where
|
|
||||||
F: FnMut(&Self, ElementSelectorFlags),
|
|
||||||
{
|
|
||||||
use selectors::matching::*;
|
use selectors::matching::*;
|
||||||
match *pseudo_class {
|
match *pseudo_class {
|
||||||
NonTSPseudoClass::Autofill |
|
NonTSPseudoClass::Autofill |
|
||||||
|
@ -2009,7 +2006,7 @@ impl<'le> ::selectors::Element for GeckoElement<'le> {
|
||||||
NonTSPseudoClass::MozDirAttrRTL |
|
NonTSPseudoClass::MozDirAttrRTL |
|
||||||
NonTSPseudoClass::MozDirAttrLikeAuto |
|
NonTSPseudoClass::MozDirAttrLikeAuto |
|
||||||
NonTSPseudoClass::MozModalDialog |
|
NonTSPseudoClass::MozModalDialog |
|
||||||
NonTSPseudoClass::MozTopmostModalDialog |
|
NonTSPseudoClass::MozTopmostModal |
|
||||||
NonTSPseudoClass::Active |
|
NonTSPseudoClass::Active |
|
||||||
NonTSPseudoClass::Hover |
|
NonTSPseudoClass::Hover |
|
||||||
NonTSPseudoClass::MozAutofillPreview |
|
NonTSPseudoClass::MozAutofillPreview |
|
||||||
|
@ -2024,7 +2021,9 @@ impl<'le> ::selectors::Element for GeckoElement<'le> {
|
||||||
self.is_link() && context.visited_handling().matches_visited()
|
self.is_link() && context.visited_handling().matches_visited()
|
||||||
},
|
},
|
||||||
NonTSPseudoClass::MozFirstNode => {
|
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();
|
let mut elem = self.as_node();
|
||||||
while let Some(prev) = elem.prev_sibling() {
|
while let Some(prev) = elem.prev_sibling() {
|
||||||
if prev.contains_non_whitespace_content() {
|
if prev.contains_non_whitespace_content() {
|
||||||
|
@ -2035,7 +2034,9 @@ impl<'le> ::selectors::Element for GeckoElement<'le> {
|
||||||
true
|
true
|
||||||
},
|
},
|
||||||
NonTSPseudoClass::MozLastNode => {
|
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();
|
let mut elem = self.as_node();
|
||||||
while let Some(next) = elem.next_sibling() {
|
while let Some(next) = elem.next_sibling() {
|
||||||
if next.contains_non_whitespace_content() {
|
if next.contains_non_whitespace_content() {
|
||||||
|
@ -2046,7 +2047,9 @@ impl<'le> ::selectors::Element for GeckoElement<'le> {
|
||||||
true
|
true
|
||||||
},
|
},
|
||||||
NonTSPseudoClass::MozOnlyWhitespace => {
|
NonTSPseudoClass::MozOnlyWhitespace => {
|
||||||
flags_setter(self, ElementSelectorFlags::HAS_EMPTY_SELECTOR);
|
if context.needs_selector_flags() {
|
||||||
|
self.apply_selector_flags(ElementSelectorFlags::HAS_EMPTY_SELECTOR);
|
||||||
|
}
|
||||||
if self
|
if self
|
||||||
.as_node()
|
.as_node()
|
||||||
.dom_children()
|
.dom_children()
|
||||||
|
@ -2068,8 +2071,6 @@ impl<'le> ::selectors::Element for GeckoElement<'le> {
|
||||||
NonTSPseudoClass::MozIsHTML => self.is_html_element_in_html_document(),
|
NonTSPseudoClass::MozIsHTML => self.is_html_element_in_html_document(),
|
||||||
|
|
||||||
NonTSPseudoClass::MozLWTheme |
|
NonTSPseudoClass::MozLWTheme |
|
||||||
NonTSPseudoClass::MozLWThemeBrightText |
|
|
||||||
NonTSPseudoClass::MozLWThemeDarkText |
|
|
||||||
NonTSPseudoClass::MozLocaleDir(..) |
|
NonTSPseudoClass::MozLocaleDir(..) |
|
||||||
NonTSPseudoClass::MozWindowInactive => {
|
NonTSPseudoClass::MozWindowInactive => {
|
||||||
let state_bit = pseudo_class.document_state_flag();
|
let state_bit = pseudo_class.document_state_flag();
|
||||||
|
|
|
@ -12,11 +12,11 @@ use crate::shared_lock::SharedRwLock;
|
||||||
use crate::thread_state;
|
use crate::thread_state;
|
||||||
#[cfg(feature = "gecko")]
|
#[cfg(feature = "gecko")]
|
||||||
use gecko_profiler;
|
use gecko_profiler;
|
||||||
use parking_lot::{RwLock, RwLockReadGuard};
|
use parking_lot::{Mutex, RwLock, RwLockReadGuard};
|
||||||
use rayon;
|
use rayon;
|
||||||
use std::env;
|
use std::env;
|
||||||
use std::sync::atomic::{AtomicUsize, Ordering};
|
use std::io;
|
||||||
use std::sync::Mutex;
|
use std::thread;
|
||||||
|
|
||||||
/// Global style data
|
/// Global style data
|
||||||
pub struct GlobalStyleData {
|
pub struct GlobalStyleData {
|
||||||
|
@ -43,12 +43,32 @@ fn thread_name(index: usize) -> String {
|
||||||
format!("Style#{}", index)
|
format!("Style#{}", index)
|
||||||
}
|
}
|
||||||
|
|
||||||
// A counter so that we can wait for shutdown of all threads. See
|
lazy_static! {
|
||||||
// StyleThreadPool::shutdown.
|
/// JoinHandles for spawned style threads. These will be joined during
|
||||||
static ALIVE_WORKER_THREADS: AtomicUsize = AtomicUsize::new(0);
|
/// StyleThreadPool::shutdown() after exiting the thread pool.
|
||||||
|
///
|
||||||
|
/// This would be quite inefficient if rayon destroyed and re-created
|
||||||
|
/// threads regularly during threadpool operation in response to demand,
|
||||||
|
/// however rayon actually never destroys its threads until the entire
|
||||||
|
/// thread pool is shut-down, so the size of this list is bounded.
|
||||||
|
static ref STYLE_THREAD_JOIN_HANDLES: Mutex<Vec<thread::JoinHandle<()>>> =
|
||||||
|
Mutex::new(Vec::new());
|
||||||
|
}
|
||||||
|
|
||||||
|
fn thread_spawn(options: rayon::ThreadBuilder) -> io::Result<()> {
|
||||||
|
let mut b = thread::Builder::new();
|
||||||
|
if let Some(name) = options.name() {
|
||||||
|
b = b.name(name.to_owned());
|
||||||
|
}
|
||||||
|
if let Some(stack_size) = options.stack_size() {
|
||||||
|
b = b.stack_size(stack_size);
|
||||||
|
}
|
||||||
|
let join_handle = b.spawn(|| options.run())?;
|
||||||
|
STYLE_THREAD_JOIN_HANDLES.lock().push(join_handle);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
fn thread_startup(_index: usize) {
|
fn thread_startup(_index: usize) {
|
||||||
ALIVE_WORKER_THREADS.fetch_add(1, Ordering::Relaxed);
|
|
||||||
thread_state::initialize_layout_worker_thread();
|
thread_state::initialize_layout_worker_thread();
|
||||||
#[cfg(feature = "gecko")]
|
#[cfg(feature = "gecko")]
|
||||||
unsafe {
|
unsafe {
|
||||||
|
@ -64,33 +84,24 @@ fn thread_shutdown(_: usize) {
|
||||||
gecko_profiler::unregister_thread();
|
gecko_profiler::unregister_thread();
|
||||||
bindings::Gecko_SetJemallocThreadLocalArena(false);
|
bindings::Gecko_SetJemallocThreadLocalArena(false);
|
||||||
}
|
}
|
||||||
ALIVE_WORKER_THREADS.fetch_sub(1, Ordering::Relaxed);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl StyleThreadPool {
|
impl StyleThreadPool {
|
||||||
/// Shuts down the thread pool, waiting for all work to complete.
|
/// Shuts down the thread pool, waiting for all work to complete.
|
||||||
pub fn shutdown() {
|
pub fn shutdown() {
|
||||||
if ALIVE_WORKER_THREADS.load(Ordering::Relaxed) == 0 {
|
if STYLE_THREAD_JOIN_HANDLES.lock().is_empty() {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
{
|
{
|
||||||
// Drop the pool.
|
// Drop the pool.
|
||||||
let _ = STYLE_THREAD_POOL.lock().unwrap().style_thread_pool.write().take();
|
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
|
// Join spawned threads until all of the threads have been joined. This
|
||||||
// running.
|
// 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
|
while let Some(join_handle) = STYLE_THREAD_JOIN_HANDLES.lock().pop() {
|
||||||
// destructors running on the worker threads. For that we'd need help
|
let _ = join_handle.join();
|
||||||
// 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();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -105,7 +116,7 @@ impl StyleThreadPool {
|
||||||
|
|
||||||
lazy_static! {
|
lazy_static! {
|
||||||
/// Global thread pool
|
/// Global thread pool
|
||||||
pub static ref STYLE_THREAD_POOL: Mutex<StyleThreadPool> = {
|
pub static ref STYLE_THREAD_POOL: std::sync::Mutex<StyleThreadPool> = {
|
||||||
let stylo_threads = env::var("STYLO_THREADS")
|
let stylo_threads = env::var("STYLO_THREADS")
|
||||||
.map(|s| s.parse::<usize>().expect("invalid STYLO_THREADS value"));
|
.map(|s| s.parse::<usize>().expect("invalid STYLO_THREADS value"));
|
||||||
let mut num_threads = match stylo_threads {
|
let mut num_threads = match stylo_threads {
|
||||||
|
@ -149,6 +160,7 @@ lazy_static! {
|
||||||
None
|
None
|
||||||
} else {
|
} else {
|
||||||
let workers = rayon::ThreadPoolBuilder::new()
|
let workers = rayon::ThreadPoolBuilder::new()
|
||||||
|
.spawn_handler(thread_spawn)
|
||||||
.num_threads(num_threads)
|
.num_threads(num_threads)
|
||||||
.thread_name(thread_name)
|
.thread_name(thread_name)
|
||||||
.start_handler(thread_startup)
|
.start_handler(thread_startup)
|
||||||
|
@ -158,7 +170,7 @@ lazy_static! {
|
||||||
workers.ok()
|
workers.ok()
|
||||||
};
|
};
|
||||||
|
|
||||||
Mutex::new(StyleThreadPool {
|
std::sync::Mutex::new(StyleThreadPool {
|
||||||
num_threads: if num_threads > 0 {
|
num_threads: if num_threads > 0 {
|
||||||
Some(num_threads)
|
Some(num_threads)
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -11,7 +11,7 @@ use crate::invalidation::element::invalidator::{DescendantInvalidationLists, Inv
|
||||||
use crate::invalidation::element::invalidator::{Invalidation, InvalidationProcessor};
|
use crate::invalidation::element::invalidator::{Invalidation, InvalidationProcessor};
|
||||||
use crate::invalidation::element::state_and_attributes;
|
use crate::invalidation::element::state_and_attributes;
|
||||||
use crate::stylist::CascadeData;
|
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
|
/// A struct holding the members necessary to invalidate document state
|
||||||
/// selectors.
|
/// selectors.
|
||||||
|
@ -47,6 +47,7 @@ impl<'a, E: TElement, I> DocumentStateInvalidationProcessor<'a, E, I> {
|
||||||
None,
|
None,
|
||||||
VisitedHandlingMode::AllLinksVisitedAndUnvisited,
|
VisitedHandlingMode::AllLinksVisitedAndUnvisited,
|
||||||
quirks_mode,
|
quirks_mode,
|
||||||
|
NeedsSelectorFlags::No,
|
||||||
);
|
);
|
||||||
|
|
||||||
matching_context.extra_data = InvalidationMatchingData {
|
matching_context.extra_data = InvalidationMatchingData {
|
||||||
|
|
|
@ -166,15 +166,11 @@ where
|
||||||
{
|
{
|
||||||
type Impl = SelectorImpl;
|
type Impl = SelectorImpl;
|
||||||
|
|
||||||
fn match_non_ts_pseudo_class<F>(
|
fn match_non_ts_pseudo_class(
|
||||||
&self,
|
&self,
|
||||||
pseudo_class: &NonTSPseudoClass,
|
pseudo_class: &NonTSPseudoClass,
|
||||||
context: &mut MatchingContext<Self::Impl>,
|
context: &mut MatchingContext<Self::Impl>,
|
||||||
_setter: &mut F,
|
) -> bool {
|
||||||
) -> bool
|
|
||||||
where
|
|
||||||
F: FnMut(&Self, ElementSelectorFlags),
|
|
||||||
{
|
|
||||||
// Some pseudo-classes need special handling to evaluate them against
|
// Some pseudo-classes need special handling to evaluate them against
|
||||||
// the snapshot.
|
// the snapshot.
|
||||||
match *pseudo_class {
|
match *pseudo_class {
|
||||||
|
@ -232,16 +228,20 @@ where
|
||||||
if flag.is_empty() {
|
if flag.is_empty() {
|
||||||
return self
|
return self
|
||||||
.element
|
.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()) {
|
match self.snapshot().and_then(|s| s.state()) {
|
||||||
Some(snapshot_state) => snapshot_state.intersects(flag),
|
Some(snapshot_state) => snapshot_state.intersects(flag),
|
||||||
None => self
|
None => self
|
||||||
.element
|
.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(
|
fn match_pseudo_element(
|
||||||
&self,
|
&self,
|
||||||
pseudo_element: &PseudoElement,
|
pseudo_element: &PseudoElement,
|
||||||
|
|
|
@ -67,6 +67,8 @@ pub struct Dependency {
|
||||||
pub parent: Option<Box<Dependency>>,
|
pub parent: Option<Box<Dependency>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
size_of_test!(Dependency, 24);
|
||||||
|
|
||||||
/// The kind of elements down the tree this dependency may affect.
|
/// The kind of elements down the tree this dependency may affect.
|
||||||
#[derive(Debug, Eq, PartialEq)]
|
#[derive(Debug, Eq, PartialEq)]
|
||||||
pub enum DependencyInvalidationKind {
|
pub enum DependencyInvalidationKind {
|
||||||
|
|
|
@ -19,8 +19,7 @@ use crate::selector_parser::Snapshot;
|
||||||
use crate::stylesheets::origin::OriginSet;
|
use crate::stylesheets::origin::OriginSet;
|
||||||
use crate::{Atom, WeakAtom};
|
use crate::{Atom, WeakAtom};
|
||||||
use selectors::attr::CaseSensitivity;
|
use selectors::attr::CaseSensitivity;
|
||||||
use selectors::matching::matches_selector;
|
use selectors::matching::{matches_selector, MatchingContext, MatchingMode, VisitedHandlingMode, NeedsSelectorFlags};
|
||||||
use selectors::matching::{MatchingContext, MatchingMode, VisitedHandlingMode};
|
|
||||||
use selectors::NthIndexCache;
|
use selectors::NthIndexCache;
|
||||||
use smallvec::SmallVec;
|
use smallvec::SmallVec;
|
||||||
|
|
||||||
|
@ -67,6 +66,7 @@ impl<'a, 'b: 'a, E: TElement + 'b> StateAndAttrInvalidationProcessor<'a, 'b, E>
|
||||||
Some(nth_index_cache),
|
Some(nth_index_cache),
|
||||||
VisitedHandlingMode::AllLinksVisitedAndUnvisited,
|
VisitedHandlingMode::AllLinksVisitedAndUnvisited,
|
||||||
shared_context.quirks_mode(),
|
shared_context.quirks_mode(),
|
||||||
|
NeedsSelectorFlags::No,
|
||||||
);
|
);
|
||||||
|
|
||||||
Self {
|
Self {
|
||||||
|
@ -84,7 +84,7 @@ pub fn check_dependency<E, W>(
|
||||||
dependency: &Dependency,
|
dependency: &Dependency,
|
||||||
element: &E,
|
element: &E,
|
||||||
wrapper: &W,
|
wrapper: &W,
|
||||||
mut context: &mut MatchingContext<'_, E::Impl>,
|
context: &mut MatchingContext<'_, E::Impl>,
|
||||||
) -> bool
|
) -> bool
|
||||||
where
|
where
|
||||||
E: TElement,
|
E: TElement,
|
||||||
|
@ -95,8 +95,7 @@ where
|
||||||
dependency.selector_offset,
|
dependency.selector_offset,
|
||||||
None,
|
None,
|
||||||
element,
|
element,
|
||||||
&mut context,
|
context,
|
||||||
&mut |_, _| {},
|
|
||||||
);
|
);
|
||||||
|
|
||||||
let matched_then = matches_selector(
|
let matched_then = matches_selector(
|
||||||
|
@ -104,8 +103,7 @@ where
|
||||||
dependency.selector_offset,
|
dependency.selector_offset,
|
||||||
None,
|
None,
|
||||||
wrapper,
|
wrapper,
|
||||||
&mut context,
|
context,
|
||||||
&mut |_, _| {},
|
|
||||||
);
|
);
|
||||||
|
|
||||||
matched_then != matches_now
|
matched_then != matches_now
|
||||||
|
|
|
@ -556,6 +556,7 @@ impl StylesheetInvalidationSet {
|
||||||
FontFace(..) |
|
FontFace(..) |
|
||||||
Keyframes(..) |
|
Keyframes(..) |
|
||||||
ScrollTimeline(..) |
|
ScrollTimeline(..) |
|
||||||
|
Container(..) |
|
||||||
Style(..) => {
|
Style(..) => {
|
||||||
if is_generic_change {
|
if is_generic_change {
|
||||||
// TODO(emilio): We need to do this for selector / keyframe
|
// TODO(emilio): We need to do this for selector / keyframe
|
||||||
|
@ -610,7 +611,7 @@ impl StylesheetInvalidationSet {
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
Document(..) | Namespace(..) | Import(..) | Media(..) | Supports(..) |
|
Document(..) | Namespace(..) | Import(..) | Media(..) | Supports(..) |
|
||||||
LayerStatement(..) | LayerBlock(..) => {
|
Container(..) | LayerStatement(..) | LayerBlock(..) => {
|
||||||
// Do nothing, relevant nested rules are visited as part of the
|
// Do nothing, relevant nested rules are visited as part of the
|
||||||
// iteration.
|
// iteration.
|
||||||
},
|
},
|
||||||
|
|
|
@ -63,6 +63,8 @@ pub use servo_arc;
|
||||||
#[macro_use]
|
#[macro_use]
|
||||||
extern crate servo_atoms;
|
extern crate servo_atoms;
|
||||||
#[macro_use]
|
#[macro_use]
|
||||||
|
extern crate static_assertions;
|
||||||
|
#[macro_use]
|
||||||
extern crate style_derive;
|
extern crate style_derive;
|
||||||
#[macro_use]
|
#[macro_use]
|
||||||
extern crate to_shmem_derive;
|
extern crate to_shmem_derive;
|
||||||
|
@ -101,10 +103,12 @@ pub mod invalidation;
|
||||||
#[allow(missing_docs)] // TODO.
|
#[allow(missing_docs)] // TODO.
|
||||||
pub mod logical_geometry;
|
pub mod logical_geometry;
|
||||||
pub mod matching;
|
pub mod matching;
|
||||||
#[macro_use]
|
|
||||||
pub mod media_queries;
|
pub mod media_queries;
|
||||||
pub mod parallel;
|
pub mod parallel;
|
||||||
pub mod parser;
|
pub mod parser;
|
||||||
|
pub mod piecewise_linear;
|
||||||
|
#[macro_use]
|
||||||
|
pub mod queries;
|
||||||
pub mod rule_cache;
|
pub mod rule_cache;
|
||||||
pub mod rule_collector;
|
pub mod rule_collector;
|
||||||
pub mod rule_tree;
|
pub mod rule_tree;
|
||||||
|
@ -184,7 +188,7 @@ pub mod gecko_properties {
|
||||||
}
|
}
|
||||||
|
|
||||||
macro_rules! reexport_computed_values {
|
macro_rules! reexport_computed_values {
|
||||||
( $( { $name: ident, $boxed: expr } )+ ) => {
|
( $( { $name: ident } )+ ) => {
|
||||||
/// Types for [computed values][computed].
|
/// Types for [computed values][computed].
|
||||||
///
|
///
|
||||||
/// [computed]: https://drafts.csswg.org/css-cascade/#computed
|
/// [computed]: https://drafts.csswg.org/css-cascade/#computed
|
||||||
|
@ -198,7 +202,6 @@ macro_rules! reexport_computed_values {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
longhand_properties_idents!(reexport_computed_values);
|
longhand_properties_idents!(reexport_computed_values);
|
||||||
|
|
||||||
#[cfg(feature = "gecko")]
|
#[cfg(feature = "gecko")]
|
||||||
use crate::gecko_string_cache::WeakAtom;
|
use crate::gecko_string_cache::WeakAtom;
|
||||||
#[cfg(feature = "servo")]
|
#[cfg(feature = "servo")]
|
||||||
|
|
|
@ -168,6 +168,11 @@ impl WritingMode {
|
||||||
flags
|
flags
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns the `horizontal-tb` value.
|
||||||
|
pub fn horizontal_tb() -> Self {
|
||||||
|
Self::from_bits_truncate(0)
|
||||||
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn is_vertical(&self) -> bool {
|
pub fn is_vertical(&self) -> bool {
|
||||||
self.intersects(WritingMode::VERTICAL)
|
self.intersects(WritingMode::VERTICAL)
|
||||||
|
@ -872,10 +877,10 @@ impl<T> LogicalMargin<T> {
|
||||||
inline_start: T,
|
inline_start: T,
|
||||||
) -> LogicalMargin<T> {
|
) -> LogicalMargin<T> {
|
||||||
LogicalMargin {
|
LogicalMargin {
|
||||||
block_start: block_start,
|
block_start,
|
||||||
inline_end: inline_end,
|
inline_end,
|
||||||
block_end: block_end,
|
block_end,
|
||||||
inline_start: inline_start,
|
inline_start,
|
||||||
debug_writing_mode: DebugWritingMode::new(mode),
|
debug_writing_mode: DebugWritingMode::new(mode),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1050,6 +1055,18 @@ impl<T: Copy> LogicalMargin<T> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub fn convert(&self, mode_from: WritingMode, mode_to: WritingMode) -> LogicalMargin<T> {
|
||||||
|
if mode_from == mode_to {
|
||||||
|
self.debug_writing_mode.check(mode_from);
|
||||||
|
*self
|
||||||
|
} else {
|
||||||
|
LogicalMargin::from_physical(mode_to, self.to_physical(mode_from))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: Clone> LogicalMargin<T> {
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn to_physical(&self, mode: WritingMode) -> SideOffsets2D<T> {
|
pub fn to_physical(&self, mode: WritingMode) -> SideOffsets2D<T> {
|
||||||
self.debug_writing_mode.check(mode);
|
self.debug_writing_mode.check(mode);
|
||||||
|
@ -1059,42 +1076,32 @@ impl<T: Copy> LogicalMargin<T> {
|
||||||
let left;
|
let left;
|
||||||
if mode.is_vertical() {
|
if mode.is_vertical() {
|
||||||
if mode.is_vertical_lr() {
|
if mode.is_vertical_lr() {
|
||||||
left = self.block_start;
|
left = self.block_start.clone();
|
||||||
right = self.block_end;
|
right = self.block_end.clone();
|
||||||
} else {
|
} else {
|
||||||
right = self.block_start;
|
right = self.block_start.clone();
|
||||||
left = self.block_end;
|
left = self.block_end.clone();
|
||||||
}
|
}
|
||||||
if mode.is_inline_tb() {
|
if mode.is_inline_tb() {
|
||||||
top = self.inline_start;
|
top = self.inline_start.clone();
|
||||||
bottom = self.inline_end;
|
bottom = self.inline_end.clone();
|
||||||
} else {
|
} else {
|
||||||
bottom = self.inline_start;
|
bottom = self.inline_start.clone();
|
||||||
top = self.inline_end;
|
top = self.inline_end.clone();
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
top = self.block_start;
|
top = self.block_start.clone();
|
||||||
bottom = self.block_end;
|
bottom = self.block_end.clone();
|
||||||
if mode.is_bidi_ltr() {
|
if mode.is_bidi_ltr() {
|
||||||
left = self.inline_start;
|
left = self.inline_start.clone();
|
||||||
right = self.inline_end;
|
right = self.inline_end.clone();
|
||||||
} else {
|
} else {
|
||||||
right = self.inline_start;
|
right = self.inline_start.clone();
|
||||||
left = self.inline_end;
|
left = self.inline_end.clone();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
SideOffsets2D::new(top, right, bottom, left)
|
SideOffsets2D::new(top, right, bottom, left)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
|
||||||
pub fn convert(&self, mode_from: WritingMode, mode_to: WritingMode) -> LogicalMargin<T> {
|
|
||||||
if mode_from == mode_to {
|
|
||||||
self.debug_writing_mode.check(mode_from);
|
|
||||||
*self
|
|
||||||
} else {
|
|
||||||
LogicalMargin::from_physical(mode_to, self.to_physical(mode_from))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T: PartialEq + Zero> LogicalMargin<T> {
|
impl<T: PartialEq + Zero> LogicalMargin<T> {
|
||||||
|
|
|
@ -128,3 +128,11 @@ macro_rules! local_name {
|
||||||
$crate::values::AtomIdent(atom!($s))
|
$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);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
|
@ -8,7 +8,7 @@
|
||||||
#![deny(missing_docs)]
|
#![deny(missing_docs)]
|
||||||
|
|
||||||
use crate::computed_value_flags::ComputedValueFlags;
|
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::context::{SharedStyleContext, StyleContext};
|
||||||
use crate::data::{ElementData, ElementStyles};
|
use crate::data::{ElementData, ElementStyles};
|
||||||
use crate::dom::TElement;
|
use crate::dom::TElement;
|
||||||
|
@ -26,7 +26,6 @@ use crate::style_resolver::{PseudoElementResolution, StyleResolverForElement};
|
||||||
use crate::stylesheets::layer_rule::LayerOrder;
|
use crate::stylesheets::layer_rule::LayerOrder;
|
||||||
use crate::stylist::RuleInclusion;
|
use crate::stylist::RuleInclusion;
|
||||||
use crate::traversal_flags::TraversalFlags;
|
use crate::traversal_flags::TraversalFlags;
|
||||||
use selectors::matching::ElementSelectorFlags;
|
|
||||||
use servo_arc::{Arc, ArcBorrow};
|
use servo_arc::{Arc, ArcBorrow};
|
||||||
|
|
||||||
/// Represents the result of comparing an element's old and new style.
|
/// Represents the result of comparing an element's old and new style.
|
||||||
|
@ -255,8 +254,8 @@ trait PrivateMatchMethods: TElement {
|
||||||
new_style: &ComputedValues,
|
new_style: &ComputedValues,
|
||||||
pseudo_element: Option<PseudoElement>,
|
pseudo_element: Option<PseudoElement>,
|
||||||
) -> bool {
|
) -> bool {
|
||||||
let new_box_style = new_style.get_box();
|
let new_ui_style = new_style.get_ui();
|
||||||
let new_style_specifies_animations = new_box_style.specifies_animations();
|
let new_style_specifies_animations = new_ui_style.specifies_animations();
|
||||||
|
|
||||||
let has_animations = self.has_css_animations(&context.shared, pseudo_element);
|
let has_animations = self.has_css_animations(&context.shared, pseudo_element);
|
||||||
if !new_style_specifies_animations && !has_animations {
|
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
|
let keyframes_or_timeline_could_have_changed = context
|
||||||
.shared
|
.shared
|
||||||
|
@ -302,12 +301,12 @@ trait PrivateMatchMethods: TElement {
|
||||||
}
|
}
|
||||||
|
|
||||||
// If the animations changed, well...
|
// 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;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
let old_display = old_box_style.clone_display();
|
let old_display = old_style.clone_display();
|
||||||
let new_display = new_box_style.clone_display();
|
let new_display = new_style.clone_display();
|
||||||
|
|
||||||
// If we were display: none, we may need to trigger animations.
|
// If we were display: none, we may need to trigger animations.
|
||||||
if old_display == Display::None && new_display != Display::None {
|
if old_display == Display::None && new_display != Display::None {
|
||||||
|
@ -342,14 +341,13 @@ trait PrivateMatchMethods: TElement {
|
||||||
None => return false,
|
None => return false,
|
||||||
};
|
};
|
||||||
|
|
||||||
let new_box_style = new_style.get_box();
|
|
||||||
if !self.has_css_transitions(context.shared, pseudo_element) &&
|
if !self.has_css_transitions(context.shared, pseudo_element) &&
|
||||||
!new_box_style.specifies_transitions()
|
!new_style.get_ui().specifies_transitions()
|
||||||
{
|
{
|
||||||
return false;
|
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;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -766,8 +764,8 @@ trait PrivateMatchMethods: TElement {
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
let old_display = old_values.get_box().clone_display();
|
let old_display = old_values.clone_display();
|
||||||
let new_display = new_values.get_box().clone_display();
|
let new_display = new_values.clone_display();
|
||||||
|
|
||||||
if old_display != new_display {
|
if old_display != new_display {
|
||||||
// If we used to be a display: none element, and no longer are, our
|
// If we used to be a display: none element, and no longer are, our
|
||||||
|
@ -1007,51 +1005,6 @@ pub trait MatchMethods: TElement {
|
||||||
cascade_requirement
|
cascade_requirement
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Applies selector flags to an element, deferring mutations of the parent
|
|
||||||
/// until after the traversal.
|
|
||||||
///
|
|
||||||
/// TODO(emilio): This is somewhat inefficient, because it doesn't take
|
|
||||||
/// advantage of us knowing that the traversal is sequential.
|
|
||||||
fn apply_selector_flags(
|
|
||||||
&self,
|
|
||||||
map: &mut SelectorFlagsMap<Self>,
|
|
||||||
element: &Self,
|
|
||||||
flags: ElementSelectorFlags,
|
|
||||||
) {
|
|
||||||
// Handle flags that apply to the element.
|
|
||||||
let self_flags = flags.for_self();
|
|
||||||
if !self_flags.is_empty() {
|
|
||||||
if element == self {
|
|
||||||
// If this is the element we're styling, we have exclusive
|
|
||||||
// access to the element, and thus it's fine inserting them,
|
|
||||||
// even from the worker.
|
|
||||||
unsafe {
|
|
||||||
element.set_selector_flags(self_flags);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// Otherwise, this element is an ancestor of the current element
|
|
||||||
// we're styling, and thus multiple children could write to it
|
|
||||||
// if we did from here.
|
|
||||||
//
|
|
||||||
// Instead, we can read them, and post them if necessary as a
|
|
||||||
// sequential task in order for them to be processed later.
|
|
||||||
if !element.has_selector_flags(self_flags) {
|
|
||||||
map.insert_flags(*element, self_flags);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Handle flags that apply to the parent.
|
|
||||||
let parent_flags = flags.for_parent();
|
|
||||||
if !parent_flags.is_empty() {
|
|
||||||
if let Some(p) = element.parent_element() {
|
|
||||||
if !p.has_selector_flags(parent_flags) {
|
|
||||||
map.insert_flags(p, parent_flags);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Updates the rule nodes without re-running selector matching, using just
|
/// Updates the rule nodes without re-running selector matching, using just
|
||||||
/// the rule tree.
|
/// the rule tree.
|
||||||
///
|
///
|
||||||
|
|
|
@ -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<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
|
|
||||||
where
|
|
||||||
W: fmt::Write,
|
|
||||||
{
|
|
||||||
dest.write_str(match *self {
|
|
||||||
Operator::Equal => "=",
|
|
||||||
Operator::LessThan => "<",
|
|
||||||
Operator::LessThanEqual => "<=",
|
|
||||||
Operator::GreaterThan => ">",
|
|
||||||
Operator::GreaterThanEqual => ">=",
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Either a `Range` or an `Operator`.
|
|
||||||
///
|
|
||||||
/// Ranged media features are not allowed with operations (that'd make no
|
|
||||||
/// sense).
|
|
||||||
#[derive(Clone, Copy, Debug, Eq, MallocSizeOf, PartialEq, ToShmem)]
|
|
||||||
pub enum RangeOrOperator {
|
|
||||||
/// A `Range`.
|
|
||||||
Range(Range),
|
|
||||||
/// An `Operator`.
|
|
||||||
Operator(Operator),
|
|
||||||
}
|
|
||||||
|
|
||||||
impl RangeOrOperator {
|
|
||||||
/// Evaluate a given range given an optional query value and a value from
|
|
||||||
/// the browser.
|
|
||||||
pub fn evaluate<T>(range_or_op: Option<Self>, query_value: Option<T>, value: T) -> bool
|
|
||||||
where
|
|
||||||
T: PartialOrd + Zero,
|
|
||||||
{
|
|
||||||
match query_value {
|
|
||||||
Some(v) => Self::evaluate_with_query_value(range_or_op, v, value),
|
|
||||||
None => !value.is_zero(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Evaluate a given range given a non-optional query value and a value from
|
|
||||||
/// the browser.
|
|
||||||
pub fn evaluate_with_query_value<T>(range_or_op: Option<Self>, query_value: T, value: T) -> bool
|
|
||||||
where
|
|
||||||
T: PartialOrd,
|
|
||||||
{
|
|
||||||
let cmp = match value.partial_cmp(&query_value) {
|
|
||||||
Some(c) => c,
|
|
||||||
None => return false,
|
|
||||||
};
|
|
||||||
|
|
||||||
let range_or_op = match range_or_op {
|
|
||||||
Some(r) => r,
|
|
||||||
None => return cmp == Ordering::Equal,
|
|
||||||
};
|
|
||||||
|
|
||||||
match range_or_op {
|
|
||||||
RangeOrOperator::Range(range) => {
|
|
||||||
cmp == Ordering::Equal ||
|
|
||||||
match range {
|
|
||||||
Range::Min => cmp == Ordering::Greater,
|
|
||||||
Range::Max => cmp == Ordering::Less,
|
|
||||||
}
|
|
||||||
},
|
|
||||||
RangeOrOperator::Operator(op) => match op {
|
|
||||||
Operator::Equal => cmp == Ordering::Equal,
|
|
||||||
Operator::GreaterThan => cmp == Ordering::Greater,
|
|
||||||
Operator::GreaterThanEqual => cmp == Ordering::Equal || cmp == Ordering::Greater,
|
|
||||||
Operator::LessThan => cmp == Ordering::Less,
|
|
||||||
Operator::LessThanEqual => cmp == Ordering::Equal || cmp == Ordering::Less,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// A feature expression contains a reference to the media feature, the value
|
|
||||||
/// the media query contained, and the range to evaluate.
|
|
||||||
#[derive(Clone, Debug, MallocSizeOf, ToShmem)]
|
|
||||||
pub struct MediaFeatureExpression {
|
|
||||||
feature_index: usize,
|
|
||||||
value: Option<MediaExpressionValue>,
|
|
||||||
range_or_operator: Option<RangeOrOperator>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl PartialEq for MediaFeatureExpression {
|
|
||||||
fn eq(&self, other: &Self) -> bool {
|
|
||||||
self.feature_index == other.feature_index &&
|
|
||||||
self.value == other.value &&
|
|
||||||
self.range_or_operator == other.range_or_operator
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl ToCss for MediaFeatureExpression {
|
|
||||||
fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
|
|
||||||
where
|
|
||||||
W: fmt::Write,
|
|
||||||
{
|
|
||||||
dest.write_str("(")?;
|
|
||||||
|
|
||||||
let feature = self.feature();
|
|
||||||
|
|
||||||
if feature
|
|
||||||
.requirements
|
|
||||||
.contains(ParsingRequirements::WEBKIT_PREFIX)
|
|
||||||
{
|
|
||||||
dest.write_str("-webkit-")?;
|
|
||||||
}
|
|
||||||
|
|
||||||
if let Some(RangeOrOperator::Range(range)) = self.range_or_operator {
|
|
||||||
match range {
|
|
||||||
Range::Min => dest.write_str("min-")?,
|
|
||||||
Range::Max => dest.write_str("max-")?,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// NB: CssStringWriter not needed, feature names are under control.
|
|
||||||
write!(dest, "{}", feature.name)?;
|
|
||||||
|
|
||||||
if let Some(RangeOrOperator::Operator(op)) = self.range_or_operator {
|
|
||||||
dest.write_char(' ')?;
|
|
||||||
op.to_css(dest)?;
|
|
||||||
dest.write_char(' ')?;
|
|
||||||
} else if self.value.is_some() {
|
|
||||||
dest.write_str(": ")?;
|
|
||||||
}
|
|
||||||
|
|
||||||
if let Some(ref val) = self.value {
|
|
||||||
val.to_css(dest, self)?;
|
|
||||||
}
|
|
||||||
|
|
||||||
dest.write_str(")")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Consumes an operation or a colon, or returns an error.
|
|
||||||
fn consume_operation_or_colon(input: &mut Parser) -> Result<Option<Operator>, ()> {
|
|
||||||
let first_delim = {
|
|
||||||
let next_token = match input.next() {
|
|
||||||
Ok(t) => t,
|
|
||||||
Err(..) => return Err(()),
|
|
||||||
};
|
|
||||||
|
|
||||||
match *next_token {
|
|
||||||
Token::Colon => return Ok(None),
|
|
||||||
Token::Delim(oper) => oper,
|
|
||||||
_ => return Err(()),
|
|
||||||
}
|
|
||||||
};
|
|
||||||
let operator = match first_delim {
|
|
||||||
'=' => return Ok(Some(Operator::Equal)),
|
|
||||||
'>' => Operator::GreaterThan,
|
|
||||||
'<' => Operator::LessThan,
|
|
||||||
_ => return Err(()),
|
|
||||||
};
|
|
||||||
|
|
||||||
// https://drafts.csswg.org/mediaqueries-4/#mq-syntax:
|
|
||||||
//
|
|
||||||
// No whitespace is allowed between the “<” or “>”
|
|
||||||
// <delim-token>s and the following “=” <delim-token>, if 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<MediaExpressionValue>,
|
|
||||||
range_or_operator: Option<RangeOrOperator>,
|
|
||||||
) -> Self {
|
|
||||||
debug_assert!(feature_index < MEDIA_FEATURES.len());
|
|
||||||
Self {
|
|
||||||
feature_index,
|
|
||||||
value,
|
|
||||||
range_or_operator,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn feature(&self) -> &'static MediaFeatureDescription {
|
|
||||||
&MEDIA_FEATURES[self.feature_index]
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Parse a media expression of the form:
|
|
||||||
///
|
|
||||||
/// ```
|
|
||||||
/// (media-feature: media-value)
|
|
||||||
/// ```
|
|
||||||
pub fn parse<'i, 't>(
|
|
||||||
context: &ParserContext,
|
|
||||||
input: &mut Parser<'i, 't>,
|
|
||||||
) -> Result<Self, ParseError<'i>> {
|
|
||||||
input.expect_parenthesis_block()?;
|
|
||||||
input.parse_nested_block(|input| Self::parse_in_parenthesis_block(context, input))
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Parse a media feature expression where we've already consumed the
|
|
||||||
/// parenthesis.
|
|
||||||
pub fn parse_in_parenthesis_block<'i, 't>(
|
|
||||||
context: &ParserContext,
|
|
||||||
input: &mut Parser<'i, 't>,
|
|
||||||
) -> Result<Self, ParseError<'i>> {
|
|
||||||
let mut requirements = ParsingRequirements::empty();
|
|
||||||
let location = input.current_source_location();
|
|
||||||
let ident = input.expect_ident()?;
|
|
||||||
|
|
||||||
if context.in_ua_or_chrome_sheet() {
|
|
||||||
requirements.insert(ParsingRequirements::CHROME_AND_UA_ONLY);
|
|
||||||
}
|
|
||||||
|
|
||||||
let mut feature_name = &**ident;
|
|
||||||
|
|
||||||
if starts_with_ignore_ascii_case(feature_name, "-webkit-") {
|
|
||||||
feature_name = &feature_name[8..];
|
|
||||||
requirements.insert(ParsingRequirements::WEBKIT_PREFIX);
|
|
||||||
}
|
|
||||||
|
|
||||||
let range = if starts_with_ignore_ascii_case(feature_name, "min-") {
|
|
||||||
feature_name = &feature_name[4..];
|
|
||||||
Some(Range::Min)
|
|
||||||
} else if starts_with_ignore_ascii_case(feature_name, "max-") {
|
|
||||||
feature_name = &feature_name[4..];
|
|
||||||
Some(Range::Max)
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
};
|
|
||||||
|
|
||||||
let atom = Atom::from(string_as_ascii_lowercase(feature_name));
|
|
||||||
|
|
||||||
let (feature_index, feature) = match MEDIA_FEATURES
|
|
||||||
.iter()
|
|
||||||
.enumerate()
|
|
||||||
.find(|(_, f)| f.name == atom)
|
|
||||||
{
|
|
||||||
Some((i, f)) => (i, f),
|
|
||||||
None => {
|
|
||||||
return Err(location.new_custom_error(
|
|
||||||
StyleParseErrorKind::MediaQueryExpectedFeatureName(ident.clone()),
|
|
||||||
))
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
if disabled_by_pref(&feature.name, context) ||
|
|
||||||
!requirements.contains(feature.requirements) ||
|
|
||||||
(range.is_some() && !feature.allows_ranges())
|
|
||||||
{
|
|
||||||
return Err(location.new_custom_error(
|
|
||||||
StyleParseErrorKind::MediaQueryExpectedFeatureName(ident.clone()),
|
|
||||||
));
|
|
||||||
}
|
|
||||||
|
|
||||||
let operator = input.try_parse(consume_operation_or_colon);
|
|
||||||
let operator = match operator {
|
|
||||||
Err(..) => {
|
|
||||||
// If there's no colon, this is a media query of the
|
|
||||||
// form '(<feature>)', that is, there's no value
|
|
||||||
// specified.
|
|
||||||
//
|
|
||||||
// Gecko doesn't allow ranged expressions without a
|
|
||||||
// value, so just reject them here too.
|
|
||||||
if range.is_some() {
|
|
||||||
return Err(
|
|
||||||
input.new_custom_error(StyleParseErrorKind::RangedExpressionWithNoValue)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return Ok(Self::new(feature_index, None, None));
|
|
||||||
},
|
|
||||||
Ok(operator) => operator,
|
|
||||||
};
|
|
||||||
|
|
||||||
let range_or_operator = match range {
|
|
||||||
Some(range) => {
|
|
||||||
if operator.is_some() {
|
|
||||||
return Err(
|
|
||||||
input.new_custom_error(StyleParseErrorKind::MediaQueryUnexpectedOperator)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
Some(RangeOrOperator::Range(range))
|
|
||||||
},
|
|
||||||
None => match operator {
|
|
||||||
Some(operator) => {
|
|
||||||
if !feature.allows_ranges() {
|
|
||||||
return Err(input
|
|
||||||
.new_custom_error(StyleParseErrorKind::MediaQueryUnexpectedOperator));
|
|
||||||
}
|
|
||||||
Some(RangeOrOperator::Operator(operator))
|
|
||||||
},
|
|
||||||
None => None,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
let value = MediaExpressionValue::parse(feature, context, input).map_err(|err| {
|
|
||||||
err.location
|
|
||||||
.new_custom_error(StyleParseErrorKind::MediaQueryExpectedFeatureValue)
|
|
||||||
})?;
|
|
||||||
|
|
||||||
Ok(Self::new(feature_index, Some(value), range_or_operator))
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns whether this media query evaluates to true for the given device.
|
|
||||||
pub fn matches(&self, device: &Device, quirks_mode: QuirksMode) -> bool {
|
|
||||||
let value = self.value.as_ref();
|
|
||||||
|
|
||||||
macro_rules! expect {
|
|
||||||
($variant:ident) => {
|
|
||||||
value.map(|value| match *value {
|
|
||||||
MediaExpressionValue::$variant(ref v) => v,
|
|
||||||
_ => unreachable!("Unexpected MediaExpressionValue"),
|
|
||||||
})
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
match self.feature().evaluator {
|
|
||||||
Evaluator::Length(eval) => {
|
|
||||||
let computed = expect!(Length).map(|specified| {
|
|
||||||
computed::Context::for_media_query_evaluation(device, quirks_mode, |context| {
|
|
||||||
specified.to_computed_value(context)
|
|
||||||
})
|
|
||||||
});
|
|
||||||
eval(device, computed, self.range_or_operator)
|
|
||||||
},
|
|
||||||
Evaluator::Integer(eval) => {
|
|
||||||
eval(device, expect!(Integer).cloned(), self.range_or_operator)
|
|
||||||
},
|
|
||||||
Evaluator::Float(eval) => eval(device, expect!(Float).cloned(), self.range_or_operator),
|
|
||||||
Evaluator::NumberRatio(eval) => eval(
|
|
||||||
device,
|
|
||||||
expect!(NumberRatio).cloned(),
|
|
||||||
self.range_or_operator,
|
|
||||||
),
|
|
||||||
Evaluator::Resolution(eval) => {
|
|
||||||
let computed = expect!(Resolution).map(|specified| {
|
|
||||||
computed::Context::for_media_query_evaluation(device, quirks_mode, |context| {
|
|
||||||
specified.to_computed_value(context)
|
|
||||||
})
|
|
||||||
});
|
|
||||||
eval(device, computed, self.range_or_operator)
|
|
||||||
},
|
|
||||||
Evaluator::Enumerated { evaluator, .. } => {
|
|
||||||
evaluator(device, expect!(Enumerated).cloned(), self.range_or_operator)
|
|
||||||
},
|
|
||||||
Evaluator::Ident(eval) => eval(device, expect!(Ident).cloned(), self.range_or_operator),
|
|
||||||
Evaluator::BoolInteger(eval) => eval(
|
|
||||||
device,
|
|
||||||
expect!(BoolInteger).cloned(),
|
|
||||||
self.range_or_operator,
|
|
||||||
),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// A value found or expected in a media expression.
|
|
||||||
///
|
|
||||||
/// FIXME(emilio): How should calc() serialize in the Number / Integer /
|
|
||||||
/// BoolInteger / NumberRatio case, as computed or as specified value?
|
|
||||||
///
|
|
||||||
/// If the first, this would need to store the relevant values.
|
|
||||||
///
|
|
||||||
/// See: https://github.com/w3c/csswg-drafts/issues/1968
|
|
||||||
#[derive(Clone, Debug, MallocSizeOf, PartialEq, ToShmem)]
|
|
||||||
pub enum MediaExpressionValue {
|
|
||||||
/// A length.
|
|
||||||
Length(Length),
|
|
||||||
/// A (non-negative) integer.
|
|
||||||
Integer(u32),
|
|
||||||
/// A floating point value.
|
|
||||||
Float(CSSFloat),
|
|
||||||
/// A boolean value, specified as an integer (i.e., either 0 or 1).
|
|
||||||
BoolInteger(bool),
|
|
||||||
/// A single non-negative number or two non-negative numbers separated by '/',
|
|
||||||
/// with optional whitespace on either side of the '/'.
|
|
||||||
NumberRatio(Ratio),
|
|
||||||
/// A resolution.
|
|
||||||
Resolution(Resolution),
|
|
||||||
/// An enumerated value, defined by the variant keyword table in the
|
|
||||||
/// feature's `mData` member.
|
|
||||||
Enumerated(KeywordDiscriminant),
|
|
||||||
/// An identifier.
|
|
||||||
Ident(Atom),
|
|
||||||
}
|
|
||||||
|
|
||||||
impl MediaExpressionValue {
|
|
||||||
fn to_css<W>(&self, dest: &mut CssWriter<W>, for_expr: &MediaFeatureExpression) -> fmt::Result
|
|
||||||
where
|
|
||||||
W: fmt::Write,
|
|
||||||
{
|
|
||||||
match *self {
|
|
||||||
MediaExpressionValue::Length(ref l) => l.to_css(dest),
|
|
||||||
MediaExpressionValue::Integer(v) => v.to_css(dest),
|
|
||||||
MediaExpressionValue::Float(v) => v.to_css(dest),
|
|
||||||
MediaExpressionValue::BoolInteger(v) => dest.write_str(if v { "1" } else { "0" }),
|
|
||||||
MediaExpressionValue::NumberRatio(ratio) => ratio.to_css(dest),
|
|
||||||
MediaExpressionValue::Resolution(ref r) => r.to_css(dest),
|
|
||||||
MediaExpressionValue::Ident(ref ident) => serialize_atom_identifier(ident, dest),
|
|
||||||
MediaExpressionValue::Enumerated(value) => match for_expr.feature().evaluator {
|
|
||||||
Evaluator::Enumerated { serializer, .. } => dest.write_str(&*serializer(value)),
|
|
||||||
_ => unreachable!(),
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn parse<'i, 't>(
|
|
||||||
for_feature: &MediaFeatureDescription,
|
|
||||||
context: &ParserContext,
|
|
||||||
input: &mut Parser<'i, 't>,
|
|
||||||
) -> Result<MediaExpressionValue, ParseError<'i>> {
|
|
||||||
Ok(match for_feature.evaluator {
|
|
||||||
Evaluator::Length(..) => {
|
|
||||||
let length = Length::parse_non_negative(context, input)?;
|
|
||||||
MediaExpressionValue::Length(length)
|
|
||||||
},
|
|
||||||
Evaluator::Integer(..) => {
|
|
||||||
let integer = Integer::parse_non_negative(context, input)?;
|
|
||||||
MediaExpressionValue::Integer(integer.value() as u32)
|
|
||||||
},
|
|
||||||
Evaluator::BoolInteger(..) => {
|
|
||||||
let integer = Integer::parse_non_negative(context, input)?;
|
|
||||||
let value = integer.value();
|
|
||||||
if value > 1 {
|
|
||||||
return Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError));
|
|
||||||
}
|
|
||||||
MediaExpressionValue::BoolInteger(value == 1)
|
|
||||||
},
|
|
||||||
Evaluator::Float(..) => {
|
|
||||||
let number = Number::parse(context, input)?;
|
|
||||||
MediaExpressionValue::Float(number.get())
|
|
||||||
},
|
|
||||||
Evaluator::NumberRatio(..) => {
|
|
||||||
use crate::values::specified::Ratio as SpecifiedRatio;
|
|
||||||
let ratio = SpecifiedRatio::parse(context, input)?;
|
|
||||||
MediaExpressionValue::NumberRatio(Ratio::new(ratio.0.get(), ratio.1.get()))
|
|
||||||
},
|
|
||||||
Evaluator::Resolution(..) => {
|
|
||||||
MediaExpressionValue::Resolution(Resolution::parse(context, input)?)
|
|
||||||
},
|
|
||||||
Evaluator::Enumerated { parser, .. } => {
|
|
||||||
MediaExpressionValue::Enumerated(parser(context, input)?)
|
|
||||||
},
|
|
||||||
Evaluator::Ident(..) => {
|
|
||||||
MediaExpressionValue::Ident(Atom::from(input.expect_ident()?.as_ref()))
|
|
||||||
},
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -10,6 +10,7 @@ use super::{Device, MediaQuery, Qualifier};
|
||||||
use crate::context::QuirksMode;
|
use crate::context::QuirksMode;
|
||||||
use crate::error_reporting::ContextualParseError;
|
use crate::error_reporting::ContextualParseError;
|
||||||
use crate::parser::ParserContext;
|
use crate::parser::ParserContext;
|
||||||
|
use crate::values::computed;
|
||||||
use cssparser::{Delimiter, Parser};
|
use cssparser::{Delimiter, Parser};
|
||||||
use cssparser::{ParserInput, Token};
|
use cssparser::{ParserInput, Token};
|
||||||
|
|
||||||
|
@ -74,15 +75,17 @@ impl MediaList {
|
||||||
pub fn evaluate(&self, device: &Device, quirks_mode: QuirksMode) -> bool {
|
pub fn evaluate(&self, device: &Device, quirks_mode: QuirksMode) -> bool {
|
||||||
// Check if it is an empty media query list or any queries match.
|
// Check if it is an empty media query list or any queries match.
|
||||||
// https://drafts.csswg.org/mediaqueries-4/#mq-list
|
// 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| {
|
self.media_queries.iter().any(|mq| {
|
||||||
let media_match = mq.media_type.matches(device.media_type());
|
let media_match = mq.media_type.matches(device.media_type());
|
||||||
|
|
||||||
// Check if the media condition match.
|
// Check if the media condition match.
|
||||||
let query_match = media_match &&
|
let query_match = media_match &&
|
||||||
mq.condition
|
mq.condition.as_ref().map_or(true, |c| c.matches(context));
|
||||||
.as_ref()
|
|
||||||
.map_or(true, |c| c.matches(device, quirks_mode));
|
|
||||||
|
|
||||||
// Apply the logical NOT qualifier to the result
|
// Apply the logical NOT qualifier to the result
|
||||||
match mq.qualifier {
|
match mq.qualifier {
|
||||||
|
@ -90,6 +93,7 @@ impl MediaList {
|
||||||
_ => query_match,
|
_ => query_match,
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Whether this `MediaList` contains no media queries.
|
/// Whether this `MediaList` contains no media queries.
|
||||||
|
|
|
@ -6,7 +6,7 @@
|
||||||
//!
|
//!
|
||||||
//! https://drafts.csswg.org/mediaqueries/#typedef-media-query
|
//! https://drafts.csswg.org/mediaqueries/#typedef-media-query
|
||||||
|
|
||||||
use super::media_condition::MediaCondition;
|
use crate::queries::{QueryCondition, FeatureType};
|
||||||
use crate::parser::ParserContext;
|
use crate::parser::ParserContext;
|
||||||
use crate::str::string_as_ascii_lowercase;
|
use crate::str::string_as_ascii_lowercase;
|
||||||
use crate::values::CustomIdent;
|
use crate::values::CustomIdent;
|
||||||
|
@ -66,7 +66,7 @@ pub struct MediaQuery {
|
||||||
pub media_type: MediaQueryType,
|
pub media_type: MediaQueryType,
|
||||||
/// The condition that this media query contains. This cannot have `or`
|
/// The condition that this media query contains. This cannot have `or`
|
||||||
/// in the first level.
|
/// in the first level.
|
||||||
pub condition: Option<MediaCondition>,
|
pub condition: Option<QueryCondition>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ToCss for MediaQuery {
|
impl ToCss for MediaQuery {
|
||||||
|
@ -134,9 +134,9 @@ impl MediaQuery {
|
||||||
.unwrap_or_default();
|
.unwrap_or_default();
|
||||||
|
|
||||||
let condition = if explicit_media_type.is_none() {
|
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() {
|
} 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 {
|
} else {
|
||||||
None
|
None
|
||||||
};
|
};
|
||||||
|
|
|
@ -6,15 +6,9 @@
|
||||||
//!
|
//!
|
||||||
//! [mq]: https://drafts.csswg.org/mediaqueries/
|
//! [mq]: https://drafts.csswg.org/mediaqueries/
|
||||||
|
|
||||||
mod media_condition;
|
|
||||||
mod media_list;
|
mod media_list;
|
||||||
mod media_query;
|
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_list::MediaList;
|
||||||
pub use self::media_query::{MediaQuery, MediaQueryType, MediaType, Qualifier};
|
pub use self::media_query::{MediaQuery, MediaQueryType, MediaType, Qualifier};
|
||||||
|
|
||||||
|
|
223
components/style/piecewise_linear.rs
Normal file
223
components/style/piecewise_linear.rs
Normal file
|
@ -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<Entry>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PiecewiseLinearFunction {
|
||||||
|
/// Interpolate y value given x and two points. The linear function will be rooted at the asymptote.
|
||||||
|
fn interpolate(x: ValueType, prev: Entry, next: Entry, asymptote: &Entry) -> ValueType {
|
||||||
|
// Line is vertical, or the two points are identical. Avoid infinite slope by pretending
|
||||||
|
// the line is flat.
|
||||||
|
if prev.x.approx_eq(&next.x) {
|
||||||
|
return asymptote.y;
|
||||||
|
}
|
||||||
|
let slope = (next.y - prev.y) / (next.x - prev.x);
|
||||||
|
return slope * (x - asymptote.x) + asymptote.y;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get the y value of the piecewise linear function given the x value.
|
||||||
|
pub fn at(&self, x: ValueType) -> ValueType {
|
||||||
|
if !x.is_finite() {
|
||||||
|
return if x > 0.0 { 1.0 } else { 0.0 };
|
||||||
|
}
|
||||||
|
if self.entries.is_empty() {
|
||||||
|
// Implied y = x, as per spec.
|
||||||
|
return x;
|
||||||
|
}
|
||||||
|
if self.entries.len() == 1 {
|
||||||
|
// Implied y = <constant>, as per spec.
|
||||||
|
return self.entries[0].y;
|
||||||
|
}
|
||||||
|
// Spec dictates the valid input domain is [0, 1]. Outside of this range, the output
|
||||||
|
// should be calculated as if the slopes at start and end extend to infinity. However, if the
|
||||||
|
// start/end have two points of the same position, the line should extend along the x-axis.
|
||||||
|
// The function doesn't have to cover the input domain, in which case the extension logic
|
||||||
|
// applies even if the input falls in the input domain.
|
||||||
|
// Also, we're guaranteed to have at least two elements at this point.
|
||||||
|
if x < self.entries[0].x {
|
||||||
|
return Self::interpolate(x, self.entries[0], self.entries[1], &self.entries[0]);
|
||||||
|
}
|
||||||
|
let mut rev_iter = self.entries.iter().rev();
|
||||||
|
let last = rev_iter.next().unwrap();
|
||||||
|
if x > last.x {
|
||||||
|
let second_last = rev_iter.next().unwrap();
|
||||||
|
return Self::interpolate(x, *second_last, *last, last);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Now we know the input sits within the domain explicitly defined by our function.
|
||||||
|
for (prev, next) in self.entries.iter().tuple_windows() {
|
||||||
|
if x > next.x {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
// Prefer left hand side value
|
||||||
|
if x.approx_eq(&prev.x) {
|
||||||
|
return prev.y;
|
||||||
|
}
|
||||||
|
if x.approx_eq(&next.x) {
|
||||||
|
return next.y;
|
||||||
|
}
|
||||||
|
return Self::interpolate(x, *prev, *next, prev);
|
||||||
|
}
|
||||||
|
unreachable!("Input is supposed to be within the entries' min & max!");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Entry of a piecewise linear function while building, where the calculation of x value can be deferred.
|
||||||
|
#[derive(Clone, Copy)]
|
||||||
|
struct BuildEntry {
|
||||||
|
x: Option<ValueType>,
|
||||||
|
y: ValueType,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Builder object to generate a linear function.
|
||||||
|
#[derive(Default)]
|
||||||
|
pub struct PiecewiseLinearFunctionBuilder {
|
||||||
|
largest_x: Option<ValueType>,
|
||||||
|
smallest_x: Option<ValueType>,
|
||||||
|
entries: Vec<BuildEntry>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PiecewiseLinearFunctionBuilder {
|
||||||
|
#[allow(missing_docs)]
|
||||||
|
pub fn new() -> Self {
|
||||||
|
PiecewiseLinearFunctionBuilder::default()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn create_entry(&mut self, y: ValueType, x: Option<ValueType>) {
|
||||||
|
let x = match x {
|
||||||
|
Some(x) if x.is_finite() => x,
|
||||||
|
_ => {
|
||||||
|
self.entries.push(BuildEntry { x: None, y });
|
||||||
|
return;
|
||||||
|
},
|
||||||
|
};
|
||||||
|
// Specified x value cannot regress, as per spec.
|
||||||
|
let x = match self.largest_x {
|
||||||
|
Some(largest_x) => x.max(largest_x),
|
||||||
|
None => x,
|
||||||
|
};
|
||||||
|
self.largest_x = Some(x);
|
||||||
|
// Whatever we see the earliest is the smallest value.
|
||||||
|
if self.smallest_x.is_none() {
|
||||||
|
self.smallest_x = Some(x);
|
||||||
|
}
|
||||||
|
self.entries.push(BuildEntry { x: Some(x), y });
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Add a new entry into the piecewise linear function with specified y value.
|
||||||
|
/// If the start x value is given, that is where the x value will be. Otherwise,
|
||||||
|
/// the x value is calculated later. If the end x value is specified, a flat segment
|
||||||
|
/// is generated. If start x value is not specified but end x is, it is treated as
|
||||||
|
/// start x.
|
||||||
|
pub fn push(mut self, y: CSSFloat, x_start: Option<CSSFloat>, x_end: Option<CSSFloat>) -> Self {
|
||||||
|
self.create_entry(y, x_start);
|
||||||
|
if x_end.is_some() {
|
||||||
|
self.create_entry(y, x_end.map(|x| x));
|
||||||
|
}
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Finish building the piecewise linear function by resolving all undefined x values,
|
||||||
|
/// then return the result.
|
||||||
|
pub fn build(mut self) -> PiecewiseLinearFunction {
|
||||||
|
if self.entries.is_empty() {
|
||||||
|
return PiecewiseLinearFunction::default();
|
||||||
|
}
|
||||||
|
if self.entries.len() == 1 {
|
||||||
|
// Don't bother resolving anything.
|
||||||
|
return PiecewiseLinearFunction {
|
||||||
|
entries: crate::OwnedSlice::from_slice(&[Entry {
|
||||||
|
x: 0.,
|
||||||
|
y: self.entries[0].y,
|
||||||
|
}]),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
// Guaranteed at least two elements.
|
||||||
|
// Start and end elements guaranteed to have defined x value.
|
||||||
|
// Note(dshin): Spec asserts that start/end elements are supposed to have 0/1 assigned
|
||||||
|
// respectively if their x values are undefined at this time; however, the spec does
|
||||||
|
// not disallow negative/100%+ inputs, and inputs like `linear(0, 0.1 -10%, 0.9 110%, 1.0)`
|
||||||
|
// would break the assumption that the x values in the list increase monotonically.
|
||||||
|
// Otherwise, we still want 0/1 assigned to the start/end values regardless of
|
||||||
|
// adjacent x values (i.e. `linear(0, 0.1 10%, 0.9 90%, 1.0)` ==
|
||||||
|
// `linear(0 0%, 0.1 10%, 0.9 90%, 1.0)` != `linear(0 10%, 0.1 10%, 0.9 90%, 1.0 90%)`)
|
||||||
|
self.entries[0]
|
||||||
|
.x
|
||||||
|
.get_or_insert(self.smallest_x.filter(|x| x < &0.0).unwrap_or(0.0));
|
||||||
|
self.entries
|
||||||
|
.last_mut()
|
||||||
|
.unwrap()
|
||||||
|
.x
|
||||||
|
.get_or_insert(self.largest_x.filter(|x| x > &1.0).unwrap_or(1.0));
|
||||||
|
|
||||||
|
let mut result = Vec::with_capacity(self.entries.len());
|
||||||
|
result.push(Entry {
|
||||||
|
x: self.entries[0].x.unwrap(),
|
||||||
|
y: self.entries[0].y,
|
||||||
|
});
|
||||||
|
for (i, e) in self.entries.iter().enumerate().skip(1) {
|
||||||
|
if e.x.is_none() {
|
||||||
|
// Need to calculate x values by first finding an entry with the first
|
||||||
|
// defined x value (Guaranteed to exist as the list end has it defined).
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
// x is defined for this element.
|
||||||
|
let divisor = i - result.len() + 1;
|
||||||
|
// Any element(s) with undefined x to assign?
|
||||||
|
if divisor != 1 {
|
||||||
|
// Have at least one element in result at all times.
|
||||||
|
let start_x = result.last().unwrap().x;
|
||||||
|
let increment = (e.x.unwrap() - start_x) / divisor as ValueType;
|
||||||
|
// Grab every element with undefined x to this point, which starts at the end of the result
|
||||||
|
// array, and ending right before the current index. Then, assigned the evenly divided
|
||||||
|
// x values.
|
||||||
|
result.extend(
|
||||||
|
self.entries[result.len()..i]
|
||||||
|
.iter()
|
||||||
|
.enumerate()
|
||||||
|
.map(|(j, e)| {
|
||||||
|
debug_assert!(e.x.is_none(), "Expected an entry with x undefined!");
|
||||||
|
Entry {
|
||||||
|
x: increment * (j + 1) as ValueType + start_x,
|
||||||
|
y: e.y,
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
result.push(Entry {
|
||||||
|
x: e.x.unwrap(),
|
||||||
|
y: e.y,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
debug_assert_eq!(
|
||||||
|
result.len(),
|
||||||
|
self.entries.len(),
|
||||||
|
"Should've mapped one-to-one!"
|
||||||
|
);
|
||||||
|
PiecewiseLinearFunction {
|
||||||
|
entries: result.into(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -28,34 +28,8 @@ use smallvec::SmallVec;
|
||||||
use std::borrow::Cow;
|
use std::borrow::Cow;
|
||||||
use std::cell::RefCell;
|
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)]
|
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
|
||||||
enum ApplyResetProperties {
|
enum CanHaveLogicalProperties {
|
||||||
No,
|
No,
|
||||||
Yes,
|
Yes,
|
||||||
}
|
}
|
||||||
|
@ -283,6 +257,7 @@ where
|
||||||
let inherited_style = parent_style.unwrap_or(device.default_computed_values());
|
let inherited_style = parent_style.unwrap_or(device.default_computed_values());
|
||||||
|
|
||||||
let mut declarations = SmallVec::<[(&_, CascadePriority); 32]>::new();
|
let mut declarations = SmallVec::<[(&_, CascadePriority); 32]>::new();
|
||||||
|
let mut referenced_properties = LonghandIdSet::default();
|
||||||
let custom_properties = {
|
let custom_properties = {
|
||||||
let mut builder = CustomPropertiesBuilder::new(inherited_style.custom_properties(), device);
|
let mut builder = CustomPropertiesBuilder::new(inherited_style.custom_properties(), device);
|
||||||
|
|
||||||
|
@ -290,6 +265,8 @@ where
|
||||||
declarations.push((declaration, priority));
|
declarations.push((declaration, priority));
|
||||||
if let PropertyDeclaration::Custom(ref declaration) = *declaration {
|
if let PropertyDeclaration::Custom(ref declaration) = *declaration {
|
||||||
builder.cascade(declaration, priority);
|
builder.cascade(declaration, priority);
|
||||||
|
} else {
|
||||||
|
referenced_properties.insert(declaration.id().as_longhand().unwrap());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -315,46 +292,74 @@ where
|
||||||
in_media_query: false,
|
in_media_query: false,
|
||||||
for_smil_animation: false,
|
for_smil_animation: false,
|
||||||
for_non_inherited_property: None,
|
for_non_inherited_property: None,
|
||||||
|
container_info: None,
|
||||||
quirks_mode,
|
quirks_mode,
|
||||||
rule_cache_conditions: RefCell::new(rule_cache_conditions),
|
rule_cache_conditions: RefCell::new(rule_cache_conditions),
|
||||||
};
|
};
|
||||||
|
|
||||||
let using_cached_reset_properties = {
|
let using_cached_reset_properties;
|
||||||
let mut cascade = Cascade::new(&mut context, cascade_mode);
|
let mut cascade = Cascade::new(&mut context, cascade_mode, &referenced_properties);
|
||||||
let mut shorthand_cache = ShorthandsWithPropertyReferencesCache::default();
|
let mut shorthand_cache = ShorthandsWithPropertyReferencesCache::default();
|
||||||
|
|
||||||
cascade.apply_properties::<EarlyProperties, _>(
|
let properties_to_apply = match cascade.cascade_mode {
|
||||||
ApplyResetProperties::Yes,
|
CascadeMode::Visited { writing_mode } => {
|
||||||
declarations.iter().cloned(),
|
cascade.context.builder.writing_mode = writing_mode;
|
||||||
&mut shorthand_cache,
|
// 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(
|
if cascade.apply_properties(
|
||||||
element,
|
CanHaveLogicalProperties::No,
|
||||||
parent_style,
|
LonghandIdSet::fonts_and_color_group(),
|
||||||
parent_style_ignoring_first_line,
|
declarations.iter().cloned(),
|
||||||
layout_parent_style,
|
&mut shorthand_cache,
|
||||||
guards,
|
) {
|
||||||
);
|
cascade.fixup_font_stuff();
|
||||||
|
}
|
||||||
|
|
||||||
let using_cached_reset_properties =
|
if let Some(visited_rules) = visited_rules {
|
||||||
cascade.try_to_use_cached_reset_properties(rule_cache, guards);
|
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 {
|
using_cached_reset_properties =
|
||||||
ApplyResetProperties::No
|
cascade.try_to_use_cached_reset_properties(rule_cache, guards);
|
||||||
} else {
|
|
||||||
ApplyResetProperties::Yes
|
|
||||||
};
|
|
||||||
|
|
||||||
cascade.apply_properties::<LateProperties, _>(
|
if using_cached_reset_properties {
|
||||||
apply_reset,
|
LonghandIdSet::late_group_only_inherited()
|
||||||
declarations.iter().cloned(),
|
} else {
|
||||||
&mut shorthand_cache,
|
LonghandIdSet::late_group()
|
||||||
);
|
}
|
||||||
|
}
|
||||||
using_cached_reset_properties
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
cascade.apply_properties(
|
||||||
|
CanHaveLogicalProperties::Yes,
|
||||||
|
properties_to_apply,
|
||||||
|
declarations.iter().cloned(),
|
||||||
|
&mut shorthand_cache,
|
||||||
|
);
|
||||||
|
|
||||||
|
cascade.finished_applying_properties();
|
||||||
|
|
||||||
context.builder.clear_modified_reset();
|
context.builder.clear_modified_reset();
|
||||||
|
|
||||||
if matches!(cascade_mode, CascadeMode::Unvisited { .. }) {
|
if matches!(cascade_mode, CascadeMode::Unvisited { .. }) {
|
||||||
|
@ -413,7 +418,7 @@ fn tweak_when_ignoring_colors(
|
||||||
|
|
||||||
fn alpha_channel(color: &Color, context: &computed::Context) -> u8 {
|
fn alpha_channel(color: &Color, context: &computed::Context) -> u8 {
|
||||||
// We assume here currentColor is opaque.
|
// 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
|
color.alpha
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -428,14 +433,17 @@ fn tweak_when_ignoring_colors(
|
||||||
// otherwise, this is needed to preserve semi-transparent
|
// otherwise, this is needed to preserve semi-transparent
|
||||||
// backgrounds.
|
// backgrounds.
|
||||||
//
|
//
|
||||||
// NOTE(emilio): We revert even for alpha == 0. Not doing so would
|
// NOTE(emilio): We honor transparent unconditionally, like we do
|
||||||
// be a bit special casey, even though it causes issues like
|
// for color, even though it causes issues like bug 1625036. The
|
||||||
// bug 1625036. The reasoning is that the conditions that trigger
|
// reasoning is that the conditions that trigger that (having
|
||||||
// that (having mismatched widget and default backgrounds) are both
|
// mismatched widget and default backgrounds) are both uncommon, and
|
||||||
// uncommon, and broken in other applications as well, and not
|
// broken in other applications as well, and not honoring
|
||||||
// honoring transparent makes stuff uglier or break unconditionally
|
// transparent makes stuff uglier or break unconditionally
|
||||||
// (bug 1666059, bug 1755713).
|
// (bug 1666059, bug 1755713).
|
||||||
let alpha = alpha_channel(color, context);
|
let alpha = alpha_channel(color, context);
|
||||||
|
if alpha == 0 {
|
||||||
|
return;
|
||||||
|
}
|
||||||
let mut color = context.builder.device.default_background_color();
|
let mut color = context.builder.device.default_background_color();
|
||||||
color.alpha = alpha;
|
color.alpha = alpha;
|
||||||
declarations_to_apply_unless_overriden
|
declarations_to_apply_unless_overriden
|
||||||
|
@ -497,6 +505,8 @@ fn tweak_when_ignoring_colors(
|
||||||
struct Cascade<'a, 'b: 'a> {
|
struct Cascade<'a, 'b: 'a> {
|
||||||
context: &'a mut computed::Context<'b>,
|
context: &'a mut computed::Context<'b>,
|
||||||
cascade_mode: CascadeMode<'a>,
|
cascade_mode: CascadeMode<'a>,
|
||||||
|
/// All the properties that have a declaration in the cascade.
|
||||||
|
referenced: &'a LonghandIdSet,
|
||||||
seen: LonghandIdSet,
|
seen: LonghandIdSet,
|
||||||
author_specified: LonghandIdSet,
|
author_specified: LonghandIdSet,
|
||||||
reverted_set: LonghandIdSet,
|
reverted_set: LonghandIdSet,
|
||||||
|
@ -504,10 +514,15 @@ struct Cascade<'a, 'b: 'a> {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a, 'b: 'a> Cascade<'a, 'b> {
|
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 {
|
Self {
|
||||||
context,
|
context,
|
||||||
cascade_mode,
|
cascade_mode,
|
||||||
|
referenced,
|
||||||
seen: LonghandIdSet::default(),
|
seen: LonghandIdSet::default(),
|
||||||
author_specified: LonghandIdSet::default(),
|
author_specified: LonghandIdSet::default(),
|
||||||
reverted_set: Default::default(),
|
reverted_set: Default::default(),
|
||||||
|
@ -575,23 +590,21 @@ impl<'a, 'b: 'a> Cascade<'a, 'b> {
|
||||||
(CASCADE_PROPERTY[discriminant])(declaration, &mut self.context);
|
(CASCADE_PROPERTY[discriminant])(declaration, &mut self.context);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn apply_properties<'decls, Phase, I>(
|
fn apply_properties<'decls, I>(
|
||||||
&mut self,
|
&mut self,
|
||||||
apply_reset: ApplyResetProperties,
|
can_have_logical_properties: CanHaveLogicalProperties,
|
||||||
|
properties_to_apply: &'a LonghandIdSet,
|
||||||
declarations: I,
|
declarations: I,
|
||||||
mut shorthand_cache: &mut ShorthandsWithPropertyReferencesCache,
|
mut shorthand_cache: &mut ShorthandsWithPropertyReferencesCache,
|
||||||
) where
|
) -> bool
|
||||||
Phase: CascadePhase,
|
where
|
||||||
I: Iterator<Item = (&'decls PropertyDeclaration, CascadePriority)>,
|
I: Iterator<Item = (&'decls PropertyDeclaration, CascadePriority)>,
|
||||||
{
|
{
|
||||||
let apply_reset = apply_reset == ApplyResetProperties::Yes;
|
if !self.referenced.contains_any(properties_to_apply) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
debug_assert!(
|
let can_have_logical_properties = can_have_logical_properties == CanHaveLogicalProperties::Yes;
|
||||||
!Phase::is_early() || apply_reset,
|
|
||||||
"Should always apply reset properties in the early phase, since we \
|
|
||||||
need to know font-size / writing-mode to decide whether to use the \
|
|
||||||
cached reset properties"
|
|
||||||
);
|
|
||||||
|
|
||||||
let ignore_colors = !self.context.builder.device.use_document_colors();
|
let ignore_colors = !self.context.builder.device.use_document_colors();
|
||||||
let mut declarations_to_apply_unless_overriden = DeclarationsToApplyUnlessOverriden::new();
|
let mut declarations_to_apply_unless_overriden = DeclarationsToApplyUnlessOverriden::new();
|
||||||
|
@ -605,20 +618,15 @@ impl<'a, 'b: 'a> Cascade<'a, 'b> {
|
||||||
PropertyDeclarationId::Custom(..) => continue,
|
PropertyDeclarationId::Custom(..) => continue,
|
||||||
};
|
};
|
||||||
|
|
||||||
let inherited = longhand_id.inherited();
|
if !properties_to_apply.contains(longhand_id) {
|
||||||
if !apply_reset && !inherited {
|
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if Phase::is_early() != longhand_id.is_early_property() {
|
debug_assert!(can_have_logical_properties || !longhand_id.is_logical());
|
||||||
continue;
|
let physical_longhand_id = if can_have_logical_properties {
|
||||||
}
|
|
||||||
|
|
||||||
debug_assert!(!Phase::is_early() || !longhand_id.is_logical());
|
|
||||||
let physical_longhand_id = if Phase::is_early() {
|
|
||||||
longhand_id
|
|
||||||
} else {
|
|
||||||
longhand_id.to_physical(self.context.builder.writing_mode)
|
longhand_id.to_physical(self.context.builder.writing_mode)
|
||||||
|
} else {
|
||||||
|
longhand_id
|
||||||
};
|
};
|
||||||
|
|
||||||
if self.seen.contains(physical_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 =
|
let mut declaration =
|
||||||
self.substitute_variables_if_needed(declaration, &mut shorthand_cache);
|
self.substitute_variables_if_needed(declaration, &mut shorthand_cache);
|
||||||
|
|
||||||
|
@ -675,8 +674,8 @@ impl<'a, 'b: 'a> Cascade<'a, 'b> {
|
||||||
continue;
|
continue;
|
||||||
},
|
},
|
||||||
CSSWideKeyword::Unset => true,
|
CSSWideKeyword::Unset => true,
|
||||||
CSSWideKeyword::Inherit => inherited,
|
CSSWideKeyword::Inherit => longhand_id.inherited(),
|
||||||
CSSWideKeyword::Initial => !inherited,
|
CSSWideKeyword::Initial => !longhand_id.inherited(),
|
||||||
},
|
},
|
||||||
None => false,
|
None => false,
|
||||||
};
|
};
|
||||||
|
@ -710,22 +709,13 @@ impl<'a, 'b: 'a> Cascade<'a, 'b> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if Phase::is_early() {
|
true
|
||||||
self.fixup_font_stuff();
|
|
||||||
self.compute_writing_mode();
|
|
||||||
} else {
|
|
||||||
self.finished_applying_properties();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn compute_writing_mode(&mut self) {
|
fn compute_writing_mode(&mut self) {
|
||||||
let writing_mode = match self.cascade_mode {
|
debug_assert!(matches!(self.cascade_mode, CascadeMode::Unvisited { .. }));
|
||||||
CascadeMode::Unvisited { .. } => {
|
self.context.builder.writing_mode =
|
||||||
WritingMode::new(self.context.builder.get_inherited_box())
|
WritingMode::new(self.context.builder.get_inherited_box())
|
||||||
},
|
|
||||||
CascadeMode::Visited { writing_mode } => writing_mode,
|
|
||||||
};
|
|
||||||
self.context.builder.writing_mode = writing_mode;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn compute_visited_style_if_needed<E>(
|
fn compute_visited_style_if_needed<E>(
|
||||||
|
@ -734,20 +724,12 @@ impl<'a, 'b: 'a> Cascade<'a, 'b> {
|
||||||
parent_style: Option<&ComputedValues>,
|
parent_style: Option<&ComputedValues>,
|
||||||
parent_style_ignoring_first_line: Option<&ComputedValues>,
|
parent_style_ignoring_first_line: Option<&ComputedValues>,
|
||||||
layout_parent_style: Option<&ComputedValues>,
|
layout_parent_style: Option<&ComputedValues>,
|
||||||
|
visited_rules: &StrongRuleNode,
|
||||||
guards: &StylesheetGuards,
|
guards: &StylesheetGuards,
|
||||||
) where
|
) where
|
||||||
E: TElement,
|
E: TElement,
|
||||||
{
|
{
|
||||||
let visited_rules = match self.cascade_mode {
|
debug_assert!(matches!(self.cascade_mode, CascadeMode::Unvisited { .. }));
|
||||||
CascadeMode::Unvisited { visited_rules } => visited_rules,
|
|
||||||
CascadeMode::Visited { .. } => return,
|
|
||||||
};
|
|
||||||
|
|
||||||
let visited_rules = match visited_rules {
|
|
||||||
Some(rules) => rules,
|
|
||||||
None => return,
|
|
||||||
};
|
|
||||||
|
|
||||||
let is_link = self.context.builder.pseudo.is_none() && element.unwrap().is_link();
|
let is_link = self.context.builder.pseudo.is_none() && element.unwrap().is_link();
|
||||||
|
|
||||||
macro_rules! visited_parent {
|
macro_rules! visited_parent {
|
||||||
|
|
|
@ -73,7 +73,6 @@ COUNTED_UNKNOWN_PROPERTIES = [
|
||||||
"-webkit-perspective-origin-y",
|
"-webkit-perspective-origin-y",
|
||||||
"-webkit-margin-before-collapse",
|
"-webkit-margin-before-collapse",
|
||||||
"-webkit-border-before-style",
|
"-webkit-border-before-style",
|
||||||
"scroll-snap-stop",
|
|
||||||
"-webkit-margin-bottom-collapse",
|
"-webkit-margin-bottom-collapse",
|
||||||
"-webkit-ruby-position",
|
"-webkit-ruby-position",
|
||||||
"-webkit-column-break-after",
|
"-webkit-column-break-after",
|
||||||
|
|
|
@ -444,6 +444,7 @@ class Longhand(Property):
|
||||||
"ColumnCount",
|
"ColumnCount",
|
||||||
"Contain",
|
"Contain",
|
||||||
"ContentVisibility",
|
"ContentVisibility",
|
||||||
|
"ContainerType",
|
||||||
"Display",
|
"Display",
|
||||||
"FillRule",
|
"FillRule",
|
||||||
"Float",
|
"Float",
|
||||||
|
@ -491,6 +492,7 @@ class Longhand(Property):
|
||||||
"ScrollbarGutter",
|
"ScrollbarGutter",
|
||||||
"ScrollSnapAlign",
|
"ScrollSnapAlign",
|
||||||
"ScrollSnapAxis",
|
"ScrollSnapAxis",
|
||||||
|
"ScrollSnapStop",
|
||||||
"ScrollSnapStrictness",
|
"ScrollSnapStrictness",
|
||||||
"ScrollSnapType",
|
"ScrollSnapType",
|
||||||
"TextAlign",
|
"TextAlign",
|
||||||
|
|
|
@ -755,7 +755,6 @@ fn static_assert() {
|
||||||
<%self:impl_trait style_struct_name="Margin"
|
<%self:impl_trait style_struct_name="Margin"
|
||||||
skip_longhands="${skip_margin_longhands}
|
skip_longhands="${skip_margin_longhands}
|
||||||
${skip_scroll_margin_longhands}">
|
${skip_scroll_margin_longhands}">
|
||||||
|
|
||||||
% for side in SIDES:
|
% for side in SIDES:
|
||||||
<% impl_split_style_coord("margin_%s" % side.ident,
|
<% impl_split_style_coord("margin_%s" % side.ident,
|
||||||
"mMargin",
|
"mMargin",
|
||||||
|
@ -1181,11 +1180,7 @@ fn static_assert() {
|
||||||
</%def>
|
</%def>
|
||||||
|
|
||||||
<% skip_box_longhands= """display
|
<% skip_box_longhands= """display
|
||||||
animation-name animation-delay animation-duration
|
clear
|
||||||
animation-direction animation-fill-mode animation-play-state
|
|
||||||
animation-iteration-count animation-timeline animation-timing-function
|
|
||||||
clear transition-duration transition-delay
|
|
||||||
transition-timing-function transition-property
|
|
||||||
-webkit-line-clamp""" %>
|
-webkit-line-clamp""" %>
|
||||||
<%self:impl_trait style_struct_name="Box" skip_longhands="${skip_box_longhands}">
|
<%self:impl_trait style_struct_name="Box" skip_longhands="${skip_box_longhands}">
|
||||||
#[inline]
|
#[inline]
|
||||||
|
@ -1227,245 +1222,6 @@ fn static_assert() {
|
||||||
) %>
|
) %>
|
||||||
${impl_keyword('clear', 'mBreakType', clear_keyword)}
|
${impl_keyword('clear', 'mBreakType', clear_keyword)}
|
||||||
|
|
||||||
${impl_transition_time_value('delay', 'Delay')}
|
|
||||||
${impl_transition_time_value('duration', 'Duration')}
|
|
||||||
${impl_animation_or_transition_timing_function('transition')}
|
|
||||||
|
|
||||||
pub fn transition_combined_duration_at(&self, index: usize) -> f32 {
|
|
||||||
// https://drafts.csswg.org/css-transitions/#transition-combined-duration
|
|
||||||
self.gecko.mTransitions[index % self.gecko.mTransitionDurationCount as usize].mDuration.max(0.0)
|
|
||||||
+ self.gecko.mTransitions[index % self.gecko.mTransitionDelayCount as usize].mDelay
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn set_transition_property<I>(&mut self, v: I)
|
|
||||||
where I: IntoIterator<Item = longhands::transition_property::computed_value::single_value::T>,
|
|
||||||
I::IntoIter: ExactSizeIterator
|
|
||||||
{
|
|
||||||
use crate::gecko_bindings::structs::nsCSSPropertyID::eCSSPropertyExtra_no_properties;
|
|
||||||
use crate::gecko_bindings::structs::nsCSSPropertyID::eCSSPropertyExtra_variable;
|
|
||||||
use crate::gecko_bindings::structs::nsCSSPropertyID::eCSSProperty_UNKNOWN;
|
|
||||||
|
|
||||||
let v = v.into_iter();
|
|
||||||
|
|
||||||
if v.len() != 0 {
|
|
||||||
self.gecko.mTransitions.ensure_len(v.len());
|
|
||||||
self.gecko.mTransitionPropertyCount = v.len() as u32;
|
|
||||||
for (servo, gecko) in v.zip(self.gecko.mTransitions.iter_mut()) {
|
|
||||||
unsafe { gecko.mUnknownProperty.clear() };
|
|
||||||
|
|
||||||
match servo {
|
|
||||||
TransitionProperty::Unsupported(ident) => {
|
|
||||||
gecko.mProperty = eCSSProperty_UNKNOWN;
|
|
||||||
gecko.mUnknownProperty.mRawPtr = ident.0.into_addrefed();
|
|
||||||
},
|
|
||||||
TransitionProperty::Custom(name) => {
|
|
||||||
gecko.mProperty = eCSSPropertyExtra_variable;
|
|
||||||
gecko.mUnknownProperty.mRawPtr = name.into_addrefed();
|
|
||||||
}
|
|
||||||
_ => gecko.mProperty = servo.to_nscsspropertyid().unwrap(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// In gecko |none| is represented by eCSSPropertyExtra_no_properties.
|
|
||||||
self.gecko.mTransitionPropertyCount = 1;
|
|
||||||
self.gecko.mTransitions[0].mProperty = eCSSPropertyExtra_no_properties;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns whether there are any transitions specified.
|
|
||||||
pub fn specifies_transitions(&self) -> bool {
|
|
||||||
use crate::gecko_bindings::structs::nsCSSPropertyID::eCSSPropertyExtra_all_properties;
|
|
||||||
if self.gecko.mTransitionPropertyCount == 1 &&
|
|
||||||
self.gecko.mTransitions[0].mProperty == eCSSPropertyExtra_all_properties &&
|
|
||||||
self.transition_combined_duration_at(0) <= 0.0f32 {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
self.gecko.mTransitionPropertyCount > 0
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn transition_property_at(&self, index: usize)
|
|
||||||
-> longhands::transition_property::computed_value::SingleComputedValue {
|
|
||||||
use crate::gecko_bindings::structs::nsCSSPropertyID::eCSSPropertyExtra_no_properties;
|
|
||||||
use crate::gecko_bindings::structs::nsCSSPropertyID::eCSSPropertyExtra_variable;
|
|
||||||
use crate::gecko_bindings::structs::nsCSSPropertyID::eCSSProperty_UNKNOWN;
|
|
||||||
|
|
||||||
let property = self.gecko.mTransitions[index].mProperty;
|
|
||||||
if property == eCSSProperty_UNKNOWN {
|
|
||||||
let atom = self.gecko.mTransitions[index].mUnknownProperty.mRawPtr;
|
|
||||||
debug_assert!(!atom.is_null());
|
|
||||||
TransitionProperty::Unsupported(CustomIdent(unsafe{
|
|
||||||
Atom::from_raw(atom)
|
|
||||||
}))
|
|
||||||
} else if property == eCSSPropertyExtra_variable {
|
|
||||||
let atom = self.gecko.mTransitions[index].mUnknownProperty.mRawPtr;
|
|
||||||
debug_assert!(!atom.is_null());
|
|
||||||
TransitionProperty::Custom(unsafe{
|
|
||||||
Atom::from_raw(atom)
|
|
||||||
})
|
|
||||||
} else if property == eCSSPropertyExtra_no_properties {
|
|
||||||
// Actually, we don't expect TransitionProperty::Unsupported also
|
|
||||||
// represents "none", but if the caller wants to convert it, it is
|
|
||||||
// fine. Please use it carefully.
|
|
||||||
//
|
|
||||||
// FIXME(emilio): This is a hack, is this reachable?
|
|
||||||
TransitionProperty::Unsupported(CustomIdent(atom!("none")))
|
|
||||||
} else {
|
|
||||||
property.into()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn transition_nscsspropertyid_at(&self, index: usize) -> nsCSSPropertyID {
|
|
||||||
self.gecko.mTransitions[index].mProperty
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn copy_transition_property_from(&mut self, other: &Self) {
|
|
||||||
use crate::gecko_bindings::structs::nsCSSPropertyID::eCSSPropertyExtra_variable;
|
|
||||||
use crate::gecko_bindings::structs::nsCSSPropertyID::eCSSProperty_UNKNOWN;
|
|
||||||
self.gecko.mTransitions.ensure_len(other.gecko.mTransitions.len());
|
|
||||||
|
|
||||||
let count = other.gecko.mTransitionPropertyCount;
|
|
||||||
self.gecko.mTransitionPropertyCount = count;
|
|
||||||
|
|
||||||
for (index, transition) in self.gecko.mTransitions.iter_mut().enumerate().take(count as usize) {
|
|
||||||
transition.mProperty = other.gecko.mTransitions[index].mProperty;
|
|
||||||
unsafe { transition.mUnknownProperty.clear() };
|
|
||||||
if transition.mProperty == eCSSProperty_UNKNOWN ||
|
|
||||||
transition.mProperty == eCSSPropertyExtra_variable {
|
|
||||||
let atom = other.gecko.mTransitions[index].mUnknownProperty.mRawPtr;
|
|
||||||
debug_assert!(!atom.is_null());
|
|
||||||
transition.mUnknownProperty.mRawPtr = unsafe { Atom::from_raw(atom) }.into_addrefed();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn reset_transition_property(&mut self, other: &Self) {
|
|
||||||
self.copy_transition_property_from(other)
|
|
||||||
}
|
|
||||||
|
|
||||||
${impl_transition_count('property', 'Property')}
|
|
||||||
|
|
||||||
pub fn animations_equals(&self, other: &Self) -> bool {
|
|
||||||
return self.gecko.mAnimationNameCount == other.gecko.mAnimationNameCount
|
|
||||||
&& self.gecko.mAnimationDelayCount == other.gecko.mAnimationDelayCount
|
|
||||||
&& self.gecko.mAnimationDirectionCount == other.gecko.mAnimationDirectionCount
|
|
||||||
&& self.gecko.mAnimationDurationCount == other.gecko.mAnimationDurationCount
|
|
||||||
&& self.gecko.mAnimationFillModeCount == other.gecko.mAnimationFillModeCount
|
|
||||||
&& self.gecko.mAnimationIterationCountCount == other.gecko.mAnimationIterationCountCount
|
|
||||||
&& self.gecko.mAnimationPlayStateCount == other.gecko.mAnimationPlayStateCount
|
|
||||||
&& self.gecko.mAnimationTimingFunctionCount == other.gecko.mAnimationTimingFunctionCount
|
|
||||||
&& unsafe { bindings::Gecko_StyleAnimationsEquals(&self.gecko.mAnimations, &other.gecko.mAnimations) }
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn set_animation_name<I>(&mut self, v: I)
|
|
||||||
where I: IntoIterator<Item = longhands::animation_name::computed_value::single_value::T>,
|
|
||||||
I::IntoIter: ExactSizeIterator
|
|
||||||
{
|
|
||||||
let v = v.into_iter();
|
|
||||||
debug_assert_ne!(v.len(), 0);
|
|
||||||
self.gecko.mAnimations.ensure_len(v.len());
|
|
||||||
|
|
||||||
self.gecko.mAnimationNameCount = v.len() as u32;
|
|
||||||
for (servo, gecko) in v.zip(self.gecko.mAnimations.iter_mut()) {
|
|
||||||
let atom = match servo.0 {
|
|
||||||
None => atom!(""),
|
|
||||||
Some(ref name) => name.as_atom().clone(),
|
|
||||||
};
|
|
||||||
unsafe { bindings::Gecko_SetAnimationName(gecko, atom.into_addrefed()); }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
pub fn animation_name_at(&self, index: usize)
|
|
||||||
-> longhands::animation_name::computed_value::SingleComputedValue {
|
|
||||||
use crate::properties::longhands::animation_name::single_value::SpecifiedValue as AnimationName;
|
|
||||||
|
|
||||||
let atom = self.gecko.mAnimations[index].mName.mRawPtr;
|
|
||||||
if atom == atom!("").as_ptr() {
|
|
||||||
return AnimationName(None)
|
|
||||||
}
|
|
||||||
AnimationName(Some(KeyframesName::from_atom(unsafe { Atom::from_raw(atom) })))
|
|
||||||
}
|
|
||||||
pub fn copy_animation_name_from(&mut self, other: &Self) {
|
|
||||||
self.gecko.mAnimationNameCount = other.gecko.mAnimationNameCount;
|
|
||||||
unsafe { bindings::Gecko_CopyAnimationNames(&mut self.gecko.mAnimations, &other.gecko.mAnimations); }
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn reset_animation_name(&mut self, other: &Self) {
|
|
||||||
self.copy_animation_name_from(other)
|
|
||||||
}
|
|
||||||
|
|
||||||
${impl_animation_count('name', 'Name')}
|
|
||||||
|
|
||||||
${impl_animation_time_value('delay', 'Delay')}
|
|
||||||
${impl_animation_time_value('duration', 'Duration')}
|
|
||||||
|
|
||||||
${impl_animation_keyword('direction', 'Direction',
|
|
||||||
data.longhands_by_name["animation-direction"].keyword)}
|
|
||||||
${impl_animation_keyword('fill_mode', 'FillMode',
|
|
||||||
data.longhands_by_name["animation-fill-mode"].keyword)}
|
|
||||||
${impl_animation_keyword('play_state', 'PlayState',
|
|
||||||
data.longhands_by_name["animation-play-state"].keyword)}
|
|
||||||
|
|
||||||
pub fn set_animation_iteration_count<I>(&mut self, v: I)
|
|
||||||
where
|
|
||||||
I: IntoIterator<Item = values::computed::AnimationIterationCount>,
|
|
||||||
I::IntoIter: ExactSizeIterator + Clone
|
|
||||||
{
|
|
||||||
use std::f32;
|
|
||||||
use crate::values::generics::box_::AnimationIterationCount;
|
|
||||||
|
|
||||||
let v = v.into_iter();
|
|
||||||
|
|
||||||
debug_assert_ne!(v.len(), 0);
|
|
||||||
let input_len = v.len();
|
|
||||||
self.gecko.mAnimations.ensure_len(input_len);
|
|
||||||
|
|
||||||
self.gecko.mAnimationIterationCountCount = input_len as u32;
|
|
||||||
for (gecko, servo) in self.gecko.mAnimations.iter_mut().take(input_len as usize).zip(v) {
|
|
||||||
match servo {
|
|
||||||
AnimationIterationCount::Number(n) => gecko.mIterationCount = n,
|
|
||||||
AnimationIterationCount::Infinite => gecko.mIterationCount = f32::INFINITY,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn animation_iteration_count_at(
|
|
||||||
&self,
|
|
||||||
index: usize,
|
|
||||||
) -> values::computed::AnimationIterationCount {
|
|
||||||
use crate::values::generics::box_::AnimationIterationCount;
|
|
||||||
|
|
||||||
if self.gecko.mAnimations[index].mIterationCount.is_infinite() {
|
|
||||||
AnimationIterationCount::Infinite
|
|
||||||
} else {
|
|
||||||
AnimationIterationCount::Number(self.gecko.mAnimations[index].mIterationCount)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
${impl_animation_count('iteration_count', 'IterationCount')}
|
|
||||||
${impl_copy_animation_value('iteration_count', 'IterationCount')}
|
|
||||||
${impl_animation_or_transition_timing_function('animation')}
|
|
||||||
|
|
||||||
pub fn set_animation_timeline<I>(&mut self, v: I)
|
|
||||||
where
|
|
||||||
I: IntoIterator<Item = longhands::animation_timeline::computed_value::single_value::T>,
|
|
||||||
I::IntoIter: ExactSizeIterator
|
|
||||||
{
|
|
||||||
let v = v.into_iter();
|
|
||||||
debug_assert_ne!(v.len(), 0);
|
|
||||||
let input_len = v.len();
|
|
||||||
self.gecko.mAnimations.ensure_len(input_len);
|
|
||||||
|
|
||||||
self.gecko.mAnimationTimelineCount = input_len as u32;
|
|
||||||
for (gecko, servo) in self.gecko.mAnimations.iter_mut().take(input_len as usize).zip(v) {
|
|
||||||
gecko.mTimeline = servo;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
pub fn animation_timeline_at(&self, index: usize) -> values::specified::box_::AnimationTimeline {
|
|
||||||
self.gecko.mAnimations[index].mTimeline.clone()
|
|
||||||
}
|
|
||||||
${impl_animation_count('timeline', 'Timeline')}
|
|
||||||
${impl_copy_animation_value('timeline', 'Timeline')}
|
|
||||||
|
|
||||||
#[allow(non_snake_case)]
|
#[allow(non_snake_case)]
|
||||||
pub fn set__webkit_line_clamp(&mut self, v: longhands::_webkit_line_clamp::computed_value::T) {
|
pub fn set__webkit_line_clamp(&mut self, v: longhands::_webkit_line_clamp::computed_value::T) {
|
||||||
self.gecko.mLineClamp = match v {
|
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>
|
</%self:impl_trait>
|
||||||
|
|
||||||
<%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<I>(&mut self, v: I)
|
||||||
|
where I: IntoIterator<Item = longhands::transition_property::computed_value::single_value::T>,
|
||||||
|
I::IntoIter: ExactSizeIterator
|
||||||
|
{
|
||||||
|
use crate::gecko_bindings::structs::nsCSSPropertyID::eCSSPropertyExtra_no_properties;
|
||||||
|
use crate::gecko_bindings::structs::nsCSSPropertyID::eCSSPropertyExtra_variable;
|
||||||
|
use crate::gecko_bindings::structs::nsCSSPropertyID::eCSSProperty_UNKNOWN;
|
||||||
|
|
||||||
|
let v = v.into_iter();
|
||||||
|
|
||||||
|
if v.len() != 0 {
|
||||||
|
self.gecko.mTransitions.ensure_len(v.len());
|
||||||
|
self.gecko.mTransitionPropertyCount = v.len() as u32;
|
||||||
|
for (servo, gecko) in v.zip(self.gecko.mTransitions.iter_mut()) {
|
||||||
|
unsafe { gecko.mUnknownProperty.clear() };
|
||||||
|
|
||||||
|
match servo {
|
||||||
|
TransitionProperty::Unsupported(ident) => {
|
||||||
|
gecko.mProperty = eCSSProperty_UNKNOWN;
|
||||||
|
gecko.mUnknownProperty.mRawPtr = ident.0.into_addrefed();
|
||||||
|
},
|
||||||
|
TransitionProperty::Custom(name) => {
|
||||||
|
gecko.mProperty = eCSSPropertyExtra_variable;
|
||||||
|
gecko.mUnknownProperty.mRawPtr = name.into_addrefed();
|
||||||
|
}
|
||||||
|
_ => gecko.mProperty = servo.to_nscsspropertyid().unwrap(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// In gecko |none| is represented by eCSSPropertyExtra_no_properties.
|
||||||
|
self.gecko.mTransitionPropertyCount = 1;
|
||||||
|
self.gecko.mTransitions[0].mProperty = eCSSPropertyExtra_no_properties;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns whether there are any transitions specified.
|
||||||
|
pub fn specifies_transitions(&self) -> bool {
|
||||||
|
use crate::gecko_bindings::structs::nsCSSPropertyID::eCSSPropertyExtra_all_properties;
|
||||||
|
if self.gecko.mTransitionPropertyCount == 1 &&
|
||||||
|
self.gecko.mTransitions[0].mProperty == eCSSPropertyExtra_all_properties &&
|
||||||
|
self.transition_combined_duration_at(0) <= 0.0f32 {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
self.gecko.mTransitionPropertyCount > 0
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn transition_property_at(&self, index: usize)
|
||||||
|
-> longhands::transition_property::computed_value::SingleComputedValue {
|
||||||
|
use crate::gecko_bindings::structs::nsCSSPropertyID::eCSSPropertyExtra_no_properties;
|
||||||
|
use crate::gecko_bindings::structs::nsCSSPropertyID::eCSSPropertyExtra_variable;
|
||||||
|
use crate::gecko_bindings::structs::nsCSSPropertyID::eCSSProperty_UNKNOWN;
|
||||||
|
|
||||||
|
let property = self.gecko.mTransitions[index].mProperty;
|
||||||
|
if property == eCSSProperty_UNKNOWN {
|
||||||
|
let atom = self.gecko.mTransitions[index].mUnknownProperty.mRawPtr;
|
||||||
|
debug_assert!(!atom.is_null());
|
||||||
|
TransitionProperty::Unsupported(CustomIdent(unsafe{
|
||||||
|
Atom::from_raw(atom)
|
||||||
|
}))
|
||||||
|
} else if property == eCSSPropertyExtra_variable {
|
||||||
|
let atom = self.gecko.mTransitions[index].mUnknownProperty.mRawPtr;
|
||||||
|
debug_assert!(!atom.is_null());
|
||||||
|
TransitionProperty::Custom(unsafe{
|
||||||
|
Atom::from_raw(atom)
|
||||||
|
})
|
||||||
|
} else if property == eCSSPropertyExtra_no_properties {
|
||||||
|
// Actually, we don't expect TransitionProperty::Unsupported also
|
||||||
|
// represents "none", but if the caller wants to convert it, it is
|
||||||
|
// fine. Please use it carefully.
|
||||||
|
//
|
||||||
|
// FIXME(emilio): This is a hack, is this reachable?
|
||||||
|
TransitionProperty::Unsupported(CustomIdent(atom!("none")))
|
||||||
|
} else {
|
||||||
|
property.into()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn transition_nscsspropertyid_at(&self, index: usize) -> nsCSSPropertyID {
|
||||||
|
self.gecko.mTransitions[index].mProperty
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn copy_transition_property_from(&mut self, other: &Self) {
|
||||||
|
use crate::gecko_bindings::structs::nsCSSPropertyID::eCSSPropertyExtra_variable;
|
||||||
|
use crate::gecko_bindings::structs::nsCSSPropertyID::eCSSProperty_UNKNOWN;
|
||||||
|
self.gecko.mTransitions.ensure_len(other.gecko.mTransitions.len());
|
||||||
|
|
||||||
|
let count = other.gecko.mTransitionPropertyCount;
|
||||||
|
self.gecko.mTransitionPropertyCount = count;
|
||||||
|
|
||||||
|
for (index, transition) in self.gecko.mTransitions.iter_mut().enumerate().take(count as usize) {
|
||||||
|
transition.mProperty = other.gecko.mTransitions[index].mProperty;
|
||||||
|
unsafe { transition.mUnknownProperty.clear() };
|
||||||
|
if transition.mProperty == eCSSProperty_UNKNOWN ||
|
||||||
|
transition.mProperty == eCSSPropertyExtra_variable {
|
||||||
|
let atom = other.gecko.mTransitions[index].mUnknownProperty.mRawPtr;
|
||||||
|
debug_assert!(!atom.is_null());
|
||||||
|
transition.mUnknownProperty.mRawPtr = unsafe { Atom::from_raw(atom) }.into_addrefed();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn reset_transition_property(&mut self, other: &Self) {
|
||||||
|
self.copy_transition_property_from(other)
|
||||||
|
}
|
||||||
|
|
||||||
|
${impl_transition_count('property', 'Property')}
|
||||||
|
|
||||||
|
pub fn animations_equals(&self, other: &Self) -> bool {
|
||||||
|
return self.gecko.mAnimationNameCount == other.gecko.mAnimationNameCount
|
||||||
|
&& self.gecko.mAnimationDelayCount == other.gecko.mAnimationDelayCount
|
||||||
|
&& self.gecko.mAnimationDirectionCount == other.gecko.mAnimationDirectionCount
|
||||||
|
&& self.gecko.mAnimationDurationCount == other.gecko.mAnimationDurationCount
|
||||||
|
&& self.gecko.mAnimationFillModeCount == other.gecko.mAnimationFillModeCount
|
||||||
|
&& self.gecko.mAnimationIterationCountCount == other.gecko.mAnimationIterationCountCount
|
||||||
|
&& self.gecko.mAnimationPlayStateCount == other.gecko.mAnimationPlayStateCount
|
||||||
|
&& self.gecko.mAnimationTimingFunctionCount == other.gecko.mAnimationTimingFunctionCount
|
||||||
|
&& unsafe { bindings::Gecko_StyleAnimationsEquals(&self.gecko.mAnimations, &other.gecko.mAnimations) }
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_animation_name<I>(&mut self, v: I)
|
||||||
|
where I: IntoIterator<Item = longhands::animation_name::computed_value::single_value::T>,
|
||||||
|
I::IntoIter: ExactSizeIterator
|
||||||
|
{
|
||||||
|
let v = v.into_iter();
|
||||||
|
debug_assert_ne!(v.len(), 0);
|
||||||
|
self.gecko.mAnimations.ensure_len(v.len());
|
||||||
|
|
||||||
|
self.gecko.mAnimationNameCount = v.len() as u32;
|
||||||
|
for (servo, gecko) in v.zip(self.gecko.mAnimations.iter_mut()) {
|
||||||
|
let atom = servo.0.as_atom().clone();
|
||||||
|
unsafe { bindings::Gecko_SetAnimationName(gecko, atom.into_addrefed()); }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pub fn animation_name_at(&self, index: usize)
|
||||||
|
-> longhands::animation_name::computed_value::SingleComputedValue {
|
||||||
|
use crate::properties::longhands::animation_name::single_value::SpecifiedValue as AnimationName;
|
||||||
|
|
||||||
|
let atom = self.gecko.mAnimations[index].mName.mRawPtr;
|
||||||
|
AnimationName(KeyframesName::from_atom(unsafe { Atom::from_raw(atom) }))
|
||||||
|
}
|
||||||
|
pub fn copy_animation_name_from(&mut self, other: &Self) {
|
||||||
|
self.gecko.mAnimationNameCount = other.gecko.mAnimationNameCount;
|
||||||
|
unsafe { bindings::Gecko_CopyAnimationNames(&mut self.gecko.mAnimations, &other.gecko.mAnimations); }
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn reset_animation_name(&mut self, other: &Self) {
|
||||||
|
self.copy_animation_name_from(other)
|
||||||
|
}
|
||||||
|
|
||||||
|
${impl_animation_count('name', 'Name')}
|
||||||
|
|
||||||
|
${impl_animation_time_value('delay', 'Delay')}
|
||||||
|
${impl_animation_time_value('duration', 'Duration')}
|
||||||
|
|
||||||
|
${impl_animation_keyword('direction', 'Direction',
|
||||||
|
data.longhands_by_name["animation-direction"].keyword)}
|
||||||
|
${impl_animation_keyword('fill_mode', 'FillMode',
|
||||||
|
data.longhands_by_name["animation-fill-mode"].keyword)}
|
||||||
|
${impl_animation_keyword('play_state', 'PlayState',
|
||||||
|
data.longhands_by_name["animation-play-state"].keyword)}
|
||||||
|
|
||||||
|
pub fn set_animation_iteration_count<I>(&mut self, v: I)
|
||||||
|
where
|
||||||
|
I: IntoIterator<Item = values::computed::AnimationIterationCount>,
|
||||||
|
I::IntoIter: ExactSizeIterator + Clone
|
||||||
|
{
|
||||||
|
use std::f32;
|
||||||
|
use crate::values::generics::box_::AnimationIterationCount;
|
||||||
|
|
||||||
|
let v = v.into_iter();
|
||||||
|
|
||||||
|
debug_assert_ne!(v.len(), 0);
|
||||||
|
let input_len = v.len();
|
||||||
|
self.gecko.mAnimations.ensure_len(input_len);
|
||||||
|
|
||||||
|
self.gecko.mAnimationIterationCountCount = input_len as u32;
|
||||||
|
for (gecko, servo) in self.gecko.mAnimations.iter_mut().take(input_len as usize).zip(v) {
|
||||||
|
match servo {
|
||||||
|
AnimationIterationCount::Number(n) => gecko.mIterationCount = n,
|
||||||
|
AnimationIterationCount::Infinite => gecko.mIterationCount = f32::INFINITY,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn animation_iteration_count_at(
|
||||||
|
&self,
|
||||||
|
index: usize,
|
||||||
|
) -> values::computed::AnimationIterationCount {
|
||||||
|
use crate::values::generics::box_::AnimationIterationCount;
|
||||||
|
|
||||||
|
if self.gecko.mAnimations[index].mIterationCount.is_infinite() {
|
||||||
|
AnimationIterationCount::Infinite
|
||||||
|
} else {
|
||||||
|
AnimationIterationCount::Number(self.gecko.mAnimations[index].mIterationCount)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
${impl_animation_count('iteration_count', 'IterationCount')}
|
||||||
|
${impl_copy_animation_value('iteration_count', 'IterationCount')}
|
||||||
|
${impl_animation_or_transition_timing_function('animation')}
|
||||||
|
|
||||||
|
pub fn set_animation_timeline<I>(&mut self, v: I)
|
||||||
|
where
|
||||||
|
I: IntoIterator<Item = longhands::animation_timeline::computed_value::single_value::T>,
|
||||||
|
I::IntoIter: ExactSizeIterator
|
||||||
|
{
|
||||||
|
let v = v.into_iter();
|
||||||
|
debug_assert_ne!(v.len(), 0);
|
||||||
|
let input_len = v.len();
|
||||||
|
self.gecko.mAnimations.ensure_len(input_len);
|
||||||
|
|
||||||
|
self.gecko.mAnimationTimelineCount = input_len as u32;
|
||||||
|
for (gecko, servo) in self.gecko.mAnimations.iter_mut().take(input_len as usize).zip(v) {
|
||||||
|
gecko.mTimeline = servo;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pub fn animation_timeline_at(&self, index: usize) -> values::specified::box_::AnimationTimeline {
|
||||||
|
self.gecko.mAnimations[index].mTimeline.clone()
|
||||||
|
}
|
||||||
|
${impl_animation_count('timeline', 'Timeline')}
|
||||||
|
${impl_copy_animation_value('timeline', 'Timeline')}
|
||||||
|
|
||||||
</%self:impl_trait>
|
</%self:impl_trait>
|
||||||
|
|
||||||
<%self:impl_trait style_struct_name="XUL">
|
<%self:impl_trait style_struct_name="XUL">
|
||||||
|
|
|
@ -799,7 +799,7 @@ impl<'a> TransitionPropertyIterator<'a> {
|
||||||
pub fn from_style(style: &'a ComputedValues) -> Self {
|
pub fn from_style(style: &'a ComputedValues) -> Self {
|
||||||
Self {
|
Self {
|
||||||
style,
|
style,
|
||||||
index_range: 0..style.get_box().transition_property_count(),
|
index_range: 0..style.get_ui().transition_property_count(),
|
||||||
longhand_iterator: None,
|
longhand_iterator: None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -832,7 +832,7 @@ impl<'a> Iterator for TransitionPropertyIterator<'a> {
|
||||||
}
|
}
|
||||||
|
|
||||||
let index = self.index_range.next()?;
|
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) => {
|
TransitionProperty::Longhand(longhand_id) => {
|
||||||
return Some(TransitionPropertyIteration {
|
return Some(TransitionPropertyIteration {
|
||||||
longhand_id,
|
longhand_id,
|
||||||
|
|
|
@ -3,7 +3,7 @@
|
||||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
|
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
|
||||||
|
|
||||||
<%namespace name="helpers" file="/helpers.mako.rs" />
|
<%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",
|
<% data.new_style_struct("Box",
|
||||||
inherited=False,
|
inherited=False,
|
||||||
|
@ -150,193 +150,6 @@ ${helpers.predefined_type(
|
||||||
animation_value_type="discrete",
|
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" %>
|
<% transform_extra_prefixes = "moz:layout.css.prefixes.transforms webkit" %>
|
||||||
|
|
||||||
${helpers.predefined_type(
|
${helpers.predefined_type(
|
||||||
|
@ -472,6 +285,15 @@ ${helpers.predefined_type(
|
||||||
animation_value_type="discrete",
|
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:
|
% for (axis, logical) in ALL_AXES:
|
||||||
${helpers.predefined_type(
|
${helpers.predefined_type(
|
||||||
"overscroll-behavior-" + axis,
|
"overscroll-behavior-" + axis,
|
||||||
|
@ -623,6 +445,28 @@ ${helpers.predefined_type(
|
||||||
animation_value_type="none",
|
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(
|
${helpers.predefined_type(
|
||||||
"appearance",
|
"appearance",
|
||||||
"Appearance",
|
"Appearance",
|
||||||
|
|
|
@ -28,6 +28,16 @@
|
||||||
)}
|
)}
|
||||||
% endfor
|
% 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:
|
% for side in ALL_SIDES:
|
||||||
${helpers.predefined_type(
|
${helpers.predefined_type(
|
||||||
"scroll-margin-%s" % side[0],
|
"scroll-margin-%s" % side[0],
|
||||||
|
|
|
@ -20,7 +20,7 @@ ${helpers.single_keyword(
|
||||||
${helpers.predefined_type(
|
${helpers.predefined_type(
|
||||||
"stop-color",
|
"stop-color",
|
||||||
"Color",
|
"Color",
|
||||||
"RGBA::new(0, 0, 0, 255).into()",
|
"computed::Color::black()",
|
||||||
engines="gecko",
|
engines="gecko",
|
||||||
animation_value_type="AnimatedRGBA",
|
animation_value_type="AnimatedRGBA",
|
||||||
spec="https://www.w3.org/TR/SVGTiny12/painting.html#StopColorProperty",
|
spec="https://www.w3.org/TR/SVGTiny12/painting.html#StopColorProperty",
|
||||||
|
@ -40,7 +40,7 @@ ${helpers.predefined_type(
|
||||||
${helpers.predefined_type(
|
${helpers.predefined_type(
|
||||||
"flood-color",
|
"flood-color",
|
||||||
"Color",
|
"Color",
|
||||||
"RGBA::new(0, 0, 0, 255).into()",
|
"computed::Color::black()",
|
||||||
engines="gecko",
|
engines="gecko",
|
||||||
animation_value_type="AnimatedColor",
|
animation_value_type="AnimatedColor",
|
||||||
spec="https://www.w3.org/TR/SVG/filters.html#FloodColorProperty",
|
spec="https://www.w3.org/TR/SVG/filters.html#FloodColorProperty",
|
||||||
|
@ -58,7 +58,7 @@ ${helpers.predefined_type(
|
||||||
${helpers.predefined_type(
|
${helpers.predefined_type(
|
||||||
"lighting-color",
|
"lighting-color",
|
||||||
"Color",
|
"Color",
|
||||||
"RGBA::new(255, 255, 255, 255).into()",
|
"computed::Color::white()",
|
||||||
engines="gecko",
|
engines="gecko",
|
||||||
animation_value_type="AnimatedColor",
|
animation_value_type="AnimatedColor",
|
||||||
spec="https://www.w3.org/TR/SVG/filters.html#LightingColorProperty",
|
spec="https://www.w3.org/TR/SVG/filters.html#LightingColorProperty",
|
||||||
|
|
|
@ -3,7 +3,7 @@
|
||||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
|
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
|
||||||
|
|
||||||
<%namespace name="helpers" file="/helpers.mako.rs" />
|
<%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
|
// CSS Basic User Interface Module Level 1
|
||||||
// https://drafts.csswg.org/css-ui-3/
|
// https://drafts.csswg.org/css-ui-3/
|
||||||
|
@ -51,12 +51,15 @@ ${helpers.single_keyword(
|
||||||
spec="None (Nonstandard Firefox-only property)",
|
spec="None (Nonstandard Firefox-only property)",
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
// TODO(emilio): Maybe make shadow behavior on macOS match Linux / Windows, and remove this
|
||||||
|
// property.
|
||||||
${helpers.single_keyword(
|
${helpers.single_keyword(
|
||||||
"-moz-window-shadow",
|
"-moz-window-shadow",
|
||||||
"default none menu tooltip sheet cliprounded",
|
"default none",
|
||||||
engines="gecko",
|
engines="gecko",
|
||||||
gecko_ffi_name="mWindowShadow",
|
gecko_ffi_name="mWindowShadow",
|
||||||
gecko_enum_prefix="StyleWindowShadow",
|
gecko_enum_prefix="StyleWindowShadow",
|
||||||
|
gecko_inexhaustive=True,
|
||||||
animation_value_type="discrete",
|
animation_value_type="discrete",
|
||||||
enabled_in="chrome",
|
enabled_in="chrome",
|
||||||
spec="None (Nonstandard internal property)",
|
spec="None (Nonstandard internal property)",
|
||||||
|
@ -95,6 +98,16 @@ ${helpers.predefined_type(
|
||||||
enabled_in="chrome",
|
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.
|
// TODO(emilio): Probably also should be hidden from content.
|
||||||
${helpers.predefined_type(
|
${helpers.predefined_type(
|
||||||
"-moz-force-broken-image-icon",
|
"-moz-force-broken-image-icon",
|
||||||
|
@ -104,3 +117,190 @@ ${helpers.predefined_type(
|
||||||
animation_value_type="discrete",
|
animation_value_type="discrete",
|
||||||
spec="None (Nonstandard Firefox-only property)",
|
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,
|
||||||
|
)}
|
||||||
|
|
|
@ -267,6 +267,9 @@ pub enum PropertyDeclaration {
|
||||||
% endfor
|
% endfor
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// There's one of these for each parsed declaration so it better be small.
|
||||||
|
size_of_test!(PropertyDeclaration, 32);
|
||||||
|
|
||||||
#[repr(C)]
|
#[repr(C)]
|
||||||
struct PropertyDeclarationVariantRepr<T> {
|
struct PropertyDeclarationVariantRepr<T> {
|
||||||
tag: u16,
|
tag: u16,
|
||||||
|
@ -474,9 +477,10 @@ impl NonCustomPropertyId {
|
||||||
self.0
|
self.0
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Convert a `NonCustomPropertyId` into a `nsCSSPropertyID`.
|
||||||
#[cfg(feature = "gecko")]
|
#[cfg(feature = "gecko")]
|
||||||
#[inline]
|
#[inline]
|
||||||
fn to_nscsspropertyid(self) -> nsCSSPropertyID {
|
pub fn to_nscsspropertyid(self) -> nsCSSPropertyID {
|
||||||
// unsafe: guaranteed by static_assert_nscsspropertyid above.
|
// unsafe: guaranteed by static_assert_nscsspropertyid above.
|
||||||
unsafe { std::mem::transmute(self.0 as i32) }
|
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 {
|
impl LonghandIdSet {
|
||||||
#[inline]
|
#[inline]
|
||||||
fn reset() -> &'static Self {
|
fn reset() -> &'static Self {
|
||||||
|
@ -920,7 +990,7 @@ impl LonghandIdSet {
|
||||||
/// Returns the set of longhands that are ignored when document colors are
|
/// Returns the set of longhands that are ignored when document colors are
|
||||||
/// disabled.
|
/// disabled.
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn ignored_when_colors_disabled() -> &'static Self {
|
fn ignored_when_colors_disabled() -> &'static Self {
|
||||||
${static_longhand_id_set(
|
${static_longhand_id_set(
|
||||||
"IGNORED_WHEN_COLORS_DISABLED",
|
"IGNORED_WHEN_COLORS_DISABLED",
|
||||||
lambda p: p.ignored_when_colors_disabled
|
lambda p: p.ignored_when_colors_disabled
|
||||||
|
@ -928,6 +998,48 @@ impl LonghandIdSet {
|
||||||
&IGNORED_WHEN_COLORS_DISABLED
|
&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
|
/// Returns the set of properties that are declared as having no effect on
|
||||||
/// Gecko <scrollbar> elements or their descendant scrollbar parts.
|
/// Gecko <scrollbar> elements or their descendant scrollbar parts.
|
||||||
#[cfg(debug_assertions)]
|
#[cfg(debug_assertions)]
|
||||||
|
@ -1330,91 +1442,12 @@ impl LonghandId {
|
||||||
PropertyFlags::from_bits_truncate(FLAGS[self as usize])
|
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
|
/// Returns true if the property is one that is ignored when document
|
||||||
/// colors are disabled.
|
/// colors are disabled.
|
||||||
#[inline]
|
#[inline]
|
||||||
fn ignored_when_document_colors_disabled(self) -> bool {
|
fn ignored_when_document_colors_disabled(self) -> bool {
|
||||||
LonghandIdSet::ignored_when_colors_disabled().contains(self)
|
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
|
/// An iterator over all the property ids that are enabled for a given
|
||||||
|
@ -2560,9 +2593,11 @@ impl PropertyDeclaration {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
type SubpropertiesVec<T> = ArrayVec<T, ${max(len(s.sub_properties) \
|
const SUB_PROPERTIES_ARRAY_CAP: usize =
|
||||||
for s in data.shorthands_except_all()) \
|
${max(len(s.sub_properties) for s in data.shorthands_except_all()) \
|
||||||
if data.shorthands_except_all() else 0}>;
|
if data.shorthands_except_all() else 0};
|
||||||
|
|
||||||
|
type SubpropertiesVec<T> = ArrayVec<T, SUB_PROPERTIES_ARRAY_CAP>;
|
||||||
|
|
||||||
/// A stack-allocated vector of `PropertyDeclaration`
|
/// A stack-allocated vector of `PropertyDeclaration`
|
||||||
/// large enough to parse one CSS `key: value` declaration.
|
/// large enough to parse one CSS `key: value` declaration.
|
||||||
|
@ -2574,6 +2609,10 @@ pub struct SourcePropertyDeclaration {
|
||||||
all_shorthand: AllShorthand,
|
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 {
|
impl SourcePropertyDeclaration {
|
||||||
/// Create one. It’s big, try not to move it around.
|
/// Create one. It’s big, try not to move it around.
|
||||||
#[inline]
|
#[inline]
|
||||||
|
@ -2618,11 +2657,7 @@ impl SourcePropertyDeclaration {
|
||||||
|
|
||||||
/// Return type of SourcePropertyDeclaration::drain
|
/// Return type of SourcePropertyDeclaration::drain
|
||||||
pub struct SourcePropertyDeclarationDrain<'a> {
|
pub struct SourcePropertyDeclarationDrain<'a> {
|
||||||
declarations: ArrayVecDrain<
|
declarations: ArrayVecDrain<'a, PropertyDeclaration, SUB_PROPERTIES_ARRAY_CAP>,
|
||||||
'a, PropertyDeclaration,
|
|
||||||
${max(len(s.sub_properties) for s in data.shorthands_except_all()) \
|
|
||||||
if data.shorthands_except_all() else 0}
|
|
||||||
>,
|
|
||||||
all_shorthand: AllShorthand,
|
all_shorthand: AllShorthand,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2903,11 +2938,11 @@ pub mod style_structs {
|
||||||
% endif
|
% endif
|
||||||
% endfor
|
% endfor
|
||||||
|
|
||||||
% if style_struct.name == "Box":
|
% if style_struct.name == "UI":
|
||||||
/// Returns whether there is any animation specified with
|
/// Returns whether there is any animation specified with
|
||||||
/// animation-name other than `none`.
|
/// animation-name other than `none`.
|
||||||
pub fn specifies_animations(&self) -> bool {
|
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.
|
/// Returns whether there are any transitions specified.
|
||||||
|
@ -3035,7 +3070,7 @@ impl ComputedValues {
|
||||||
|
|
||||||
/// Returns whether this style's display value is equal to contents.
|
/// Returns whether this style's display value is equal to contents.
|
||||||
pub fn is_display_contents(&self) -> bool {
|
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.
|
/// 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());
|
/// style.resolve_color(style.get_border().clone_border_top_color());
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn resolve_color(&self, color: computed::Color) -> RGBA {
|
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
|
/// 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:
|
/// 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
|
/// … where the boolean indicates whether the property value type
|
||||||
|
@ -4198,12 +4233,39 @@ macro_rules! longhand_properties_idents {
|
||||||
($macro_name: ident) => {
|
($macro_name: ident) => {
|
||||||
$macro_name! {
|
$macro_name! {
|
||||||
% for property in data.longhands:
|
% for property in data.longhands:
|
||||||
{ ${property.ident}, ${"true" if property.boxed else "false"} }
|
{ ${property.ident} }
|
||||||
% endfor
|
% endfor
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Large pages generate tens of thousands of ComputedValues.
|
||||||
|
size_of_test!(ComputedValues, 192);
|
||||||
|
// FFI relies on this.
|
||||||
|
size_of_test!(Option<Arc<ComputedValues>>, 8);
|
||||||
|
|
||||||
|
// There are two reasons for this test to fail:
|
||||||
|
//
|
||||||
|
// * Your changes made a specified value type for a given property go
|
||||||
|
// over the threshold. In that case, you should try to shrink it again
|
||||||
|
// or, if not possible, mark the property as boxed in the property
|
||||||
|
// definition.
|
||||||
|
//
|
||||||
|
// * Your changes made a specified value type smaller, so that it no
|
||||||
|
// longer needs to be boxed. In this case you just need to remove
|
||||||
|
// boxed=True from the property definition. Nice job!
|
||||||
|
#[cfg(target_pointer_width = "64")]
|
||||||
|
#[allow(dead_code)] // https://github.com/rust-lang/rust/issues/96952
|
||||||
|
const BOX_THRESHOLD: usize = 24;
|
||||||
|
% for longhand in data.longhands:
|
||||||
|
#[cfg(target_pointer_width = "64")]
|
||||||
|
% if longhand.boxed:
|
||||||
|
const_assert!(std::mem::size_of::<longhands::${longhand.ident}::SpecifiedValue>() > BOX_THRESHOLD);
|
||||||
|
% else:
|
||||||
|
const_assert!(std::mem::size_of::<longhands::${longhand.ident}::SpecifiedValue>() <= BOX_THRESHOLD);
|
||||||
|
% endif
|
||||||
|
% endfor
|
||||||
|
|
||||||
% if engine == "servo":
|
% if engine == "servo":
|
||||||
% for effect_name in ["repaint", "reflow_out_of_flow", "reflow", "rebuild_and_reflow_inline", "rebuild_and_reflow"]:
|
% for effect_name in ["repaint", "reflow_out_of_flow", "reflow", "rebuild_and_reflow_inline", "rebuild_and_reflow"]:
|
||||||
macro_rules! restyle_damage_${effect_name} {
|
macro_rules! restyle_damage_${effect_name} {
|
||||||
|
|
|
@ -24,318 +24,6 @@ ${helpers.two_properties_shorthand(
|
||||||
"(https://developer.mozilla.org/en-US/docs/Web/CSS/overflow-clip-box)",
|
"(https://developer.mozilla.org/en-US/docs/Web/CSS/overflow-clip-box)",
|
||||||
)}
|
)}
|
||||||
|
|
||||||
macro_rules! try_parse_one {
|
|
||||||
($context: expr, $input: expr, $var: ident, $prop_module: ident) => {
|
|
||||||
if $var.is_none() {
|
|
||||||
if let Ok(value) = $input.try_parse(|i| {
|
|
||||||
$prop_module::single_value::parse($context, i)
|
|
||||||
}) {
|
|
||||||
$var = Some(value);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
<%helpers:shorthand name="transition"
|
|
||||||
engines="gecko servo"
|
|
||||||
extra_prefixes="moz:layout.css.prefixes.transitions webkit"
|
|
||||||
sub_properties="transition-property transition-duration
|
|
||||||
transition-timing-function
|
|
||||||
transition-delay"
|
|
||||||
spec="https://drafts.csswg.org/css-transitions/#propdef-transition">
|
|
||||||
use crate::parser::Parse;
|
|
||||||
% for prop in "delay duration property timing_function".split():
|
|
||||||
use crate::properties::longhands::transition_${prop};
|
|
||||||
% endfor
|
|
||||||
use crate::values::specified::TransitionProperty;
|
|
||||||
|
|
||||||
pub fn parse_value<'i, 't>(
|
|
||||||
context: &ParserContext,
|
|
||||||
input: &mut Parser<'i, 't>,
|
|
||||||
) -> Result<Longhands, ParseError<'i>> {
|
|
||||||
struct SingleTransition {
|
|
||||||
% for prop in "duration timing_function delay".split():
|
|
||||||
transition_${prop}: transition_${prop}::SingleSpecifiedValue,
|
|
||||||
% endfor
|
|
||||||
// Unlike other properties, transition-property uses an Option<> to
|
|
||||||
// represent 'none' as `None`.
|
|
||||||
transition_property: Option<TransitionProperty>,
|
|
||||||
}
|
|
||||||
|
|
||||||
fn parse_one_transition<'i, 't>(
|
|
||||||
context: &ParserContext,
|
|
||||||
input: &mut Parser<'i, 't>,
|
|
||||||
) -> Result<SingleTransition,ParseError<'i>> {
|
|
||||||
% for prop in "property duration timing_function delay".split():
|
|
||||||
let mut ${prop} = None;
|
|
||||||
% endfor
|
|
||||||
|
|
||||||
let mut parsed = 0;
|
|
||||||
loop {
|
|
||||||
parsed += 1;
|
|
||||||
|
|
||||||
try_parse_one!(context, input, duration, transition_duration);
|
|
||||||
try_parse_one!(context, input, timing_function, transition_timing_function);
|
|
||||||
try_parse_one!(context, input, delay, transition_delay);
|
|
||||||
// Must check 'transition-property' after 'transition-timing-function' since
|
|
||||||
// 'transition-property' accepts any keyword.
|
|
||||||
if property.is_none() {
|
|
||||||
if let Ok(value) = input.try_parse(|i| TransitionProperty::parse(context, i)) {
|
|
||||||
property = Some(Some(value));
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if input.try_parse(|i| i.expect_ident_matching("none")).is_ok() {
|
|
||||||
// 'none' is not a valid value for <single-transition-property>,
|
|
||||||
// so it's not acceptable in the function above.
|
|
||||||
property = Some(None);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
parsed -= 1;
|
|
||||||
break
|
|
||||||
}
|
|
||||||
|
|
||||||
if parsed != 0 {
|
|
||||||
Ok(SingleTransition {
|
|
||||||
% for prop in "duration timing_function delay".split():
|
|
||||||
transition_${prop}: ${prop}.unwrap_or_else(transition_${prop}::single_value
|
|
||||||
::get_initial_specified_value),
|
|
||||||
% endfor
|
|
||||||
transition_property: property.unwrap_or(
|
|
||||||
Some(transition_property::single_value::get_initial_specified_value())),
|
|
||||||
})
|
|
||||||
} else {
|
|
||||||
Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
% for prop in "property duration timing_function delay".split():
|
|
||||||
let mut ${prop}s = Vec::new();
|
|
||||||
% endfor
|
|
||||||
|
|
||||||
let results = input.parse_comma_separated(|i| parse_one_transition(context, i))?;
|
|
||||||
let multiple_items = results.len() >= 2;
|
|
||||||
for result in results {
|
|
||||||
if let Some(value) = result.transition_property {
|
|
||||||
propertys.push(value);
|
|
||||||
} else if multiple_items {
|
|
||||||
// If there is more than one item, and any of transitions has 'none',
|
|
||||||
// then it's invalid. Othersize, leave propertys to be empty (which
|
|
||||||
// means "transition-property: none");
|
|
||||||
return Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError));
|
|
||||||
}
|
|
||||||
|
|
||||||
% for prop in "duration timing_function delay".split():
|
|
||||||
${prop}s.push(result.transition_${prop});
|
|
||||||
% endfor
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(expanded! {
|
|
||||||
% for prop in "property duration timing_function delay".split():
|
|
||||||
transition_${prop}: transition_${prop}::SpecifiedValue(${prop}s.into()),
|
|
||||||
% endfor
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'a> ToCss for LonghandsToSerialize<'a> {
|
|
||||||
fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result where W: fmt::Write {
|
|
||||||
let property_len = self.transition_property.0.len();
|
|
||||||
|
|
||||||
// There are two cases that we can do shorthand serialization:
|
|
||||||
// * when all value lists have the same length, or
|
|
||||||
// * when transition-property is none, and other value lists have exactly one item.
|
|
||||||
if property_len == 0 {
|
|
||||||
% for name in "duration delay timing_function".split():
|
|
||||||
if self.transition_${name}.0.len() != 1 {
|
|
||||||
return Ok(());
|
|
||||||
}
|
|
||||||
% endfor
|
|
||||||
} else {
|
|
||||||
% for name in "duration delay timing_function".split():
|
|
||||||
if self.transition_${name}.0.len() != property_len {
|
|
||||||
return Ok(());
|
|
||||||
}
|
|
||||||
% endfor
|
|
||||||
}
|
|
||||||
|
|
||||||
// Representative length.
|
|
||||||
let len = self.transition_duration.0.len();
|
|
||||||
|
|
||||||
for i in 0..len {
|
|
||||||
if i != 0 {
|
|
||||||
dest.write_str(", ")?;
|
|
||||||
}
|
|
||||||
if property_len == 0 {
|
|
||||||
dest.write_str("none")?;
|
|
||||||
} else {
|
|
||||||
self.transition_property.0[i].to_css(dest)?;
|
|
||||||
}
|
|
||||||
% for name in "duration timing_function delay".split():
|
|
||||||
dest.write_str(" ")?;
|
|
||||||
self.transition_${name}.0[i].to_css(dest)?;
|
|
||||||
% endfor
|
|
||||||
}
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</%helpers:shorthand>
|
|
||||||
|
|
||||||
<%helpers:shorthand name="animation"
|
|
||||||
engines="gecko servo"
|
|
||||||
extra_prefixes="moz:layout.css.prefixes.animations webkit"
|
|
||||||
sub_properties="animation-name animation-duration
|
|
||||||
animation-timing-function animation-delay
|
|
||||||
animation-iteration-count animation-direction
|
|
||||||
animation-fill-mode animation-play-state animation-timeline"
|
|
||||||
rule_types_allowed="Style"
|
|
||||||
spec="https://drafts.csswg.org/css-animations/#propdef-animation">
|
|
||||||
<%
|
|
||||||
props = "name timeline duration timing_function delay iteration_count \
|
|
||||||
direction fill_mode play_state".split()
|
|
||||||
%>
|
|
||||||
% for prop in props:
|
|
||||||
use crate::properties::longhands::animation_${prop};
|
|
||||||
% endfor
|
|
||||||
|
|
||||||
pub fn parse_value<'i, 't>(
|
|
||||||
context: &ParserContext,
|
|
||||||
input: &mut Parser<'i, 't>,
|
|
||||||
) -> Result<Longhands, ParseError<'i>> {
|
|
||||||
struct SingleAnimation {
|
|
||||||
% for prop in props:
|
|
||||||
animation_${prop}: animation_${prop}::SingleSpecifiedValue,
|
|
||||||
% endfor
|
|
||||||
}
|
|
||||||
|
|
||||||
fn scroll_linked_animations_enabled() -> bool {
|
|
||||||
#[cfg(feature = "gecko")]
|
|
||||||
return static_prefs::pref!("layout.css.scroll-linked-animations.enabled");
|
|
||||||
#[cfg(feature = "servo")]
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
fn parse_one_animation<'i, 't>(
|
|
||||||
context: &ParserContext,
|
|
||||||
input: &mut Parser<'i, 't>,
|
|
||||||
) -> Result<SingleAnimation, ParseError<'i>> {
|
|
||||||
% for prop in props:
|
|
||||||
let mut ${prop} = None;
|
|
||||||
% endfor
|
|
||||||
|
|
||||||
let mut parsed = 0;
|
|
||||||
// NB: Name must be the last one here so that keywords valid for other
|
|
||||||
// longhands are not interpreted as names.
|
|
||||||
//
|
|
||||||
// Also, duration must be before delay, see
|
|
||||||
// https://drafts.csswg.org/css-animations/#typedef-single-animation
|
|
||||||
loop {
|
|
||||||
parsed += 1;
|
|
||||||
try_parse_one!(context, input, duration, animation_duration);
|
|
||||||
try_parse_one!(context, input, timing_function, animation_timing_function);
|
|
||||||
try_parse_one!(context, input, delay, animation_delay);
|
|
||||||
try_parse_one!(context, input, iteration_count, animation_iteration_count);
|
|
||||||
try_parse_one!(context, input, direction, animation_direction);
|
|
||||||
try_parse_one!(context, input, fill_mode, animation_fill_mode);
|
|
||||||
try_parse_one!(context, input, play_state, animation_play_state);
|
|
||||||
try_parse_one!(context, input, name, animation_name);
|
|
||||||
if scroll_linked_animations_enabled() {
|
|
||||||
try_parse_one!(context, input, timeline, animation_timeline);
|
|
||||||
}
|
|
||||||
|
|
||||||
parsed -= 1;
|
|
||||||
break
|
|
||||||
}
|
|
||||||
|
|
||||||
// If nothing is parsed, this is an invalid entry.
|
|
||||||
if parsed == 0 {
|
|
||||||
Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError))
|
|
||||||
} else {
|
|
||||||
Ok(SingleAnimation {
|
|
||||||
% for prop in props:
|
|
||||||
animation_${prop}: ${prop}.unwrap_or_else(animation_${prop}::single_value
|
|
||||||
::get_initial_specified_value),
|
|
||||||
% endfor
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
% for prop in props:
|
|
||||||
let mut ${prop}s = vec![];
|
|
||||||
% endfor
|
|
||||||
|
|
||||||
let results = input.parse_comma_separated(|i| parse_one_animation(context, i))?;
|
|
||||||
for result in results.into_iter() {
|
|
||||||
% for prop in props:
|
|
||||||
${prop}s.push(result.animation_${prop});
|
|
||||||
% endfor
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(expanded! {
|
|
||||||
% for prop in props:
|
|
||||||
animation_${prop}: animation_${prop}::SpecifiedValue(${prop}s.into()),
|
|
||||||
% endfor
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'a> ToCss for LonghandsToSerialize<'a> {
|
|
||||||
fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result where W: fmt::Write {
|
|
||||||
let len = self.animation_name.0.len();
|
|
||||||
// There should be at least one declared value
|
|
||||||
if len == 0 {
|
|
||||||
return Ok(());
|
|
||||||
}
|
|
||||||
|
|
||||||
// If any value list length is differs then we don't do a shorthand serialization
|
|
||||||
// either.
|
|
||||||
% for name in props[2:]:
|
|
||||||
if len != self.animation_${name}.0.len() {
|
|
||||||
return Ok(())
|
|
||||||
}
|
|
||||||
% endfor
|
|
||||||
|
|
||||||
// If the preference of animation-timeline is disabled, `self.animation_timeline` is
|
|
||||||
// None.
|
|
||||||
if self.animation_timeline.map_or(false, |v| len != v.0.len()) {
|
|
||||||
return Ok(());
|
|
||||||
}
|
|
||||||
|
|
||||||
for i in 0..len {
|
|
||||||
if i != 0 {
|
|
||||||
dest.write_str(", ")?;
|
|
||||||
}
|
|
||||||
|
|
||||||
% for name in props[2:]:
|
|
||||||
self.animation_${name}.0[i].to_css(dest)?;
|
|
||||||
dest.write_str(" ")?;
|
|
||||||
% endfor
|
|
||||||
|
|
||||||
self.animation_name.0[i].to_css(dest)?;
|
|
||||||
|
|
||||||
// Based on the spec, the default values of other properties must be output in at
|
|
||||||
// least the cases necessary to distinguish an animation-name. The serialization
|
|
||||||
// order of animation-timeline is always later than animation-name, so it's fine
|
|
||||||
// to not serialize it if it is the default value. It's still possible to
|
|
||||||
// distinguish them (because we always serialize animation-name).
|
|
||||||
// https://drafts.csswg.org/css-animations-1/#animation
|
|
||||||
// https://drafts.csswg.org/css-animations-2/#typedef-single-animation
|
|
||||||
//
|
|
||||||
// Note: it's also fine to always serialize this. However, it seems Blink
|
|
||||||
// doesn't serialize default animation-timeline now, so we follow the same rule.
|
|
||||||
if let Some(ref timeline) = self.animation_timeline {
|
|
||||||
if !timeline.0[i].is_auto() {
|
|
||||||
dest.write_char(' ')?;
|
|
||||||
timeline.0[i].to_css(dest)?;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</%helpers:shorthand>
|
|
||||||
|
|
||||||
${helpers.two_properties_shorthand(
|
${helpers.two_properties_shorthand(
|
||||||
"overscroll-behavior",
|
"overscroll-behavior",
|
||||||
"overscroll-behavior-x",
|
"overscroll-behavior-x",
|
||||||
|
@ -345,6 +33,45 @@ ${helpers.two_properties_shorthand(
|
||||||
spec="https://wicg.github.io/overscroll-behavior/#overscroll-behavior-properties",
|
spec="https://wicg.github.io/overscroll-behavior/#overscroll-behavior-properties",
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
<%helpers:shorthand
|
||||||
|
engines="gecko"
|
||||||
|
name="container"
|
||||||
|
sub_properties="container-name container-type"
|
||||||
|
gecko_pref="layout.css.container-queries.enabled",
|
||||||
|
spec="https://drafts.csswg.org/css-contain-3/#container-shorthand"
|
||||||
|
>
|
||||||
|
pub fn parse_value<'i>(
|
||||||
|
context: &ParserContext,
|
||||||
|
input: &mut Parser<'i, '_>,
|
||||||
|
) -> Result<Longhands, ParseError<'i>> {
|
||||||
|
use crate::parser::Parse;
|
||||||
|
use crate::values::specified::box_::{ContainerName, ContainerType};
|
||||||
|
// See https://github.com/w3c/csswg-drafts/issues/7180 for why we don't
|
||||||
|
// match the spec.
|
||||||
|
let container_name = ContainerName::parse(context, input)?;
|
||||||
|
let container_type = if input.try_parse(|input| input.expect_delim('/')).is_ok() {
|
||||||
|
ContainerType::parse(context, input)?
|
||||||
|
} else {
|
||||||
|
ContainerType::NONE
|
||||||
|
};
|
||||||
|
Ok(expanded! {
|
||||||
|
container_name: container_name,
|
||||||
|
container_type: container_type,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> ToCss for LonghandsToSerialize<'a> {
|
||||||
|
fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result where W: fmt::Write {
|
||||||
|
self.container_name.to_css(dest)?;
|
||||||
|
if !self.container_type.is_empty() {
|
||||||
|
dest.write_str(" / ")?;
|
||||||
|
self.container_type.to_css(dest)?;
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</%helpers:shorthand>
|
||||||
|
|
||||||
<%helpers:shorthand
|
<%helpers:shorthand
|
||||||
engines="gecko"
|
engines="gecko"
|
||||||
name="page-break-before"
|
name="page-break-before"
|
||||||
|
|
317
components/style/properties/shorthands/ui.mako.rs
Normal file
317
components/style/properties/shorthands/ui.mako.rs
Normal file
|
@ -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<Longhands, ParseError<'i>> {
|
||||||
|
struct SingleTransition {
|
||||||
|
% for prop in "duration timing_function delay".split():
|
||||||
|
transition_${prop}: transition_${prop}::SingleSpecifiedValue,
|
||||||
|
% endfor
|
||||||
|
// Unlike other properties, transition-property uses an Option<> to
|
||||||
|
// represent 'none' as `None`.
|
||||||
|
transition_property: Option<TransitionProperty>,
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parse_one_transition<'i, 't>(
|
||||||
|
context: &ParserContext,
|
||||||
|
input: &mut Parser<'i, 't>,
|
||||||
|
) -> Result<SingleTransition,ParseError<'i>> {
|
||||||
|
% for prop in "property duration timing_function delay".split():
|
||||||
|
let mut ${prop} = None;
|
||||||
|
% endfor
|
||||||
|
|
||||||
|
let mut parsed = 0;
|
||||||
|
loop {
|
||||||
|
parsed += 1;
|
||||||
|
|
||||||
|
try_parse_one!(context, input, duration, transition_duration);
|
||||||
|
try_parse_one!(context, input, timing_function, transition_timing_function);
|
||||||
|
try_parse_one!(context, input, delay, transition_delay);
|
||||||
|
// Must check 'transition-property' after 'transition-timing-function' since
|
||||||
|
// 'transition-property' accepts any keyword.
|
||||||
|
if property.is_none() {
|
||||||
|
if let Ok(value) = input.try_parse(|i| TransitionProperty::parse(context, i)) {
|
||||||
|
property = Some(Some(value));
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if input.try_parse(|i| i.expect_ident_matching("none")).is_ok() {
|
||||||
|
// 'none' is not a valid value for <single-transition-property>,
|
||||||
|
// so it's not acceptable in the function above.
|
||||||
|
property = Some(None);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
parsed -= 1;
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
if parsed != 0 {
|
||||||
|
Ok(SingleTransition {
|
||||||
|
% for prop in "duration timing_function delay".split():
|
||||||
|
transition_${prop}: ${prop}.unwrap_or_else(transition_${prop}::single_value
|
||||||
|
::get_initial_specified_value),
|
||||||
|
% endfor
|
||||||
|
transition_property: property.unwrap_or(
|
||||||
|
Some(transition_property::single_value::get_initial_specified_value())),
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
% for prop in "property duration timing_function delay".split():
|
||||||
|
let mut ${prop}s = Vec::new();
|
||||||
|
% endfor
|
||||||
|
|
||||||
|
let results = input.parse_comma_separated(|i| parse_one_transition(context, i))?;
|
||||||
|
let multiple_items = results.len() >= 2;
|
||||||
|
for result in results {
|
||||||
|
if let Some(value) = result.transition_property {
|
||||||
|
propertys.push(value);
|
||||||
|
} else if multiple_items {
|
||||||
|
// If there is more than one item, and any of transitions has 'none',
|
||||||
|
// then it's invalid. Othersize, leave propertys to be empty (which
|
||||||
|
// means "transition-property: none");
|
||||||
|
return Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError));
|
||||||
|
}
|
||||||
|
|
||||||
|
% for prop in "duration timing_function delay".split():
|
||||||
|
${prop}s.push(result.transition_${prop});
|
||||||
|
% endfor
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(expanded! {
|
||||||
|
% for prop in "property duration timing_function delay".split():
|
||||||
|
transition_${prop}: transition_${prop}::SpecifiedValue(${prop}s.into()),
|
||||||
|
% endfor
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> ToCss for LonghandsToSerialize<'a> {
|
||||||
|
fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result where W: fmt::Write {
|
||||||
|
let property_len = self.transition_property.0.len();
|
||||||
|
|
||||||
|
// There are two cases that we can do shorthand serialization:
|
||||||
|
// * when all value lists have the same length, or
|
||||||
|
// * when transition-property is none, and other value lists have exactly one item.
|
||||||
|
if property_len == 0 {
|
||||||
|
% for name in "duration delay timing_function".split():
|
||||||
|
if self.transition_${name}.0.len() != 1 {
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
% endfor
|
||||||
|
} else {
|
||||||
|
% for name in "duration delay timing_function".split():
|
||||||
|
if self.transition_${name}.0.len() != property_len {
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
% endfor
|
||||||
|
}
|
||||||
|
|
||||||
|
// Representative length.
|
||||||
|
let len = self.transition_duration.0.len();
|
||||||
|
|
||||||
|
for i in 0..len {
|
||||||
|
if i != 0 {
|
||||||
|
dest.write_str(", ")?;
|
||||||
|
}
|
||||||
|
if property_len == 0 {
|
||||||
|
dest.write_str("none")?;
|
||||||
|
} else {
|
||||||
|
self.transition_property.0[i].to_css(dest)?;
|
||||||
|
}
|
||||||
|
% for name in "duration timing_function delay".split():
|
||||||
|
dest.write_str(" ")?;
|
||||||
|
self.transition_${name}.0[i].to_css(dest)?;
|
||||||
|
% endfor
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</%helpers:shorthand>
|
||||||
|
|
||||||
|
<%helpers:shorthand name="animation"
|
||||||
|
engines="gecko servo"
|
||||||
|
extra_prefixes="moz:layout.css.prefixes.animations webkit"
|
||||||
|
sub_properties="animation-name animation-duration
|
||||||
|
animation-timing-function animation-delay
|
||||||
|
animation-iteration-count animation-direction
|
||||||
|
animation-fill-mode animation-play-state animation-timeline"
|
||||||
|
rule_types_allowed="Style"
|
||||||
|
spec="https://drafts.csswg.org/css-animations/#propdef-animation">
|
||||||
|
<%
|
||||||
|
props = "name timeline duration timing_function delay iteration_count \
|
||||||
|
direction fill_mode play_state".split()
|
||||||
|
%>
|
||||||
|
% for prop in props:
|
||||||
|
use crate::properties::longhands::animation_${prop};
|
||||||
|
% endfor
|
||||||
|
|
||||||
|
pub fn parse_value<'i, 't>(
|
||||||
|
context: &ParserContext,
|
||||||
|
input: &mut Parser<'i, 't>,
|
||||||
|
) -> Result<Longhands, ParseError<'i>> {
|
||||||
|
struct SingleAnimation {
|
||||||
|
% for prop in props:
|
||||||
|
animation_${prop}: animation_${prop}::SingleSpecifiedValue,
|
||||||
|
% endfor
|
||||||
|
}
|
||||||
|
|
||||||
|
fn scroll_linked_animations_enabled() -> bool {
|
||||||
|
#[cfg(feature = "gecko")]
|
||||||
|
return static_prefs::pref!("layout.css.scroll-linked-animations.enabled");
|
||||||
|
#[cfg(feature = "servo")]
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parse_one_animation<'i, 't>(
|
||||||
|
context: &ParserContext,
|
||||||
|
input: &mut Parser<'i, 't>,
|
||||||
|
) -> Result<SingleAnimation, ParseError<'i>> {
|
||||||
|
% for prop in props:
|
||||||
|
let mut ${prop} = None;
|
||||||
|
% endfor
|
||||||
|
|
||||||
|
let mut parsed = 0;
|
||||||
|
// NB: Name must be the last one here so that keywords valid for other
|
||||||
|
// longhands are not interpreted as names.
|
||||||
|
//
|
||||||
|
// Also, duration must be before delay, see
|
||||||
|
// https://drafts.csswg.org/css-animations/#typedef-single-animation
|
||||||
|
loop {
|
||||||
|
parsed += 1;
|
||||||
|
try_parse_one!(context, input, duration, animation_duration);
|
||||||
|
try_parse_one!(context, input, timing_function, animation_timing_function);
|
||||||
|
try_parse_one!(context, input, delay, animation_delay);
|
||||||
|
try_parse_one!(context, input, iteration_count, animation_iteration_count);
|
||||||
|
try_parse_one!(context, input, direction, animation_direction);
|
||||||
|
try_parse_one!(context, input, fill_mode, animation_fill_mode);
|
||||||
|
try_parse_one!(context, input, play_state, animation_play_state);
|
||||||
|
try_parse_one!(context, input, name, animation_name);
|
||||||
|
if scroll_linked_animations_enabled() {
|
||||||
|
try_parse_one!(context, input, timeline, animation_timeline);
|
||||||
|
}
|
||||||
|
|
||||||
|
parsed -= 1;
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
// If nothing is parsed, this is an invalid entry.
|
||||||
|
if parsed == 0 {
|
||||||
|
Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError))
|
||||||
|
} else {
|
||||||
|
Ok(SingleAnimation {
|
||||||
|
% for prop in props:
|
||||||
|
animation_${prop}: ${prop}.unwrap_or_else(animation_${prop}::single_value
|
||||||
|
::get_initial_specified_value),
|
||||||
|
% endfor
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
% for prop in props:
|
||||||
|
let mut ${prop}s = vec![];
|
||||||
|
% endfor
|
||||||
|
|
||||||
|
let results = input.parse_comma_separated(|i| parse_one_animation(context, i))?;
|
||||||
|
for result in results.into_iter() {
|
||||||
|
% for prop in props:
|
||||||
|
${prop}s.push(result.animation_${prop});
|
||||||
|
% endfor
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(expanded! {
|
||||||
|
% for prop in props:
|
||||||
|
animation_${prop}: animation_${prop}::SpecifiedValue(${prop}s.into()),
|
||||||
|
% endfor
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> ToCss for LonghandsToSerialize<'a> {
|
||||||
|
fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result where W: fmt::Write {
|
||||||
|
let len = self.animation_name.0.len();
|
||||||
|
// There should be at least one declared value
|
||||||
|
if len == 0 {
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
|
||||||
|
// If any value list length is differs then we don't do a shorthand serialization
|
||||||
|
// either.
|
||||||
|
% for name in props[2:]:
|
||||||
|
if len != self.animation_${name}.0.len() {
|
||||||
|
return Ok(())
|
||||||
|
}
|
||||||
|
% endfor
|
||||||
|
|
||||||
|
// If the preference of animation-timeline is disabled, `self.animation_timeline` is
|
||||||
|
// None.
|
||||||
|
if self.animation_timeline.map_or(false, |v| len != v.0.len()) {
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
|
||||||
|
for i in 0..len {
|
||||||
|
if i != 0 {
|
||||||
|
dest.write_str(", ")?;
|
||||||
|
}
|
||||||
|
|
||||||
|
% for name in props[2:]:
|
||||||
|
self.animation_${name}.0[i].to_css(dest)?;
|
||||||
|
dest.write_str(" ")?;
|
||||||
|
% endfor
|
||||||
|
|
||||||
|
self.animation_name.0[i].to_css(dest)?;
|
||||||
|
|
||||||
|
// Based on the spec, the default values of other properties must be output in at
|
||||||
|
// least the cases necessary to distinguish an animation-name. The serialization
|
||||||
|
// order of animation-timeline is always later than animation-name, so it's fine
|
||||||
|
// to not serialize it if it is the default value. It's still possible to
|
||||||
|
// distinguish them (because we always serialize animation-name).
|
||||||
|
// https://drafts.csswg.org/css-animations-1/#animation
|
||||||
|
// https://drafts.csswg.org/css-animations-2/#typedef-single-animation
|
||||||
|
//
|
||||||
|
// Note: it's also fine to always serialize this. However, it seems Blink
|
||||||
|
// doesn't serialize default animation-timeline now, so we follow the same rule.
|
||||||
|
if let Some(ref timeline) = self.animation_timeline {
|
||||||
|
if !timeline.0[i].is_auto() {
|
||||||
|
dest.write_char(' ')?;
|
||||||
|
timeline.0[i].to_css(dest)?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</%helpers:shorthand>
|
|
@ -2,13 +2,14 @@
|
||||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
* 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/. */
|
* 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/mediaqueries-4/#typedef-media-condition
|
||||||
|
//! https://drafts.csswg.org/css-contain-3/#typedef-container-condition
|
||||||
|
|
||||||
use super::{Device, MediaFeatureExpression};
|
use super::{QueryFeatureExpression, FeatureType, FeatureFlags};
|
||||||
use crate::context::QuirksMode;
|
|
||||||
use crate::parser::ParserContext;
|
use crate::parser::ParserContext;
|
||||||
|
use crate::values::computed;
|
||||||
use cssparser::{Parser, Token};
|
use cssparser::{Parser, Token};
|
||||||
use std::fmt::{self, Write};
|
use std::fmt::{self, Write};
|
||||||
use style_traits::{CssWriter, ParseError, StyleParseErrorKind, ToCss};
|
use style_traits::{CssWriter, ParseError, StyleParseErrorKind, ToCss};
|
||||||
|
@ -28,38 +29,38 @@ enum AllowOr {
|
||||||
No,
|
No,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Represents a media condition.
|
/// Represents a condition.
|
||||||
#[derive(Clone, Debug, MallocSizeOf, PartialEq, ToShmem)]
|
#[derive(Clone, Debug, MallocSizeOf, PartialEq, ToShmem)]
|
||||||
pub enum MediaCondition {
|
pub enum QueryCondition {
|
||||||
/// A simple media feature expression, implicitly parenthesized.
|
/// A simple feature expression, implicitly parenthesized.
|
||||||
Feature(MediaFeatureExpression),
|
Feature(QueryFeatureExpression),
|
||||||
/// A negation of a condition.
|
/// A negation of a condition.
|
||||||
Not(Box<MediaCondition>),
|
Not(Box<QueryCondition>),
|
||||||
/// A set of joint operations.
|
/// A set of joint operations.
|
||||||
Operation(Box<[MediaCondition]>, Operator),
|
Operation(Box<[QueryCondition]>, Operator),
|
||||||
/// A condition wrapped in parenthesis.
|
/// A condition wrapped in parenthesis.
|
||||||
InParens(Box<MediaCondition>),
|
InParens(Box<QueryCondition>),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ToCss for MediaCondition {
|
impl ToCss for QueryCondition {
|
||||||
fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
|
fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
|
||||||
where
|
where
|
||||||
W: fmt::Write,
|
W: fmt::Write,
|
||||||
{
|
{
|
||||||
match *self {
|
match *self {
|
||||||
// NOTE(emilio): MediaFeatureExpression already includes the
|
// NOTE(emilio): QueryFeatureExpression already includes the
|
||||||
// parenthesis.
|
// parenthesis.
|
||||||
MediaCondition::Feature(ref f) => f.to_css(dest),
|
QueryCondition::Feature(ref f) => f.to_css(dest),
|
||||||
MediaCondition::Not(ref c) => {
|
QueryCondition::Not(ref c) => {
|
||||||
dest.write_str("not ")?;
|
dest.write_str("not ")?;
|
||||||
c.to_css(dest)
|
c.to_css(dest)
|
||||||
},
|
},
|
||||||
MediaCondition::InParens(ref c) => {
|
QueryCondition::InParens(ref c) => {
|
||||||
dest.write_char('(')?;
|
dest.write_char('(')?;
|
||||||
c.to_css(dest)?;
|
c.to_css(dest)?;
|
||||||
dest.write_char(')')
|
dest.write_char(')')
|
||||||
},
|
},
|
||||||
MediaCondition::Operation(ref list, op) => {
|
QueryCondition::Operation(ref list, op) => {
|
||||||
let mut iter = list.iter();
|
let mut iter = list.iter();
|
||||||
iter.next().unwrap().to_css(dest)?;
|
iter.next().unwrap().to_css(dest)?;
|
||||||
for item in iter {
|
for item in iter {
|
||||||
|
@ -74,28 +75,60 @@ impl ToCss for MediaCondition {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl MediaCondition {
|
impl QueryCondition {
|
||||||
/// Parse a single media condition.
|
/// Parse a single condition.
|
||||||
pub fn parse<'i, 't>(
|
pub fn parse<'i, 't>(
|
||||||
context: &ParserContext,
|
context: &ParserContext,
|
||||||
input: &mut Parser<'i, 't>,
|
input: &mut Parser<'i, 't>,
|
||||||
|
feature_type: FeatureType,
|
||||||
) -> Result<Self, ParseError<'i>> {
|
) -> Result<Self, ParseError<'i>> {
|
||||||
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<F>(&self, visitor: &mut F)
|
||||||
|
where
|
||||||
|
F: FnMut(&Self),
|
||||||
|
{
|
||||||
|
visitor(self);
|
||||||
|
match *self {
|
||||||
|
Self::Feature(..) => {},
|
||||||
|
Self::Not(ref cond) => cond.visit(visitor),
|
||||||
|
Self::Operation(ref conds, _op) => {
|
||||||
|
for cond in conds.iter() {
|
||||||
|
cond.visit(visitor);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
Self::InParens(ref cond) => cond.visit(visitor),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the union of all flags in the expression. This is useful for
|
||||||
|
/// container queries.
|
||||||
|
pub fn cumulative_flags(&self) -> FeatureFlags {
|
||||||
|
let mut result = FeatureFlags::empty();
|
||||||
|
self.visit(&mut |condition| {
|
||||||
|
if let Self::Feature(ref f) = condition {
|
||||||
|
result.insert(f.feature_flags())
|
||||||
|
}
|
||||||
|
});
|
||||||
|
result
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Parse a single condition, disallowing `or` expressions.
|
||||||
///
|
///
|
||||||
/// To be used from the legacy media query syntax.
|
/// To be used from the legacy query syntax.
|
||||||
pub fn parse_disallow_or<'i, 't>(
|
pub fn parse_disallow_or<'i, 't>(
|
||||||
context: &ParserContext,
|
context: &ParserContext,
|
||||||
input: &mut Parser<'i, 't>,
|
input: &mut Parser<'i, 't>,
|
||||||
|
feature_type: FeatureType,
|
||||||
) -> Result<Self, ParseError<'i>> {
|
) -> Result<Self, ParseError<'i>> {
|
||||||
Self::parse_internal(context, input, AllowOr::No)
|
Self::parse_internal(context, input, feature_type, AllowOr::No)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn parse_internal<'i, 't>(
|
fn parse_internal<'i, 't>(
|
||||||
context: &ParserContext,
|
context: &ParserContext,
|
||||||
input: &mut Parser<'i, 't>,
|
input: &mut Parser<'i, 't>,
|
||||||
|
feature_type: FeatureType,
|
||||||
allow_or: AllowOr,
|
allow_or: AllowOr,
|
||||||
) -> Result<Self, ParseError<'i>> {
|
) -> Result<Self, ParseError<'i>> {
|
||||||
let location = input.current_source_location();
|
let location = input.current_source_location();
|
||||||
|
@ -108,12 +141,12 @@ impl MediaCondition {
|
||||||
};
|
};
|
||||||
|
|
||||||
if is_negation {
|
if is_negation {
|
||||||
let inner_condition = Self::parse_in_parens(context, input)?;
|
let inner_condition = Self::parse_in_parens(context, input, feature_type)?;
|
||||||
return Ok(MediaCondition::Not(Box::new(inner_condition)));
|
return Ok(QueryCondition::Not(Box::new(inner_condition)));
|
||||||
}
|
}
|
||||||
|
|
||||||
// ParenthesisBlock.
|
// 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) {
|
let operator = match input.try_parse(Operator::parse) {
|
||||||
Ok(op) => op,
|
Ok(op) => op,
|
||||||
Err(..) => return Ok(first_condition),
|
Err(..) => return Ok(first_condition),
|
||||||
|
@ -125,7 +158,7 @@ impl MediaCondition {
|
||||||
|
|
||||||
let mut conditions = vec![];
|
let mut conditions = vec![];
|
||||||
conditions.push(first_condition);
|
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 {
|
let delim = match operator {
|
||||||
Operator::And => "and",
|
Operator::And => "and",
|
||||||
|
@ -134,50 +167,52 @@ impl MediaCondition {
|
||||||
|
|
||||||
loop {
|
loop {
|
||||||
if input.try_parse(|i| i.expect_ident_matching(delim)).is_err() {
|
if input.try_parse(|i| i.expect_ident_matching(delim)).is_err() {
|
||||||
return Ok(MediaCondition::Operation(
|
return Ok(QueryCondition::Operation(
|
||||||
conditions.into_boxed_slice(),
|
conditions.into_boxed_slice(),
|
||||||
operator,
|
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>(
|
pub fn parse_in_parens<'i, 't>(
|
||||||
context: &ParserContext,
|
context: &ParserContext,
|
||||||
input: &mut Parser<'i, 't>,
|
input: &mut Parser<'i, 't>,
|
||||||
|
feature_type: FeatureType,
|
||||||
) -> Result<Self, ParseError<'i>> {
|
) -> Result<Self, ParseError<'i>> {
|
||||||
input.expect_parenthesis_block()?;
|
input.expect_parenthesis_block()?;
|
||||||
Self::parse_paren_block(context, input)
|
Self::parse_paren_block(context, input, feature_type)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn parse_paren_block<'i, 't>(
|
fn parse_paren_block<'i, 't>(
|
||||||
context: &ParserContext,
|
context: &ParserContext,
|
||||||
input: &mut Parser<'i, 't>,
|
input: &mut Parser<'i, 't>,
|
||||||
|
feature_type: FeatureType,
|
||||||
) -> Result<Self, ParseError<'i>> {
|
) -> Result<Self, ParseError<'i>> {
|
||||||
input.parse_nested_block(|input| {
|
input.parse_nested_block(|input| {
|
||||||
// Base case.
|
// Base case.
|
||||||
if let Ok(inner) = input.try_parse(|i| Self::parse(context, i)) {
|
if let Ok(inner) = input.try_parse(|i| Self::parse(context, i, feature_type)) {
|
||||||
return Ok(MediaCondition::InParens(Box::new(inner)));
|
return Ok(QueryCondition::InParens(Box::new(inner)));
|
||||||
}
|
}
|
||||||
let expr = MediaFeatureExpression::parse_in_parenthesis_block(context, input)?;
|
let expr = QueryFeatureExpression::parse_in_parenthesis_block(context, input, feature_type)?;
|
||||||
Ok(MediaCondition::Feature(expr))
|
Ok(QueryCondition::Feature(expr))
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Whether this condition matches the device and quirks mode.
|
/// 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 {
|
match *self {
|
||||||
MediaCondition::Feature(ref f) => f.matches(device, quirks_mode),
|
QueryCondition::Feature(ref f) => f.matches(context),
|
||||||
MediaCondition::InParens(ref c) => c.matches(device, quirks_mode),
|
QueryCondition::InParens(ref c) => c.matches(context),
|
||||||
MediaCondition::Not(ref c) => !c.matches(device, quirks_mode),
|
QueryCondition::Not(ref c) => !c.matches(context),
|
||||||
MediaCondition::Operation(ref conditions, op) => {
|
QueryCondition::Operation(ref conditions, op) => {
|
||||||
let mut iter = conditions.iter();
|
let mut iter = conditions.iter();
|
||||||
match op {
|
match op {
|
||||||
Operator::And => iter.all(|c| c.matches(device, quirks_mode)),
|
Operator::And => iter.all(|c| c.matches(context)),
|
||||||
Operator::Or => iter.any(|c| c.matches(device, quirks_mode)),
|
Operator::Or => iter.any(|c| c.matches(context)),
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
}
|
}
|
|
@ -2,13 +2,10 @@
|
||||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
* 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/. */
|
* 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::parser::ParserContext;
|
||||||
use crate::values::computed::Ratio;
|
use crate::values::computed::{self, CSSPixelLength, Resolution, Ratio};
|
||||||
use crate::values::computed::{CSSPixelLength, Resolution};
|
|
||||||
use crate::Atom;
|
use crate::Atom;
|
||||||
use cssparser::Parser;
|
use cssparser::Parser;
|
||||||
use std::fmt;
|
use std::fmt;
|
||||||
|
@ -17,12 +14,7 @@ use style_traits::ParseError;
|
||||||
/// A generic discriminant for an enum value.
|
/// A generic discriminant for an enum value.
|
||||||
pub type KeywordDiscriminant = u8;
|
pub type KeywordDiscriminant = u8;
|
||||||
|
|
||||||
type MediaFeatureEvaluator<T> = fn(
|
type QueryFeatureGetter<T> = fn(device: &computed::Context) -> T;
|
||||||
device: &Device,
|
|
||||||
// null == no value was given in the query.
|
|
||||||
value: Option<T>,
|
|
||||||
range_or_operator: Option<RangeOrOperator>,
|
|
||||||
) -> bool;
|
|
||||||
|
|
||||||
/// Serializes a given discriminant.
|
/// Serializes a given discriminant.
|
||||||
///
|
///
|
||||||
|
@ -36,19 +28,19 @@ pub type KeywordParser = for<'a, 'i, 't> fn(
|
||||||
input: &'a mut Parser<'i, 't>,
|
input: &'a mut Parser<'i, 't>,
|
||||||
) -> Result<KeywordDiscriminant, ParseError<'i>>;
|
) -> Result<KeywordDiscriminant, ParseError<'i>>;
|
||||||
|
|
||||||
/// An evaluator for a given media feature.
|
/// An evaluator for a given feature.
|
||||||
///
|
///
|
||||||
/// This determines the kind of values that get parsed, too.
|
/// This determines the kind of values that get parsed, too.
|
||||||
#[allow(missing_docs)]
|
#[allow(missing_docs)]
|
||||||
pub enum Evaluator {
|
pub enum Evaluator {
|
||||||
Length(MediaFeatureEvaluator<CSSPixelLength>),
|
Length(QueryFeatureGetter<CSSPixelLength>),
|
||||||
Integer(MediaFeatureEvaluator<u32>),
|
Integer(QueryFeatureGetter<u32>),
|
||||||
Float(MediaFeatureEvaluator<f32>),
|
Float(QueryFeatureGetter<f32>),
|
||||||
BoolInteger(MediaFeatureEvaluator<bool>),
|
BoolInteger(QueryFeatureGetter<bool>),
|
||||||
/// A non-negative number ratio, such as the one from device-pixel-ratio.
|
/// A non-negative number ratio, such as the one from device-pixel-ratio.
|
||||||
NumberRatio(MediaFeatureEvaluator<Ratio>),
|
NumberRatio(QueryFeatureGetter<Ratio>),
|
||||||
/// A resolution.
|
/// A resolution.
|
||||||
Resolution(MediaFeatureEvaluator<Resolution>),
|
Resolution(QueryFeatureGetter<Resolution>),
|
||||||
/// A keyword value.
|
/// A keyword value.
|
||||||
Enumerated {
|
Enumerated {
|
||||||
/// The parser to get a discriminant given a string.
|
/// The parser to get a discriminant given a string.
|
||||||
|
@ -60,9 +52,8 @@ pub enum Evaluator {
|
||||||
serializer: KeywordSerializer,
|
serializer: KeywordSerializer,
|
||||||
/// The evaluator itself. This is guaranteed to be called with a
|
/// The evaluator itself. This is guaranteed to be called with a
|
||||||
/// keyword that `parser` has produced.
|
/// keyword that `parser` has produced.
|
||||||
evaluator: MediaFeatureEvaluator<KeywordDiscriminant>,
|
evaluator: fn(&computed::Context, Option<KeywordDiscriminant>) -> bool,
|
||||||
},
|
},
|
||||||
Ident(MediaFeatureEvaluator<Atom>),
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A simple helper macro to create a keyword evaluator.
|
/// A simple helper macro to create a keyword evaluator.
|
||||||
|
@ -76,14 +67,14 @@ macro_rules! keyword_evaluator {
|
||||||
context: &$crate::parser::ParserContext,
|
context: &$crate::parser::ParserContext,
|
||||||
input: &mut $crate::cssparser::Parser<'i, 't>,
|
input: &mut $crate::cssparser::Parser<'i, 't>,
|
||||||
) -> Result<
|
) -> Result<
|
||||||
$crate::media_queries::media_feature::KeywordDiscriminant,
|
$crate::queries::feature::KeywordDiscriminant,
|
||||||
::style_traits::ParseError<'i>,
|
::style_traits::ParseError<'i>,
|
||||||
> {
|
> {
|
||||||
let kw = <$keyword_type as $crate::parser::Parse>::parse(context, input)?;
|
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
|
// This unwrap is ok because the only discriminants that get
|
||||||
// back to us is the ones that `parse` produces.
|
// back to us is the ones that `parse` produces.
|
||||||
let value: $keyword_type = ::num_traits::cast::FromPrimitive::from_u8(kw).unwrap();
|
let value: $keyword_type = ::num_traits::cast::FromPrimitive::from_u8(kw).unwrap();
|
||||||
|
@ -91,24 +82,17 @@ macro_rules! keyword_evaluator {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn __evaluate(
|
fn __evaluate(
|
||||||
device: &$crate::media_queries::Device,
|
context: &$crate::values::computed::Context,
|
||||||
value: Option<$crate::media_queries::media_feature::KeywordDiscriminant>,
|
value: Option<$crate::queries::feature::KeywordDiscriminant>,
|
||||||
range_or_operator: Option<
|
|
||||||
$crate::media_queries::media_feature_expression::RangeOrOperator,
|
|
||||||
>,
|
|
||||||
) -> bool {
|
) -> bool {
|
||||||
debug_assert!(
|
|
||||||
range_or_operator.is_none(),
|
|
||||||
"Since when do keywords accept ranges?"
|
|
||||||
);
|
|
||||||
// This unwrap is ok because the only discriminants that get
|
// This unwrap is ok because the only discriminants that get
|
||||||
// back to us is the ones that `parse` produces.
|
// back to us is the ones that `parse` produces.
|
||||||
let value: Option<$keyword_type> =
|
let value: Option<$keyword_type> =
|
||||||
value.map(|kw| ::num_traits::cast::FromPrimitive::from_u8(kw).unwrap());
|
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,
|
parser: __parse,
|
||||||
serializer: __serialize,
|
serializer: __serialize,
|
||||||
evaluator: __evaluate,
|
evaluator: __evaluate,
|
||||||
|
@ -117,17 +101,46 @@ macro_rules! keyword_evaluator {
|
||||||
}
|
}
|
||||||
|
|
||||||
bitflags! {
|
bitflags! {
|
||||||
/// Different requirements or toggles that change how a expression is
|
/// Different flags or toggles that change how a expression is parsed or
|
||||||
/// parsed.
|
/// evaluated.
|
||||||
pub struct ParsingRequirements: u8 {
|
#[derive(ToShmem)]
|
||||||
|
pub struct FeatureFlags : u8 {
|
||||||
/// The feature should only be parsed in chrome and ua sheets.
|
/// The feature should only be parsed in chrome and ua sheets.
|
||||||
const CHROME_AND_UA_ONLY = 1 << 0;
|
const CHROME_AND_UA_ONLY = 1 << 0;
|
||||||
/// The feature requires a -webkit- prefix.
|
/// The feature requires a -webkit- prefix.
|
||||||
const WEBKIT_PREFIX = 1 << 1;
|
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)]
|
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
|
||||||
#[allow(missing_docs)]
|
#[allow(missing_docs)]
|
||||||
pub enum AllowsRanges {
|
pub enum AllowsRanges {
|
||||||
|
@ -135,46 +148,45 @@ pub enum AllowsRanges {
|
||||||
No,
|
No,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A description of a media feature.
|
/// A description of a feature.
|
||||||
pub struct MediaFeatureDescription {
|
pub struct QueryFeatureDescription {
|
||||||
/// The media feature name, in ascii lowercase.
|
/// The feature name, in ascii lowercase.
|
||||||
pub name: Atom,
|
pub name: Atom,
|
||||||
/// Whether min- / max- prefixes are allowed or not.
|
/// Whether min- / max- prefixes are allowed or not.
|
||||||
pub allows_ranges: AllowsRanges,
|
pub allows_ranges: AllowsRanges,
|
||||||
/// The evaluator, which we also use to determine which kind of value to
|
/// The evaluator, which we also use to determine which kind of value to
|
||||||
/// parse.
|
/// parse.
|
||||||
pub evaluator: Evaluator,
|
pub evaluator: Evaluator,
|
||||||
/// Different requirements that need to hold for the feature to be
|
/// Different feature-specific flags.
|
||||||
/// successfully parsed.
|
pub flags: FeatureFlags,
|
||||||
pub requirements: ParsingRequirements,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl MediaFeatureDescription {
|
impl QueryFeatureDescription {
|
||||||
/// Whether this media feature allows ranges.
|
/// Whether this feature allows ranges.
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn allows_ranges(&self) -> bool {
|
pub fn allows_ranges(&self) -> bool {
|
||||||
self.allows_ranges == AllowsRanges::Yes
|
self.allows_ranges == AllowsRanges::Yes
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A simple helper to construct a `MediaFeatureDescription`.
|
/// A simple helper to construct a `QueryFeatureDescription`.
|
||||||
macro_rules! feature {
|
macro_rules! feature {
|
||||||
($name:expr, $allows_ranges:expr, $evaluator:expr, $reqs:expr,) => {
|
($name:expr, $allows_ranges:expr, $evaluator:expr, $flags:expr,) => {
|
||||||
$crate::media_queries::media_feature::MediaFeatureDescription {
|
$crate::queries::feature::QueryFeatureDescription {
|
||||||
name: $name,
|
name: $name,
|
||||||
allows_ranges: $allows_ranges,
|
allows_ranges: $allows_ranges,
|
||||||
evaluator: $evaluator,
|
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 {
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||||
f.debug_struct("MediaFeatureExpression")
|
f.debug_struct("QueryFeatureDescription")
|
||||||
.field("name", &self.name)
|
.field("name", &self.name)
|
||||||
.field("allows_ranges", &self.allows_ranges)
|
.field("allows_ranges", &self.allows_ranges)
|
||||||
.field("requirements", &self.requirements)
|
.field("flags", &self.flags)
|
||||||
.finish()
|
.finish()
|
||||||
}
|
}
|
||||||
}
|
}
|
711
components/style/queries/feature_expression.rs
Normal file
711
components/style/queries/feature_expression.rs
Normal file
|
@ -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<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
|
||||||
|
where
|
||||||
|
W: fmt::Write,
|
||||||
|
{
|
||||||
|
dest.write_str(match *self {
|
||||||
|
Self::Equal => "=",
|
||||||
|
Self::LessThan => "<",
|
||||||
|
Self::LessThanEqual => "<=",
|
||||||
|
Self::GreaterThan => ">",
|
||||||
|
Self::GreaterThanEqual => ">=",
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Operator {
|
||||||
|
fn is_compatible_with(self, right_op: Self) -> bool {
|
||||||
|
// Some operators are not compatible with each other in multi-range
|
||||||
|
// context.
|
||||||
|
match self {
|
||||||
|
Self::Equal => false,
|
||||||
|
Self::GreaterThan | Self::GreaterThanEqual => matches!(right_op, Self::GreaterThan | Self::GreaterThanEqual),
|
||||||
|
Self::LessThan | Self::LessThanEqual => matches!(right_op, Self::LessThan | Self::LessThanEqual),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn evaluate(&self, cmp: Ordering) -> bool {
|
||||||
|
match *self {
|
||||||
|
Self::Equal => cmp == Ordering::Equal,
|
||||||
|
Self::GreaterThan => cmp == Ordering::Greater,
|
||||||
|
Self::GreaterThanEqual => cmp == Ordering::Equal || cmp == Ordering::Greater,
|
||||||
|
Self::LessThan => cmp == Ordering::Less,
|
||||||
|
Self::LessThanEqual => cmp == Ordering::Equal || cmp == Ordering::Less,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parse<'i>(input: &mut Parser<'i, '_>) -> Result<Self, ParseError<'i>> {
|
||||||
|
let location = input.current_source_location();
|
||||||
|
let operator = match *input.next()? {
|
||||||
|
Token::Delim('=') => return Ok(Operator::Equal),
|
||||||
|
Token::Delim('>') => Operator::GreaterThan,
|
||||||
|
Token::Delim('<') => Operator::LessThan,
|
||||||
|
ref t => return Err(location.new_unexpected_token_error(t.clone())),
|
||||||
|
};
|
||||||
|
|
||||||
|
// https://drafts.csswg.org/mediaqueries-4/#mq-syntax:
|
||||||
|
//
|
||||||
|
// No whitespace is allowed between the “<” or “>”
|
||||||
|
// <delim-token>s and the following “=” <delim-token>, if 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<T>(
|
||||||
|
&self,
|
||||||
|
context_value: T,
|
||||||
|
mut compute: impl FnMut(&QueryExpressionValue) -> T,
|
||||||
|
) -> bool
|
||||||
|
where
|
||||||
|
T: PartialOrd + Zero,
|
||||||
|
{
|
||||||
|
match *self {
|
||||||
|
Self::Empty => return !context_value.is_zero(),
|
||||||
|
Self::Single(ref value) => {
|
||||||
|
let value = compute(value);
|
||||||
|
let cmp = match context_value.partial_cmp(&value) {
|
||||||
|
Some(c) => c,
|
||||||
|
None => return false,
|
||||||
|
};
|
||||||
|
cmp == Ordering::Equal
|
||||||
|
},
|
||||||
|
Self::LegacyRange(ref range, ref value) => {
|
||||||
|
let value = compute(value);
|
||||||
|
let cmp = match context_value.partial_cmp(&value) {
|
||||||
|
Some(c) => c,
|
||||||
|
None => return false,
|
||||||
|
};
|
||||||
|
cmp == Ordering::Equal ||
|
||||||
|
match range {
|
||||||
|
LegacyRange::Min => cmp == Ordering::Greater,
|
||||||
|
LegacyRange::Max => cmp == Ordering::Less,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
Self::Range {
|
||||||
|
ref left,
|
||||||
|
ref right,
|
||||||
|
} => {
|
||||||
|
debug_assert!(left.is_some() || right.is_some());
|
||||||
|
if let Some((ref op, ref value)) = left {
|
||||||
|
let value = compute(value);
|
||||||
|
let cmp = match value.partial_cmp(&context_value) {
|
||||||
|
Some(c) => c,
|
||||||
|
None => return false,
|
||||||
|
};
|
||||||
|
if !op.evaluate(cmp) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if let Some((ref op, ref value)) = right {
|
||||||
|
let value = compute(value);
|
||||||
|
let cmp = match context_value.partial_cmp(&value) {
|
||||||
|
Some(c) => c,
|
||||||
|
None => return false,
|
||||||
|
};
|
||||||
|
if !op.evaluate(cmp) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
true
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Non-ranged features only need to compare to one value at most.
|
||||||
|
fn non_ranged_value(&self) -> Option<&QueryExpressionValue> {
|
||||||
|
match *self {
|
||||||
|
Self::Empty => None,
|
||||||
|
Self::Single(ref v) => Some(v),
|
||||||
|
Self::LegacyRange(..) | Self::Range { .. } => {
|
||||||
|
debug_assert!(false, "Unexpected ranged value in non-ranged feature!");
|
||||||
|
None
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A feature expression contains a reference to the feature, the value the
|
||||||
|
/// query contained, and the range to evaluate.
|
||||||
|
#[derive(Clone, Debug, MallocSizeOf, ToShmem, PartialEq)]
|
||||||
|
pub struct QueryFeatureExpression {
|
||||||
|
feature_type: FeatureType,
|
||||||
|
feature_index: usize,
|
||||||
|
kind: QueryFeatureExpressionKind,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ToCss for QueryFeatureExpression {
|
||||||
|
fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
|
||||||
|
where
|
||||||
|
W: fmt::Write,
|
||||||
|
{
|
||||||
|
dest.write_str("(")?;
|
||||||
|
|
||||||
|
match self.kind {
|
||||||
|
QueryFeatureExpressionKind::Empty => self.write_name(dest)?,
|
||||||
|
QueryFeatureExpressionKind::Single(ref v) |
|
||||||
|
QueryFeatureExpressionKind::LegacyRange(_, ref v) => {
|
||||||
|
self.write_name(dest)?;
|
||||||
|
dest.write_str(": ")?;
|
||||||
|
v.to_css(dest, self)?;
|
||||||
|
},
|
||||||
|
QueryFeatureExpressionKind::Range {
|
||||||
|
ref left,
|
||||||
|
ref right,
|
||||||
|
} => {
|
||||||
|
if let Some((ref op, ref val)) = left {
|
||||||
|
val.to_css(dest, self)?;
|
||||||
|
dest.write_char(' ')?;
|
||||||
|
op.to_css(dest)?;
|
||||||
|
dest.write_char(' ')?;
|
||||||
|
}
|
||||||
|
self.write_name(dest)?;
|
||||||
|
if let Some((ref op, ref val)) = right {
|
||||||
|
dest.write_char(' ')?;
|
||||||
|
op.to_css(dest)?;
|
||||||
|
dest.write_char(' ')?;
|
||||||
|
val.to_css(dest, self)?;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
dest.write_char(')')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn consume_operation_or_colon<'i>(
|
||||||
|
input: &mut Parser<'i, '_>,
|
||||||
|
) -> Result<Option<Operator>, ParseError<'i>> {
|
||||||
|
if input.try_parse(|input| input.expect_colon()).is_ok() {
|
||||||
|
return Ok(None);
|
||||||
|
}
|
||||||
|
Operator::parse(input).map(|op| Some(op))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[allow(unused_variables)]
|
||||||
|
fn disabled_by_pref(feature: &Atom, context: &ParserContext) -> bool {
|
||||||
|
#[cfg(feature = "gecko")]
|
||||||
|
{
|
||||||
|
if *feature == atom!("forced-colors") {
|
||||||
|
// forced-colors is always enabled in the ua and chrome. On
|
||||||
|
// the web it is hidden behind a preference, which is defaulted
|
||||||
|
// to 'true' as of bug 1659511.
|
||||||
|
return !context.in_ua_or_chrome_sheet() &&
|
||||||
|
!static_prefs::pref!("layout.css.forced-colors.enabled");
|
||||||
|
}
|
||||||
|
// prefers-contrast is always enabled in the ua and chrome. On
|
||||||
|
// the web it is hidden behind a preference.
|
||||||
|
if *feature == atom!("prefers-contrast") {
|
||||||
|
return !context.in_ua_or_chrome_sheet() &&
|
||||||
|
!static_prefs::pref!("layout.css.prefers-contrast.enabled");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
false
|
||||||
|
}
|
||||||
|
|
||||||
|
impl QueryFeatureExpression {
|
||||||
|
fn new(
|
||||||
|
feature_type: FeatureType,
|
||||||
|
feature_index: usize,
|
||||||
|
kind: QueryFeatureExpressionKind,
|
||||||
|
) -> Self {
|
||||||
|
debug_assert!(feature_index < feature_type.features().len());
|
||||||
|
Self {
|
||||||
|
feature_type,
|
||||||
|
feature_index,
|
||||||
|
kind,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn write_name<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
|
||||||
|
where
|
||||||
|
W: fmt::Write,
|
||||||
|
{
|
||||||
|
let feature = self.feature();
|
||||||
|
if feature.flags.contains(FeatureFlags::WEBKIT_PREFIX) {
|
||||||
|
dest.write_str("-webkit-")?;
|
||||||
|
}
|
||||||
|
|
||||||
|
if let QueryFeatureExpressionKind::LegacyRange(range, _) = self.kind {
|
||||||
|
match range {
|
||||||
|
LegacyRange::Min => dest.write_str("min-")?,
|
||||||
|
LegacyRange::Max => dest.write_str("max-")?,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// NB: CssStringWriter not needed, feature names are under control.
|
||||||
|
write!(dest, "{}", feature.name)?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn feature(&self) -> &'static QueryFeatureDescription {
|
||||||
|
&self.feature_type.features()[self.feature_index]
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the feature flags for our feature.
|
||||||
|
pub fn feature_flags(&self) -> FeatureFlags {
|
||||||
|
self.feature().flags
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Parse a feature expression of the form:
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// (media-feature: media-value)
|
||||||
|
/// ```
|
||||||
|
pub fn parse<'i, 't>(
|
||||||
|
context: &ParserContext,
|
||||||
|
input: &mut Parser<'i, 't>,
|
||||||
|
feature_type: FeatureType,
|
||||||
|
) -> Result<Self, ParseError<'i>> {
|
||||||
|
input.expect_parenthesis_block()?;
|
||||||
|
input.parse_nested_block(|input| {
|
||||||
|
Self::parse_in_parenthesis_block(context, input, feature_type)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parse_feature_name<'i, 't>(
|
||||||
|
context: &ParserContext,
|
||||||
|
input: &mut Parser<'i, 't>,
|
||||||
|
feature_type: FeatureType,
|
||||||
|
) -> Result<(usize, Option<LegacyRange>), ParseError<'i>> {
|
||||||
|
let mut flags = FeatureFlags::empty();
|
||||||
|
let location = input.current_source_location();
|
||||||
|
let ident = input.expect_ident()?;
|
||||||
|
|
||||||
|
if context.in_ua_or_chrome_sheet() {
|
||||||
|
flags.insert(FeatureFlags::CHROME_AND_UA_ONLY);
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut feature_name = &**ident;
|
||||||
|
if starts_with_ignore_ascii_case(feature_name, "-webkit-") {
|
||||||
|
feature_name = &feature_name[8..];
|
||||||
|
flags.insert(FeatureFlags::WEBKIT_PREFIX);
|
||||||
|
}
|
||||||
|
|
||||||
|
let range = if starts_with_ignore_ascii_case(feature_name, "min-") {
|
||||||
|
feature_name = &feature_name[4..];
|
||||||
|
Some(LegacyRange::Min)
|
||||||
|
} else if starts_with_ignore_ascii_case(feature_name, "max-") {
|
||||||
|
feature_name = &feature_name[4..];
|
||||||
|
Some(LegacyRange::Max)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
};
|
||||||
|
|
||||||
|
let atom = Atom::from(string_as_ascii_lowercase(feature_name));
|
||||||
|
let (feature_index, feature) = match feature_type.find_feature(&atom) {
|
||||||
|
Some((i, f)) => (i, f),
|
||||||
|
None => {
|
||||||
|
return Err(location.new_custom_error(
|
||||||
|
StyleParseErrorKind::MediaQueryExpectedFeatureName(ident.clone()),
|
||||||
|
))
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
if disabled_by_pref(&feature.name, context) ||
|
||||||
|
!flags.contains(feature.flags.parsing_requirements()) ||
|
||||||
|
(range.is_some() && !feature.allows_ranges())
|
||||||
|
{
|
||||||
|
return Err(location.new_custom_error(
|
||||||
|
StyleParseErrorKind::MediaQueryExpectedFeatureName(ident.clone()),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok((feature_index, range))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Parses the following range syntax:
|
||||||
|
///
|
||||||
|
/// (feature-value <operator> feature-name)
|
||||||
|
/// (feature-value <operator> feature-name <operator> feature-value)
|
||||||
|
fn parse_multi_range_syntax<'i, 't>(
|
||||||
|
context: &ParserContext,
|
||||||
|
input: &mut Parser<'i, 't>,
|
||||||
|
feature_type: FeatureType,
|
||||||
|
) -> Result<Self, ParseError<'i>> {
|
||||||
|
let start = input.state();
|
||||||
|
|
||||||
|
// To parse the values, we first need to find the feature name. We rely
|
||||||
|
// on feature values for ranged features not being able to be top-level
|
||||||
|
// <ident>s, which holds.
|
||||||
|
let feature_index = loop {
|
||||||
|
// NOTE: parse_feature_name advances the input.
|
||||||
|
if let Ok((index, range)) = Self::parse_feature_name(context, input, feature_type) {
|
||||||
|
if range.is_some() {
|
||||||
|
// Ranged names are not allowed here.
|
||||||
|
return Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError));
|
||||||
|
}
|
||||||
|
break index;
|
||||||
|
}
|
||||||
|
if input.is_exhausted() {
|
||||||
|
return Err(start
|
||||||
|
.source_location()
|
||||||
|
.new_custom_error(StyleParseErrorKind::UnspecifiedError));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
input.reset(&start);
|
||||||
|
|
||||||
|
let feature = &feature_type.features()[feature_index];
|
||||||
|
let left_val = QueryExpressionValue::parse(feature, context, input)?;
|
||||||
|
let left_op = Operator::parse(input)?;
|
||||||
|
|
||||||
|
{
|
||||||
|
let (parsed_index, _) = Self::parse_feature_name(context, input, feature_type)?;
|
||||||
|
debug_assert_eq!(
|
||||||
|
parsed_index, feature_index,
|
||||||
|
"How did we find a different feature?"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
let right_op = input.try_parse(Operator::parse).ok();
|
||||||
|
let right = match right_op {
|
||||||
|
Some(op) => {
|
||||||
|
if !left_op.is_compatible_with(op) {
|
||||||
|
return Err(
|
||||||
|
input.new_custom_error(StyleParseErrorKind::UnspecifiedError)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
Some((op, QueryExpressionValue::parse(feature, context, input)?))
|
||||||
|
},
|
||||||
|
None => None,
|
||||||
|
};
|
||||||
|
Ok(Self::new(
|
||||||
|
feature_type,
|
||||||
|
feature_index,
|
||||||
|
QueryFeatureExpressionKind::Range {
|
||||||
|
left: Some((left_op, left_val)),
|
||||||
|
right,
|
||||||
|
},
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Parse a feature expression where we've already consumed the parenthesis.
|
||||||
|
pub fn parse_in_parenthesis_block<'i, 't>(
|
||||||
|
context: &ParserContext,
|
||||||
|
input: &mut Parser<'i, 't>,
|
||||||
|
feature_type: FeatureType,
|
||||||
|
) -> Result<Self, ParseError<'i>> {
|
||||||
|
let (feature_index, range) =
|
||||||
|
match input.try_parse(|input| Self::parse_feature_name(context, input, feature_type)) {
|
||||||
|
Ok(v) => v,
|
||||||
|
Err(e) => {
|
||||||
|
if let Ok(expr) = Self::parse_multi_range_syntax(context, input, feature_type) {
|
||||||
|
return Ok(expr);
|
||||||
|
}
|
||||||
|
return Err(e);
|
||||||
|
},
|
||||||
|
};
|
||||||
|
let operator = input.try_parse(consume_operation_or_colon);
|
||||||
|
let operator = match operator {
|
||||||
|
Err(..) => {
|
||||||
|
// If there's no colon, this is a query of the form
|
||||||
|
// '(<feature>)', that is, there's no value specified.
|
||||||
|
//
|
||||||
|
// Gecko doesn't allow ranged expressions without a
|
||||||
|
// value, so just reject them here too.
|
||||||
|
if range.is_some() {
|
||||||
|
return Err(
|
||||||
|
input.new_custom_error(StyleParseErrorKind::RangedExpressionWithNoValue)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return Ok(Self::new(
|
||||||
|
feature_type,
|
||||||
|
feature_index,
|
||||||
|
QueryFeatureExpressionKind::Empty,
|
||||||
|
));
|
||||||
|
},
|
||||||
|
Ok(operator) => operator,
|
||||||
|
};
|
||||||
|
|
||||||
|
let feature = &feature_type.features()[feature_index];
|
||||||
|
|
||||||
|
let value = QueryExpressionValue::parse(feature, context, input).map_err(|err| {
|
||||||
|
err.location
|
||||||
|
.new_custom_error(StyleParseErrorKind::MediaQueryExpectedFeatureValue)
|
||||||
|
})?;
|
||||||
|
|
||||||
|
let kind = match range {
|
||||||
|
Some(range) => {
|
||||||
|
if operator.is_some() {
|
||||||
|
return Err(
|
||||||
|
input.new_custom_error(StyleParseErrorKind::MediaQueryUnexpectedOperator)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
QueryFeatureExpressionKind::LegacyRange(range, value)
|
||||||
|
},
|
||||||
|
None => match operator {
|
||||||
|
Some(operator) => {
|
||||||
|
if !feature.allows_ranges() {
|
||||||
|
return Err(input
|
||||||
|
.new_custom_error(StyleParseErrorKind::MediaQueryUnexpectedOperator));
|
||||||
|
}
|
||||||
|
QueryFeatureExpressionKind::Range {
|
||||||
|
left: None,
|
||||||
|
right: Some((operator, value)),
|
||||||
|
}
|
||||||
|
},
|
||||||
|
None => QueryFeatureExpressionKind::Single(value),
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok(Self::new(feature_type, feature_index, kind))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns whether this query evaluates to true for the given device.
|
||||||
|
pub fn matches(&self, context: &computed::Context) -> bool {
|
||||||
|
macro_rules! expect {
|
||||||
|
($variant:ident, $v:expr) => {
|
||||||
|
match *$v {
|
||||||
|
QueryExpressionValue::$variant(ref v) => v,
|
||||||
|
_ => unreachable!("Unexpected QueryExpressionValue"),
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
match self.feature().evaluator {
|
||||||
|
Evaluator::Length(eval) => {
|
||||||
|
let v = eval(context);
|
||||||
|
self.kind.evaluate(v, |v| {
|
||||||
|
expect!(Length, v).to_computed_value(context)
|
||||||
|
})
|
||||||
|
},
|
||||||
|
Evaluator::Integer(eval) => {
|
||||||
|
let v = eval(context);
|
||||||
|
self.kind.evaluate(v, |v| *expect!(Integer, v))
|
||||||
|
},
|
||||||
|
Evaluator::Float(eval) => {
|
||||||
|
let v = eval(context);
|
||||||
|
self.kind.evaluate(v, |v| *expect!(Float, v))
|
||||||
|
}
|
||||||
|
Evaluator::NumberRatio(eval) => {
|
||||||
|
let ratio = eval(context);
|
||||||
|
// A ratio of 0/0 behaves as the ratio 1/0, so we need to call used_value()
|
||||||
|
// to convert it if necessary.
|
||||||
|
// FIXME: we may need to update here once
|
||||||
|
// https://github.com/w3c/csswg-drafts/issues/4954 got resolved.
|
||||||
|
self.kind.evaluate(ratio, |v| expect!(NumberRatio, v).used_value())
|
||||||
|
},
|
||||||
|
Evaluator::Resolution(eval) => {
|
||||||
|
let v = eval(context).dppx();
|
||||||
|
self.kind.evaluate(v, |v| {
|
||||||
|
expect!(Resolution, v).to_computed_value(context).dppx()
|
||||||
|
})
|
||||||
|
},
|
||||||
|
Evaluator::Enumerated { evaluator, .. } => {
|
||||||
|
let computed = self.kind.non_ranged_value().map(|v| *expect!(Enumerated, v));
|
||||||
|
evaluator(context, computed)
|
||||||
|
},
|
||||||
|
Evaluator::BoolInteger(eval) => {
|
||||||
|
let computed = self.kind.non_ranged_value().map(|v| *expect!(BoolInteger, v));
|
||||||
|
let boolean = eval(context);
|
||||||
|
computed.map_or(boolean, |v| v == boolean)
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A value found or expected in a expression.
|
||||||
|
///
|
||||||
|
/// FIXME(emilio): How should calc() serialize in the Number / Integer /
|
||||||
|
/// BoolInteger / NumberRatio case, as computed or as specified value?
|
||||||
|
///
|
||||||
|
/// If the first, this would need to store the relevant values.
|
||||||
|
///
|
||||||
|
/// See: https://github.com/w3c/csswg-drafts/issues/1968
|
||||||
|
#[derive(Clone, Debug, MallocSizeOf, PartialEq, ToShmem)]
|
||||||
|
pub enum QueryExpressionValue {
|
||||||
|
/// A length.
|
||||||
|
Length(Length),
|
||||||
|
/// A (non-negative) integer.
|
||||||
|
Integer(u32),
|
||||||
|
/// A floating point value.
|
||||||
|
Float(CSSFloat),
|
||||||
|
/// A boolean value, specified as an integer (i.e., either 0 or 1).
|
||||||
|
BoolInteger(bool),
|
||||||
|
/// A single non-negative number or two non-negative numbers separated by '/',
|
||||||
|
/// with optional whitespace on either side of the '/'.
|
||||||
|
NumberRatio(Ratio),
|
||||||
|
/// A resolution.
|
||||||
|
Resolution(Resolution),
|
||||||
|
/// An enumerated value, defined by the variant keyword table in the
|
||||||
|
/// feature's `mData` member.
|
||||||
|
Enumerated(KeywordDiscriminant),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl QueryExpressionValue {
|
||||||
|
fn to_css<W>(&self, dest: &mut CssWriter<W>, for_expr: &QueryFeatureExpression) -> fmt::Result
|
||||||
|
where
|
||||||
|
W: fmt::Write,
|
||||||
|
{
|
||||||
|
match *self {
|
||||||
|
QueryExpressionValue::Length(ref l) => l.to_css(dest),
|
||||||
|
QueryExpressionValue::Integer(v) => v.to_css(dest),
|
||||||
|
QueryExpressionValue::Float(v) => v.to_css(dest),
|
||||||
|
QueryExpressionValue::BoolInteger(v) => dest.write_str(if v { "1" } else { "0" }),
|
||||||
|
QueryExpressionValue::NumberRatio(ratio) => ratio.to_css(dest),
|
||||||
|
QueryExpressionValue::Resolution(ref r) => r.to_css(dest),
|
||||||
|
QueryExpressionValue::Enumerated(value) => match for_expr.feature().evaluator {
|
||||||
|
Evaluator::Enumerated { serializer, .. } => dest.write_str(&*serializer(value)),
|
||||||
|
_ => unreachable!(),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parse<'i, 't>(
|
||||||
|
for_feature: &QueryFeatureDescription,
|
||||||
|
context: &ParserContext,
|
||||||
|
input: &mut Parser<'i, 't>,
|
||||||
|
) -> Result<QueryExpressionValue, ParseError<'i>> {
|
||||||
|
Ok(match for_feature.evaluator {
|
||||||
|
Evaluator::Length(..) => {
|
||||||
|
let length = Length::parse_non_negative(context, input)?;
|
||||||
|
QueryExpressionValue::Length(length)
|
||||||
|
},
|
||||||
|
Evaluator::Integer(..) => {
|
||||||
|
let integer = Integer::parse_non_negative(context, input)?;
|
||||||
|
QueryExpressionValue::Integer(integer.value() as u32)
|
||||||
|
},
|
||||||
|
Evaluator::BoolInteger(..) => {
|
||||||
|
let integer = Integer::parse_non_negative(context, input)?;
|
||||||
|
let value = integer.value();
|
||||||
|
if value > 1 {
|
||||||
|
return Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError));
|
||||||
|
}
|
||||||
|
QueryExpressionValue::BoolInteger(value == 1)
|
||||||
|
},
|
||||||
|
Evaluator::Float(..) => {
|
||||||
|
let number = Number::parse(context, input)?;
|
||||||
|
QueryExpressionValue::Float(number.get())
|
||||||
|
},
|
||||||
|
Evaluator::NumberRatio(..) => {
|
||||||
|
use crate::values::specified::Ratio as SpecifiedRatio;
|
||||||
|
let ratio = SpecifiedRatio::parse(context, input)?;
|
||||||
|
QueryExpressionValue::NumberRatio(Ratio::new(ratio.0.get(), ratio.1.get()))
|
||||||
|
},
|
||||||
|
Evaluator::Resolution(..) => {
|
||||||
|
QueryExpressionValue::Resolution(Resolution::parse(context, input)?)
|
||||||
|
},
|
||||||
|
Evaluator::Enumerated { parser, .. } => {
|
||||||
|
QueryExpressionValue::Enumerated(parser(context, input)?)
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
19
components/style/queries/mod.rs
Normal file
19
components/style/queries/mod.rs
Normal file
|
@ -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};
|
36
components/style/queries/values.rs
Normal file
36
components/style/queries/values.rs
Normal file
|
@ -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<T>(size: euclid::default::Size2D<T>, value: Option<Self>) -> bool
|
||||||
|
where
|
||||||
|
T: PartialOrd,
|
||||||
|
{
|
||||||
|
let query_orientation = match value {
|
||||||
|
Some(v) => v,
|
||||||
|
None => return true,
|
||||||
|
};
|
||||||
|
|
||||||
|
// Per spec, square viewports should be 'portrait'
|
||||||
|
let is_landscape = size.width > size.height;
|
||||||
|
match query_orientation {
|
||||||
|
Self::Landscape => is_landscape,
|
||||||
|
Self::Portrait => !is_landscape,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -13,7 +13,7 @@ use crate::selector_parser::PseudoElement;
|
||||||
use crate::shared_lock::Locked;
|
use crate::shared_lock::Locked;
|
||||||
use crate::stylesheets::{layer_rule::LayerOrder, Origin};
|
use crate::stylesheets::{layer_rule::LayerOrder, Origin};
|
||||||
use crate::stylist::{AuthorStylesEnabled, CascadeData, Rule, RuleInclusion, Stylist};
|
use crate::stylist::{AuthorStylesEnabled, CascadeData, Rule, RuleInclusion, Stylist};
|
||||||
use selectors::matching::{ElementSelectorFlags, MatchingContext, MatchingMode};
|
use selectors::matching::{MatchingContext, MatchingMode};
|
||||||
use servo_arc::ArcBorrow;
|
use servo_arc::ArcBorrow;
|
||||||
use smallvec::SmallVec;
|
use smallvec::SmallVec;
|
||||||
|
|
||||||
|
@ -59,7 +59,7 @@ pub fn containing_shadow_ignoring_svg_use<E: TElement>(
|
||||||
///
|
///
|
||||||
/// This is done basically to be able to organize the cascade in smaller
|
/// This is done basically to be able to organize the cascade in smaller
|
||||||
/// functions, and be able to reason about it easily.
|
/// 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
|
where
|
||||||
E: TElement,
|
E: TElement,
|
||||||
{
|
{
|
||||||
|
@ -73,16 +73,14 @@ where
|
||||||
rule_inclusion: RuleInclusion,
|
rule_inclusion: RuleInclusion,
|
||||||
rules: &'a mut ApplicableDeclarationList,
|
rules: &'a mut ApplicableDeclarationList,
|
||||||
context: &'a mut MatchingContext<'b, E::Impl>,
|
context: &'a mut MatchingContext<'b, E::Impl>,
|
||||||
flags_setter: &'a mut F,
|
|
||||||
matches_user_and_author_rules: bool,
|
matches_user_and_author_rules: bool,
|
||||||
matches_document_author_rules: bool,
|
matches_document_author_rules: bool,
|
||||||
in_sort_scope: 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
|
where
|
||||||
E: TElement,
|
E: TElement,
|
||||||
F: FnMut(&E, ElementSelectorFlags),
|
|
||||||
{
|
{
|
||||||
/// Trivially construct a new collector.
|
/// Trivially construct a new collector.
|
||||||
pub fn new(
|
pub fn new(
|
||||||
|
@ -95,7 +93,6 @@ where
|
||||||
rule_inclusion: RuleInclusion,
|
rule_inclusion: RuleInclusion,
|
||||||
rules: &'a mut ApplicableDeclarationList,
|
rules: &'a mut ApplicableDeclarationList,
|
||||||
context: &'a mut MatchingContext<'b, E::Impl>,
|
context: &'a mut MatchingContext<'b, E::Impl>,
|
||||||
flags_setter: &'a mut F,
|
|
||||||
) -> Self {
|
) -> Self {
|
||||||
// When we're matching with matching_mode =
|
// When we're matching with matching_mode =
|
||||||
// `ForStatelessPseudoeElement`, the "target" for the rule hash is the
|
// `ForStatelessPseudoeElement`, the "target" for the rule hash is the
|
||||||
|
@ -125,7 +122,6 @@ where
|
||||||
animation_declarations,
|
animation_declarations,
|
||||||
rule_inclusion,
|
rule_inclusion,
|
||||||
context,
|
context,
|
||||||
flags_setter,
|
|
||||||
rules,
|
rules,
|
||||||
matches_user_and_author_rules,
|
matches_user_and_author_rules,
|
||||||
matches_document_author_rules: matches_user_and_author_rules,
|
matches_document_author_rules: matches_user_and_author_rules,
|
||||||
|
@ -227,9 +223,9 @@ where
|
||||||
part_rules,
|
part_rules,
|
||||||
&mut self.rules,
|
&mut self.rules,
|
||||||
&mut self.context,
|
&mut self.context,
|
||||||
&mut self.flags_setter,
|
|
||||||
cascade_level,
|
cascade_level,
|
||||||
cascade_data,
|
cascade_data,
|
||||||
|
&self.stylist,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -246,9 +242,9 @@ where
|
||||||
self.rule_hash_target,
|
self.rule_hash_target,
|
||||||
&mut self.rules,
|
&mut self.rules,
|
||||||
&mut self.context,
|
&mut self.context,
|
||||||
&mut self.flags_setter,
|
|
||||||
cascade_level,
|
cascade_level,
|
||||||
cascade_data,
|
cascade_data,
|
||||||
|
&self.stylist,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -203,9 +203,6 @@ impl RuleTree {
|
||||||
/// where it likely did not result from a rigorous performance analysis.)
|
/// where it likely did not result from a rigorous performance analysis.)
|
||||||
const RULE_TREE_GC_INTERVAL: usize = 300;
|
const RULE_TREE_GC_INTERVAL: usize = 300;
|
||||||
|
|
||||||
/// Used for some size assertions.
|
|
||||||
pub const RULE_NODE_SIZE: usize = std::mem::size_of::<RuleNode>();
|
|
||||||
|
|
||||||
/// A node in the rule tree.
|
/// A node in the rule tree.
|
||||||
struct RuleNode {
|
struct RuleNode {
|
||||||
/// The root node. Only the root has no root pointer, for obvious reasons.
|
/// 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)
|
(&*self.p as *const RuleNode).hash(state)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Large pages generate thousands of RuleNode objects.
|
||||||
|
size_of_test!(RuleNode, 80);
|
||||||
|
// StrongRuleNode should be pointer-sized even inside an option.
|
||||||
|
size_of_test!(Option<StrongRuleNode>, 8);
|
||||||
|
|
|
@ -20,7 +20,7 @@ mod map;
|
||||||
mod source;
|
mod source;
|
||||||
mod unsafe_box;
|
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::level::{CascadeLevel, ShadowCascadeOrder};
|
||||||
pub use self::source::StyleSource;
|
pub use self::source::StyleSource;
|
||||||
|
|
||||||
|
|
|
@ -10,11 +10,11 @@ use crate::context::QuirksMode;
|
||||||
use crate::dom::TElement;
|
use crate::dom::TElement;
|
||||||
use crate::rule_tree::CascadeLevel;
|
use crate::rule_tree::CascadeLevel;
|
||||||
use crate::selector_parser::SelectorImpl;
|
use crate::selector_parser::SelectorImpl;
|
||||||
use crate::stylist::{CascadeData, Rule};
|
use crate::stylist::{Stylist, CascadeData, Rule, ContainerConditionId};
|
||||||
use crate::AllocErr;
|
use crate::AllocErr;
|
||||||
use crate::{Atom, LocalName, Namespace, ShrinkIfNeeded, WeakAtom};
|
use crate::{Atom, LocalName, Namespace, ShrinkIfNeeded, WeakAtom};
|
||||||
use precomputed_hash::PrecomputedHash;
|
use precomputed_hash::PrecomputedHash;
|
||||||
use selectors::matching::{matches_selector, ElementSelectorFlags, MatchingContext};
|
use selectors::matching::{matches_selector, MatchingContext};
|
||||||
use selectors::parser::{Combinator, Component, SelectorIter};
|
use selectors::parser::{Combinator, Component, SelectorIter};
|
||||||
use smallvec::SmallVec;
|
use smallvec::SmallVec;
|
||||||
use std::collections::hash_map;
|
use std::collections::hash_map;
|
||||||
|
@ -186,34 +186,33 @@ impl SelectorMap<Rule> {
|
||||||
///
|
///
|
||||||
/// Extract matching rules as per element's ID, classes, tag name, etc..
|
/// Extract matching rules as per element's ID, classes, tag name, etc..
|
||||||
/// Sort the Rules at the end to maintain cascading order.
|
/// Sort the Rules at the end to maintain cascading order.
|
||||||
pub fn get_all_matching_rules<E, F>(
|
pub fn get_all_matching_rules<E>(
|
||||||
&self,
|
&self,
|
||||||
element: E,
|
element: E,
|
||||||
rule_hash_target: E,
|
rule_hash_target: E,
|
||||||
matching_rules_list: &mut ApplicableDeclarationList,
|
matching_rules_list: &mut ApplicableDeclarationList,
|
||||||
context: &mut MatchingContext<E::Impl>,
|
matching_context: &mut MatchingContext<E::Impl>,
|
||||||
flags_setter: &mut F,
|
|
||||||
cascade_level: CascadeLevel,
|
cascade_level: CascadeLevel,
|
||||||
cascade_data: &CascadeData,
|
cascade_data: &CascadeData,
|
||||||
|
stylist: &Stylist,
|
||||||
) where
|
) where
|
||||||
E: TElement,
|
E: TElement,
|
||||||
F: FnMut(&E, ElementSelectorFlags),
|
|
||||||
{
|
{
|
||||||
if self.is_empty() {
|
if self.is_empty() {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
let quirks_mode = context.quirks_mode();
|
let quirks_mode = matching_context.quirks_mode();
|
||||||
|
|
||||||
if rule_hash_target.is_root() {
|
if rule_hash_target.is_root() {
|
||||||
SelectorMap::get_matching_rules(
|
SelectorMap::get_matching_rules(
|
||||||
element,
|
element,
|
||||||
&self.root,
|
&self.root,
|
||||||
matching_rules_list,
|
matching_rules_list,
|
||||||
context,
|
matching_context,
|
||||||
flags_setter,
|
|
||||||
cascade_level,
|
cascade_level,
|
||||||
cascade_data,
|
cascade_data,
|
||||||
|
stylist,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -223,10 +222,10 @@ impl SelectorMap<Rule> {
|
||||||
element,
|
element,
|
||||||
rules,
|
rules,
|
||||||
matching_rules_list,
|
matching_rules_list,
|
||||||
context,
|
matching_context,
|
||||||
flags_setter,
|
|
||||||
cascade_level,
|
cascade_level,
|
||||||
cascade_data,
|
cascade_data,
|
||||||
|
stylist,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -237,10 +236,10 @@ impl SelectorMap<Rule> {
|
||||||
element,
|
element,
|
||||||
rules,
|
rules,
|
||||||
matching_rules_list,
|
matching_rules_list,
|
||||||
context,
|
matching_context,
|
||||||
flags_setter,
|
|
||||||
cascade_level,
|
cascade_level,
|
||||||
cascade_data,
|
cascade_data,
|
||||||
|
stylist,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -252,10 +251,10 @@ impl SelectorMap<Rule> {
|
||||||
element,
|
element,
|
||||||
rules,
|
rules,
|
||||||
matching_rules_list,
|
matching_rules_list,
|
||||||
context,
|
matching_context,
|
||||||
flags_setter,
|
|
||||||
cascade_level,
|
cascade_level,
|
||||||
cascade_data,
|
cascade_data,
|
||||||
|
stylist,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -266,10 +265,10 @@ impl SelectorMap<Rule> {
|
||||||
element,
|
element,
|
||||||
rules,
|
rules,
|
||||||
matching_rules_list,
|
matching_rules_list,
|
||||||
context,
|
matching_context,
|
||||||
flags_setter,
|
|
||||||
cascade_level,
|
cascade_level,
|
||||||
cascade_data,
|
cascade_data,
|
||||||
|
stylist,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -278,10 +277,10 @@ impl SelectorMap<Rule> {
|
||||||
element,
|
element,
|
||||||
rules,
|
rules,
|
||||||
matching_rules_list,
|
matching_rules_list,
|
||||||
context,
|
matching_context,
|
||||||
flags_setter,
|
|
||||||
cascade_level,
|
cascade_level,
|
||||||
cascade_data,
|
cascade_data,
|
||||||
|
stylist,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -289,38 +288,44 @@ impl SelectorMap<Rule> {
|
||||||
element,
|
element,
|
||||||
&self.other,
|
&self.other,
|
||||||
matching_rules_list,
|
matching_rules_list,
|
||||||
context,
|
matching_context,
|
||||||
flags_setter,
|
|
||||||
cascade_level,
|
cascade_level,
|
||||||
cascade_data,
|
cascade_data,
|
||||||
|
stylist,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Adds rules in `rules` that match `element` to the `matching_rules` list.
|
/// Adds rules in `rules` that match `element` to the `matching_rules` list.
|
||||||
pub(crate) fn get_matching_rules<E, F>(
|
pub(crate) fn get_matching_rules<E>(
|
||||||
element: E,
|
element: E,
|
||||||
rules: &[Rule],
|
rules: &[Rule],
|
||||||
matching_rules: &mut ApplicableDeclarationList,
|
matching_rules: &mut ApplicableDeclarationList,
|
||||||
context: &mut MatchingContext<E::Impl>,
|
matching_context: &mut MatchingContext<E::Impl>,
|
||||||
flags_setter: &mut F,
|
|
||||||
cascade_level: CascadeLevel,
|
cascade_level: CascadeLevel,
|
||||||
cascade_data: &CascadeData,
|
cascade_data: &CascadeData,
|
||||||
|
stylist: &Stylist,
|
||||||
) where
|
) where
|
||||||
E: TElement,
|
E: TElement,
|
||||||
F: FnMut(&E, ElementSelectorFlags),
|
|
||||||
{
|
{
|
||||||
for rule in rules {
|
for rule in rules {
|
||||||
if matches_selector(
|
if !matches_selector(
|
||||||
&rule.selector,
|
&rule.selector,
|
||||||
0,
|
0,
|
||||||
Some(&rule.hashes),
|
Some(&rule.hashes),
|
||||||
&element,
|
&element,
|
||||||
context,
|
matching_context,
|
||||||
flags_setter,
|
|
||||||
) {
|
) {
|
||||||
matching_rules
|
continue;
|
||||||
.push(rule.to_applicable_declaration_block(cascade_level, cascade_data));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if rule.container_condition_id != ContainerConditionId::none() {
|
||||||
|
if !cascade_data.container_condition_matches(rule.container_condition_id, stylist, element) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
matching_rules
|
||||||
|
.push(rule.to_applicable_declaration_block(cascade_level, cascade_data));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,13 +7,13 @@
|
||||||
use crate::context::QuirksMode;
|
use crate::context::QuirksMode;
|
||||||
use crate::custom_properties::CssEnvironment;
|
use crate::custom_properties::CssEnvironment;
|
||||||
use crate::font_metrics::FontMetrics;
|
use crate::font_metrics::FontMetrics;
|
||||||
use crate::media_queries::media_feature::{AllowsRanges, ParsingRequirements};
|
use crate::queries::feature::{AllowsRanges, Evaluator, FeatureFlags, QueryFeatureDescription};
|
||||||
use crate::media_queries::media_feature::{Evaluator, MediaFeatureDescription};
|
|
||||||
use crate::media_queries::media_feature_expression::RangeOrOperator;
|
|
||||||
use crate::media_queries::MediaType;
|
use crate::media_queries::MediaType;
|
||||||
use crate::properties::ComputedValues;
|
use crate::properties::ComputedValues;
|
||||||
use crate::values::computed::CSSPixelLength;
|
use crate::values::computed::CSSPixelLength;
|
||||||
|
use crate::values::computed::Context;
|
||||||
use crate::values::specified::font::FONT_MEDIUM_PX;
|
use crate::values::specified::font::FONT_MEDIUM_PX;
|
||||||
|
use crate::values::specified::ViewportVariant;
|
||||||
use crate::values::KeyframesName;
|
use crate::values::KeyframesName;
|
||||||
use app_units::Au;
|
use app_units::Au;
|
||||||
use cssparser::RGBA;
|
use cssparser::RGBA;
|
||||||
|
@ -143,8 +143,13 @@ impl Device {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Like the above, but records that we've used viewport units.
|
/// Like the above, but records that we've used viewport units.
|
||||||
pub fn au_viewport_size_for_viewport_unit_resolution(&self) -> UntypedSize2D<Au> {
|
pub fn au_viewport_size_for_viewport_unit_resolution(
|
||||||
|
&self,
|
||||||
|
_: ViewportVariant,
|
||||||
|
) -> UntypedSize2D<Au> {
|
||||||
self.used_viewport_units.store(true, Ordering::Relaxed);
|
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()
|
self.au_viewport_size()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -229,16 +234,8 @@ impl Device {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// https://drafts.csswg.org/mediaqueries-4/#width
|
/// https://drafts.csswg.org/mediaqueries-4/#width
|
||||||
fn eval_width(
|
fn eval_width(context: &Context) -> CSSPixelLength {
|
||||||
device: &Device,
|
CSSPixelLength::new(context.device().au_viewport_size().width.to_f32_px())
|
||||||
value: Option<CSSPixelLength>,
|
|
||||||
range_or_operator: Option<RangeOrOperator>,
|
|
||||||
) -> bool {
|
|
||||||
RangeOrOperator::evaluate(
|
|
||||||
range_or_operator,
|
|
||||||
value.map(Au::from),
|
|
||||||
device.au_viewport_size().width,
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Copy, Debug, FromPrimitive, Parse, ToCss)]
|
#[derive(Clone, Copy, Debug, FromPrimitive, Parse, ToCss)]
|
||||||
|
@ -249,7 +246,7 @@ enum Scan {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// https://drafts.csswg.org/mediaqueries-4/#scan
|
/// https://drafts.csswg.org/mediaqueries-4/#scan
|
||||||
fn eval_scan(_: &Device, _: Option<Scan>) -> bool {
|
fn eval_scan(_: &Context, _: Option<Scan>) -> bool {
|
||||||
// Since we doesn't support the 'tv' media type, the 'scan' feature never
|
// Since we doesn't support the 'tv' media type, the 'scan' feature never
|
||||||
// matches.
|
// matches.
|
||||||
false
|
false
|
||||||
|
@ -257,18 +254,18 @@ fn eval_scan(_: &Device, _: Option<Scan>) -> bool {
|
||||||
|
|
||||||
lazy_static! {
|
lazy_static! {
|
||||||
/// A list with all the media features that Servo supports.
|
/// 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!(
|
feature!(
|
||||||
atom!("width"),
|
atom!("width"),
|
||||||
AllowsRanges::Yes,
|
AllowsRanges::Yes,
|
||||||
Evaluator::Length(eval_width),
|
Evaluator::Length(eval_width),
|
||||||
ParsingRequirements::empty(),
|
FeatureFlags::empty(),
|
||||||
),
|
),
|
||||||
feature!(
|
feature!(
|
||||||
atom!("scan"),
|
atom!("scan"),
|
||||||
AllowsRanges::No,
|
AllowsRanges::No,
|
||||||
keyword_evaluator!(eval_scan, Scan),
|
keyword_evaluator!(eval_scan, Scan),
|
||||||
ParsingRequirements::empty(),
|
FeatureFlags::empty(),
|
||||||
),
|
),
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,7 +7,7 @@
|
||||||
//! elements can indeed share the same style.
|
//! elements can indeed share the same style.
|
||||||
|
|
||||||
use crate::bloom::StyleBloom;
|
use crate::bloom::StyleBloom;
|
||||||
use crate::context::{SelectorFlagsMap, SharedStyleContext};
|
use crate::context::SharedStyleContext;
|
||||||
use crate::dom::TElement;
|
use crate::dom::TElement;
|
||||||
use crate::sharing::{StyleSharingCandidate, StyleSharingTarget};
|
use crate::sharing::{StyleSharingCandidate, StyleSharingTarget};
|
||||||
use selectors::NthIndexCache;
|
use selectors::NthIndexCache;
|
||||||
|
@ -120,7 +120,6 @@ pub fn revalidate<E>(
|
||||||
shared_context: &SharedStyleContext,
|
shared_context: &SharedStyleContext,
|
||||||
bloom: &StyleBloom<E>,
|
bloom: &StyleBloom<E>,
|
||||||
nth_index_cache: &mut NthIndexCache,
|
nth_index_cache: &mut NthIndexCache,
|
||||||
selector_flags_map: &mut SelectorFlagsMap<E>,
|
|
||||||
) -> bool
|
) -> bool
|
||||||
where
|
where
|
||||||
E: TElement,
|
E: TElement,
|
||||||
|
@ -128,7 +127,7 @@ where
|
||||||
let stylist = &shared_context.stylist;
|
let stylist = &shared_context.stylist;
|
||||||
|
|
||||||
let for_element =
|
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);
|
let for_candidate = candidate.revalidation_match_results(stylist, bloom, nth_index_cache);
|
||||||
|
|
||||||
|
|
|
@ -66,9 +66,8 @@
|
||||||
|
|
||||||
use crate::applicable_declarations::ApplicableDeclarationBlock;
|
use crate::applicable_declarations::ApplicableDeclarationBlock;
|
||||||
use crate::bloom::StyleBloom;
|
use crate::bloom::StyleBloom;
|
||||||
use crate::context::{SelectorFlagsMap, SharedStyleContext, StyleContext};
|
use crate::context::{SharedStyleContext, StyleContext};
|
||||||
use crate::dom::{SendElement, TElement};
|
use crate::dom::{SendElement, TElement};
|
||||||
use crate::matching::MatchMethods;
|
|
||||||
use crate::properties::ComputedValues;
|
use crate::properties::ComputedValues;
|
||||||
use crate::rule_tree::StrongRuleNode;
|
use crate::rule_tree::StrongRuleNode;
|
||||||
use crate::style_resolver::{PrimaryStyle, ResolvedElementStyles};
|
use crate::style_resolver::{PrimaryStyle, ResolvedElementStyles};
|
||||||
|
@ -76,7 +75,7 @@ use crate::stylist::Stylist;
|
||||||
use crate::values::AtomIdent;
|
use crate::values::AtomIdent;
|
||||||
use atomic_refcell::{AtomicRefCell, AtomicRefMut};
|
use atomic_refcell::{AtomicRefCell, AtomicRefMut};
|
||||||
use owning_ref::OwningHandle;
|
use owning_ref::OwningHandle;
|
||||||
use selectors::matching::{ElementSelectorFlags, VisitedHandlingMode};
|
use selectors::matching::{VisitedHandlingMode, NeedsSelectorFlags};
|
||||||
use selectors::NthIndexCache;
|
use selectors::NthIndexCache;
|
||||||
use servo_arc::Arc;
|
use servo_arc::Arc;
|
||||||
use smallbitvec::SmallBitVec;
|
use smallbitvec::SmallBitVec;
|
||||||
|
@ -223,18 +222,17 @@ impl ValidationData {
|
||||||
/// Computes the revalidation results if needed, and returns it.
|
/// Computes the revalidation results if needed, and returns it.
|
||||||
/// Inline so we know at compile time what bloom_known_valid is.
|
/// Inline so we know at compile time what bloom_known_valid is.
|
||||||
#[inline]
|
#[inline]
|
||||||
fn revalidation_match_results<E, F>(
|
fn revalidation_match_results<E>(
|
||||||
&mut self,
|
&mut self,
|
||||||
element: E,
|
element: E,
|
||||||
stylist: &Stylist,
|
stylist: &Stylist,
|
||||||
bloom: &StyleBloom<E>,
|
bloom: &StyleBloom<E>,
|
||||||
nth_index_cache: &mut NthIndexCache,
|
nth_index_cache: &mut NthIndexCache,
|
||||||
bloom_known_valid: bool,
|
bloom_known_valid: bool,
|
||||||
flags_setter: &mut F,
|
needs_selector_flags: NeedsSelectorFlags,
|
||||||
) -> &SmallBitVec
|
) -> &SmallBitVec
|
||||||
where
|
where
|
||||||
E: TElement,
|
E: TElement,
|
||||||
F: FnMut(&E, ElementSelectorFlags),
|
|
||||||
{
|
{
|
||||||
self.revalidation_match_results.get_or_insert_with(|| {
|
self.revalidation_match_results.get_or_insert_with(|| {
|
||||||
// The bloom filter may already be set up for our element.
|
// The bloom filter may already be set up for our element.
|
||||||
|
@ -257,7 +255,7 @@ impl ValidationData {
|
||||||
element,
|
element,
|
||||||
bloom_to_use,
|
bloom_to_use,
|
||||||
nth_index_cache,
|
nth_index_cache,
|
||||||
flags_setter,
|
needs_selector_flags,
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -327,7 +325,9 @@ impl<E: TElement> StyleSharingCandidate<E> {
|
||||||
bloom,
|
bloom,
|
||||||
nth_index_cache,
|
nth_index_cache,
|
||||||
/* bloom_known_valid = */ false,
|
/* bloom_known_valid = */ false,
|
||||||
&mut |_, _| {},
|
// The candidate must already have the right bits already, if
|
||||||
|
// needed.
|
||||||
|
NeedsSelectorFlags::No,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -384,7 +384,6 @@ impl<E: TElement> StyleSharingTarget<E> {
|
||||||
stylist: &Stylist,
|
stylist: &Stylist,
|
||||||
bloom: &StyleBloom<E>,
|
bloom: &StyleBloom<E>,
|
||||||
nth_index_cache: &mut NthIndexCache,
|
nth_index_cache: &mut NthIndexCache,
|
||||||
selector_flags_map: &mut SelectorFlagsMap<E>,
|
|
||||||
) -> &SmallBitVec {
|
) -> &SmallBitVec {
|
||||||
// It's important to set the selector flags. Otherwise, if we succeed in
|
// 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
|
// sharing the style, we may not set the slow selector flags for the
|
||||||
|
@ -401,18 +400,13 @@ impl<E: TElement> StyleSharingTarget<E> {
|
||||||
// The style sharing cache will get a hit for the second span. When the
|
// The style sharing cache will get a hit for the second span. When the
|
||||||
// child span is subsequently removed from the DOM, missing selector
|
// child span is subsequently removed from the DOM, missing selector
|
||||||
// flags would cause us to miss the restyle on the second span.
|
// 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.validation_data.revalidation_match_results(
|
||||||
self.element,
|
self.element,
|
||||||
stylist,
|
stylist,
|
||||||
bloom,
|
bloom,
|
||||||
nth_index_cache,
|
nth_index_cache,
|
||||||
/* bloom_known_valid = */ true,
|
/* bloom_known_valid = */ true,
|
||||||
&mut set_selector_flags,
|
NeedsSelectorFlags::Yes,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -423,7 +417,6 @@ impl<E: TElement> StyleSharingTarget<E> {
|
||||||
) -> Option<ResolvedElementStyles> {
|
) -> Option<ResolvedElementStyles> {
|
||||||
let cache = &mut context.thread_local.sharing_cache;
|
let cache = &mut context.thread_local.sharing_cache;
|
||||||
let shared_context = &context.shared;
|
let shared_context = &context.shared;
|
||||||
let selector_flags_map = &mut context.thread_local.selector_flags;
|
|
||||||
let bloom_filter = &context.thread_local.bloom_filter;
|
let bloom_filter = &context.thread_local.bloom_filter;
|
||||||
let nth_index_cache = &mut context.thread_local.nth_index_cache;
|
let nth_index_cache = &mut context.thread_local.nth_index_cache;
|
||||||
|
|
||||||
|
@ -443,7 +436,6 @@ impl<E: TElement> StyleSharingTarget<E> {
|
||||||
|
|
||||||
cache.share_style_if_possible(
|
cache.share_style_if_possible(
|
||||||
shared_context,
|
shared_context,
|
||||||
selector_flags_map,
|
|
||||||
bloom_filter,
|
bloom_filter,
|
||||||
nth_index_cache,
|
nth_index_cache,
|
||||||
self,
|
self,
|
||||||
|
@ -631,13 +623,13 @@ impl<E: TElement> StyleSharingCache<E> {
|
||||||
//
|
//
|
||||||
// These are things we don't check in the candidate match because they
|
// These are things we don't check in the candidate match because they
|
||||||
// are either uncommon or expensive.
|
// are either uncommon or expensive.
|
||||||
let box_style = style.style().get_box();
|
let ui_style = style.style().get_ui();
|
||||||
if box_style.specifies_transitions() {
|
if ui_style.specifies_transitions() {
|
||||||
debug!("Failing to insert to the cache: transitions");
|
debug!("Failing to insert to the cache: transitions");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if box_style.specifies_animations() {
|
if ui_style.specifies_animations() {
|
||||||
debug!("Failing to insert to the cache: animations");
|
debug!("Failing to insert to the cache: animations");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -667,7 +659,6 @@ impl<E: TElement> StyleSharingCache<E> {
|
||||||
fn share_style_if_possible(
|
fn share_style_if_possible(
|
||||||
&mut self,
|
&mut self,
|
||||||
shared_context: &SharedStyleContext,
|
shared_context: &SharedStyleContext,
|
||||||
selector_flags_map: &mut SelectorFlagsMap<E>,
|
|
||||||
bloom_filter: &StyleBloom<E>,
|
bloom_filter: &StyleBloom<E>,
|
||||||
nth_index_cache: &mut NthIndexCache,
|
nth_index_cache: &mut NthIndexCache,
|
||||||
target: &mut StyleSharingTarget<E>,
|
target: &mut StyleSharingTarget<E>,
|
||||||
|
@ -700,7 +691,6 @@ impl<E: TElement> StyleSharingCache<E> {
|
||||||
&shared_context,
|
&shared_context,
|
||||||
bloom_filter,
|
bloom_filter,
|
||||||
nth_index_cache,
|
nth_index_cache,
|
||||||
selector_flags_map,
|
|
||||||
shared_context,
|
shared_context,
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
@ -712,7 +702,6 @@ impl<E: TElement> StyleSharingCache<E> {
|
||||||
shared: &SharedStyleContext,
|
shared: &SharedStyleContext,
|
||||||
bloom: &StyleBloom<E>,
|
bloom: &StyleBloom<E>,
|
||||||
nth_index_cache: &mut NthIndexCache,
|
nth_index_cache: &mut NthIndexCache,
|
||||||
selector_flags_map: &mut SelectorFlagsMap<E>,
|
|
||||||
shared_context: &SharedStyleContext,
|
shared_context: &SharedStyleContext,
|
||||||
) -> Option<ResolvedElementStyles> {
|
) -> Option<ResolvedElementStyles> {
|
||||||
debug_assert!(!target.is_in_native_anonymous_subtree());
|
debug_assert!(!target.is_in_native_anonymous_subtree());
|
||||||
|
@ -817,7 +806,6 @@ impl<E: TElement> StyleSharingCache<E> {
|
||||||
shared,
|
shared,
|
||||||
bloom,
|
bloom,
|
||||||
nth_index_cache,
|
nth_index_cache,
|
||||||
selector_flags_map,
|
|
||||||
) {
|
) {
|
||||||
trace!("Miss: Revalidation");
|
trace!("Miss: Revalidation");
|
||||||
return None;
|
return None;
|
||||||
|
|
|
@ -15,7 +15,7 @@ use crate::rule_tree::StrongRuleNode;
|
||||||
use crate::selector_parser::{PseudoElement, SelectorImpl};
|
use crate::selector_parser::{PseudoElement, SelectorImpl};
|
||||||
use crate::stylist::RuleInclusion;
|
use crate::stylist::RuleInclusion;
|
||||||
use log::Level::Trace;
|
use log::Level::Trace;
|
||||||
use selectors::matching::{ElementSelectorFlags, MatchingContext};
|
use selectors::matching::{NeedsSelectorFlags, MatchingContext};
|
||||||
use selectors::matching::{MatchingMode, VisitedHandlingMode};
|
use selectors::matching::{MatchingMode, VisitedHandlingMode};
|
||||||
use servo_arc::Arc;
|
use servo_arc::Arc;
|
||||||
|
|
||||||
|
@ -451,7 +451,6 @@ where
|
||||||
);
|
);
|
||||||
let mut applicable_declarations = ApplicableDeclarationList::new();
|
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 bloom_filter = self.context.thread_local.bloom_filter.filter();
|
||||||
let nth_index_cache = &mut self.context.thread_local.nth_index_cache;
|
let nth_index_cache = &mut self.context.thread_local.nth_index_cache;
|
||||||
let mut matching_context = MatchingContext::new_for_visited(
|
let mut matching_context = MatchingContext::new_for_visited(
|
||||||
|
@ -460,29 +459,22 @@ where
|
||||||
Some(nth_index_cache),
|
Some(nth_index_cache),
|
||||||
visited_handling,
|
visited_handling,
|
||||||
self.context.shared.quirks_mode(),
|
self.context.shared.quirks_mode(),
|
||||||
|
NeedsSelectorFlags::Yes,
|
||||||
);
|
);
|
||||||
|
|
||||||
let stylist = &self.context.shared.stylist;
|
let stylist = &self.context.shared.stylist;
|
||||||
let implemented_pseudo = self.element.implemented_pseudo_element();
|
let implemented_pseudo = self.element.implemented_pseudo_element();
|
||||||
{
|
// Compute the primary rule node.
|
||||||
let resolving_element = self.element;
|
stylist.push_applicable_declarations(
|
||||||
let mut set_selector_flags = |element: &E, flags: ElementSelectorFlags| {
|
self.element,
|
||||||
resolving_element.apply_selector_flags(map, element, flags);
|
implemented_pseudo.as_ref(),
|
||||||
};
|
self.element.style_attribute(),
|
||||||
|
self.element.smil_override(),
|
||||||
// Compute the primary rule node.
|
self.element.animation_declarations(self.context.shared),
|
||||||
stylist.push_applicable_declarations(
|
self.rule_inclusion,
|
||||||
self.element,
|
&mut applicable_declarations,
|
||||||
implemented_pseudo.as_ref(),
|
&mut matching_context,
|
||||||
self.element.style_attribute(),
|
);
|
||||||
self.element.smil_override(),
|
|
||||||
self.element.animation_declarations(self.context.shared),
|
|
||||||
self.rule_inclusion,
|
|
||||||
&mut applicable_declarations,
|
|
||||||
&mut matching_context,
|
|
||||||
&mut set_selector_flags,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
// FIXME(emilio): This is a hack for animations, and should go away.
|
// FIXME(emilio): This is a hack for animations, and should go away.
|
||||||
self.element.unset_dirty_style_attribute();
|
self.element.unset_dirty_style_attribute();
|
||||||
|
@ -540,14 +532,9 @@ where
|
||||||
Some(nth_index_cache),
|
Some(nth_index_cache),
|
||||||
visited_handling,
|
visited_handling,
|
||||||
self.context.shared.quirks_mode(),
|
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
|
// NB: We handle animation rules for ::before and ::after when
|
||||||
// traversing them.
|
// traversing them.
|
||||||
stylist.push_applicable_declarations(
|
stylist.push_applicable_declarations(
|
||||||
|
@ -559,7 +546,6 @@ where
|
||||||
self.rule_inclusion,
|
self.rule_inclusion,
|
||||||
&mut applicable_declarations,
|
&mut applicable_declarations,
|
||||||
&mut matching_context,
|
&mut matching_context,
|
||||||
&mut set_selector_flags,
|
|
||||||
);
|
);
|
||||||
|
|
||||||
if applicable_declarations.is_empty() {
|
if applicable_declarations.is_empty() {
|
||||||
|
|
281
components/style/stylesheets/container_rule.rs
Normal file
281
components/style/stylesheets/container_rule.rs
Normal file
|
@ -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<ContainerCondition>,
|
||||||
|
/// The nested rules inside the block.
|
||||||
|
pub rules: Arc<Locked<CssRules>>,
|
||||||
|
/// The source position where this rule was found.
|
||||||
|
pub source_location: SourceLocation,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ContainerRule {
|
||||||
|
/// Returns the query condition.
|
||||||
|
pub fn query_condition(&self) -> &QueryCondition {
|
||||||
|
&self.condition.condition
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Measure heap usage.
|
||||||
|
#[cfg(feature = "gecko")]
|
||||||
|
pub fn size_of(&self, guard: &SharedRwLockReadGuard, ops: &mut MallocSizeOfOps) -> usize {
|
||||||
|
// Measurement of other fields may be added later.
|
||||||
|
self.rules.unconditional_shallow_size_of(ops)
|
||||||
|
+ self.rules.read_with(guard).size_of(guard, ops)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl DeepCloneWithLock for ContainerRule {
|
||||||
|
fn deep_clone_with_lock(
|
||||||
|
&self,
|
||||||
|
lock: &SharedRwLock,
|
||||||
|
guard: &SharedRwLockReadGuard,
|
||||||
|
params: &DeepCloneParams,
|
||||||
|
) -> Self {
|
||||||
|
let rules = self.rules.read_with(guard);
|
||||||
|
Self {
|
||||||
|
condition: self.condition.clone(),
|
||||||
|
rules: Arc::new(lock.wrap(rules.deep_clone_with_lock(lock, guard, params))),
|
||||||
|
source_location: self.source_location.clone(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ToCssWithGuard for ContainerRule {
|
||||||
|
fn to_css(&self, guard: &SharedRwLockReadGuard, dest: &mut CssStringWriter) -> fmt::Result {
|
||||||
|
dest.write_str("@container ")?;
|
||||||
|
{
|
||||||
|
let mut writer = CssWriter::new(dest);
|
||||||
|
if !self.condition.name.is_none() {
|
||||||
|
self.condition.name.to_css(&mut writer)?;
|
||||||
|
writer.write_char(' ')?;
|
||||||
|
}
|
||||||
|
self.condition.condition.to_css(&mut writer)?;
|
||||||
|
}
|
||||||
|
self.rules.read_with(guard).to_css_block(guard, dest)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A container condition and filter, combined.
|
||||||
|
#[derive(Debug, ToShmem)]
|
||||||
|
pub struct ContainerCondition {
|
||||||
|
name: ContainerName,
|
||||||
|
condition: QueryCondition,
|
||||||
|
flags: FeatureFlags,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ContainerCondition {
|
||||||
|
/// Parse a container condition.
|
||||||
|
pub fn parse<'a>(
|
||||||
|
context: &ParserContext,
|
||||||
|
input: &mut Parser<'a, '_>,
|
||||||
|
) -> Result<Self, ParseError<'a>> {
|
||||||
|
use crate::parser::Parse;
|
||||||
|
|
||||||
|
// FIXME: This is a bit ambiguous:
|
||||||
|
// https://github.com/w3c/csswg-drafts/issues/7203
|
||||||
|
let name = input.try_parse(|input| {
|
||||||
|
ContainerName::parse(context, input)
|
||||||
|
}).ok().unwrap_or_else(ContainerName::none);
|
||||||
|
let condition = QueryCondition::parse(context, input, FeatureType::Container)?;
|
||||||
|
let flags = condition.cumulative_flags();
|
||||||
|
Ok(Self { name, condition, flags })
|
||||||
|
}
|
||||||
|
|
||||||
|
fn valid_container_info<E>(&self, potential_container: E) -> Option<(ContainerInfo, Arc<ComputedValues>)>
|
||||||
|
where
|
||||||
|
E: TElement,
|
||||||
|
{
|
||||||
|
use crate::values::computed::ContainerType;
|
||||||
|
|
||||||
|
fn container_type_axes(ty_: ContainerType, wm: WritingMode) -> FeatureFlags {
|
||||||
|
if ty_.intersects(ContainerType::SIZE) {
|
||||||
|
return FeatureFlags::all_container_axes()
|
||||||
|
}
|
||||||
|
if ty_.intersects(ContainerType::INLINE_SIZE) {
|
||||||
|
let physical_axis = if wm.is_vertical() {
|
||||||
|
FeatureFlags::CONTAINER_REQUIRES_HEIGHT_AXIS
|
||||||
|
} else {
|
||||||
|
FeatureFlags::CONTAINER_REQUIRES_WIDTH_AXIS
|
||||||
|
};
|
||||||
|
return FeatureFlags::CONTAINER_REQUIRES_INLINE_AXIS | physical_axis
|
||||||
|
}
|
||||||
|
FeatureFlags::empty()
|
||||||
|
}
|
||||||
|
|
||||||
|
let data = match potential_container.borrow_data() {
|
||||||
|
Some(data) => data,
|
||||||
|
None => return None,
|
||||||
|
};
|
||||||
|
let style = data.styles.primary();
|
||||||
|
let wm = style.writing_mode;
|
||||||
|
let box_style = style.get_box();
|
||||||
|
|
||||||
|
// Filter by container-type.
|
||||||
|
let container_type = box_style.clone_container_type();
|
||||||
|
let available_axes = container_type_axes(container_type, wm);
|
||||||
|
if !available_axes.contains(self.flags.container_axes()) {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Filter by container-name.
|
||||||
|
let container_name = box_style.clone_container_name();
|
||||||
|
for filter_name in self.name.0.iter() {
|
||||||
|
if !container_name.0.contains(filter_name) {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let size = potential_container.primary_box_size();
|
||||||
|
let style = style.clone();
|
||||||
|
Some((ContainerInfo { size, wm }, style))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn find_container<E>(&self, mut e: E) -> Option<(ContainerInfo, Arc<ComputedValues>)>
|
||||||
|
where
|
||||||
|
E: TElement,
|
||||||
|
{
|
||||||
|
while let Some(element) = e.traversal_parent() {
|
||||||
|
if let Some(info) = self.valid_container_info(element) {
|
||||||
|
return Some(info);
|
||||||
|
}
|
||||||
|
e = element;
|
||||||
|
}
|
||||||
|
|
||||||
|
None
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Tries to match a container query condition for a given element.
|
||||||
|
pub(crate) fn matches<E>(&self, device: &Device, element: E) -> bool
|
||||||
|
where
|
||||||
|
E: TElement,
|
||||||
|
{
|
||||||
|
let info = self.find_container(element);
|
||||||
|
Context::for_container_query_evaluation(device, info, |context| {
|
||||||
|
self.condition.matches(context)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/// Information needed to evaluate an individual container query.
|
||||||
|
#[derive(Copy, Clone)]
|
||||||
|
pub struct ContainerInfo {
|
||||||
|
size: Size2D<Au>,
|
||||||
|
wm: WritingMode,
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_container(context: &Context) -> ContainerInfo {
|
||||||
|
if let Some(ref info) = context.container_info {
|
||||||
|
return info.clone()
|
||||||
|
}
|
||||||
|
ContainerInfo {
|
||||||
|
size: context.device().au_viewport_size(),
|
||||||
|
wm: WritingMode::horizontal_tb(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn eval_width(context: &Context) -> CSSPixelLength {
|
||||||
|
let info = get_container(context);
|
||||||
|
CSSPixelLength::new(info.size.width.to_f32_px())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn eval_height(context: &Context) -> CSSPixelLength {
|
||||||
|
let info = get_container(context);
|
||||||
|
CSSPixelLength::new(info.size.height.to_f32_px())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn eval_inline_size(context: &Context) -> CSSPixelLength {
|
||||||
|
let info = get_container(context);
|
||||||
|
CSSPixelLength::new(LogicalSize::from_physical(info.wm, info.size).inline.to_f32_px())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn eval_block_size(context: &Context) -> CSSPixelLength {
|
||||||
|
let info = get_container(context);
|
||||||
|
CSSPixelLength::new(LogicalSize::from_physical(info.wm, info.size).block.to_f32_px())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn eval_aspect_ratio(context: &Context) -> Ratio {
|
||||||
|
let info = get_container(context);
|
||||||
|
Ratio::new(info.size.width.0 as f32, info.size.height.0 as f32)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn eval_orientation(context: &Context, value: Option<Orientation>) -> bool {
|
||||||
|
let info = get_container(context);
|
||||||
|
Orientation::eval(info.size, value)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// https://drafts.csswg.org/css-contain-3/#container-features
|
||||||
|
///
|
||||||
|
/// TODO: Support style queries, perhaps.
|
||||||
|
pub static CONTAINER_FEATURES: [QueryFeatureDescription; 6] = [
|
||||||
|
feature!(
|
||||||
|
atom!("width"),
|
||||||
|
AllowsRanges::Yes,
|
||||||
|
Evaluator::Length(eval_width),
|
||||||
|
FeatureFlags::CONTAINER_REQUIRES_WIDTH_AXIS,
|
||||||
|
),
|
||||||
|
feature!(
|
||||||
|
atom!("height"),
|
||||||
|
AllowsRanges::Yes,
|
||||||
|
Evaluator::Length(eval_height),
|
||||||
|
FeatureFlags::CONTAINER_REQUIRES_HEIGHT_AXIS,
|
||||||
|
),
|
||||||
|
feature!(
|
||||||
|
atom!("inline-size"),
|
||||||
|
AllowsRanges::Yes,
|
||||||
|
Evaluator::Length(eval_inline_size),
|
||||||
|
FeatureFlags::CONTAINER_REQUIRES_INLINE_AXIS,
|
||||||
|
),
|
||||||
|
feature!(
|
||||||
|
atom!("block-size"),
|
||||||
|
AllowsRanges::Yes,
|
||||||
|
Evaluator::Length(eval_block_size),
|
||||||
|
FeatureFlags::CONTAINER_REQUIRES_BLOCK_AXIS,
|
||||||
|
),
|
||||||
|
feature!(
|
||||||
|
atom!("aspect-ratio"),
|
||||||
|
AllowsRanges::Yes,
|
||||||
|
Evaluator::NumberRatio(eval_aspect_ratio),
|
||||||
|
// XXX from_bits_truncate is const, but the pipe operator isn't, so this
|
||||||
|
// works around it.
|
||||||
|
FeatureFlags::from_bits_truncate(FeatureFlags::CONTAINER_REQUIRES_BLOCK_AXIS.bits() | FeatureFlags::CONTAINER_REQUIRES_INLINE_AXIS.bits()),
|
||||||
|
),
|
||||||
|
feature!(
|
||||||
|
atom!("orientation"),
|
||||||
|
AllowsRanges::No,
|
||||||
|
keyword_evaluator!(eval_orientation, Orientation),
|
||||||
|
FeatureFlags::from_bits_truncate(FeatureFlags::CONTAINER_REQUIRES_BLOCK_AXIS.bits() | FeatureFlags::CONTAINER_REQUIRES_INLINE_AXIS.bits()),
|
||||||
|
),
|
||||||
|
];
|
|
@ -2,7 +2,7 @@
|
||||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
* 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/. */
|
* 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
|
//! [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 `<layer-name>`: https://drafts.csswg.org/css-cascade-5/#typedef-layer-name
|
/// A `<layer-name>`: https://drafts.csswg.org/css-cascade-5/#typedef-layer-name
|
||||||
#[derive(Clone, Debug, Eq, Hash, MallocSizeOf, PartialEq, ToShmem)]
|
#[derive(Clone, Debug, Eq, Hash, MallocSizeOf, PartialEq, ToShmem)]
|
||||||
pub struct LayerName(pub SmallVec<[AtomIdent; 1]>);
|
pub struct LayerName(pub SmallVec<[AtomIdent; 1]>);
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
* 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/. */
|
* 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
|
//! [media]: https://drafts.csswg.org/css-conditional/#at-ruledef-media
|
||||||
|
|
||||||
|
@ -18,7 +18,7 @@ use servo_arc::Arc;
|
||||||
use std::fmt::{self, Write};
|
use std::fmt::{self, Write};
|
||||||
use style_traits::{CssWriter, ToCss};
|
use style_traits::{CssWriter, ToCss};
|
||||||
|
|
||||||
/// An [`@media`][media] urle.
|
/// An [`@media`][media] rule.
|
||||||
///
|
///
|
||||||
/// [media]: https://drafts.csswg.org/css-conditional/#at-ruledef-media
|
/// [media]: https://drafts.csswg.org/css-conditional/#at-ruledef-media
|
||||||
#[derive(Debug, ToShmem)]
|
#[derive(Debug, ToShmem)]
|
||||||
|
|
|
@ -12,6 +12,7 @@ pub mod font_feature_values_rule;
|
||||||
pub mod import_rule;
|
pub mod import_rule;
|
||||||
pub mod keyframes_rule;
|
pub mod keyframes_rule;
|
||||||
pub mod layer_rule;
|
pub mod layer_rule;
|
||||||
|
pub mod container_rule;
|
||||||
mod loader;
|
mod loader;
|
||||||
mod media_rule;
|
mod media_rule;
|
||||||
mod namespace_rule;
|
mod namespace_rule;
|
||||||
|
@ -53,6 +54,7 @@ pub use self::import_rule::ImportRule;
|
||||||
pub use self::keyframes_rule::KeyframesRule;
|
pub use self::keyframes_rule::KeyframesRule;
|
||||||
pub use self::layer_rule::{LayerBlockRule, LayerStatementRule};
|
pub use self::layer_rule::{LayerBlockRule, LayerStatementRule};
|
||||||
pub use self::loader::StylesheetLoader;
|
pub use self::loader::StylesheetLoader;
|
||||||
|
pub use self::container_rule::ContainerRule;
|
||||||
pub use self::media_rule::MediaRule;
|
pub use self::media_rule::MediaRule;
|
||||||
pub use self::namespace_rule::NamespaceRule;
|
pub use self::namespace_rule::NamespaceRule;
|
||||||
pub use self::origin::{Origin, OriginSet, OriginSetIterator, PerOrigin, PerOriginIter};
|
pub use self::origin::{Origin, OriginSet, OriginSetIterator, PerOrigin, PerOriginIter};
|
||||||
|
@ -253,6 +255,7 @@ pub enum CssRule {
|
||||||
Import(Arc<Locked<ImportRule>>),
|
Import(Arc<Locked<ImportRule>>),
|
||||||
Style(Arc<Locked<StyleRule>>),
|
Style(Arc<Locked<StyleRule>>),
|
||||||
Media(Arc<Locked<MediaRule>>),
|
Media(Arc<Locked<MediaRule>>),
|
||||||
|
Container(Arc<Locked<ContainerRule>>),
|
||||||
FontFace(Arc<Locked<FontFaceRule>>),
|
FontFace(Arc<Locked<FontFaceRule>>),
|
||||||
FontFeatureValues(Arc<Locked<FontFeatureValuesRule>>),
|
FontFeatureValues(Arc<Locked<FontFeatureValuesRule>>),
|
||||||
CounterStyle(Arc<Locked<CounterStyleRule>>),
|
CounterStyle(Arc<Locked<CounterStyleRule>>),
|
||||||
|
@ -287,6 +290,10 @@ impl CssRule {
|
||||||
lock.unconditional_shallow_size_of(ops) + lock.read_with(guard).size_of(guard, ops)
|
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::FontFace(_) => 0,
|
||||||
CssRule::FontFeatureValues(_) => 0,
|
CssRule::FontFeatureValues(_) => 0,
|
||||||
CssRule::CounterStyle(_) => 0,
|
CssRule::CounterStyle(_) => 0,
|
||||||
|
@ -344,6 +351,7 @@ pub enum CssRuleType {
|
||||||
LayerBlock = 16,
|
LayerBlock = 16,
|
||||||
LayerStatement = 17,
|
LayerStatement = 17,
|
||||||
ScrollTimeline = 18,
|
ScrollTimeline = 18,
|
||||||
|
Container = 19,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[allow(missing_docs)]
|
#[allow(missing_docs)]
|
||||||
|
@ -373,16 +381,7 @@ impl CssRule {
|
||||||
CssRule::LayerBlock(_) => CssRuleType::LayerBlock,
|
CssRule::LayerBlock(_) => CssRuleType::LayerBlock,
|
||||||
CssRule::LayerStatement(_) => CssRuleType::LayerStatement,
|
CssRule::LayerStatement(_) => CssRuleType::LayerStatement,
|
||||||
CssRule::ScrollTimeline(_) => CssRuleType::ScrollTimeline,
|
CssRule::ScrollTimeline(_) => CssRuleType::ScrollTimeline,
|
||||||
}
|
CssRule::Container(_) => CssRuleType::Container,
|
||||||
}
|
|
||||||
|
|
||||||
fn rule_state(&self) -> State {
|
|
||||||
match *self {
|
|
||||||
// CssRule::Charset(..) => State::Start,
|
|
||||||
CssRule::Import(..) => State::Imports,
|
|
||||||
CssRule::Namespace(..) => State::Namespaces,
|
|
||||||
// TODO(emilio): Do we need something for EarlyLayers?
|
|
||||||
_ => State::Body,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -460,6 +459,12 @@ impl DeepCloneWithLock for CssRule {
|
||||||
lock.wrap(rule.deep_clone_with_lock(lock, guard, params)),
|
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) => {
|
CssRule::Media(ref arc) => {
|
||||||
let rule = arc.read_with(guard);
|
let rule = arc.read_with(guard);
|
||||||
CssRule::Media(Arc::new(
|
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::LayerBlock(ref lock) => lock.read_with(guard).to_css(guard, dest),
|
||||||
CssRule::LayerStatement(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::ScrollTimeline(ref lock) => lock.read_with(guard).to_css(guard, dest),
|
||||||
|
CssRule::Container(ref lock) => lock.read_with(guard).to_css(guard, dest),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -153,21 +153,17 @@ impl CssRulesHelpers for RawOffsetArc<Locked<CssRules>> {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Computes the parser state at the given index
|
// Computes the parser state at the given index
|
||||||
|
let insert_rule_context = InsertRuleContext {
|
||||||
|
rule_list: &rules.0,
|
||||||
|
index,
|
||||||
|
};
|
||||||
|
|
||||||
let state = if nested {
|
let state = if nested {
|
||||||
State::Body
|
State::Body
|
||||||
} else if index == 0 {
|
} else if index == 0 {
|
||||||
State::Start
|
State::Start
|
||||||
} else {
|
} else {
|
||||||
rules
|
insert_rule_context.max_rule_state_at_index(index - 1)
|
||||||
.0
|
|
||||||
.get(index - 1)
|
|
||||||
.map(CssRule::rule_state)
|
|
||||||
.unwrap_or(State::Body)
|
|
||||||
};
|
|
||||||
|
|
||||||
let insert_rule_context = InsertRuleContext {
|
|
||||||
rule_list: &rules.0,
|
|
||||||
index,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// Steps 3, 4, 5, 6
|
// Steps 3, 4, 5, 6
|
||||||
|
|
|
@ -13,6 +13,7 @@ use crate::properties::parse_property_declaration_list;
|
||||||
use crate::selector_parser::{SelectorImpl, SelectorParser};
|
use crate::selector_parser::{SelectorImpl, SelectorParser};
|
||||||
use crate::shared_lock::{Locked, SharedRwLock};
|
use crate::shared_lock::{Locked, SharedRwLock};
|
||||||
use crate::str::starts_with_ignore_ascii_case;
|
use crate::str::starts_with_ignore_ascii_case;
|
||||||
|
use crate::stylesheets::container_rule::{ContainerRule, ContainerCondition};
|
||||||
use crate::stylesheets::document_rule::DocumentCondition;
|
use crate::stylesheets::document_rule::DocumentCondition;
|
||||||
use crate::stylesheets::font_feature_values_rule::parse_family_name_list;
|
use crate::stylesheets::font_feature_values_rule::parse_family_name_list;
|
||||||
use crate::stylesheets::import_rule::ImportLayer;
|
use crate::stylesheets::import_rule::ImportLayer;
|
||||||
|
@ -45,6 +46,36 @@ pub struct InsertRuleContext<'a> {
|
||||||
pub index: usize,
|
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.
|
/// The parser for the top-level rules in a stylesheet.
|
||||||
pub struct TopLevelRuleParser<'a> {
|
pub struct TopLevelRuleParser<'a> {
|
||||||
/// A reference to the lock we need to use to create rules.
|
/// A reference to the lock we need to use to create rules.
|
||||||
|
@ -102,12 +133,8 @@ impl<'b> TopLevelRuleParser<'b> {
|
||||||
None => return true,
|
None => return true,
|
||||||
};
|
};
|
||||||
|
|
||||||
let next_rule_state = match ctx.rule_list.get(ctx.index) {
|
let max_rule_state = ctx.max_rule_state_at_index(ctx.index);
|
||||||
None => return true,
|
if new_state > max_rule_state {
|
||||||
Some(rule) => rule.rule_state(),
|
|
||||||
};
|
|
||||||
|
|
||||||
if new_state > next_rule_state {
|
|
||||||
self.dom_error = Some(RulesMutateError::HierarchyRequest);
|
self.dom_error = Some(RulesMutateError::HierarchyRequest);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
@ -162,6 +189,8 @@ pub enum AtRulePrelude {
|
||||||
CounterStyle(CustomIdent),
|
CounterStyle(CustomIdent),
|
||||||
/// A @media rule prelude, with its media queries.
|
/// A @media rule prelude, with its media queries.
|
||||||
Media(Arc<Locked<MediaList>>),
|
Media(Arc<Locked<MediaList>>),
|
||||||
|
/// A @container rule prelude.
|
||||||
|
Container(Arc<ContainerCondition>),
|
||||||
/// An @supports rule, with its conditional
|
/// An @supports rule, with its conditional
|
||||||
Supports(SupportsCondition),
|
Supports(SupportsCondition),
|
||||||
/// A @viewport rule prelude.
|
/// A @viewport rule prelude.
|
||||||
|
@ -262,11 +291,23 @@ impl<'a, 'i> AtRuleParser<'i> for TopLevelRuleParser<'a> {
|
||||||
self.dom_error = Some(RulesMutateError::HierarchyRequest);
|
self.dom_error = Some(RulesMutateError::HierarchyRequest);
|
||||||
return Err(input.new_custom_error(StyleParseErrorKind::UnexpectedCharsetRule))
|
return Err(input.new_custom_error(StyleParseErrorKind::UnexpectedCharsetRule))
|
||||||
},
|
},
|
||||||
_ => {}
|
"layer" => {
|
||||||
}
|
let state_to_check = if self.state <= State::EarlyLayers {
|
||||||
|
// The real state depends on whether there's a block or not.
|
||||||
if !self.check_state(State::Body) {
|
// We don't know that yet, but the parse_block check deals
|
||||||
return Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError));
|
// 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)
|
AtRuleParser::parse_prelude(&mut self.nested(), name, input)
|
||||||
|
@ -279,6 +320,9 @@ impl<'a, 'i> AtRuleParser<'i> for TopLevelRuleParser<'a> {
|
||||||
start: &ParserState,
|
start: &ParserState,
|
||||||
input: &mut Parser<'i, 't>,
|
input: &mut Parser<'i, 't>,
|
||||||
) -> Result<Self::AtRule, ParseError<'i>> {
|
) -> Result<Self::AtRule, ParseError<'i>> {
|
||||||
|
if !self.check_state(State::Body) {
|
||||||
|
return Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError));
|
||||||
|
}
|
||||||
let rule = AtRuleParser::parse_block(&mut self.nested(), prelude, start, input)?;
|
let rule = AtRuleParser::parse_block(&mut self.nested(), prelude, start, input)?;
|
||||||
self.state = State::Body;
|
self.state = State::Body;
|
||||||
Ok((start.position(), rule))
|
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> {
|
impl<'a, 'b, 'i> AtRuleParser<'i> for NestedRuleParser<'a, 'b> {
|
||||||
type Prelude = AtRulePrelude;
|
type Prelude = AtRulePrelude;
|
||||||
type AtRule = CssRule;
|
type AtRule = CssRule;
|
||||||
|
@ -433,6 +487,10 @@ impl<'a, 'b, 'i> AtRuleParser<'i> for NestedRuleParser<'a, 'b> {
|
||||||
"font-face" => {
|
"font-face" => {
|
||||||
AtRulePrelude::FontFace
|
AtRulePrelude::FontFace
|
||||||
},
|
},
|
||||||
|
"container" if container_queries_enabled() => {
|
||||||
|
let condition = Arc::new(ContainerCondition::parse(self.context, input)?);
|
||||||
|
AtRulePrelude::Container(condition)
|
||||||
|
},
|
||||||
"layer" => {
|
"layer" => {
|
||||||
let names = input.try_parse(|input| {
|
let names = input.try_parse(|input| {
|
||||||
input.parse_comma_separated(|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) => {
|
AtRulePrelude::Layer(names) => {
|
||||||
let name = match names.len() {
|
let name = match names.len() {
|
||||||
0 | 1 => names.into_iter().next(),
|
0 | 1 => names.into_iter().next(),
|
||||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue