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:
Oriol Brufau 2023-08-16 01:11:39 +02:00 committed by GitHub
parent 8255e8e318
commit 8e15389cae
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
152 changed files with 5857 additions and 4617 deletions

View file

@ -62,13 +62,14 @@ servo_config = { path = "../config", optional = true }
servo_url = { path = "../url", optional = true }
smallbitvec = "2.3.0"
smallvec = "1.0"
static_assertions = "1.1"
string_cache = { version = "0.8", optional = true }
style_derive = { path = "../style_derive" }
style_traits = { path = "../style_traits" }
time = "0.1"
to_shmem = { path = "../to_shmem" }
to_shmem_derive = { path = "../to_shmem_derive" }
uluru = "2"
uluru = "3.0"
unicode-bidi = "0.3"
unicode-segmentation = "1.0"
void = "1.0.2"

View file

@ -455,8 +455,8 @@ pub struct Animation {
impl Animation {
/// Whether or not this animation is cancelled by changes from a new style.
fn is_cancelled_in_new_style(&self, new_style: &Arc<ComputedValues>) -> bool {
let index = new_style
.get_box()
let new_ui = new_style.get_ui();
let index = new_ui
.animation_name_iter()
.position(|animation_name| Some(&self.name) == animation_name.as_atom());
let index = match index {
@ -464,7 +464,7 @@ impl Animation {
None => return true,
};
new_style.get_box().animation_duration_mod(index).seconds() == 0.
new_ui.animation_duration_mod(index).seconds() == 0.
}
/// Given the current time, advances this animation to the next iteration,
@ -1073,10 +1073,10 @@ impl ElementAnimationSet {
old_style: &ComputedValues,
new_style: &Arc<ComputedValues>,
) {
let box_style = new_style.get_box();
let timing_function = box_style.transition_timing_function_mod(index);
let duration = box_style.transition_duration_mod(index);
let delay = box_style.transition_delay_mod(index).seconds() as f64;
let style = new_style.get_ui();
let timing_function = style.transition_timing_function_mod(index);
let duration = style.transition_duration_mod(index);
let delay = style.transition_delay_mod(index).seconds() as f64;
let now = context.current_time_for_animations;
// Only start a new transition if the style actually changes between
@ -1344,15 +1344,15 @@ pub fn maybe_start_animations<E>(
) where
E: TElement,
{
let box_style = new_style.get_box();
for (i, name) in box_style.animation_name_iter().enumerate() {
let style = new_style.get_ui();
for (i, name) in style.animation_name_iter().enumerate() {
let name = match name.as_atom() {
Some(atom) => atom,
None => continue,
};
debug!("maybe_start_animations: name={}", name);
let duration = box_style.animation_duration_mod(i).seconds() as f64;
let duration = style.animation_duration_mod(i).seconds() as f64;
if duration == 0. {
continue;
}
@ -1375,14 +1375,14 @@ pub fn maybe_start_animations<E>(
// NB: This delay may be negative, meaning that the animation may be created
// in a state where we have advanced one or more iterations or even that the
// animation begins in a finished state.
let delay = box_style.animation_delay_mod(i).seconds();
let delay = style.animation_delay_mod(i).seconds();
let iteration_state = match box_style.animation_iteration_count_mod(i) {
let iteration_state = match style.animation_iteration_count_mod(i) {
AnimationIterationCount::Infinite => KeyframesIterationState::Infinite(0.0),
AnimationIterationCount::Number(n) => KeyframesIterationState::Finite(0.0, n.into()),
};
let animation_direction = box_style.animation_direction_mod(i);
let animation_direction = style.animation_direction_mod(i);
let initial_direction = match animation_direction {
AnimationDirection::Normal | AnimationDirection::Alternate => {
@ -1396,7 +1396,7 @@ pub fn maybe_start_animations<E>(
let now = context.current_time_for_animations;
let started_at = now + delay as f64;
let mut starting_progress = (now - started_at) / duration;
let state = match box_style.animation_play_state_mod(i) {
let state = match style.animation_play_state_mod(i) {
AnimationPlayState::Paused => AnimationState::Paused(starting_progress),
AnimationPlayState::Running => AnimationState::Pending,
};
@ -1406,7 +1406,7 @@ pub fn maybe_start_animations<E>(
&keyframe_animation,
context,
new_style,
new_style.get_box().animation_timing_function_mod(i),
style.animation_timing_function_mod(i),
resolver,
);
@ -1416,7 +1416,7 @@ pub fn maybe_start_animations<E>(
computed_steps,
started_at,
duration,
fill_mode: box_style.animation_fill_mode_mod(i),
fill_mode: style.animation_fill_mode_mod(i),
delay: delay as f64,
iteration_state,
state,

View file

@ -42,13 +42,7 @@ pub struct CascadePriority {
layer_order: LayerOrder,
}
#[allow(dead_code)]
fn size_assert() {
#[allow(unsafe_code)]
unsafe {
std::mem::transmute::<u32, CascadePriority>(0u32)
};
}
const_assert_eq!(std::mem::size_of::<CascadePriority>(), std::mem::size_of::<u32>());
impl PartialOrd for CascadePriority {
#[inline]
@ -208,3 +202,6 @@ impl ApplicableDeclarationBlock {
(self.source, self.cascade_priority)
}
}
// Size of this struct determines sorting and selector-matching performance.
size_of_test!(ApplicableDeclarationBlock, 24);

View file

@ -285,11 +285,11 @@ fn generate_structs() {
let mut fixups = vec![];
let builder = BuilderWithConfig::new(builder, CONFIG["structs"].as_table().unwrap())
.handle_common(&mut fixups)
.handle_str_items("whitelist-functions", |b, item| b.allowlist_function(item))
.handle_str_items("allowlist-functions", |b, item| b.allowlist_function(item))
.handle_str_items("bitfield-enums", |b, item| b.bitfield_enum(item))
.handle_str_items("rusty-enums", |b, item| b.rustified_enum(item))
.handle_str_items("whitelist-vars", |b, item| b.allowlist_var(item))
.handle_str_items("whitelist-types", |b, item| b.allowlist_type(item))
.handle_str_items("allowlist-vars", |b, item| b.allowlist_var(item))
.handle_str_items("allowlist-types", |b, item| b.allowlist_type(item))
.handle_str_items("opaque-types", |b, item| b.opaque_type(item))
.handle_table_items("cbindgen-types", |b, item| {
let gecko = item["gecko"].as_str().unwrap();

View file

@ -27,8 +27,8 @@ use crate::traversal_flags::TraversalFlags;
use app_units::Au;
use euclid::default::Size2D;
use euclid::Scale;
#[cfg(feature = "servo")]
use fxhash::FxHashMap;
use selectors::matching::ElementSelectorFlags;
use selectors::NthIndexCache;
#[cfg(feature = "gecko")]
use servo_arc::Arc;
@ -41,7 +41,6 @@ use style_traits::DevicePixel;
#[cfg(feature = "servo")]
use style_traits::SpeculativePainter;
use time;
use uluru::LRUCache;
pub use selectors::matching::QuirksMode;
@ -517,65 +516,6 @@ impl<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.
pub struct SequentialTaskList<E>(Vec<SequentialTask<E>>)
where
@ -697,11 +637,6 @@ pub struct ThreadLocalStyleContext<E: TElement> {
/// filter, to ensure they're dropped before we execute the tasks, which
/// could create another ThreadLocalStyleContext for style computation.
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.
pub statistics: PerThreadTraversalStatistics,
/// 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(),
bloom_filter: StyleBloom::new(),
tasks: SequentialTaskList(Vec::new()),
selector_flags: SelectorFlagsMap::new(),
statistics: PerThreadTraversalStatistics::default(),
stack_limit_checker: StackLimitChecker::new(
(STYLE_THREAD_STACK_SIZE_KB - STACK_SAFETY_MARGIN_KB) * 1024,
@ -729,15 +663,6 @@ impl<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
/// shared style context, and a mutable reference to a local one.
pub struct StyleContext<'a, E: TElement + 'a> {

View file

@ -64,6 +64,27 @@ fn get_safearea_inset_right(device: &Device) -> VariableValue {
VariableValue::pixels(device.safe_area_insets().right)
}
#[cfg(feature = "gecko")]
fn get_content_preferred_color_scheme(device: &Device) -> VariableValue {
use crate::gecko::media_features::PrefersColorScheme;
let prefers_color_scheme = unsafe {
crate::gecko_bindings::bindings::Gecko_MediaFeatures_PrefersColorScheme(
device.document(),
/* use_content = */ true,
)
};
VariableValue::ident(match prefers_color_scheme {
PrefersColorScheme::Light => "light",
PrefersColorScheme::Dark => "dark",
})
}
#[cfg(feature = "servo")]
fn get_content_preferred_color_scheme(_device: &Device) -> VariableValue {
// TODO: implement this.
VariableValue::ident("light")
}
static ENVIRONMENT_VARIABLES: [EnvironmentVariable; 4] = [
make_variable!(atom!("safe-area-inset-top"), get_safearea_inset_top),
make_variable!(atom!("safe-area-inset-bottom"), get_safearea_inset_bottom),
@ -99,7 +120,7 @@ macro_rules! lnf_int_variable {
}};
}
static CHROME_ENVIRONMENT_VARIABLES: [EnvironmentVariable; 5] = [
static CHROME_ENVIRONMENT_VARIABLES: [EnvironmentVariable; 6] = [
lnf_int_variable!(
atom!("-moz-gtk-csd-titlebar-radius"),
TitlebarRadius,
@ -121,6 +142,7 @@ static CHROME_ENVIRONMENT_VARIABLES: [EnvironmentVariable; 5] = [
GTKCSDMaximizeButtonPosition,
integer
),
make_variable!(atom!("-moz-content-preferred-color-scheme"), get_content_preferred_color_scheme),
];
impl CssEnvironment {
@ -328,6 +350,11 @@ impl VariableValue {
})
}
/// Create VariableValue from an int.
fn ident(ident: &'static str) -> Self {
Self::from_token(Token::Ident(ident.into()))
}
/// Create VariableValue from a float amount of CSS pixels.
fn pixels(number: f32) -> Self {
// FIXME (https://github.com/servo/rust-cssparser/issues/266):

View file

@ -159,6 +159,9 @@ pub struct ElementStyles {
pub pseudos: EagerPseudoStyles,
}
// There's one of these per rendered elements so it better be small.
size_of_test!(ElementStyles, 16);
impl ElementStyles {
/// Returns the primary style.
pub fn get_primary(&self) -> Option<&Arc<ComputedValues>> {
@ -249,6 +252,9 @@ pub struct ElementData {
pub flags: ElementDataFlags,
}
// There's one of these per rendered elements so it better be small.
size_of_test!(ElementData, 24);
/// The kind of restyle that a single element should do.
#[derive(Debug)]
pub enum RestyleKind {

View file

@ -22,7 +22,7 @@ use crate::traversal_flags::TraversalFlags;
use crate::values::AtomIdent;
use crate::{LocalName, Namespace, WeakAtom};
use atomic_refcell::{AtomicRef, AtomicRefMut};
use selectors::matching::{ElementSelectorFlags, QuirksMode, VisitedHandlingMode};
use selectors::matching::{QuirksMode, VisitedHandlingMode};
use selectors::sink::Push;
use selectors::Element as SelectorsElement;
use servo_arc::{Arc, ArcBorrow};
@ -734,19 +734,6 @@ pub trait TElement:
/// native anonymous content can opt out of this style fixup.)
fn skip_item_display_fixup(&self) -> bool;
/// Sets selector flags, which indicate what kinds of selectors may have
/// matched on this element and therefore what kind of work may need to
/// be performed when DOM state changes.
///
/// This is unsafe, like all the flag-setting methods, because it's only safe
/// to call with exclusive access to the element. When setting flags on the
/// parent during parallel traversal, we use SequentialTask to queue up the
/// set to run after the threads join.
unsafe fn set_selector_flags(&self, flags: ElementSelectorFlags);
/// Returns true if the element has all the specified selector flags.
fn has_selector_flags(&self, flags: ElementSelectorFlags) -> bool;
/// In Gecko, element has a flag that represents the element may have
/// any type of animations or not to bail out animation stuff early.
/// Whereas Servo doesn't have such flag.
@ -941,6 +928,9 @@ pub trait TElement:
/// Returns element's namespace.
fn namespace(&self)
-> &<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

View file

@ -12,7 +12,7 @@ use crate::invalidation::element::invalidator::{DescendantInvalidationLists, Inv
use crate::invalidation::element::invalidator::{InvalidationProcessor, InvalidationVector};
use crate::values::AtomIdent;
use selectors::attr::CaseSensitivity;
use selectors::matching::{self, MatchingContext, MatchingMode};
use selectors::matching::{self, MatchingContext, MatchingMode, NeedsSelectorFlags};
use selectors::parser::{Combinator, Component, LocalName, SelectorImpl};
use selectors::{Element, NthIndexCache, SelectorList};
use smallvec::SmallVec;
@ -26,7 +26,13 @@ pub fn element_matches<E>(
where
E: Element,
{
let mut context = MatchingContext::new(MatchingMode::Normal, None, None, quirks_mode);
let mut context = MatchingContext::new(
MatchingMode::Normal,
None,
None,
quirks_mode,
NeedsSelectorFlags::No,
);
context.scope_element = Some(element.opaque());
context.current_host = element.containing_shadow_host().map(|e| e.opaque());
matching::matches_selector_list(selector_list, element, &mut context)
@ -48,6 +54,7 @@ where
None,
Some(&mut nth_index_cache),
quirks_mode,
NeedsSelectorFlags::No,
);
context.scope_element = Some(element.opaque());
context.current_host = element.containing_shadow_host().map(|e| e.opaque());
@ -618,8 +625,8 @@ pub fn query_selector<E, Q>(
None,
Some(&mut nth_index_cache),
quirks_mode,
NeedsSelectorFlags::No,
);
let root_element = root.as_element();
matching_context.scope_element = root_element.map(|e| e.opaque());
matching_context.current_host = match root_element {

View file

@ -118,8 +118,8 @@ bitflags! {
const IN_MODAL_DIALOG_STATE = 1 << 42;
/// <https://html.spec.whatwg.org/multipage/#inert-subtrees>
const IN_MOZINERT_STATE = 1 << 43;
/// State for the topmost dialog element in top layer
const IN_TOPMOST_MODAL_DIALOG_STATE = 1 << 44;
/// State for the topmost modal element in top layer
const IN_TOPMOST_MODAL_TOP_LAYER_STATE = 1 << 44;
/// Initially used for the devtools highlighter, but now somehow only
/// used for the devtools accessibility inspector.
const IN_DEVTOOLS_HIGHLIGHTED_STATE = 1 << 45;
@ -148,9 +148,5 @@ bitflags! {
const LTR_LOCALE = 1 << 2;
/// LWTheme status
const LWTHEME = 1 << 3;
/// LWTheme status
const LWTHEME_BRIGHTTEXT = 1 << 4;
/// LWTheme status
const LWTHEME_DARKTEXT = 1 << 5;
}
}

View file

@ -15,7 +15,7 @@ use crate::gecko_bindings::structs::{
RawServoKeyframesRule, RawServoLayerBlockRule, RawServoLayerStatementRule, RawServoMediaList,
RawServoMediaRule, RawServoMozDocumentRule, RawServoNamespaceRule, RawServoPageRule,
RawServoScrollTimelineRule, RawServoStyleRule, RawServoStyleSheetContents,
RawServoSupportsRule, ServoCssRules,
RawServoSupportsRule, RawServoContainerRule, ServoCssRules,
};
use crate::gecko_bindings::sugar::ownership::{HasArcFFI, HasFFI, Strong};
use crate::media_queries::MediaList;
@ -26,7 +26,7 @@ use crate::stylesheets::keyframes_rule::Keyframe;
use crate::stylesheets::{
CounterStyleRule, CssRules, DocumentRule, FontFaceRule, FontFeatureValuesRule, ImportRule,
KeyframesRule, LayerBlockRule, LayerStatementRule, MediaRule, NamespaceRule, PageRule,
ScrollTimelineRule, StyleRule, StylesheetContents, SupportsRule,
ScrollTimelineRule, StyleRule, StylesheetContents, SupportsRule, ContainerRule,
};
use servo_arc::{Arc, ArcBorrow};
use std::{mem, ptr};
@ -98,6 +98,9 @@ impl_arc_ffi!(Locked<ScrollTimelineRule> => RawServoScrollTimelineRule
impl_arc_ffi!(Locked<SupportsRule> => RawServoSupportsRule
[Servo_SupportsRule_AddRef, Servo_SupportsRule_Release]);
impl_arc_ffi!(Locked<ContainerRule> => RawServoContainerRule
[Servo_ContainerRule_AddRef, Servo_ContainerRule_Release]);
impl_arc_ffi!(Locked<DocumentRule> => RawServoMozDocumentRule
[Servo_DocumentRule_AddRef, Servo_DocumentRule_Release]);

View file

@ -129,6 +129,12 @@ impl StylesheetInDocument for GeckoStyleSheet {
pub struct PerDocumentStyleDataImpl {
/// Rule processor.
pub stylist: Stylist,
/// A cache from element to resolved style.
pub undisplayed_style_cache: crate::traversal::UndisplayedStyleCache,
/// The generation for which our cache is valid.
pub undisplayed_style_cache_generation: u64,
}
/// The data itself is an `AtomicRefCell`, which guarantees the proper semantics
@ -143,6 +149,8 @@ impl PerDocumentStyleData {
PerDocumentStyleData(AtomicRefCell::new(PerDocumentStyleDataImpl {
stylist: Stylist::new(device, quirks_mode.into()),
undisplayed_style_cache: Default::default(),
undisplayed_style_cache_generation: 0,
}))
}
@ -177,12 +185,6 @@ impl PerDocumentStyleDataImpl {
self.stylist.device().default_computed_values_arc()
}
/// Returns whether visited styles are enabled.
#[inline]
pub fn visited_styles_enabled(&self) -> bool {
unsafe { bindings::Gecko_VisitedStylesEnabled(self.stylist.device().document()) }
}
/// Measure heap usage.
pub fn add_size_of(&self, ops: &mut MallocSizeOfOps, sizes: &mut ServoStyleSetSizes) {
self.stylist.add_size_of(ops, sizes);

View file

@ -6,13 +6,10 @@
use crate::gecko_bindings::bindings;
use crate::gecko_bindings::structs;
use crate::media_queries::media_feature::{AllowsRanges, ParsingRequirements};
use crate::media_queries::media_feature::{Evaluator, MediaFeatureDescription};
use crate::media_queries::media_feature_expression::RangeOrOperator;
use crate::queries::feature::{AllowsRanges, Evaluator, FeatureFlags, QueryFeatureDescription};
use crate::queries::values::Orientation;
use crate::media_queries::{Device, MediaType};
use crate::values::computed::CSSPixelLength;
use crate::values::computed::Ratio;
use crate::values::computed::Resolution;
use crate::values::computed::{Context, CSSPixelLength, Ratio, Resolution};
use app_units::Au;
use euclid::default::Size2D;
@ -26,150 +23,56 @@ fn device_size(device: &Device) -> Size2D<Au> {
}
/// https://drafts.csswg.org/mediaqueries-4/#width
fn eval_width(
device: &Device,
value: Option<CSSPixelLength>,
range_or_operator: Option<RangeOrOperator>,
) -> bool {
RangeOrOperator::evaluate(
range_or_operator,
value.map(Au::from),
device.au_viewport_size().width,
)
fn eval_width(context: &Context) -> CSSPixelLength {
CSSPixelLength::new(context.device().au_viewport_size().width.to_f32_px())
}
/// https://drafts.csswg.org/mediaqueries-4/#device-width
fn eval_device_width(
device: &Device,
value: Option<CSSPixelLength>,
range_or_operator: Option<RangeOrOperator>,
) -> bool {
RangeOrOperator::evaluate(
range_or_operator,
value.map(Au::from),
device_size(device).width,
)
fn eval_device_width(context: &Context) -> CSSPixelLength {
CSSPixelLength::new(device_size(context.device()).width.to_f32_px())
}
/// https://drafts.csswg.org/mediaqueries-4/#height
fn eval_height(
device: &Device,
value: Option<CSSPixelLength>,
range_or_operator: Option<RangeOrOperator>,
) -> bool {
RangeOrOperator::evaluate(
range_or_operator,
value.map(Au::from),
device.au_viewport_size().height,
)
fn eval_height(context: &Context) -> CSSPixelLength {
CSSPixelLength::new(context.device().au_viewport_size().height.to_f32_px())
}
/// https://drafts.csswg.org/mediaqueries-4/#device-height
fn eval_device_height(
device: &Device,
value: Option<CSSPixelLength>,
range_or_operator: Option<RangeOrOperator>,
) -> bool {
RangeOrOperator::evaluate(
range_or_operator,
value.map(Au::from),
device_size(device).height,
)
fn eval_device_height(context: &Context) -> CSSPixelLength {
CSSPixelLength::new(device_size(context.device()).height.to_f32_px())
}
fn eval_aspect_ratio_for<F>(
device: &Device,
query_value: Option<Ratio>,
range_or_operator: Option<RangeOrOperator>,
get_size: F,
) -> bool
fn eval_aspect_ratio_for<F>(context: &Context, get_size: F) -> Ratio
where
F: FnOnce(&Device) -> Size2D<Au>,
{
// A ratio of 0/0 behaves as the ratio 1/0, so we need to call used_value()
// to convert it if necessary.
// FIXME: we may need to update here once
// https://github.com/w3c/csswg-drafts/issues/4954 got resolved.
let query_value = match query_value {
Some(v) => v.used_value(),
None => return true,
};
let size = get_size(device);
let value = Ratio::new(size.width.0 as f32, size.height.0 as f32);
RangeOrOperator::evaluate_with_query_value(range_or_operator, query_value, value)
let size = get_size(context.device());
Ratio::new(size.width.0 as f32, size.height.0 as f32)
}
/// https://drafts.csswg.org/mediaqueries-4/#aspect-ratio
fn eval_aspect_ratio(
device: &Device,
query_value: Option<Ratio>,
range_or_operator: Option<RangeOrOperator>,
) -> bool {
eval_aspect_ratio_for(
device,
query_value,
range_or_operator,
Device::au_viewport_size,
)
fn eval_aspect_ratio(context: &Context) -> Ratio {
eval_aspect_ratio_for(context, Device::au_viewport_size)
}
/// https://drafts.csswg.org/mediaqueries-4/#device-aspect-ratio
fn eval_device_aspect_ratio(
device: &Device,
query_value: Option<Ratio>,
range_or_operator: Option<RangeOrOperator>,
) -> bool {
eval_aspect_ratio_for(device, query_value, range_or_operator, device_size)
fn eval_device_aspect_ratio(context: &Context) -> Ratio {
eval_aspect_ratio_for(context, device_size)
}
/// https://compat.spec.whatwg.org/#css-media-queries-webkit-device-pixel-ratio
fn eval_device_pixel_ratio(
device: &Device,
query_value: Option<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,
}
fn eval_device_pixel_ratio(context: &Context) -> f32 {
eval_resolution(context).dppx()
}
/// https://drafts.csswg.org/mediaqueries-4/#orientation
fn eval_orientation(device: &Device, value: Option<Orientation>) -> bool {
eval_orientation_for(device, value, Device::au_viewport_size)
fn eval_orientation(context: &Context, value: Option<Orientation>) -> bool {
Orientation::eval(context.device().au_viewport_size(), value)
}
/// FIXME: There's no spec for `-moz-device-orientation`.
fn eval_device_orientation(device: &Device, value: Option<Orientation>) -> bool {
eval_orientation_for(device, value, device_size)
fn eval_device_orientation(context: &Context, value: Option<Orientation>) -> bool {
Orientation::eval(device_size(context.device()), value)
}
/// Values for the display-mode media feature.
@ -184,25 +87,23 @@ pub enum DisplayMode {
}
/// https://w3c.github.io/manifest/#the-display-mode-media-feature
fn eval_display_mode(device: &Device, query_value: Option<DisplayMode>) -> bool {
fn eval_display_mode(context: &Context, query_value: Option<DisplayMode>) -> bool {
match query_value {
Some(v) => v == unsafe { bindings::Gecko_MediaFeatures_GetDisplayMode(device.document()) },
Some(v) => v == unsafe { bindings::Gecko_MediaFeatures_GetDisplayMode(context.device().document()) },
None => true,
}
}
/// https://drafts.csswg.org/mediaqueries-4/#grid
fn eval_grid(_: &Device, query_value: Option<bool>, _: Option<RangeOrOperator>) -> bool {
fn eval_grid(_: &Context) -> bool {
// Gecko doesn't support grid devices (e.g., ttys), so the 'grid' feature
// is always 0.
let supports_grid = false;
query_value.map_or(supports_grid, |v| v == supports_grid)
false
}
/// https://compat.spec.whatwg.org/#css-media-queries-webkit-transform-3d
fn eval_transform_3d(_: &Device, query_value: Option<bool>, _: Option<RangeOrOperator>) -> bool {
let supports_transforms = true;
query_value.map_or(supports_transforms, |v| v == supports_transforms)
fn eval_transform_3d(_: &Context) -> bool {
true
}
#[derive(Clone, Copy, Debug, FromPrimitive, Parse, ToCss)]
@ -213,58 +114,33 @@ enum Scan {
}
/// https://drafts.csswg.org/mediaqueries-4/#scan
fn eval_scan(_: &Device, _: Option<Scan>) -> bool {
fn eval_scan(_: &Context, _: Option<Scan>) -> bool {
// Since Gecko doesn't support the 'tv' media type, the 'scan' feature never
// matches.
false
}
/// https://drafts.csswg.org/mediaqueries-4/#color
fn eval_color(
device: &Device,
query_value: Option<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)
fn eval_color(context: &Context) -> u32 {
unsafe { bindings::Gecko_MediaFeatures_GetColorDepth(context.device().document()) }
}
/// https://drafts.csswg.org/mediaqueries-4/#color-index
fn eval_color_index(
_: &Device,
query_value: Option<u32>,
range_or_operator: Option<RangeOrOperator>,
) -> bool {
fn eval_color_index(_: &Context) -> u32 {
// We should return zero if the device does not use a color lookup table.
let index = 0;
RangeOrOperator::evaluate(range_or_operator, query_value, index)
0
}
/// https://drafts.csswg.org/mediaqueries-4/#monochrome
fn eval_monochrome(
device: &Device,
query_value: Option<u32>,
range_or_operator: Option<RangeOrOperator>,
) -> bool {
fn eval_monochrome(context: &Context) -> u32 {
// For color devices we should return 0.
let depth =
unsafe { bindings::Gecko_MediaFeatures_GetMonochromeBitsPerPixel(device.document()) };
RangeOrOperator::evaluate(range_or_operator, query_value, depth)
unsafe { bindings::Gecko_MediaFeatures_GetMonochromeBitsPerPixel(context.device().document()) }
}
/// https://drafts.csswg.org/mediaqueries-4/#resolution
fn eval_resolution(
device: &Device,
query_value: Option<Resolution>,
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,
)
fn eval_resolution(context: &Context) -> Resolution {
let resolution_dppx = unsafe { bindings::Gecko_MediaFeatures_GetResolution(context.device().document()) };
Resolution::from_dppx(resolution_dppx)
}
#[derive(Clone, Copy, Debug, FromPrimitive, Parse, ToCss)]
@ -283,10 +159,22 @@ pub enum PrefersColorScheme {
Dark,
}
/// Values for the dynamic-range and video-dynamic-range media features.
/// https://drafts.csswg.org/mediaqueries-5/#dynamic-range
/// This implements PartialOrd so that lower values will correctly match
/// higher capabilities.
#[derive(Clone, Copy, Debug, FromPrimitive, Parse, PartialEq, PartialOrd, ToCss)]
#[repr(u8)]
#[allow(missing_docs)]
pub enum DynamicRange {
Standard,
High,
}
/// https://drafts.csswg.org/mediaqueries-5/#prefers-reduced-motion
fn eval_prefers_reduced_motion(device: &Device, query_value: Option<PrefersReducedMotion>) -> bool {
fn eval_prefers_reduced_motion(context: &Context, query_value: Option<PrefersReducedMotion>) -> bool {
let prefers_reduced =
unsafe { bindings::Gecko_MediaFeatures_PrefersReducedMotion(device.document()) };
unsafe { bindings::Gecko_MediaFeatures_PrefersReducedMotion(context.device().document()) };
let query_value = match query_value {
Some(v) => v,
None => return prefers_reduced,
@ -303,19 +191,20 @@ fn eval_prefers_reduced_motion(device: &Device, query_value: Option<PrefersReduc
#[derive(Clone, Copy, Debug, FromPrimitive, Parse, PartialEq, ToCss)]
#[repr(u8)]
pub enum PrefersContrast {
/// More contrast is preferred. Corresponds to an accessibility theme
/// being enabled or Firefox forcing high contrast colors.
/// More contrast is preferred.
More,
/// Low contrast is preferred.
Less,
/// Custom (not more, not less).
Custom,
/// The default value if neither high or low contrast is enabled.
NoPreference,
}
/// https://drafts.csswg.org/mediaqueries-5/#prefers-contrast
fn eval_prefers_contrast(device: &Device, query_value: Option<PrefersContrast>) -> bool {
fn eval_prefers_contrast(context: &Context, query_value: Option<PrefersContrast>) -> bool {
let prefers_contrast =
unsafe { bindings::Gecko_MediaFeatures_PrefersContrast(device.document()) };
unsafe { bindings::Gecko_MediaFeatures_PrefersContrast(context.device().document()) };
match query_value {
Some(v) => v == prefers_contrast,
None => prefers_contrast != PrefersContrast::NoPreference,
@ -334,8 +223,8 @@ pub enum ForcedColors {
}
/// https://drafts.csswg.org/mediaqueries-5/#forced-colors
fn eval_forced_colors(device: &Device, query_value: Option<ForcedColors>) -> bool {
let forced = !device.use_document_colors();
fn eval_forced_colors(context: &Context, query_value: Option<ForcedColors>) -> bool {
let forced = !context.device().use_document_colors();
match query_value {
Some(query_value) => forced == (query_value == ForcedColors::Active),
None => forced,
@ -352,7 +241,7 @@ enum OverflowBlock {
}
/// https://drafts.csswg.org/mediaqueries-4/#mf-overflow-block
fn eval_overflow_block(device: &Device, query_value: Option<OverflowBlock>) -> bool {
fn eval_overflow_block(context: &Context, query_value: Option<OverflowBlock>) -> bool {
// For the time being, assume that printing (including previews)
// is the only time when we paginate, and we are otherwise always
// scrolling. This is true at the moment in Firefox, but may need
@ -360,7 +249,7 @@ fn eval_overflow_block(device: &Device, query_value: Option<OverflowBlock>) -> b
// billboard mode that doesn't support overflow at all).
//
// If this ever changes, don't forget to change eval_overflow_inline too.
let scrolling = device.media_type() != MediaType::print();
let scrolling = context.device().media_type() != MediaType::print();
let query_value = match query_value {
Some(v) => v,
None => return true,
@ -381,9 +270,9 @@ enum OverflowInline {
}
/// https://drafts.csswg.org/mediaqueries-4/#mf-overflow-inline
fn eval_overflow_inline(device: &Device, query_value: Option<OverflowInline>) -> bool {
fn eval_overflow_inline(context: &Context, query_value: Option<OverflowInline>) -> bool {
// See the note in eval_overflow_block.
let scrolling = device.media_type() != MediaType::print();
let scrolling = context.device().media_type() != MediaType::print();
let query_value = match query_value {
Some(v) => v,
None => return scrolling,
@ -395,13 +284,41 @@ fn eval_overflow_inline(device: &Device, query_value: Option<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(
device: &Device,
context: &Context,
use_content: bool,
query_value: Option<PrefersColorScheme>,
) -> bool {
let prefers_color_scheme =
unsafe { bindings::Gecko_MediaFeatures_PrefersColorScheme(device.document(), use_content) };
unsafe { bindings::Gecko_MediaFeatures_PrefersColorScheme(context.device().document(), use_content) };
match query_value {
Some(v) => prefers_color_scheme == v,
None => true,
@ -409,15 +326,34 @@ fn do_eval_prefers_color_scheme(
}
/// https://drafts.csswg.org/mediaqueries-5/#prefers-color-scheme
fn eval_prefers_color_scheme(device: &Device, query_value: Option<PrefersColorScheme>) -> bool {
do_eval_prefers_color_scheme(device, /* use_content = */ false, query_value)
fn eval_prefers_color_scheme(context: &Context, query_value: Option<PrefersColorScheme>) -> bool {
do_eval_prefers_color_scheme(context, /* use_content = */ false, query_value)
}
fn eval_content_prefers_color_scheme(
device: &Device,
context: &Context,
query_value: Option<PrefersColorScheme>,
) -> 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! {
@ -429,15 +365,15 @@ bitflags! {
}
}
fn primary_pointer_capabilities(device: &Device) -> PointerCapabilities {
fn primary_pointer_capabilities(context: &Context) -> PointerCapabilities {
PointerCapabilities::from_bits_truncate(unsafe {
bindings::Gecko_MediaFeatures_PrimaryPointerCapabilities(device.document())
bindings::Gecko_MediaFeatures_PrimaryPointerCapabilities(context.device().document())
})
}
fn all_pointer_capabilities(device: &Device) -> PointerCapabilities {
fn all_pointer_capabilities(context: &Context) -> PointerCapabilities {
PointerCapabilities::from_bits_truncate(unsafe {
bindings::Gecko_MediaFeatures_AllPointerCapabilities(device.document())
bindings::Gecko_MediaFeatures_AllPointerCapabilities(context.device().document())
})
}
@ -466,13 +402,13 @@ fn eval_pointer_capabilities(
}
/// https://drafts.csswg.org/mediaqueries-4/#pointer
fn eval_pointer(device: &Device, query_value: Option<Pointer>) -> bool {
eval_pointer_capabilities(query_value, primary_pointer_capabilities(device))
fn eval_pointer(context: &Context, query_value: Option<Pointer>) -> bool {
eval_pointer_capabilities(query_value, primary_pointer_capabilities(context))
}
/// https://drafts.csswg.org/mediaqueries-4/#descdef-media-any-pointer
fn eval_any_pointer(device: &Device, query_value: Option<Pointer>) -> bool {
eval_pointer_capabilities(query_value, all_pointer_capabilities(device))
fn eval_any_pointer(context: &Context, query_value: Option<Pointer>) -> bool {
eval_pointer_capabilities(query_value, all_pointer_capabilities(context))
}
#[derive(Clone, Copy, Debug, FromPrimitive, Parse, ToCss)]
@ -499,54 +435,33 @@ fn eval_hover_capabilities(
}
/// https://drafts.csswg.org/mediaqueries-4/#hover
fn eval_hover(device: &Device, query_value: Option<Hover>) -> bool {
eval_hover_capabilities(query_value, primary_pointer_capabilities(device))
fn eval_hover(context: &Context, query_value: Option<Hover>) -> bool {
eval_hover_capabilities(query_value, primary_pointer_capabilities(context))
}
/// https://drafts.csswg.org/mediaqueries-4/#descdef-media-any-hover
fn eval_any_hover(device: &Device, query_value: Option<Hover>) -> bool {
eval_hover_capabilities(query_value, all_pointer_capabilities(device))
fn eval_any_hover(context: &Context, query_value: Option<Hover>) -> bool {
eval_hover_capabilities(query_value, all_pointer_capabilities(context))
}
fn eval_moz_is_glyph(
device: &Device,
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_is_glyph(context: &Context) -> bool {
context.device().document().mIsSVGGlyphsDocument()
}
fn eval_moz_print_preview(
device: &Device,
query_value: Option<bool>,
_: Option<RangeOrOperator>,
) -> bool {
let is_print_preview = device.is_print_preview();
fn eval_moz_print_preview(context: &Context) -> bool {
let is_print_preview = context.device().is_print_preview();
if is_print_preview {
debug_assert_eq!(device.media_type(), MediaType::print());
debug_assert_eq!(context.device().media_type(), MediaType::print());
}
query_value.map_or(is_print_preview, |v| v == is_print_preview)
is_print_preview
}
fn eval_moz_non_native_content_theme(
device: &Device,
query_value: Option<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_non_native_content_theme(context: &Context) -> bool {
unsafe { bindings::Gecko_MediaFeatures_ShouldAvoidNativeTheme(context.device().document()) }
}
fn eval_moz_is_resource_document(
device: &Device,
query_value: Option<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)
fn eval_moz_is_resource_document(context: &Context) -> bool {
unsafe { bindings::Gecko_MediaFeatures_IsResourceDocument(context.device().document()) }
}
/// Allows front-end CSS to discern platform via media queries.
@ -572,7 +487,7 @@ pub enum Platform {
WindowsWin10,
}
fn eval_moz_platform(_: &Device, query_value: Option<Platform>) -> bool {
fn eval_moz_platform(_: &Context, query_value: Option<Platform>) -> bool {
let query_value = match query_value {
Some(v) => v,
None => return false,
@ -581,32 +496,22 @@ fn eval_moz_platform(_: &Device, query_value: Option<Platform>) -> bool {
unsafe { bindings::Gecko_MediaFeatures_MatchesPlatform(query_value) }
}
fn eval_moz_windows_non_native_menus(
device: &Device,
query_value: Option<bool>,
_: Option<RangeOrOperator>,
) -> bool {
fn eval_moz_windows_non_native_menus(context: &Context) -> bool {
let use_non_native_menus = match static_prefs::pref!("browser.display.windows.non_native_menus")
{
0 => false,
1 => true,
_ => {
eval_moz_platform(device, Some(Platform::WindowsWin10)) &&
eval_moz_platform(context, Some(Platform::WindowsWin10)) &&
get_lnf_int_as_bool(bindings::LookAndFeel_IntID::WindowsDefaultTheme as i32)
},
};
query_value.map_or(use_non_native_menus, |v| v == use_non_native_menus)
use_non_native_menus
}
fn eval_moz_overlay_scrollbars(
device: &Device,
query_value: Option<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 eval_moz_overlay_scrollbars(context: &Context) -> bool {
unsafe { bindings::Gecko_MediaFeatures_UseOverlayScrollbars(context.device().document()) }
}
fn get_lnf_int(int_id: i32) -> i32 {
@ -635,16 +540,15 @@ fn get_scrollbar_end_forward(int_id: i32) -> bool {
macro_rules! lnf_int_feature {
($feature_name:expr, $int_id:ident, $get_value:ident) => {{
fn __eval(_: &Device, query_value: Option<bool>, _: Option<RangeOrOperator>) -> bool {
let value = $get_value(bindings::LookAndFeel_IntID::$int_id as i32);
query_value.map_or(value, |v| v == value)
fn __eval(_: &Context) -> bool {
$get_value(bindings::LookAndFeel_IntID::$int_id as i32)
}
feature!(
$feature_name,
AllowsRanges::No,
Evaluator::BoolInteger(__eval),
ParsingRequirements::CHROME_AND_UA_ONLY,
FeatureFlags::CHROME_AND_UA_ONLY,
)
}};
($feature_name:expr, $int_id:ident) => {{
@ -661,18 +565,18 @@ macro_rules! lnf_int_feature {
/// pref, with `rust: true`. The feature name needs to be defined in
/// `StaticAtoms.py` just like the others. In order to support dynamic changes,
/// you also need to add them to kMediaQueryPrefs in nsXPLookAndFeel.cpp
#[allow(unused)]
macro_rules! bool_pref_feature {
($feature_name:expr, $pref:tt) => {{
fn __eval(_: &Device, query_value: Option<bool>, _: Option<RangeOrOperator>) -> bool {
let value = static_prefs::pref!($pref);
query_value.map_or(value, |v| v == value)
fn __eval(_: &Context) -> bool {
static_prefs::pref!($pref)
}
feature!(
$feature_name,
AllowsRanges::No,
Evaluator::BoolInteger(__eval),
ParsingRequirements::CHROME_AND_UA_ONLY,
FeatureFlags::CHROME_AND_UA_ONLY,
)
}};
}
@ -682,54 +586,54 @@ macro_rules! bool_pref_feature {
/// to support new types in these entries and (2) ensuring that either
/// nsPresContext::MediaFeatureValuesChanged is called when the value that
/// would be returned by the evaluator function could change.
pub static MEDIA_FEATURES: [MediaFeatureDescription; 58] = [
pub static MEDIA_FEATURES: [QueryFeatureDescription; 59] = [
feature!(
atom!("width"),
AllowsRanges::Yes,
Evaluator::Length(eval_width),
ParsingRequirements::empty(),
FeatureFlags::empty(),
),
feature!(
atom!("height"),
AllowsRanges::Yes,
Evaluator::Length(eval_height),
ParsingRequirements::empty(),
FeatureFlags::empty(),
),
feature!(
atom!("aspect-ratio"),
AllowsRanges::Yes,
Evaluator::NumberRatio(eval_aspect_ratio),
ParsingRequirements::empty(),
FeatureFlags::empty(),
),
feature!(
atom!("orientation"),
AllowsRanges::No,
keyword_evaluator!(eval_orientation, Orientation),
ParsingRequirements::empty(),
FeatureFlags::empty(),
),
feature!(
atom!("device-width"),
AllowsRanges::Yes,
Evaluator::Length(eval_device_width),
ParsingRequirements::empty(),
FeatureFlags::empty(),
),
feature!(
atom!("device-height"),
AllowsRanges::Yes,
Evaluator::Length(eval_device_height),
ParsingRequirements::empty(),
FeatureFlags::empty(),
),
feature!(
atom!("device-aspect-ratio"),
AllowsRanges::Yes,
Evaluator::NumberRatio(eval_device_aspect_ratio),
ParsingRequirements::empty(),
FeatureFlags::empty(),
),
feature!(
atom!("-moz-device-orientation"),
AllowsRanges::No,
keyword_evaluator!(eval_device_orientation, Orientation),
ParsingRequirements::empty(),
FeatureFlags::empty(),
),
// Webkit extensions that we support for de-facto web compatibility.
// -webkit-{min|max}-device-pixel-ratio (controlled with its own pref):
@ -737,68 +641,68 @@ pub static MEDIA_FEATURES: [MediaFeatureDescription; 58] = [
atom!("device-pixel-ratio"),
AllowsRanges::Yes,
Evaluator::Float(eval_device_pixel_ratio),
ParsingRequirements::WEBKIT_PREFIX,
FeatureFlags::WEBKIT_PREFIX,
),
// -webkit-transform-3d.
feature!(
atom!("transform-3d"),
AllowsRanges::No,
Evaluator::BoolInteger(eval_transform_3d),
ParsingRequirements::WEBKIT_PREFIX,
FeatureFlags::WEBKIT_PREFIX,
),
feature!(
atom!("-moz-device-pixel-ratio"),
AllowsRanges::Yes,
Evaluator::Float(eval_device_pixel_ratio),
ParsingRequirements::empty(),
FeatureFlags::empty(),
),
feature!(
atom!("resolution"),
AllowsRanges::Yes,
Evaluator::Resolution(eval_resolution),
ParsingRequirements::empty(),
FeatureFlags::empty(),
),
feature!(
atom!("display-mode"),
AllowsRanges::No,
keyword_evaluator!(eval_display_mode, DisplayMode),
ParsingRequirements::empty(),
FeatureFlags::empty(),
),
feature!(
atom!("grid"),
AllowsRanges::No,
Evaluator::BoolInteger(eval_grid),
ParsingRequirements::empty(),
FeatureFlags::empty(),
),
feature!(
atom!("scan"),
AllowsRanges::No,
keyword_evaluator!(eval_scan, Scan),
ParsingRequirements::empty(),
FeatureFlags::empty(),
),
feature!(
atom!("color"),
AllowsRanges::Yes,
Evaluator::Integer(eval_color),
ParsingRequirements::empty(),
FeatureFlags::empty(),
),
feature!(
atom!("color-index"),
AllowsRanges::Yes,
Evaluator::Integer(eval_color_index),
ParsingRequirements::empty(),
FeatureFlags::empty(),
),
feature!(
atom!("monochrome"),
AllowsRanges::Yes,
Evaluator::Integer(eval_monochrome),
ParsingRequirements::empty(),
FeatureFlags::empty(),
),
feature!(
atom!("prefers-reduced-motion"),
AllowsRanges::No,
keyword_evaluator!(eval_prefers_reduced_motion, PrefersReducedMotion),
ParsingRequirements::empty(),
FeatureFlags::empty(),
),
feature!(
atom!("prefers-contrast"),
@ -809,31 +713,49 @@ pub static MEDIA_FEATURES: [MediaFeatureDescription; 58] = [
// layout.css.prefers-contrast.enabled preference. See
// disabed_by_pref in media_feature_expression.rs for how that
// is done.
ParsingRequirements::empty(),
FeatureFlags::empty(),
),
feature!(
atom!("forced-colors"),
AllowsRanges::No,
keyword_evaluator!(eval_forced_colors, ForcedColors),
ParsingRequirements::empty(),
FeatureFlags::empty(),
),
feature!(
atom!("overflow-block"),
AllowsRanges::No,
keyword_evaluator!(eval_overflow_block, OverflowBlock),
ParsingRequirements::empty(),
FeatureFlags::empty(),
),
feature!(
atom!("overflow-inline"),
AllowsRanges::No,
keyword_evaluator!(eval_overflow_inline, OverflowInline),
ParsingRequirements::empty(),
FeatureFlags::empty(),
),
feature!(
atom!("update"),
AllowsRanges::No,
keyword_evaluator!(eval_update, Update),
FeatureFlags::empty(),
),
feature!(
atom!("prefers-color-scheme"),
AllowsRanges::No,
keyword_evaluator!(eval_prefers_color_scheme, PrefersColorScheme),
ParsingRequirements::empty(),
FeatureFlags::empty(),
),
feature!(
atom!("dynamic-range"),
AllowsRanges::No,
keyword_evaluator!(eval_dynamic_range, DynamicRange),
FeatureFlags::empty(),
),
feature!(
atom!("video-dynamic-range"),
AllowsRanges::No,
keyword_evaluator!(eval_video_dynamic_range, DynamicRange),
FeatureFlags::empty(),
),
// Evaluates to the preferred color scheme for content. Only useful in
// chrome context, where the chrome color-scheme and the content
@ -842,31 +764,31 @@ pub static MEDIA_FEATURES: [MediaFeatureDescription; 58] = [
atom!("-moz-content-prefers-color-scheme"),
AllowsRanges::No,
keyword_evaluator!(eval_content_prefers_color_scheme, PrefersColorScheme),
ParsingRequirements::CHROME_AND_UA_ONLY,
FeatureFlags::CHROME_AND_UA_ONLY,
),
feature!(
atom!("pointer"),
AllowsRanges::No,
keyword_evaluator!(eval_pointer, Pointer),
ParsingRequirements::empty(),
FeatureFlags::empty(),
),
feature!(
atom!("any-pointer"),
AllowsRanges::No,
keyword_evaluator!(eval_any_pointer, Pointer),
ParsingRequirements::empty(),
FeatureFlags::empty(),
),
feature!(
atom!("hover"),
AllowsRanges::No,
keyword_evaluator!(eval_hover, Hover),
ParsingRequirements::empty(),
FeatureFlags::empty(),
),
feature!(
atom!("any-hover"),
AllowsRanges::No,
keyword_evaluator!(eval_any_hover, Hover),
ParsingRequirements::empty(),
FeatureFlags::empty(),
),
// Internal -moz-is-glyph media feature: applies only inside SVG glyphs.
// Internal because it is really only useful in the user agent anyway
@ -875,43 +797,43 @@ pub static MEDIA_FEATURES: [MediaFeatureDescription; 58] = [
atom!("-moz-is-glyph"),
AllowsRanges::No,
Evaluator::BoolInteger(eval_moz_is_glyph),
ParsingRequirements::CHROME_AND_UA_ONLY,
FeatureFlags::CHROME_AND_UA_ONLY,
),
feature!(
atom!("-moz-is-resource-document"),
AllowsRanges::No,
Evaluator::BoolInteger(eval_moz_is_resource_document),
ParsingRequirements::CHROME_AND_UA_ONLY,
FeatureFlags::CHROME_AND_UA_ONLY,
),
feature!(
atom!("-moz-platform"),
AllowsRanges::No,
keyword_evaluator!(eval_moz_platform, Platform),
ParsingRequirements::CHROME_AND_UA_ONLY,
FeatureFlags::CHROME_AND_UA_ONLY,
),
feature!(
atom!("-moz-print-preview"),
AllowsRanges::No,
Evaluator::BoolInteger(eval_moz_print_preview),
ParsingRequirements::CHROME_AND_UA_ONLY,
FeatureFlags::CHROME_AND_UA_ONLY,
),
feature!(
atom!("-moz-non-native-content-theme"),
AllowsRanges::No,
Evaluator::BoolInteger(eval_moz_non_native_content_theme),
ParsingRequirements::CHROME_AND_UA_ONLY,
FeatureFlags::CHROME_AND_UA_ONLY,
),
feature!(
atom!("-moz-windows-non-native-menus"),
AllowsRanges::No,
Evaluator::BoolInteger(eval_moz_windows_non_native_menus),
ParsingRequirements::CHROME_AND_UA_ONLY,
FeatureFlags::CHROME_AND_UA_ONLY,
),
feature!(
atom!("-moz-overlay-scrollbars"),
AllowsRanges::No,
Evaluator::BoolInteger(eval_moz_overlay_scrollbars),
ParsingRequirements::CHROME_AND_UA_ONLY,
FeatureFlags::CHROME_AND_UA_ONLY,
),
lnf_int_feature!(
atom!("-moz-scrollbar-start-backward"),
@ -933,10 +855,6 @@ pub static MEDIA_FEATURES: [MediaFeatureDescription; 58] = [
ScrollArrowStyle,
get_scrollbar_end_forward
),
lnf_int_feature!(
atom!("-moz-scrollbar-thumb-proportional"),
ScrollSliderStyle
),
lnf_int_feature!(atom!("-moz-menubar-drag"), MenuBarDrag),
lnf_int_feature!(atom!("-moz-windows-default-theme"), WindowsDefaultTheme),
lnf_int_feature!(atom!("-moz-mac-graphite-theme"), MacGraphiteTheme),
@ -959,8 +877,4 @@ pub static MEDIA_FEATURES: [MediaFeatureDescription; 58] = [
GTKCSDReversedPlacement
),
lnf_int_feature!(atom!("-moz-system-dark-theme"), SystemUsesDarkTheme),
bool_pref_feature!(
atom!("-moz-proton-places-tooltip"),
"browser.proton.places-tooltip.enabled"
),
];

View file

@ -17,6 +17,7 @@ use crate::values::computed::font::GenericFontFamily;
use crate::values::computed::{ColorScheme, Length};
use crate::values::specified::color::SystemColor;
use crate::values::specified::font::FONT_MEDIUM_PX;
use crate::values::specified::ViewportVariant;
use crate::values::{CustomIdent, KeyframesName};
use app_units::{Au, AU_PER_PX};
use cssparser::RGBA;
@ -58,6 +59,9 @@ pub struct Device {
/// Whether any styles computed in the document relied on the viewport size
/// by using vw/vh/vmin/vmax units.
used_viewport_size: AtomicBool,
/// Whether any styles computed in the document relied on the viewport size
/// by using dvw/dvh/dvmin/dvmax units.
used_dynamic_viewport_size: AtomicBool,
/// The CssEnvironment object responsible of getting CSS environment
/// variables.
environment: CssEnvironment,
@ -100,6 +104,7 @@ impl Device {
used_root_font_size: AtomicBool::new(false),
used_font_metrics: AtomicBool::new(false),
used_viewport_size: AtomicBool::new(false),
used_dynamic_viewport_size: AtomicBool::new(false),
environment: CssEnvironment,
}
}
@ -267,6 +272,8 @@ impl Device {
self.used_root_font_size.store(false, Ordering::Relaxed);
self.used_font_metrics.store(false, Ordering::Relaxed);
self.used_viewport_size.store(false, Ordering::Relaxed);
self.used_dynamic_viewport_size
.store(false, Ordering::Relaxed);
}
/// Returns whether we ever looked up the root font size of the Device.
@ -337,7 +344,10 @@ impl Device {
/// Returns the current viewport size in app units, recording that it's been
/// used for viewport unit resolution.
pub fn au_viewport_size_for_viewport_unit_resolution(&self) -> Size2D<Au> {
pub fn au_viewport_size_for_viewport_unit_resolution(
&self,
variant: ViewportVariant,
) -> Size2D<Au> {
self.used_viewport_size.store(true, Ordering::Relaxed);
let pc = match self.pres_context() {
Some(pc) => pc,
@ -348,8 +358,42 @@ impl Device {
return self.page_size_minus_default_margin(pc);
}
let size = &pc.mSizeForViewportUnits;
Size2D::new(Au(size.width), Au(size.height))
match variant {
ViewportVariant::UADefault => {
let size = &pc.mSizeForViewportUnits;
Size2D::new(Au(size.width), Au(size.height))
},
ViewportVariant::Small => {
let size = &pc.mVisibleArea;
Size2D::new(Au(size.width), Au(size.height))
},
ViewportVariant::Large => {
let size = &pc.mVisibleArea;
// Looks like IntCoordTyped is treated as if it's u32 in Rust.
debug_assert!(
/* pc.mDynamicToolbarMaxHeight >=0 && */
pc.mDynamicToolbarMaxHeight < i32::MAX as u32
);
Size2D::new(
Au(size.width),
Au(size.height
+ pc.mDynamicToolbarMaxHeight as i32 * pc.mCurAppUnitsPerDevPixel),
)
},
ViewportVariant::Dynamic => {
self.used_dynamic_viewport_size.store(true, Ordering::Relaxed);
let size = &pc.mVisibleArea;
// Looks like IntCoordTyped is treated as if it's u32 in Rust.
debug_assert!(
/* pc.mDynamicToolbarHeight >=0 && */
pc.mDynamicToolbarHeight < i32::MAX as u32
);
Size2D::new(
Au(size.width),
Au(size.height + pc.mDynamicToolbarHeight as i32 * pc.mCurAppUnitsPerDevPixel),
)
},
}
}
/// Returns whether we ever looked up the viewport size of the Device.
@ -357,11 +401,21 @@ impl Device {
self.used_viewport_size.load(Ordering::Relaxed)
}
/// Returns whether we ever looked up the dynamic viewport size of the Device.
pub fn used_dynamic_viewport_size(&self) -> bool {
self.used_dynamic_viewport_size.load(Ordering::Relaxed)
}
/// Returns whether font metrics have been queried.
pub fn used_font_metrics(&self) -> bool {
self.used_font_metrics.load(Ordering::Relaxed)
}
/// Returns whether visited styles are enabled.
pub fn visited_styles_enabled(&self) -> bool {
unsafe { bindings::Gecko_VisitedStylesEnabled(self.document()) }
}
/// Returns the device pixel ratio.
pub fn device_pixel_ratio(&self) -> Scale<f32, CSSPixel, DevicePixel> {
let pc = match self.pres_context() {

View file

@ -54,7 +54,7 @@ macro_rules! apply_non_ts_list {
("-moz-styleeditor-transitioning", MozStyleeditorTransitioning, IN_STYLEEDITOR_TRANSITIONING_STATE, PSEUDO_CLASS_ENABLED_IN_UA_SHEETS),
("fullscreen", Fullscreen, IN_FULLSCREEN_STATE, _),
("-moz-modal-dialog", MozModalDialog, IN_MODAL_DIALOG_STATE, PSEUDO_CLASS_ENABLED_IN_UA_SHEETS),
("-moz-topmost-modal-dialog", MozTopmostModalDialog, IN_TOPMOST_MODAL_DIALOG_STATE, PSEUDO_CLASS_ENABLED_IN_UA_SHEETS),
("-moz-topmost-modal", MozTopmostModal, IN_TOPMOST_MODAL_TOP_LAYER_STATE, PSEUDO_CLASS_ENABLED_IN_UA_SHEETS),
("-moz-broken", MozBroken, IN_BROKEN_STATE, _),
("-moz-loading", MozLoading, IN_LOADING_STATE, _),
("-moz-has-dir-attr", MozHasDirAttr, IN_HAS_DIR_ATTR_STATE, PSEUDO_CLASS_ENABLED_IN_UA_SHEETS),
@ -92,8 +92,6 @@ macro_rules! apply_non_ts_list {
("-moz-is-html", MozIsHTML, _, _),
("-moz-placeholder", MozPlaceholder, _, _),
("-moz-lwtheme", MozLWTheme, _, PSEUDO_CLASS_ENABLED_IN_UA_SHEETS_AND_CHROME),
("-moz-lwtheme-brighttext", MozLWThemeBrightText, _, PSEUDO_CLASS_ENABLED_IN_UA_SHEETS_AND_CHROME),
("-moz-lwtheme-darktext", MozLWThemeDarkText, _, PSEUDO_CLASS_ENABLED_IN_UA_SHEETS_AND_CHROME),
("-moz-window-inactive", MozWindowInactive, _, _),
]
}

View file

@ -139,15 +139,6 @@ impl NonTSPseudoClass {
/// Returns whether the pseudo-class is enabled in content sheets.
#[inline]
fn is_enabled_in_content(&self) -> bool {
if matches!(
*self,
Self::MozLWTheme | Self::MozLWThemeBrightText | Self::MozLWThemeDarkText
) {
return static_prefs::pref!("layout.css.moz-lwtheme.content.enabled");
}
if let NonTSPseudoClass::MozLocaleDir(..) = *self {
return static_prefs::pref!("layout.css.moz-locale-dir.content.enabled");
}
!self.has_any_flag(NonTSPseudoClassFlag::PSEUDO_CLASS_ENABLED_IN_UA_SHEETS_AND_CHROME)
}
@ -184,8 +175,6 @@ impl NonTSPseudoClass {
},
NonTSPseudoClass::MozWindowInactive => DocumentState::WINDOW_INACTIVE,
NonTSPseudoClass::MozLWTheme => DocumentState::LWTHEME,
NonTSPseudoClass::MozLWThemeBrightText => DocumentState::LWTHEME_BRIGHTTEXT,
NonTSPseudoClass::MozLWThemeDarkText => DocumentState::LWTHEME_DARKTEXT,
_ => DocumentState::empty(),
}
}
@ -208,15 +197,13 @@ impl NonTSPseudoClass {
NonTSPseudoClass::MozNativeAnonymous |
// :-moz-placeholder is parsed but never matches.
NonTSPseudoClass::MozPlaceholder |
// :-moz-locale-dir and :-moz-window-inactive depend only on
// the state of the document, which is invariant across all
// the elements involved in a given style cache.
NonTSPseudoClass::MozLocaleDir(_) |
NonTSPseudoClass::MozWindowInactive |
// Similar for the document themes.
// :-moz-lwtheme, :-moz-locale-dir and
// :-moz-window-inactive depend only on the state of the
// document, which is invariant across all the elements
// involved in a given style cache.
NonTSPseudoClass::MozLWTheme |
NonTSPseudoClass::MozLWThemeBrightText |
NonTSPseudoClass::MozLWThemeDarkText
NonTSPseudoClass::MozLocaleDir(_) |
NonTSPseudoClass::MozWindowInactive
)
}
}
@ -458,3 +445,9 @@ unsafe impl HasFFI for SelectorList<SelectorImpl> {
}
unsafe impl HasSimpleFFI 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);

View file

@ -44,14 +44,14 @@ unsafe fn get_class_or_part_from_attr(attr: &structs::nsAttrValue) -> Class {
structs::nsAttrValue_ValueType_eAtomArray
);
// NOTE: Bindgen doesn't deal with AutoTArray, so cast it below.
let array: *mut u8 = *(*container)
let attr_array: *mut _ = *(*container)
.__bindgen_anon_1
.mValue
.as_ref()
.__bindgen_anon_1
.mAtomArray
.as_ref();
let array = array as *const structs::nsTArray<structs::RefPtr<nsAtom>>;
let array = (*attr_array).mArray.as_ptr() as *const structs::nsTArray<structs::RefPtr<nsAtom>>;
return Class::More(&**array);
}
debug_assert_eq!(base_type, structs::nsAttrValue_ValueBaseType_eStringBase);

View file

@ -66,6 +66,8 @@ use crate::stylist::CascadeData;
use crate::values::{AtomIdent, AtomString};
use crate::CaseSensitivityExt;
use crate::LocalName;
use app_units::Au;
use euclid::default::Size2D;
use atomic_refcell::{AtomicRef, AtomicRefCell, AtomicRefMut};
use fxhash::FxHashMap;
use selectors::attr::{AttrSelectorOperation, AttrSelectorOperator};
@ -321,6 +323,11 @@ impl<'ln> GeckoNode<'ln> {
self.flags() & (NODE_IS_IN_SHADOW_TREE as u32) != 0
}
#[inline]
fn is_connected(&self) -> bool {
self.get_bool_flag(nsINode_BooleanFlag::IsConnected)
}
/// WARNING: This logic is duplicated in Gecko's FlattenedTreeParentIsParent.
/// Make sure to mirror any modifications in both places.
#[inline]
@ -1030,6 +1037,21 @@ impl<'le> TElement for GeckoElement<'le> {
}
}
#[inline]
fn primary_box_size(&self) -> Size2D<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.
#[inline]
fn slotted_nodes(&self) -> &[Self::ConcreteNode] {
@ -1393,16 +1415,6 @@ impl<'le> TElement for GeckoElement<'le> {
self.is_root_of_native_anonymous_subtree()
}
unsafe fn set_selector_flags(&self, flags: ElementSelectorFlags) {
debug_assert!(!flags.is_empty());
self.set_flags(selector_flags_to_node_flags(flags));
}
fn has_selector_flags(&self, flags: ElementSelectorFlags) -> bool {
let node_flags = selector_flags_to_node_flags(flags);
(self.flags() & node_flags) == node_flags
}
#[inline]
fn may_have_animations(&self) -> bool {
if let Some(pseudo) = self.implemented_pseudo_element() {
@ -1501,7 +1513,7 @@ impl<'le> TElement for GeckoElement<'le> {
) -> bool {
use crate::properties::LonghandIdSet;
let after_change_box_style = after_change_style.get_box();
let after_change_ui_style = after_change_style.get_ui();
let existing_transitions = self.css_transitions_info();
let mut transitions_to_keep = LonghandIdSet::new();
for transition_property in after_change_style.transition_properties() {
@ -1511,7 +1523,7 @@ impl<'le> TElement for GeckoElement<'le> {
transitions_to_keep.insert(physical_longhand);
if self.needs_transitions_update_per_property(
physical_longhand,
after_change_box_style.transition_combined_duration_at(transition_property.index),
after_change_ui_style.transition_combined_duration_at(transition_property.index),
before_change_style,
after_change_style,
&existing_transitions,
@ -1593,23 +1605,9 @@ impl<'le> TElement for GeckoElement<'le> {
use crate::properties::longhands::_x_lang::SpecifiedValue as SpecifiedLang;
use crate::properties::longhands::_x_text_zoom::SpecifiedValue as SpecifiedZoom;
use crate::properties::longhands::color::SpecifiedValue as SpecifiedColor;
use crate::properties::longhands::text_align::SpecifiedValue as SpecifiedTextAlign;
use crate::stylesheets::layer_rule::LayerOrder;
use crate::values::specified::color::Color;
lazy_static! {
static ref TH_RULE: ApplicableDeclarationBlock = {
let global_style_data = &*GLOBAL_STYLE_DATA;
let pdb = PropertyDeclarationBlock::with_one(
PropertyDeclaration::TextAlign(SpecifiedTextAlign::MozCenterOrInherit),
Importance::Normal,
);
let arc = Arc::new_leaked(global_style_data.shared_lock.wrap(pdb));
ApplicableDeclarationBlock::from_declarations(
arc,
ServoCascadeLevel::PresHints,
LayerOrder::root(),
)
};
static ref TABLE_COLOR_RULE: ApplicableDeclarationBlock = {
let global_style_data = &*GLOBAL_STYLE_DATA;
let pdb = PropertyDeclarationBlock::with_one(
@ -1654,9 +1652,7 @@ impl<'le> TElement for GeckoElement<'le> {
let ns = self.namespace_id();
// <th> elements get a default MozCenterOrInherit which may get overridden
if ns == structs::kNameSpaceID_XHTML as i32 {
if self.local_name().as_ptr() == atom!("th").as_ptr() {
hints.push(TH_RULE.clone());
} else if self.local_name().as_ptr() == atom!("table").as_ptr() &&
if self.local_name().as_ptr() == atom!("table").as_ptr() &&
self.as_node().owner_doc().quirks_mode() == QuirksMode::Quirks
{
hints.push(TABLE_COLOR_RULE.clone());
@ -1848,6 +1844,11 @@ impl<'le> ::selectors::Element for GeckoElement<'le> {
None
}
fn set_selector_flags(&self, flags: ElementSelectorFlags) {
debug_assert!(!flags.is_empty());
self.set_flags(selector_flags_to_node_flags(flags));
}
fn attr_matches(
&self,
ns: &NamespaceConstraint<&Namespace>,
@ -1960,15 +1961,11 @@ impl<'le> ::selectors::Element for GeckoElement<'le> {
self.local_name() == other.local_name() && self.namespace() == other.namespace()
}
fn match_non_ts_pseudo_class<F>(
fn match_non_ts_pseudo_class(
&self,
pseudo_class: &NonTSPseudoClass,
context: &mut MatchingContext<Self::Impl>,
flags_setter: &mut F,
) -> bool
where
F: FnMut(&Self, ElementSelectorFlags),
{
) -> bool {
use selectors::matching::*;
match *pseudo_class {
NonTSPseudoClass::Autofill |
@ -2009,7 +2006,7 @@ impl<'le> ::selectors::Element for GeckoElement<'le> {
NonTSPseudoClass::MozDirAttrRTL |
NonTSPseudoClass::MozDirAttrLikeAuto |
NonTSPseudoClass::MozModalDialog |
NonTSPseudoClass::MozTopmostModalDialog |
NonTSPseudoClass::MozTopmostModal |
NonTSPseudoClass::Active |
NonTSPseudoClass::Hover |
NonTSPseudoClass::MozAutofillPreview |
@ -2024,7 +2021,9 @@ impl<'le> ::selectors::Element for GeckoElement<'le> {
self.is_link() && context.visited_handling().matches_visited()
},
NonTSPseudoClass::MozFirstNode => {
flags_setter(self, ElementSelectorFlags::HAS_EDGE_CHILD_SELECTOR);
if context.needs_selector_flags() {
self.apply_selector_flags(ElementSelectorFlags::HAS_EDGE_CHILD_SELECTOR);
}
let mut elem = self.as_node();
while let Some(prev) = elem.prev_sibling() {
if prev.contains_non_whitespace_content() {
@ -2035,7 +2034,9 @@ impl<'le> ::selectors::Element for GeckoElement<'le> {
true
},
NonTSPseudoClass::MozLastNode => {
flags_setter(self, ElementSelectorFlags::HAS_EDGE_CHILD_SELECTOR);
if context.needs_selector_flags() {
self.apply_selector_flags(ElementSelectorFlags::HAS_EDGE_CHILD_SELECTOR);
}
let mut elem = self.as_node();
while let Some(next) = elem.next_sibling() {
if next.contains_non_whitespace_content() {
@ -2046,7 +2047,9 @@ impl<'le> ::selectors::Element for GeckoElement<'le> {
true
},
NonTSPseudoClass::MozOnlyWhitespace => {
flags_setter(self, ElementSelectorFlags::HAS_EMPTY_SELECTOR);
if context.needs_selector_flags() {
self.apply_selector_flags(ElementSelectorFlags::HAS_EMPTY_SELECTOR);
}
if self
.as_node()
.dom_children()
@ -2068,8 +2071,6 @@ impl<'le> ::selectors::Element for GeckoElement<'le> {
NonTSPseudoClass::MozIsHTML => self.is_html_element_in_html_document(),
NonTSPseudoClass::MozLWTheme |
NonTSPseudoClass::MozLWThemeBrightText |
NonTSPseudoClass::MozLWThemeDarkText |
NonTSPseudoClass::MozLocaleDir(..) |
NonTSPseudoClass::MozWindowInactive => {
let state_bit = pseudo_class.document_state_flag();

View file

@ -12,11 +12,11 @@ use crate::shared_lock::SharedRwLock;
use crate::thread_state;
#[cfg(feature = "gecko")]
use gecko_profiler;
use parking_lot::{RwLock, RwLockReadGuard};
use parking_lot::{Mutex, RwLock, RwLockReadGuard};
use rayon;
use std::env;
use std::sync::atomic::{AtomicUsize, Ordering};
use std::sync::Mutex;
use std::io;
use std::thread;
/// Global style data
pub struct GlobalStyleData {
@ -43,12 +43,32 @@ fn thread_name(index: usize) -> String {
format!("Style#{}", index)
}
// A counter so that we can wait for shutdown of all threads. See
// StyleThreadPool::shutdown.
static ALIVE_WORKER_THREADS: AtomicUsize = AtomicUsize::new(0);
lazy_static! {
/// JoinHandles for spawned style threads. These will be joined during
/// StyleThreadPool::shutdown() after exiting the thread pool.
///
/// This would be quite inefficient if rayon destroyed and re-created
/// threads regularly during threadpool operation in response to demand,
/// however rayon actually never destroys its threads until the entire
/// thread pool is shut-down, so the size of this list is bounded.
static ref STYLE_THREAD_JOIN_HANDLES: Mutex<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) {
ALIVE_WORKER_THREADS.fetch_add(1, Ordering::Relaxed);
thread_state::initialize_layout_worker_thread();
#[cfg(feature = "gecko")]
unsafe {
@ -64,33 +84,24 @@ fn thread_shutdown(_: usize) {
gecko_profiler::unregister_thread();
bindings::Gecko_SetJemallocThreadLocalArena(false);
}
ALIVE_WORKER_THREADS.fetch_sub(1, Ordering::Relaxed);
}
impl StyleThreadPool {
/// Shuts down the thread pool, waiting for all work to complete.
pub fn shutdown() {
if ALIVE_WORKER_THREADS.load(Ordering::Relaxed) == 0 {
if STYLE_THREAD_JOIN_HANDLES.lock().is_empty() {
return;
}
{
// Drop the pool.
let _ = STYLE_THREAD_POOL.lock().unwrap().style_thread_pool.write().take();
}
// Spin until all our threads are done. This will usually be pretty
// fast, as on shutdown there should be basically no threads left
// running.
//
// This still _technically_ doesn't give us the guarantee of TLS
// destructors running on the worker threads. For that we'd need help
// from rayon to properly join the threads.
//
// See https://github.com/rayon-rs/rayon/issues/688
//
// So we instead intentionally leak TLS stuff (see BLOOM_KEY and co) for
// now until that's fixed.
while ALIVE_WORKER_THREADS.load(Ordering::Relaxed) != 0 {
std::thread::yield_now();
// Join spawned threads until all of the threads have been joined. This
// will usually be pretty fast, as on shutdown there should be basically
// no threads left running.
while let Some(join_handle) = STYLE_THREAD_JOIN_HANDLES.lock().pop() {
let _ = join_handle.join();
}
}
@ -105,7 +116,7 @@ impl StyleThreadPool {
lazy_static! {
/// Global thread pool
pub static ref STYLE_THREAD_POOL: Mutex<StyleThreadPool> = {
pub static ref STYLE_THREAD_POOL: std::sync::Mutex<StyleThreadPool> = {
let stylo_threads = env::var("STYLO_THREADS")
.map(|s| s.parse::<usize>().expect("invalid STYLO_THREADS value"));
let mut num_threads = match stylo_threads {
@ -149,6 +160,7 @@ lazy_static! {
None
} else {
let workers = rayon::ThreadPoolBuilder::new()
.spawn_handler(thread_spawn)
.num_threads(num_threads)
.thread_name(thread_name)
.start_handler(thread_startup)
@ -158,7 +170,7 @@ lazy_static! {
workers.ok()
};
Mutex::new(StyleThreadPool {
std::sync::Mutex::new(StyleThreadPool {
num_threads: if num_threads > 0 {
Some(num_threads)
} else {

View file

@ -11,7 +11,7 @@ use crate::invalidation::element::invalidator::{DescendantInvalidationLists, Inv
use crate::invalidation::element::invalidator::{Invalidation, InvalidationProcessor};
use crate::invalidation::element::state_and_attributes;
use crate::stylist::CascadeData;
use selectors::matching::{MatchingContext, MatchingMode, QuirksMode, VisitedHandlingMode};
use selectors::matching::{MatchingContext, MatchingMode, QuirksMode, VisitedHandlingMode, NeedsSelectorFlags};
/// A struct holding the members necessary to invalidate document state
/// selectors.
@ -47,6 +47,7 @@ impl<'a, E: TElement, I> DocumentStateInvalidationProcessor<'a, E, I> {
None,
VisitedHandlingMode::AllLinksVisitedAndUnvisited,
quirks_mode,
NeedsSelectorFlags::No,
);
matching_context.extra_data = InvalidationMatchingData {

View file

@ -166,15 +166,11 @@ where
{
type Impl = SelectorImpl;
fn match_non_ts_pseudo_class<F>(
fn match_non_ts_pseudo_class(
&self,
pseudo_class: &NonTSPseudoClass,
context: &mut MatchingContext<Self::Impl>,
_setter: &mut F,
) -> bool
where
F: FnMut(&Self, ElementSelectorFlags),
{
) -> bool {
// Some pseudo-classes need special handling to evaluate them against
// the snapshot.
match *pseudo_class {
@ -232,16 +228,20 @@ where
if flag.is_empty() {
return self
.element
.match_non_ts_pseudo_class(pseudo_class, context, &mut |_, _| {});
.match_non_ts_pseudo_class(pseudo_class, context);
}
match self.snapshot().and_then(|s| s.state()) {
Some(snapshot_state) => snapshot_state.intersects(flag),
None => self
.element
.match_non_ts_pseudo_class(pseudo_class, context, &mut |_, _| {}),
.match_non_ts_pseudo_class(pseudo_class, context),
}
}
fn set_selector_flags(&self, _flags: ElementSelectorFlags) {
debug_assert!(false, "Shouldn't need selector flags for invalidation");
}
fn match_pseudo_element(
&self,
pseudo_element: &PseudoElement,

View file

@ -67,6 +67,8 @@ pub struct Dependency {
pub parent: Option<Box<Dependency>>,
}
size_of_test!(Dependency, 24);
/// The kind of elements down the tree this dependency may affect.
#[derive(Debug, Eq, PartialEq)]
pub enum DependencyInvalidationKind {

View file

@ -19,8 +19,7 @@ use crate::selector_parser::Snapshot;
use crate::stylesheets::origin::OriginSet;
use crate::{Atom, WeakAtom};
use selectors::attr::CaseSensitivity;
use selectors::matching::matches_selector;
use selectors::matching::{MatchingContext, MatchingMode, VisitedHandlingMode};
use selectors::matching::{matches_selector, MatchingContext, MatchingMode, VisitedHandlingMode, NeedsSelectorFlags};
use selectors::NthIndexCache;
use smallvec::SmallVec;
@ -67,6 +66,7 @@ impl<'a, 'b: 'a, E: TElement + 'b> StateAndAttrInvalidationProcessor<'a, 'b, E>
Some(nth_index_cache),
VisitedHandlingMode::AllLinksVisitedAndUnvisited,
shared_context.quirks_mode(),
NeedsSelectorFlags::No,
);
Self {
@ -84,7 +84,7 @@ pub fn check_dependency<E, W>(
dependency: &Dependency,
element: &E,
wrapper: &W,
mut context: &mut MatchingContext<'_, E::Impl>,
context: &mut MatchingContext<'_, E::Impl>,
) -> bool
where
E: TElement,
@ -95,8 +95,7 @@ where
dependency.selector_offset,
None,
element,
&mut context,
&mut |_, _| {},
context,
);
let matched_then = matches_selector(
@ -104,8 +103,7 @@ where
dependency.selector_offset,
None,
wrapper,
&mut context,
&mut |_, _| {},
context,
);
matched_then != matches_now

View file

@ -556,6 +556,7 @@ impl StylesheetInvalidationSet {
FontFace(..) |
Keyframes(..) |
ScrollTimeline(..) |
Container(..) |
Style(..) => {
if is_generic_change {
// TODO(emilio): We need to do this for selector / keyframe
@ -610,7 +611,7 @@ impl StylesheetInvalidationSet {
}
},
Document(..) | Namespace(..) | Import(..) | Media(..) | Supports(..) |
LayerStatement(..) | LayerBlock(..) => {
Container(..) | LayerStatement(..) | LayerBlock(..) => {
// Do nothing, relevant nested rules are visited as part of the
// iteration.
},

View file

@ -63,6 +63,8 @@ pub use servo_arc;
#[macro_use]
extern crate servo_atoms;
#[macro_use]
extern crate static_assertions;
#[macro_use]
extern crate style_derive;
#[macro_use]
extern crate to_shmem_derive;
@ -101,10 +103,12 @@ pub mod invalidation;
#[allow(missing_docs)] // TODO.
pub mod logical_geometry;
pub mod matching;
#[macro_use]
pub mod media_queries;
pub mod parallel;
pub mod parser;
pub mod piecewise_linear;
#[macro_use]
pub mod queries;
pub mod rule_cache;
pub mod rule_collector;
pub mod rule_tree;
@ -184,7 +188,7 @@ pub mod gecko_properties {
}
macro_rules! reexport_computed_values {
( $( { $name: ident, $boxed: expr } )+ ) => {
( $( { $name: ident } )+ ) => {
/// Types for [computed values][computed].
///
/// [computed]: https://drafts.csswg.org/css-cascade/#computed
@ -198,7 +202,6 @@ macro_rules! reexport_computed_values {
}
}
longhand_properties_idents!(reexport_computed_values);
#[cfg(feature = "gecko")]
use crate::gecko_string_cache::WeakAtom;
#[cfg(feature = "servo")]

View file

@ -168,6 +168,11 @@ impl WritingMode {
flags
}
/// Returns the `horizontal-tb` value.
pub fn horizontal_tb() -> Self {
Self::from_bits_truncate(0)
}
#[inline]
pub fn is_vertical(&self) -> bool {
self.intersects(WritingMode::VERTICAL)
@ -872,10 +877,10 @@ impl<T> LogicalMargin<T> {
inline_start: T,
) -> LogicalMargin<T> {
LogicalMargin {
block_start: block_start,
inline_end: inline_end,
block_end: block_end,
inline_start: inline_start,
block_start,
inline_end,
block_end,
inline_start,
debug_writing_mode: DebugWritingMode::new(mode),
}
}
@ -1050,6 +1055,18 @@ impl<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]
pub fn to_physical(&self, mode: WritingMode) -> SideOffsets2D<T> {
self.debug_writing_mode.check(mode);
@ -1059,42 +1076,32 @@ impl<T: Copy> LogicalMargin<T> {
let left;
if mode.is_vertical() {
if mode.is_vertical_lr() {
left = self.block_start;
right = self.block_end;
left = self.block_start.clone();
right = self.block_end.clone();
} else {
right = self.block_start;
left = self.block_end;
right = self.block_start.clone();
left = self.block_end.clone();
}
if mode.is_inline_tb() {
top = self.inline_start;
bottom = self.inline_end;
top = self.inline_start.clone();
bottom = self.inline_end.clone();
} else {
bottom = self.inline_start;
top = self.inline_end;
bottom = self.inline_start.clone();
top = self.inline_end.clone();
}
} else {
top = self.block_start;
bottom = self.block_end;
top = self.block_start.clone();
bottom = self.block_end.clone();
if mode.is_bidi_ltr() {
left = self.inline_start;
right = self.inline_end;
left = self.inline_start.clone();
right = self.inline_end.clone();
} else {
right = self.inline_start;
left = self.inline_end;
right = self.inline_start.clone();
left = self.inline_end.clone();
}
}
SideOffsets2D::new(top, right, bottom, left)
}
#[inline]
pub fn convert(&self, mode_from: WritingMode, mode_to: WritingMode) -> LogicalMargin<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> {

View file

@ -128,3 +128,11 @@ macro_rules! local_name {
$crate::values::AtomIdent(atom!($s))
};
}
/// Asserts the size of a type at compile time.
macro_rules! size_of_test {
($t: ty, $expected_size: expr) => {
#[cfg(target_pointer_width = "64")]
const_assert_eq!(std::mem::size_of::<$t>(), $expected_size);
};
}

View file

@ -8,7 +8,7 @@
#![deny(missing_docs)]
use crate::computed_value_flags::ComputedValueFlags;
use crate::context::{CascadeInputs, ElementCascadeInputs, QuirksMode, SelectorFlagsMap};
use crate::context::{CascadeInputs, ElementCascadeInputs, QuirksMode};
use crate::context::{SharedStyleContext, StyleContext};
use crate::data::{ElementData, ElementStyles};
use crate::dom::TElement;
@ -26,7 +26,6 @@ use crate::style_resolver::{PseudoElementResolution, StyleResolverForElement};
use crate::stylesheets::layer_rule::LayerOrder;
use crate::stylist::RuleInclusion;
use crate::traversal_flags::TraversalFlags;
use selectors::matching::ElementSelectorFlags;
use servo_arc::{Arc, ArcBorrow};
/// Represents the result of comparing an element's old and new style.
@ -255,8 +254,8 @@ trait PrivateMatchMethods: TElement {
new_style: &ComputedValues,
pseudo_element: Option<PseudoElement>,
) -> bool {
let new_box_style = new_style.get_box();
let new_style_specifies_animations = new_box_style.specifies_animations();
let new_ui_style = new_style.get_ui();
let new_style_specifies_animations = new_ui_style.specifies_animations();
let has_animations = self.has_css_animations(&context.shared, pseudo_element);
if !new_style_specifies_animations && !has_animations {
@ -283,7 +282,7 @@ trait PrivateMatchMethods: TElement {
},
};
let old_box_style = old_style.get_box();
let old_ui_style = old_style.get_ui();
let keyframes_or_timeline_could_have_changed = context
.shared
@ -302,12 +301,12 @@ trait PrivateMatchMethods: TElement {
}
// If the animations changed, well...
if !old_box_style.animations_equals(new_box_style) {
if !old_ui_style.animations_equals(new_ui_style) {
return true;
}
let old_display = old_box_style.clone_display();
let new_display = new_box_style.clone_display();
let old_display = old_style.clone_display();
let new_display = new_style.clone_display();
// If we were display: none, we may need to trigger animations.
if old_display == Display::None && new_display != Display::None {
@ -342,14 +341,13 @@ trait PrivateMatchMethods: TElement {
None => return false,
};
let new_box_style = new_style.get_box();
if !self.has_css_transitions(context.shared, pseudo_element) &&
!new_box_style.specifies_transitions()
!new_style.get_ui().specifies_transitions()
{
return false;
}
if new_box_style.clone_display().is_none() || old_style.clone_display().is_none() {
if new_style.clone_display().is_none() || old_style.clone_display().is_none() {
return false;
}
@ -766,8 +764,8 @@ trait PrivateMatchMethods: TElement {
},
}
let old_display = old_values.get_box().clone_display();
let new_display = new_values.get_box().clone_display();
let old_display = old_values.clone_display();
let new_display = new_values.clone_display();
if old_display != new_display {
// If we used to be a display: none element, and no longer are, our
@ -1007,51 +1005,6 @@ pub trait MatchMethods: TElement {
cascade_requirement
}
/// Applies selector flags to an element, deferring mutations of the parent
/// until after the traversal.
///
/// TODO(emilio): This is somewhat inefficient, because it doesn't take
/// advantage of us knowing that the traversal is sequential.
fn apply_selector_flags(
&self,
map: &mut SelectorFlagsMap<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
/// the rule tree.
///

View file

@ -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 its
// 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()))
},
})
}
}

View file

@ -10,6 +10,7 @@ use super::{Device, MediaQuery, Qualifier};
use crate::context::QuirksMode;
use crate::error_reporting::ContextualParseError;
use crate::parser::ParserContext;
use crate::values::computed;
use cssparser::{Delimiter, Parser};
use cssparser::{ParserInput, Token};
@ -74,15 +75,17 @@ impl MediaList {
pub fn evaluate(&self, device: &Device, quirks_mode: QuirksMode) -> bool {
// Check if it is an empty media query list or any queries match.
// https://drafts.csswg.org/mediaqueries-4/#mq-list
self.media_queries.is_empty() ||
if self.media_queries.is_empty() {
return true;
}
computed::Context::for_media_query_evaluation(device, quirks_mode, |context| {
self.media_queries.iter().any(|mq| {
let media_match = mq.media_type.matches(device.media_type());
// Check if the media condition match.
let query_match = media_match &&
mq.condition
.as_ref()
.map_or(true, |c| c.matches(device, quirks_mode));
mq.condition.as_ref().map_or(true, |c| c.matches(context));
// Apply the logical NOT qualifier to the result
match mq.qualifier {
@ -90,6 +93,7 @@ impl MediaList {
_ => query_match,
}
})
})
}
/// Whether this `MediaList` contains no media queries.

View file

@ -6,7 +6,7 @@
//!
//! https://drafts.csswg.org/mediaqueries/#typedef-media-query
use super::media_condition::MediaCondition;
use crate::queries::{QueryCondition, FeatureType};
use crate::parser::ParserContext;
use crate::str::string_as_ascii_lowercase;
use crate::values::CustomIdent;
@ -66,7 +66,7 @@ pub struct MediaQuery {
pub media_type: MediaQueryType,
/// The condition that this media query contains. This cannot have `or`
/// in the first level.
pub condition: Option<MediaCondition>,
pub condition: Option<QueryCondition>,
}
impl ToCss for MediaQuery {
@ -134,9 +134,9 @@ impl MediaQuery {
.unwrap_or_default();
let condition = if explicit_media_type.is_none() {
Some(MediaCondition::parse(context, input)?)
Some(QueryCondition::parse(context, input, FeatureType::Media)?)
} else if input.try_parse(|i| i.expect_ident_matching("and")).is_ok() {
Some(MediaCondition::parse_disallow_or(context, input)?)
Some(QueryCondition::parse_disallow_or(context, input, FeatureType::Media)?)
} else {
None
};

View file

@ -6,15 +6,9 @@
//!
//! [mq]: https://drafts.csswg.org/mediaqueries/
mod media_condition;
mod media_list;
mod media_query;
#[macro_use]
pub mod media_feature;
pub mod media_feature_expression;
pub use self::media_condition::MediaCondition;
pub use self::media_feature_expression::MediaFeatureExpression;
pub use self::media_list::MediaList;
pub use self::media_query::{MediaQuery, MediaQueryType, MediaType, Qualifier};

View 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(),
}
}
}

View file

@ -28,34 +28,8 @@ use smallvec::SmallVec;
use std::borrow::Cow;
use std::cell::RefCell;
/// We split the cascade in two phases: 'early' properties, and 'late'
/// properties.
///
/// Early properties are the ones that don't have dependencies _and_ other
/// properties depend on, for example, writing-mode related properties, color
/// (for currentColor), or font-size (for em, etc).
///
/// Late properties are all the others.
trait CascadePhase {
fn is_early() -> bool;
}
struct EarlyProperties;
impl CascadePhase for EarlyProperties {
fn is_early() -> bool {
true
}
}
struct LateProperties;
impl CascadePhase for LateProperties {
fn is_early() -> bool {
false
}
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
enum ApplyResetProperties {
enum CanHaveLogicalProperties {
No,
Yes,
}
@ -283,6 +257,7 @@ where
let inherited_style = parent_style.unwrap_or(device.default_computed_values());
let mut declarations = SmallVec::<[(&_, CascadePriority); 32]>::new();
let mut referenced_properties = LonghandIdSet::default();
let custom_properties = {
let mut builder = CustomPropertiesBuilder::new(inherited_style.custom_properties(), device);
@ -290,6 +265,8 @@ where
declarations.push((declaration, priority));
if let PropertyDeclaration::Custom(ref declaration) = *declaration {
builder.cascade(declaration, priority);
} else {
referenced_properties.insert(declaration.id().as_longhand().unwrap());
}
}
@ -315,46 +292,74 @@ where
in_media_query: false,
for_smil_animation: false,
for_non_inherited_property: None,
container_info: None,
quirks_mode,
rule_cache_conditions: RefCell::new(rule_cache_conditions),
};
let using_cached_reset_properties = {
let mut cascade = Cascade::new(&mut context, cascade_mode);
let mut shorthand_cache = ShorthandsWithPropertyReferencesCache::default();
let using_cached_reset_properties;
let mut cascade = Cascade::new(&mut context, cascade_mode, &referenced_properties);
let mut shorthand_cache = ShorthandsWithPropertyReferencesCache::default();
cascade.apply_properties::<EarlyProperties, _>(
ApplyResetProperties::Yes,
declarations.iter().cloned(),
&mut shorthand_cache,
);
let properties_to_apply = match cascade.cascade_mode {
CascadeMode::Visited { writing_mode } => {
cascade.context.builder.writing_mode = writing_mode;
// We never insert visited styles into the cache so we don't need to
// try looking it up. It also wouldn't be super-profitable, only a
// handful reset properties are non-inherited.
using_cached_reset_properties = false;
LonghandIdSet::visited_dependent()
},
CascadeMode::Unvisited { visited_rules } => {
if cascade.apply_properties(
CanHaveLogicalProperties::No,
LonghandIdSet::writing_mode_group(),
declarations.iter().cloned(),
&mut shorthand_cache,
) {
cascade.compute_writing_mode();
}
cascade.compute_visited_style_if_needed(
element,
parent_style,
parent_style_ignoring_first_line,
layout_parent_style,
guards,
);
if cascade.apply_properties(
CanHaveLogicalProperties::No,
LonghandIdSet::fonts_and_color_group(),
declarations.iter().cloned(),
&mut shorthand_cache,
) {
cascade.fixup_font_stuff();
}
let using_cached_reset_properties =
cascade.try_to_use_cached_reset_properties(rule_cache, guards);
if let Some(visited_rules) = visited_rules {
cascade.compute_visited_style_if_needed(
element,
parent_style,
parent_style_ignoring_first_line,
layout_parent_style,
visited_rules,
guards,
);
}
let apply_reset = if using_cached_reset_properties {
ApplyResetProperties::No
} else {
ApplyResetProperties::Yes
};
using_cached_reset_properties =
cascade.try_to_use_cached_reset_properties(rule_cache, guards);
cascade.apply_properties::<LateProperties, _>(
apply_reset,
declarations.iter().cloned(),
&mut shorthand_cache,
);
using_cached_reset_properties
if using_cached_reset_properties {
LonghandIdSet::late_group_only_inherited()
} else {
LonghandIdSet::late_group()
}
}
};
cascade.apply_properties(
CanHaveLogicalProperties::Yes,
properties_to_apply,
declarations.iter().cloned(),
&mut shorthand_cache,
);
cascade.finished_applying_properties();
context.builder.clear_modified_reset();
if matches!(cascade_mode, CascadeMode::Unvisited { .. }) {
@ -413,7 +418,7 @@ fn tweak_when_ignoring_colors(
fn alpha_channel(color: &Color, context: &computed::Context) -> u8 {
// We assume here currentColor is opaque.
let color = color.to_computed_value(context).to_rgba(RGBA::new(0, 0, 0, 255));
let color = color.to_computed_value(context).into_rgba(RGBA::new(0, 0, 0, 255));
color.alpha
}
@ -428,14 +433,17 @@ fn tweak_when_ignoring_colors(
// otherwise, this is needed to preserve semi-transparent
// backgrounds.
//
// NOTE(emilio): We revert even for alpha == 0. Not doing so would
// be a bit special casey, even though it causes issues like
// bug 1625036. The reasoning is that the conditions that trigger
// that (having mismatched widget and default backgrounds) are both
// uncommon, and broken in other applications as well, and not
// honoring transparent makes stuff uglier or break unconditionally
// NOTE(emilio): We honor transparent unconditionally, like we do
// for color, even though it causes issues like bug 1625036. The
// reasoning is that the conditions that trigger that (having
// mismatched widget and default backgrounds) are both uncommon, and
// broken in other applications as well, and not honoring
// transparent makes stuff uglier or break unconditionally
// (bug 1666059, bug 1755713).
let alpha = alpha_channel(color, context);
if alpha == 0 {
return;
}
let mut color = context.builder.device.default_background_color();
color.alpha = alpha;
declarations_to_apply_unless_overriden
@ -497,6 +505,8 @@ fn tweak_when_ignoring_colors(
struct Cascade<'a, 'b: 'a> {
context: &'a mut computed::Context<'b>,
cascade_mode: CascadeMode<'a>,
/// All the properties that have a declaration in the cascade.
referenced: &'a LonghandIdSet,
seen: LonghandIdSet,
author_specified: LonghandIdSet,
reverted_set: LonghandIdSet,
@ -504,10 +514,15 @@ struct Cascade<'a, 'b: 'a> {
}
impl<'a, 'b: 'a> Cascade<'a, 'b> {
fn new(context: &'a mut computed::Context<'b>, cascade_mode: CascadeMode<'a>) -> Self {
fn new(
context: &'a mut computed::Context<'b>,
cascade_mode: CascadeMode<'a>,
referenced: &'a LonghandIdSet,
) -> Self {
Self {
context,
cascade_mode,
referenced,
seen: LonghandIdSet::default(),
author_specified: LonghandIdSet::default(),
reverted_set: Default::default(),
@ -575,23 +590,21 @@ impl<'a, 'b: 'a> Cascade<'a, 'b> {
(CASCADE_PROPERTY[discriminant])(declaration, &mut self.context);
}
fn apply_properties<'decls, Phase, I>(
fn apply_properties<'decls, I>(
&mut self,
apply_reset: ApplyResetProperties,
can_have_logical_properties: CanHaveLogicalProperties,
properties_to_apply: &'a LonghandIdSet,
declarations: I,
mut shorthand_cache: &mut ShorthandsWithPropertyReferencesCache,
) where
Phase: CascadePhase,
) -> bool
where
I: Iterator<Item = (&'decls PropertyDeclaration, CascadePriority)>,
{
let apply_reset = apply_reset == ApplyResetProperties::Yes;
if !self.referenced.contains_any(properties_to_apply) {
return false;
}
debug_assert!(
!Phase::is_early() || apply_reset,
"Should always apply reset properties in the early phase, since we \
need to know font-size / writing-mode to decide whether to use the \
cached reset properties"
);
let can_have_logical_properties = can_have_logical_properties == CanHaveLogicalProperties::Yes;
let ignore_colors = !self.context.builder.device.use_document_colors();
let mut declarations_to_apply_unless_overriden = DeclarationsToApplyUnlessOverriden::new();
@ -605,20 +618,15 @@ impl<'a, 'b: 'a> Cascade<'a, 'b> {
PropertyDeclarationId::Custom(..) => continue,
};
let inherited = longhand_id.inherited();
if !apply_reset && !inherited {
if !properties_to_apply.contains(longhand_id) {
continue;
}
if Phase::is_early() != longhand_id.is_early_property() {
continue;
}
debug_assert!(!Phase::is_early() || !longhand_id.is_logical());
let physical_longhand_id = if Phase::is_early() {
longhand_id
} else {
debug_assert!(can_have_logical_properties || !longhand_id.is_logical());
let physical_longhand_id = if can_have_logical_properties {
longhand_id.to_physical(self.context.builder.writing_mode)
} else {
longhand_id
};
if self.seen.contains(physical_longhand_id) {
@ -633,15 +641,6 @@ impl<'a, 'b: 'a> Cascade<'a, 'b> {
}
}
// Only a few properties are allowed to depend on the visited state
// of links. When cascading visited styles, we can save time by
// only processing these properties.
if matches!(self.cascade_mode, CascadeMode::Visited { .. }) &&
!physical_longhand_id.is_visited_dependent()
{
continue;
}
let mut declaration =
self.substitute_variables_if_needed(declaration, &mut shorthand_cache);
@ -675,8 +674,8 @@ impl<'a, 'b: 'a> Cascade<'a, 'b> {
continue;
},
CSSWideKeyword::Unset => true,
CSSWideKeyword::Inherit => inherited,
CSSWideKeyword::Initial => !inherited,
CSSWideKeyword::Inherit => longhand_id.inherited(),
CSSWideKeyword::Initial => !longhand_id.inherited(),
},
None => false,
};
@ -710,22 +709,13 @@ impl<'a, 'b: 'a> Cascade<'a, 'b> {
}
}
if Phase::is_early() {
self.fixup_font_stuff();
self.compute_writing_mode();
} else {
self.finished_applying_properties();
}
true
}
fn compute_writing_mode(&mut self) {
let writing_mode = match self.cascade_mode {
CascadeMode::Unvisited { .. } => {
WritingMode::new(self.context.builder.get_inherited_box())
},
CascadeMode::Visited { writing_mode } => writing_mode,
};
self.context.builder.writing_mode = writing_mode;
debug_assert!(matches!(self.cascade_mode, CascadeMode::Unvisited { .. }));
self.context.builder.writing_mode =
WritingMode::new(self.context.builder.get_inherited_box())
}
fn compute_visited_style_if_needed<E>(
@ -734,20 +724,12 @@ impl<'a, 'b: 'a> Cascade<'a, 'b> {
parent_style: Option<&ComputedValues>,
parent_style_ignoring_first_line: Option<&ComputedValues>,
layout_parent_style: Option<&ComputedValues>,
visited_rules: &StrongRuleNode,
guards: &StylesheetGuards,
) where
E: TElement,
{
let visited_rules = match self.cascade_mode {
CascadeMode::Unvisited { visited_rules } => visited_rules,
CascadeMode::Visited { .. } => return,
};
let visited_rules = match visited_rules {
Some(rules) => rules,
None => return,
};
debug_assert!(matches!(self.cascade_mode, CascadeMode::Unvisited { .. }));
let is_link = self.context.builder.pseudo.is_none() && element.unwrap().is_link();
macro_rules! visited_parent {

View file

@ -73,7 +73,6 @@ COUNTED_UNKNOWN_PROPERTIES = [
"-webkit-perspective-origin-y",
"-webkit-margin-before-collapse",
"-webkit-border-before-style",
"scroll-snap-stop",
"-webkit-margin-bottom-collapse",
"-webkit-ruby-position",
"-webkit-column-break-after",

View file

@ -444,6 +444,7 @@ class Longhand(Property):
"ColumnCount",
"Contain",
"ContentVisibility",
"ContainerType",
"Display",
"FillRule",
"Float",
@ -491,6 +492,7 @@ class Longhand(Property):
"ScrollbarGutter",
"ScrollSnapAlign",
"ScrollSnapAxis",
"ScrollSnapStop",
"ScrollSnapStrictness",
"ScrollSnapType",
"TextAlign",

View file

@ -755,7 +755,6 @@ fn static_assert() {
<%self:impl_trait style_struct_name="Margin"
skip_longhands="${skip_margin_longhands}
${skip_scroll_margin_longhands}">
% for side in SIDES:
<% impl_split_style_coord("margin_%s" % side.ident,
"mMargin",
@ -1181,11 +1180,7 @@ fn static_assert() {
</%def>
<% skip_box_longhands= """display
animation-name animation-delay animation-duration
animation-direction animation-fill-mode animation-play-state
animation-iteration-count animation-timeline animation-timing-function
clear transition-duration transition-delay
transition-timing-function transition-property
clear
-webkit-line-clamp""" %>
<%self:impl_trait style_struct_name="Box" skip_longhands="${skip_box_longhands}">
#[inline]
@ -1227,245 +1222,6 @@ fn static_assert() {
) %>
${impl_keyword('clear', 'mBreakType', clear_keyword)}
${impl_transition_time_value('delay', 'Delay')}
${impl_transition_time_value('duration', 'Duration')}
${impl_animation_or_transition_timing_function('transition')}
pub fn transition_combined_duration_at(&self, index: usize) -> f32 {
// https://drafts.csswg.org/css-transitions/#transition-combined-duration
self.gecko.mTransitions[index % self.gecko.mTransitionDurationCount as usize].mDuration.max(0.0)
+ self.gecko.mTransitions[index % self.gecko.mTransitionDelayCount as usize].mDelay
}
pub fn set_transition_property<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)]
pub fn set__webkit_line_clamp(&mut self, v: longhands::_webkit_line_clamp::computed_value::T) {
self.gecko.mLineClamp = match v {
@ -2020,7 +1776,247 @@ mask-mode mask-repeat mask-clip mask-origin mask-composite mask-position-x mask-
}
</%self:impl_trait>
<%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 style_struct_name="XUL">

View file

@ -799,7 +799,7 @@ impl<'a> TransitionPropertyIterator<'a> {
pub fn from_style(style: &'a ComputedValues) -> Self {
Self {
style,
index_range: 0..style.get_box().transition_property_count(),
index_range: 0..style.get_ui().transition_property_count(),
longhand_iterator: None,
}
}
@ -832,7 +832,7 @@ impl<'a> Iterator for TransitionPropertyIterator<'a> {
}
let index = self.index_range.next()?;
match self.style.get_box().transition_property_at(index) {
match self.style.get_ui().transition_property_at(index) {
TransitionProperty::Longhand(longhand_id) => {
return Some(TransitionPropertyIteration {
longhand_id,

View file

@ -3,7 +3,7 @@
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
<%namespace name="helpers" file="/helpers.mako.rs" />
<% from data import ALL_AXES, DEFAULT_RULES_EXCEPT_KEYFRAME, Keyword, Method, to_rust_ident, to_camel_case%>
<% from data import ALL_AXES, Keyword, Method, to_rust_ident, to_camel_case%>
<% data.new_style_struct("Box",
inherited=False,
@ -150,193 +150,6 @@ ${helpers.predefined_type(
animation_value_type="discrete",
)}
<% transition_extra_prefixes = "moz:layout.css.prefixes.transitions webkit" %>
${helpers.predefined_type(
"transition-duration",
"Time",
"computed::Time::zero()",
engines="gecko servo",
initial_specified_value="specified::Time::zero()",
parse_method="parse_non_negative",
vector=True,
need_index=True,
animation_value_type="none",
extra_prefixes=transition_extra_prefixes,
spec="https://drafts.csswg.org/css-transitions/#propdef-transition-duration",
)}
${helpers.predefined_type(
"transition-timing-function",
"TimingFunction",
"computed::TimingFunction::ease()",
engines="gecko servo",
initial_specified_value="specified::TimingFunction::ease()",
vector=True,
need_index=True,
animation_value_type="none",
extra_prefixes=transition_extra_prefixes,
spec="https://drafts.csswg.org/css-transitions/#propdef-transition-timing-function",
)}
${helpers.predefined_type(
"transition-property",
"TransitionProperty",
"computed::TransitionProperty::all()",
engines="gecko servo",
initial_specified_value="specified::TransitionProperty::all()",
vector=True,
allow_empty="NotInitial",
need_index=True,
animation_value_type="none",
extra_prefixes=transition_extra_prefixes,
spec="https://drafts.csswg.org/css-transitions/#propdef-transition-property",
)}
${helpers.predefined_type(
"transition-delay",
"Time",
"computed::Time::zero()",
engines="gecko servo",
initial_specified_value="specified::Time::zero()",
vector=True,
need_index=True,
animation_value_type="none",
extra_prefixes=transition_extra_prefixes,
spec="https://drafts.csswg.org/css-transitions/#propdef-transition-delay",
)}
<% animation_extra_prefixes = "moz:layout.css.prefixes.animations webkit" %>
${helpers.predefined_type(
"animation-name",
"AnimationName",
"computed::AnimationName::none()",
engines="gecko servo",
initial_specified_value="specified::AnimationName::none()",
vector=True,
need_index=True,
animation_value_type="none",
extra_prefixes=animation_extra_prefixes,
rule_types_allowed=DEFAULT_RULES_EXCEPT_KEYFRAME,
spec="https://drafts.csswg.org/css-animations/#propdef-animation-name",
)}
${helpers.predefined_type(
"animation-duration",
"Time",
"computed::Time::zero()",
engines="gecko servo",
initial_specified_value="specified::Time::zero()",
parse_method="parse_non_negative",
vector=True,
need_index=True,
animation_value_type="none",
extra_prefixes=animation_extra_prefixes,
spec="https://drafts.csswg.org/css-transitions/#propdef-transition-duration",
)}
// animation-timing-function is the exception to the rule for allowed_in_keyframe_block:
// https://drafts.csswg.org/css-animations/#keyframes
${helpers.predefined_type(
"animation-timing-function",
"TimingFunction",
"computed::TimingFunction::ease()",
engines="gecko servo",
initial_specified_value="specified::TimingFunction::ease()",
vector=True,
need_index=True,
animation_value_type="none",
extra_prefixes=animation_extra_prefixes,
spec="https://drafts.csswg.org/css-transitions/#propdef-animation-timing-function",
)}
${helpers.predefined_type(
"animation-iteration-count",
"AnimationIterationCount",
"computed::AnimationIterationCount::one()",
engines="gecko servo",
initial_specified_value="specified::AnimationIterationCount::one()",
vector=True,
need_index=True,
animation_value_type="none",
extra_prefixes=animation_extra_prefixes,
rule_types_allowed=DEFAULT_RULES_EXCEPT_KEYFRAME,
spec="https://drafts.csswg.org/css-animations/#propdef-animation-iteration-count",
)}
<% animation_direction_custom_consts = { "alternate-reverse": "Alternate_reverse" } %>
${helpers.single_keyword(
"animation-direction",
"normal reverse alternate alternate-reverse",
engines="gecko servo",
need_index=True,
animation_value_type="none",
vector=True,
gecko_enum_prefix="PlaybackDirection",
custom_consts=animation_direction_custom_consts,
extra_prefixes=animation_extra_prefixes,
gecko_inexhaustive=True,
spec="https://drafts.csswg.org/css-animations/#propdef-animation-direction",
rule_types_allowed=DEFAULT_RULES_EXCEPT_KEYFRAME,
)}
${helpers.single_keyword(
"animation-play-state",
"running paused",
engines="gecko servo",
need_index=True,
animation_value_type="none",
vector=True,
extra_prefixes=animation_extra_prefixes,
gecko_enum_prefix="StyleAnimationPlayState",
spec="https://drafts.csswg.org/css-animations/#propdef-animation-play-state",
rule_types_allowed=DEFAULT_RULES_EXCEPT_KEYFRAME,
)}
${helpers.single_keyword(
"animation-fill-mode",
"none forwards backwards both",
engines="gecko servo",
need_index=True,
animation_value_type="none",
vector=True,
gecko_enum_prefix="FillMode",
extra_prefixes=animation_extra_prefixes,
gecko_inexhaustive=True,
spec="https://drafts.csswg.org/css-animations/#propdef-animation-fill-mode",
rule_types_allowed=DEFAULT_RULES_EXCEPT_KEYFRAME,
)}
${helpers.predefined_type(
"animation-delay",
"Time",
"computed::Time::zero()",
engines="gecko servo",
initial_specified_value="specified::Time::zero()",
vector=True,
need_index=True,
animation_value_type="none",
extra_prefixes=animation_extra_prefixes,
spec="https://drafts.csswg.org/css-animations/#propdef-animation-delay",
rule_types_allowed=DEFAULT_RULES_EXCEPT_KEYFRAME,
)}
${helpers.predefined_type(
"animation-timeline",
"AnimationTimeline",
"computed::AnimationTimeline::auto()",
engines="gecko servo",
servo_pref="layout.unimplemented",
initial_specified_value="specified::AnimationTimeline::auto()",
vector=True,
need_index=True,
animation_value_type="none",
gecko_pref="layout.css.scroll-linked-animations.enabled",
spec="https://drafts.csswg.org/css-animations-2/#propdef-animation-timeline",
rule_types_allowed=DEFAULT_RULES_EXCEPT_KEYFRAME,
)}
<% transform_extra_prefixes = "moz:layout.css.prefixes.transforms webkit" %>
${helpers.predefined_type(
@ -472,6 +285,15 @@ ${helpers.predefined_type(
animation_value_type="discrete",
)}
${helpers.predefined_type(
"scroll-snap-stop",
"ScrollSnapStop",
"computed::ScrollSnapStop::Normal",
engines="gecko",
spec="https://drafts.csswg.org/css-scroll-snap-1/#scroll-snap-stop",
animation_value_type="discrete",
)}
% for (axis, logical) in ALL_AXES:
${helpers.predefined_type(
"overscroll-behavior-" + axis,
@ -623,6 +445,28 @@ ${helpers.predefined_type(
animation_value_type="none",
)}
${helpers.predefined_type(
"container-type",
"ContainerType",
"computed::ContainerType::NONE",
engines="gecko servo",
animation_value_type="none",
gecko_pref="layout.css.container-queries.enabled",
servo_pref="layout.container-queries.enabled",
spec="https://drafts.csswg.org/css-contain-3/#container-type",
)}
${helpers.predefined_type(
"container-name",
"ContainerName",
"computed::ContainerName::none()",
engines="gecko servo",
animation_value_type="none",
gecko_pref="layout.css.container-queries.enabled",
servo_pref="layout.container-queries.enabled",
spec="https://drafts.csswg.org/css-contain-3/#container-name",
)}
${helpers.predefined_type(
"appearance",
"Appearance",

View file

@ -28,6 +28,16 @@
)}
% endfor
${helpers.predefined_type(
"overflow-clip-margin",
"Length",
"computed::Length::zero()",
parse_method="parse_non_negative",
engines="gecko",
spec="https://drafts.csswg.org/css-overflow/#propdef-overflow-clip-margin",
animation_value_type="ComputedValue",
)}
% for side in ALL_SIDES:
${helpers.predefined_type(
"scroll-margin-%s" % side[0],

View file

@ -20,7 +20,7 @@ ${helpers.single_keyword(
${helpers.predefined_type(
"stop-color",
"Color",
"RGBA::new(0, 0, 0, 255).into()",
"computed::Color::black()",
engines="gecko",
animation_value_type="AnimatedRGBA",
spec="https://www.w3.org/TR/SVGTiny12/painting.html#StopColorProperty",
@ -40,7 +40,7 @@ ${helpers.predefined_type(
${helpers.predefined_type(
"flood-color",
"Color",
"RGBA::new(0, 0, 0, 255).into()",
"computed::Color::black()",
engines="gecko",
animation_value_type="AnimatedColor",
spec="https://www.w3.org/TR/SVG/filters.html#FloodColorProperty",
@ -58,7 +58,7 @@ ${helpers.predefined_type(
${helpers.predefined_type(
"lighting-color",
"Color",
"RGBA::new(255, 255, 255, 255).into()",
"computed::Color::white()",
engines="gecko",
animation_value_type="AnimatedColor",
spec="https://www.w3.org/TR/SVG/filters.html#LightingColorProperty",

View file

@ -3,7 +3,7 @@
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
<%namespace name="helpers" file="/helpers.mako.rs" />
<% from data import Method %>
<% from data import DEFAULT_RULES_EXCEPT_KEYFRAME, Method %>
// CSS Basic User Interface Module Level 1
// https://drafts.csswg.org/css-ui-3/
@ -51,12 +51,15 @@ ${helpers.single_keyword(
spec="None (Nonstandard Firefox-only property)",
)}
// TODO(emilio): Maybe make shadow behavior on macOS match Linux / Windows, and remove this
// property.
${helpers.single_keyword(
"-moz-window-shadow",
"default none menu tooltip sheet cliprounded",
"default none",
engines="gecko",
gecko_ffi_name="mWindowShadow",
gecko_enum_prefix="StyleWindowShadow",
gecko_inexhaustive=True,
animation_value_type="discrete",
enabled_in="chrome",
spec="None (Nonstandard internal property)",
@ -95,6 +98,16 @@ ${helpers.predefined_type(
enabled_in="chrome",
)}
${helpers.predefined_type(
"-moz-window-input-region-margin",
"Length",
"computed::Length::zero()",
engines="gecko",
animation_value_type="ComputedValue",
spec="None (Nonstandard internal property)",
enabled_in="chrome",
)}
// TODO(emilio): Probably also should be hidden from content.
${helpers.predefined_type(
"-moz-force-broken-image-icon",
@ -104,3 +117,190 @@ ${helpers.predefined_type(
animation_value_type="discrete",
spec="None (Nonstandard Firefox-only property)",
)}
<% transition_extra_prefixes = "moz:layout.css.prefixes.transitions webkit" %>
${helpers.predefined_type(
"transition-duration",
"Time",
"computed::Time::zero()",
engines="gecko servo",
initial_specified_value="specified::Time::zero()",
parse_method="parse_non_negative",
vector=True,
need_index=True,
animation_value_type="none",
extra_prefixes=transition_extra_prefixes,
spec="https://drafts.csswg.org/css-transitions/#propdef-transition-duration",
)}
${helpers.predefined_type(
"transition-timing-function",
"TimingFunction",
"computed::TimingFunction::ease()",
engines="gecko servo",
initial_specified_value="specified::TimingFunction::ease()",
vector=True,
need_index=True,
animation_value_type="none",
extra_prefixes=transition_extra_prefixes,
spec="https://drafts.csswg.org/css-transitions/#propdef-transition-timing-function",
)}
${helpers.predefined_type(
"transition-property",
"TransitionProperty",
"computed::TransitionProperty::all()",
engines="gecko servo",
initial_specified_value="specified::TransitionProperty::all()",
vector=True,
allow_empty="NotInitial",
need_index=True,
animation_value_type="none",
extra_prefixes=transition_extra_prefixes,
spec="https://drafts.csswg.org/css-transitions/#propdef-transition-property",
)}
${helpers.predefined_type(
"transition-delay",
"Time",
"computed::Time::zero()",
engines="gecko servo",
initial_specified_value="specified::Time::zero()",
vector=True,
need_index=True,
animation_value_type="none",
extra_prefixes=transition_extra_prefixes,
spec="https://drafts.csswg.org/css-transitions/#propdef-transition-delay",
)}
<% animation_extra_prefixes = "moz:layout.css.prefixes.animations webkit" %>
${helpers.predefined_type(
"animation-name",
"AnimationName",
"computed::AnimationName::none()",
engines="gecko servo",
initial_specified_value="specified::AnimationName::none()",
vector=True,
need_index=True,
animation_value_type="none",
extra_prefixes=animation_extra_prefixes,
rule_types_allowed=DEFAULT_RULES_EXCEPT_KEYFRAME,
spec="https://drafts.csswg.org/css-animations/#propdef-animation-name",
)}
${helpers.predefined_type(
"animation-duration",
"Time",
"computed::Time::zero()",
engines="gecko servo",
initial_specified_value="specified::Time::zero()",
parse_method="parse_non_negative",
vector=True,
need_index=True,
animation_value_type="none",
extra_prefixes=animation_extra_prefixes,
spec="https://drafts.csswg.org/css-transitions/#propdef-transition-duration",
)}
// animation-timing-function is the exception to the rule for allowed_in_keyframe_block:
// https://drafts.csswg.org/css-animations/#keyframes
${helpers.predefined_type(
"animation-timing-function",
"TimingFunction",
"computed::TimingFunction::ease()",
engines="gecko servo",
initial_specified_value="specified::TimingFunction::ease()",
vector=True,
need_index=True,
animation_value_type="none",
extra_prefixes=animation_extra_prefixes,
spec="https://drafts.csswg.org/css-transitions/#propdef-animation-timing-function",
)}
${helpers.predefined_type(
"animation-iteration-count",
"AnimationIterationCount",
"computed::AnimationIterationCount::one()",
engines="gecko servo",
initial_specified_value="specified::AnimationIterationCount::one()",
vector=True,
need_index=True,
animation_value_type="none",
extra_prefixes=animation_extra_prefixes,
rule_types_allowed=DEFAULT_RULES_EXCEPT_KEYFRAME,
spec="https://drafts.csswg.org/css-animations/#propdef-animation-iteration-count",
)}
<% animation_direction_custom_consts = { "alternate-reverse": "Alternate_reverse" } %>
${helpers.single_keyword(
"animation-direction",
"normal reverse alternate alternate-reverse",
engines="gecko servo",
need_index=True,
animation_value_type="none",
vector=True,
gecko_enum_prefix="PlaybackDirection",
custom_consts=animation_direction_custom_consts,
extra_prefixes=animation_extra_prefixes,
gecko_inexhaustive=True,
spec="https://drafts.csswg.org/css-animations/#propdef-animation-direction",
rule_types_allowed=DEFAULT_RULES_EXCEPT_KEYFRAME,
)}
${helpers.single_keyword(
"animation-play-state",
"running paused",
engines="gecko servo",
need_index=True,
animation_value_type="none",
vector=True,
extra_prefixes=animation_extra_prefixes,
gecko_enum_prefix="StyleAnimationPlayState",
spec="https://drafts.csswg.org/css-animations/#propdef-animation-play-state",
rule_types_allowed=DEFAULT_RULES_EXCEPT_KEYFRAME,
)}
${helpers.single_keyword(
"animation-fill-mode",
"none forwards backwards both",
engines="gecko servo",
need_index=True,
animation_value_type="none",
vector=True,
gecko_enum_prefix="FillMode",
extra_prefixes=animation_extra_prefixes,
gecko_inexhaustive=True,
spec="https://drafts.csswg.org/css-animations/#propdef-animation-fill-mode",
rule_types_allowed=DEFAULT_RULES_EXCEPT_KEYFRAME,
)}
${helpers.predefined_type(
"animation-delay",
"Time",
"computed::Time::zero()",
engines="gecko servo",
initial_specified_value="specified::Time::zero()",
vector=True,
need_index=True,
animation_value_type="none",
extra_prefixes=animation_extra_prefixes,
spec="https://drafts.csswg.org/css-animations/#propdef-animation-delay",
rule_types_allowed=DEFAULT_RULES_EXCEPT_KEYFRAME,
)}
${helpers.predefined_type(
"animation-timeline",
"AnimationTimeline",
"computed::AnimationTimeline::auto()",
engines="gecko servo",
servo_pref="layout.unimplemented",
initial_specified_value="specified::AnimationTimeline::auto()",
vector=True,
need_index=True,
animation_value_type="none",
gecko_pref="layout.css.scroll-linked-animations.enabled",
spec="https://drafts.csswg.org/css-animations-2/#propdef-animation-timeline",
rule_types_allowed=DEFAULT_RULES_EXCEPT_KEYFRAME,
)}

View file

@ -267,6 +267,9 @@ pub enum PropertyDeclaration {
% endfor
}
// There's one of these for each parsed declaration so it better be small.
size_of_test!(PropertyDeclaration, 32);
#[repr(C)]
struct PropertyDeclarationVariantRepr<T> {
tag: u16,
@ -474,9 +477,10 @@ impl NonCustomPropertyId {
self.0
}
/// Convert a `NonCustomPropertyId` into a `nsCSSPropertyID`.
#[cfg(feature = "gecko")]
#[inline]
fn to_nscsspropertyid(self) -> nsCSSPropertyID {
pub fn to_nscsspropertyid(self) -> nsCSSPropertyID {
// unsafe: guaranteed by static_assert_nscsspropertyid above.
unsafe { std::mem::transmute(self.0 as i32) }
}
@ -892,6 +896,72 @@ impl<'a> Iterator for LonghandIdSetIterator<'a> {
}
}
<%
CASCADE_GROUPS = {
# The writing-mode group has the most priority of all property groups, as
# sizes like font-size can depend on it.
"writing_mode": [
"writing-mode",
"direction",
"text-orientation",
],
# The fonts and colors group has the second priority, as all other lengths
# and colors depend on them.
#
# There are some interdependencies between these, but we fix them up in
# Cascade::fixup_font_stuff.
"fonts_and_color": [
# Needed to properly compute the zoomed font-size.
# FIXME(emilio): This could probably just be a cascade flag
# like IN_SVG_SUBTREE or such, and we could nuke this property.
"-x-text-zoom",
# Needed to do font-size computation in a language-dependent way.
"-x-lang",
# Needed for ruby to respect language-dependent min-font-size
# preferences properly, see bug 1165538.
"-moz-min-font-size-ratio",
# font-size depends on math-depth's computed value.
"math-depth",
# Needed to compute the first available font, in order to
# compute font-relative units correctly.
"font-size",
"font-weight",
"font-stretch",
"font-style",
"font-family",
# color-scheme affects how system colors resolve.
"color-scheme",
],
}
def in_late_group(p):
return p.name not in CASCADE_GROUPS["writing_mode"] and p.name not in CASCADE_GROUPS["fonts_and_color"]
def is_visited_dependent(p):
return p.name in [
"column-rule-color",
"text-emphasis-color",
"-webkit-text-fill-color",
"-webkit-text-stroke-color",
"text-decoration-color",
"fill",
"stroke",
"caret-color",
"background-color",
"border-top-color",
"border-right-color",
"border-bottom-color",
"border-left-color",
"border-block-start-color",
"border-inline-end-color",
"border-block-end-color",
"border-inline-start-color",
"outline-color",
"color",
]
%>
impl LonghandIdSet {
#[inline]
fn reset() -> &'static Self {
@ -920,7 +990,7 @@ impl LonghandIdSet {
/// Returns the set of longhands that are ignored when document colors are
/// disabled.
#[inline]
pub fn ignored_when_colors_disabled() -> &'static Self {
fn ignored_when_colors_disabled() -> &'static Self {
${static_longhand_id_set(
"IGNORED_WHEN_COLORS_DISABLED",
lambda p: p.ignored_when_colors_disabled
@ -928,6 +998,48 @@ impl LonghandIdSet {
&IGNORED_WHEN_COLORS_DISABLED
}
/// Only a few properties are allowed to depend on the visited state of
/// links. When cascading visited styles, we can save time by only
/// processing these properties.
fn visited_dependent() -> &'static Self {
${static_longhand_id_set(
"VISITED_DEPENDENT",
lambda p: is_visited_dependent(p)
)}
debug_assert!(Self::late_group().contains_all(&VISITED_DEPENDENT));
&VISITED_DEPENDENT
}
#[inline]
fn writing_mode_group() -> &'static Self {
${static_longhand_id_set(
"WRITING_MODE_GROUP",
lambda p: p.name in CASCADE_GROUPS["writing_mode"]
)}
&WRITING_MODE_GROUP
}
#[inline]
fn fonts_and_color_group() -> &'static Self {
${static_longhand_id_set(
"FONTS_AND_COLOR_GROUP",
lambda p: p.name in CASCADE_GROUPS["fonts_and_color"]
)}
&FONTS_AND_COLOR_GROUP
}
#[inline]
fn late_group_only_inherited() -> &'static Self {
${static_longhand_id_set("LATE_GROUP_ONLY_INHERITED", lambda p: p.style_struct.inherited and in_late_group(p))}
&LATE_GROUP_ONLY_INHERITED
}
#[inline]
fn late_group() -> &'static Self {
${static_longhand_id_set("LATE_GROUP", lambda p: in_late_group(p))}
&LATE_GROUP
}
/// Returns the set of properties that are declared as having no effect on
/// Gecko <scrollbar> elements or their descendant scrollbar parts.
#[cfg(debug_assertions)]
@ -1330,91 +1442,12 @@ impl LonghandId {
PropertyFlags::from_bits_truncate(FLAGS[self as usize])
}
/// Only a few properties are allowed to depend on the visited state of
/// links. When cascading visited styles, we can save time by only
/// processing these properties.
fn is_visited_dependent(&self) -> bool {
matches!(*self,
% if engine == "gecko":
LonghandId::ColumnRuleColor |
LonghandId::TextEmphasisColor |
LonghandId::WebkitTextFillColor |
LonghandId::WebkitTextStrokeColor |
LonghandId::TextDecorationColor |
LonghandId::Fill |
LonghandId::Stroke |
LonghandId::CaretColor |
% endif
LonghandId::BackgroundColor |
LonghandId::BorderTopColor |
LonghandId::BorderRightColor |
LonghandId::BorderBottomColor |
LonghandId::BorderLeftColor |
LonghandId::OutlineColor |
LonghandId::Color
)
}
/// Returns true if the property is one that is ignored when document
/// colors are disabled.
#[inline]
fn ignored_when_document_colors_disabled(self) -> bool {
LonghandIdSet::ignored_when_colors_disabled().contains(self)
}
/// The computed value of some properties depends on the (sometimes
/// computed) value of *other* properties.
///
/// So we classify properties into "early" and "other", such that the only
/// dependencies can be from "other" to "early".
///
/// Unfortunately, its not easy to check that this classification is
/// correct.
fn is_early_property(&self) -> bool {
matches!(*self,
% if engine == "gecko":
// Needed to properly compute the writing mode, to resolve logical
// properties, and similar stuff. In this block instead of along
// `WritingMode` and `Direction` just for convenience, since it's
// Gecko-only (for now at least).
//
// see WritingMode::new.
LonghandId::TextOrientation |
// Needed to properly compute the zoomed font-size.
//
// FIXME(emilio): This could probably just be a cascade flag like
// IN_SVG_SUBTREE or such, and we could nuke this property.
LonghandId::XTextZoom |
// Needed to do font-size computation in a language-dependent way.
LonghandId::XLang |
// Needed for ruby to respect language-dependent min-font-size
// preferences properly, see bug 1165538.
LonghandId::MozMinFontSizeRatio |
// font-size depends on math-depth's computed value.
LonghandId::MathDepth |
// color-scheme affects how system colors resolve.
LonghandId::ColorScheme |
% endif
// Needed to compute the first available font, in order to
// compute font-relative units correctly.
LonghandId::FontSize |
LonghandId::FontWeight |
LonghandId::FontStretch |
LonghandId::FontStyle |
LonghandId::FontFamily |
// Needed to properly compute the writing mode, to resolve logical
// properties, and similar stuff.
LonghandId::WritingMode |
LonghandId::Direction
)
}
}
/// An iterator over all the property ids that are enabled for a given
@ -2560,9 +2593,11 @@ impl PropertyDeclaration {
}
}
type SubpropertiesVec<T> = ArrayVec<T, ${max(len(s.sub_properties) \
for s in data.shorthands_except_all()) \
if data.shorthands_except_all() else 0}>;
const SUB_PROPERTIES_ARRAY_CAP: usize =
${max(len(s.sub_properties) for s in data.shorthands_except_all()) \
if data.shorthands_except_all() else 0};
type SubpropertiesVec<T> = ArrayVec<T, SUB_PROPERTIES_ARRAY_CAP>;
/// A stack-allocated vector of `PropertyDeclaration`
/// large enough to parse one CSS `key: value` declaration.
@ -2574,6 +2609,10 @@ pub struct SourcePropertyDeclaration {
all_shorthand: AllShorthand,
}
// This is huge, but we allocate it on the stack and then never move it,
// we only pass `&mut SourcePropertyDeclaration` references around.
size_of_test!(SourcePropertyDeclaration, 568);
impl SourcePropertyDeclaration {
/// Create one. Its big, try not to move it around.
#[inline]
@ -2618,11 +2657,7 @@ impl SourcePropertyDeclaration {
/// Return type of SourcePropertyDeclaration::drain
pub struct SourcePropertyDeclarationDrain<'a> {
declarations: ArrayVecDrain<
'a, PropertyDeclaration,
${max(len(s.sub_properties) for s in data.shorthands_except_all()) \
if data.shorthands_except_all() else 0}
>,
declarations: ArrayVecDrain<'a, PropertyDeclaration, SUB_PROPERTIES_ARRAY_CAP>,
all_shorthand: AllShorthand,
}
@ -2903,11 +2938,11 @@ pub mod style_structs {
% endif
% endfor
% if style_struct.name == "Box":
% if style_struct.name == "UI":
/// Returns whether there is any animation specified with
/// animation-name other than `none`.
pub fn specifies_animations(&self) -> bool {
self.animation_name_iter().any(|name| name.0.is_some())
self.animation_name_iter().any(|name| !name.is_none())
}
/// Returns whether there are any transitions specified.
@ -3035,7 +3070,7 @@ impl ComputedValues {
/// Returns whether this style's display value is equal to contents.
pub fn is_display_contents(&self) -> bool {
self.get_box().clone_display().is_contents()
self.clone_display().is_contents()
}
/// Gets a reference to the rule node. Panic if no rule node exists.
@ -3184,7 +3219,7 @@ impl ComputedValues {
/// style.resolve_color(style.get_border().clone_border_top_color());
#[inline]
pub fn resolve_color(&self, color: computed::Color) -> RGBA {
color.to_rgba(self.get_inherited_text().clone_color())
color.into_rgba(self.get_inherited_text().clone_color())
}
/// Returns which longhand properties have different values in the two
@ -4188,7 +4223,7 @@ macro_rules! css_properties_accessors {
/// Call the given macro with tokens like this for each longhand properties:
///
/// ```
/// { snake_case_ident, true }
/// { snake_case_ident }
/// ```
///
/// … where the boolean indicates whether the property value type
@ -4198,12 +4233,39 @@ macro_rules! longhand_properties_idents {
($macro_name: ident) => {
$macro_name! {
% for property in data.longhands:
{ ${property.ident}, ${"true" if property.boxed else "false"} }
{ ${property.ident} }
% endfor
}
}
}
// Large pages generate tens of thousands of ComputedValues.
size_of_test!(ComputedValues, 192);
// FFI relies on this.
size_of_test!(Option<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":
% for effect_name in ["repaint", "reflow_out_of_flow", "reflow", "rebuild_and_reflow_inline", "rebuild_and_reflow"]:
macro_rules! restyle_damage_${effect_name} {

View file

@ -24,318 +24,6 @@ ${helpers.two_properties_shorthand(
"(https://developer.mozilla.org/en-US/docs/Web/CSS/overflow-clip-box)",
)}
macro_rules! try_parse_one {
($context: expr, $input: expr, $var: ident, $prop_module: ident) => {
if $var.is_none() {
if let Ok(value) = $input.try_parse(|i| {
$prop_module::single_value::parse($context, i)
}) {
$var = Some(value);
continue;
}
}
};
}
<%helpers:shorthand name="transition"
engines="gecko servo"
extra_prefixes="moz:layout.css.prefixes.transitions webkit"
sub_properties="transition-property transition-duration
transition-timing-function
transition-delay"
spec="https://drafts.csswg.org/css-transitions/#propdef-transition">
use crate::parser::Parse;
% for prop in "delay duration property timing_function".split():
use crate::properties::longhands::transition_${prop};
% endfor
use crate::values::specified::TransitionProperty;
pub fn parse_value<'i, 't>(
context: &ParserContext,
input: &mut Parser<'i, 't>,
) -> Result<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(
"overscroll-behavior",
"overscroll-behavior-x",
@ -345,6 +33,45 @@ ${helpers.two_properties_shorthand(
spec="https://wicg.github.io/overscroll-behavior/#overscroll-behavior-properties",
)}
<%helpers:shorthand
engines="gecko"
name="container"
sub_properties="container-name container-type"
gecko_pref="layout.css.container-queries.enabled",
spec="https://drafts.csswg.org/css-contain-3/#container-shorthand"
>
pub fn parse_value<'i>(
context: &ParserContext,
input: &mut Parser<'i, '_>,
) -> Result<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
engines="gecko"
name="page-break-before"

View 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>

View file

@ -2,13 +2,14 @@
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
//! A media query condition:
//! A query condition:
//!
//! https://drafts.csswg.org/mediaqueries-4/#typedef-media-condition
//! https://drafts.csswg.org/css-contain-3/#typedef-container-condition
use super::{Device, MediaFeatureExpression};
use crate::context::QuirksMode;
use super::{QueryFeatureExpression, FeatureType, FeatureFlags};
use crate::parser::ParserContext;
use crate::values::computed;
use cssparser::{Parser, Token};
use std::fmt::{self, Write};
use style_traits::{CssWriter, ParseError, StyleParseErrorKind, ToCss};
@ -28,38 +29,38 @@ enum AllowOr {
No,
}
/// Represents a media condition.
/// Represents a condition.
#[derive(Clone, Debug, MallocSizeOf, PartialEq, ToShmem)]
pub enum MediaCondition {
/// A simple media feature expression, implicitly parenthesized.
Feature(MediaFeatureExpression),
pub enum QueryCondition {
/// A simple feature expression, implicitly parenthesized.
Feature(QueryFeatureExpression),
/// A negation of a condition.
Not(Box<MediaCondition>),
Not(Box<QueryCondition>),
/// A set of joint operations.
Operation(Box<[MediaCondition]>, Operator),
Operation(Box<[QueryCondition]>, Operator),
/// 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
where
W: fmt::Write,
{
match *self {
// NOTE(emilio): MediaFeatureExpression already includes the
// NOTE(emilio): QueryFeatureExpression already includes the
// parenthesis.
MediaCondition::Feature(ref f) => f.to_css(dest),
MediaCondition::Not(ref c) => {
QueryCondition::Feature(ref f) => f.to_css(dest),
QueryCondition::Not(ref c) => {
dest.write_str("not ")?;
c.to_css(dest)
},
MediaCondition::InParens(ref c) => {
QueryCondition::InParens(ref c) => {
dest.write_char('(')?;
c.to_css(dest)?;
dest.write_char(')')
},
MediaCondition::Operation(ref list, op) => {
QueryCondition::Operation(ref list, op) => {
let mut iter = list.iter();
iter.next().unwrap().to_css(dest)?;
for item in iter {
@ -74,28 +75,60 @@ impl ToCss for MediaCondition {
}
}
impl MediaCondition {
/// Parse a single media condition.
impl QueryCondition {
/// Parse a single condition.
pub fn parse<'i, 't>(
context: &ParserContext,
input: &mut Parser<'i, 't>,
feature_type: FeatureType,
) -> Result<Self, 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>(
context: &ParserContext,
input: &mut Parser<'i, 't>,
feature_type: FeatureType,
) -> 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>(
context: &ParserContext,
input: &mut Parser<'i, 't>,
feature_type: FeatureType,
allow_or: AllowOr,
) -> Result<Self, ParseError<'i>> {
let location = input.current_source_location();
@ -108,12 +141,12 @@ impl MediaCondition {
};
if is_negation {
let inner_condition = Self::parse_in_parens(context, input)?;
return Ok(MediaCondition::Not(Box::new(inner_condition)));
let inner_condition = Self::parse_in_parens(context, input, feature_type)?;
return Ok(QueryCondition::Not(Box::new(inner_condition)));
}
// ParenthesisBlock.
let first_condition = Self::parse_paren_block(context, input)?;
let first_condition = Self::parse_paren_block(context, input, feature_type)?;
let operator = match input.try_parse(Operator::parse) {
Ok(op) => op,
Err(..) => return Ok(first_condition),
@ -125,7 +158,7 @@ impl MediaCondition {
let mut conditions = vec![];
conditions.push(first_condition);
conditions.push(Self::parse_in_parens(context, input)?);
conditions.push(Self::parse_in_parens(context, input, feature_type)?);
let delim = match operator {
Operator::And => "and",
@ -134,50 +167,52 @@ impl MediaCondition {
loop {
if input.try_parse(|i| i.expect_ident_matching(delim)).is_err() {
return Ok(MediaCondition::Operation(
return Ok(QueryCondition::Operation(
conditions.into_boxed_slice(),
operator,
));
}
conditions.push(Self::parse_in_parens(context, input)?);
conditions.push(Self::parse_in_parens(context, input, feature_type)?);
}
}
/// Parse a media condition in parentheses.
/// Parse a condition in parentheses.
pub fn parse_in_parens<'i, 't>(
context: &ParserContext,
input: &mut Parser<'i, 't>,
feature_type: FeatureType,
) -> Result<Self, ParseError<'i>> {
input.expect_parenthesis_block()?;
Self::parse_paren_block(context, input)
Self::parse_paren_block(context, input, feature_type)
}
fn parse_paren_block<'i, 't>(
context: &ParserContext,
input: &mut Parser<'i, 't>,
feature_type: FeatureType,
) -> Result<Self, ParseError<'i>> {
input.parse_nested_block(|input| {
// Base case.
if let Ok(inner) = input.try_parse(|i| Self::parse(context, i)) {
return Ok(MediaCondition::InParens(Box::new(inner)));
if let Ok(inner) = input.try_parse(|i| Self::parse(context, i, feature_type)) {
return Ok(QueryCondition::InParens(Box::new(inner)));
}
let expr = MediaFeatureExpression::parse_in_parenthesis_block(context, input)?;
Ok(MediaCondition::Feature(expr))
let expr = QueryFeatureExpression::parse_in_parenthesis_block(context, input, feature_type)?;
Ok(QueryCondition::Feature(expr))
})
}
/// Whether this condition matches the device and quirks mode.
pub fn matches(&self, device: &Device, quirks_mode: QuirksMode) -> bool {
pub fn matches(&self, context: &computed::Context) -> bool {
match *self {
MediaCondition::Feature(ref f) => f.matches(device, quirks_mode),
MediaCondition::InParens(ref c) => c.matches(device, quirks_mode),
MediaCondition::Not(ref c) => !c.matches(device, quirks_mode),
MediaCondition::Operation(ref conditions, op) => {
QueryCondition::Feature(ref f) => f.matches(context),
QueryCondition::InParens(ref c) => c.matches(context),
QueryCondition::Not(ref c) => !c.matches(context),
QueryCondition::Operation(ref conditions, op) => {
let mut iter = conditions.iter();
match op {
Operator::And => iter.all(|c| c.matches(device, quirks_mode)),
Operator::Or => iter.any(|c| c.matches(device, quirks_mode)),
Operator::And => iter.all(|c| c.matches(context)),
Operator::Or => iter.any(|c| c.matches(context)),
}
},
}

View file

@ -2,13 +2,10 @@
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
//! Media features.
//! Query features.
use super::media_feature_expression::RangeOrOperator;
use super::Device;
use crate::parser::ParserContext;
use crate::values::computed::Ratio;
use crate::values::computed::{CSSPixelLength, Resolution};
use crate::values::computed::{self, CSSPixelLength, Resolution, Ratio};
use crate::Atom;
use cssparser::Parser;
use std::fmt;
@ -17,12 +14,7 @@ use style_traits::ParseError;
/// A generic discriminant for an enum value.
pub type KeywordDiscriminant = u8;
type MediaFeatureEvaluator<T> = fn(
device: &Device,
// null == no value was given in the query.
value: Option<T>,
range_or_operator: Option<RangeOrOperator>,
) -> bool;
type QueryFeatureGetter<T> = fn(device: &computed::Context) -> T;
/// Serializes a given discriminant.
///
@ -36,19 +28,19 @@ pub type KeywordParser = for<'a, 'i, 't> fn(
input: &'a mut Parser<'i, 't>,
) -> Result<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.
#[allow(missing_docs)]
pub enum Evaluator {
Length(MediaFeatureEvaluator<CSSPixelLength>),
Integer(MediaFeatureEvaluator<u32>),
Float(MediaFeatureEvaluator<f32>),
BoolInteger(MediaFeatureEvaluator<bool>),
Length(QueryFeatureGetter<CSSPixelLength>),
Integer(QueryFeatureGetter<u32>),
Float(QueryFeatureGetter<f32>),
BoolInteger(QueryFeatureGetter<bool>),
/// A non-negative number ratio, such as the one from device-pixel-ratio.
NumberRatio(MediaFeatureEvaluator<Ratio>),
NumberRatio(QueryFeatureGetter<Ratio>),
/// A resolution.
Resolution(MediaFeatureEvaluator<Resolution>),
Resolution(QueryFeatureGetter<Resolution>),
/// A keyword value.
Enumerated {
/// The parser to get a discriminant given a string.
@ -60,9 +52,8 @@ pub enum Evaluator {
serializer: KeywordSerializer,
/// The evaluator itself. This is guaranteed to be called with a
/// keyword that `parser` has produced.
evaluator: MediaFeatureEvaluator<KeywordDiscriminant>,
evaluator: fn(&computed::Context, Option<KeywordDiscriminant>) -> bool,
},
Ident(MediaFeatureEvaluator<Atom>),
}
/// A simple helper macro to create a keyword evaluator.
@ -76,14 +67,14 @@ macro_rules! keyword_evaluator {
context: &$crate::parser::ParserContext,
input: &mut $crate::cssparser::Parser<'i, 't>,
) -> Result<
$crate::media_queries::media_feature::KeywordDiscriminant,
$crate::queries::feature::KeywordDiscriminant,
::style_traits::ParseError<'i>,
> {
let kw = <$keyword_type as $crate::parser::Parse>::parse(context, input)?;
Ok(kw as $crate::media_queries::media_feature::KeywordDiscriminant)
Ok(kw as $crate::queries::feature::KeywordDiscriminant)
}
fn __serialize(kw: $crate::media_queries::media_feature::KeywordDiscriminant) -> String {
fn __serialize(kw: $crate::queries::feature::KeywordDiscriminant) -> String {
// This unwrap is ok because the only discriminants that get
// back to us is the ones that `parse` produces.
let value: $keyword_type = ::num_traits::cast::FromPrimitive::from_u8(kw).unwrap();
@ -91,24 +82,17 @@ macro_rules! keyword_evaluator {
}
fn __evaluate(
device: &$crate::media_queries::Device,
value: Option<$crate::media_queries::media_feature::KeywordDiscriminant>,
range_or_operator: Option<
$crate::media_queries::media_feature_expression::RangeOrOperator,
>,
context: &$crate::values::computed::Context,
value: Option<$crate::queries::feature::KeywordDiscriminant>,
) -> bool {
debug_assert!(
range_or_operator.is_none(),
"Since when do keywords accept ranges?"
);
// This unwrap is ok because the only discriminants that get
// back to us is the ones that `parse` produces.
let value: Option<$keyword_type> =
value.map(|kw| ::num_traits::cast::FromPrimitive::from_u8(kw).unwrap());
$actual_evaluator(device, value)
$actual_evaluator(context, value)
}
$crate::media_queries::media_feature::Evaluator::Enumerated {
$crate::queries::feature::Evaluator::Enumerated {
parser: __parse,
serializer: __serialize,
evaluator: __evaluate,
@ -117,17 +101,46 @@ macro_rules! keyword_evaluator {
}
bitflags! {
/// Different requirements or toggles that change how a expression is
/// parsed.
pub struct ParsingRequirements: u8 {
/// Different flags or toggles that change how a expression is parsed or
/// evaluated.
#[derive(ToShmem)]
pub struct FeatureFlags : u8 {
/// The feature should only be parsed in chrome and ua sheets.
const CHROME_AND_UA_ONLY = 1 << 0;
/// The feature requires a -webkit- prefix.
const WEBKIT_PREFIX = 1 << 1;
/// The feature requires the inline-axis containment.
const CONTAINER_REQUIRES_INLINE_AXIS = 1 << 2;
/// The feature requires the block-axis containment.
const CONTAINER_REQUIRES_BLOCK_AXIS = 1 << 3;
/// The feature requires containment in the physical width axis.
const CONTAINER_REQUIRES_WIDTH_AXIS = 1 << 4;
/// The feature requires containment in the physical height axis.
const CONTAINER_REQUIRES_HEIGHT_AXIS = 1 << 5;
}
}
/// Whether a media feature allows ranges or not.
impl FeatureFlags {
/// Returns parsing requirement flags.
pub fn parsing_requirements(self) -> Self {
self.intersection(Self::CHROME_AND_UA_ONLY | Self::WEBKIT_PREFIX)
}
/// Returns all the container axis flags.
pub fn all_container_axes() -> Self {
Self::CONTAINER_REQUIRES_INLINE_AXIS |
Self::CONTAINER_REQUIRES_BLOCK_AXIS |
Self::CONTAINER_REQUIRES_WIDTH_AXIS |
Self::CONTAINER_REQUIRES_HEIGHT_AXIS
}
/// Returns our subset of container axis flags.
pub fn container_axes(self) -> Self {
self.intersection(Self::all_container_axes())
}
}
/// Whether a feature allows ranges or not.
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
#[allow(missing_docs)]
pub enum AllowsRanges {
@ -135,46 +148,45 @@ pub enum AllowsRanges {
No,
}
/// A description of a media feature.
pub struct MediaFeatureDescription {
/// The media feature name, in ascii lowercase.
/// A description of a feature.
pub struct QueryFeatureDescription {
/// The feature name, in ascii lowercase.
pub name: Atom,
/// Whether min- / max- prefixes are allowed or not.
pub allows_ranges: AllowsRanges,
/// The evaluator, which we also use to determine which kind of value to
/// parse.
pub evaluator: Evaluator,
/// Different requirements that need to hold for the feature to be
/// successfully parsed.
pub requirements: ParsingRequirements,
/// Different feature-specific flags.
pub flags: FeatureFlags,
}
impl MediaFeatureDescription {
/// Whether this media feature allows ranges.
impl QueryFeatureDescription {
/// Whether this feature allows ranges.
#[inline]
pub fn allows_ranges(&self) -> bool {
self.allows_ranges == AllowsRanges::Yes
}
}
/// A simple helper to construct a `MediaFeatureDescription`.
/// A simple helper to construct a `QueryFeatureDescription`.
macro_rules! feature {
($name:expr, $allows_ranges:expr, $evaluator:expr, $reqs:expr,) => {
$crate::media_queries::media_feature::MediaFeatureDescription {
($name:expr, $allows_ranges:expr, $evaluator:expr, $flags:expr,) => {
$crate::queries::feature::QueryFeatureDescription {
name: $name,
allows_ranges: $allows_ranges,
evaluator: $evaluator,
requirements: $reqs,
flags: $flags,
}
};
}
impl fmt::Debug for MediaFeatureDescription {
impl fmt::Debug for QueryFeatureDescription {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
f.debug_struct("MediaFeatureExpression")
f.debug_struct("QueryFeatureDescription")
.field("name", &self.name)
.field("allows_ranges", &self.allows_ranges)
.field("requirements", &self.requirements)
.field("flags", &self.flags)
.finish()
}
}

View 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 its
// 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)?)
},
})
}
}

View 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};

View 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,
}
}
}

View file

@ -13,7 +13,7 @@ use crate::selector_parser::PseudoElement;
use crate::shared_lock::Locked;
use crate::stylesheets::{layer_rule::LayerOrder, Origin};
use crate::stylist::{AuthorStylesEnabled, CascadeData, Rule, RuleInclusion, Stylist};
use selectors::matching::{ElementSelectorFlags, MatchingContext, MatchingMode};
use selectors::matching::{MatchingContext, MatchingMode};
use servo_arc::ArcBorrow;
use smallvec::SmallVec;
@ -59,7 +59,7 @@ pub fn containing_shadow_ignoring_svg_use<E: TElement>(
///
/// This is done basically to be able to organize the cascade in smaller
/// functions, and be able to reason about it easily.
pub struct RuleCollector<'a, 'b: 'a, E, F: 'a>
pub struct RuleCollector<'a, 'b: 'a, E>
where
E: TElement,
{
@ -73,16 +73,14 @@ where
rule_inclusion: RuleInclusion,
rules: &'a mut ApplicableDeclarationList,
context: &'a mut MatchingContext<'b, E::Impl>,
flags_setter: &'a mut F,
matches_user_and_author_rules: bool,
matches_document_author_rules: bool,
in_sort_scope: bool,
}
impl<'a, 'b: 'a, E, F: 'a> RuleCollector<'a, 'b, E, F>
impl<'a, 'b: 'a, E> RuleCollector<'a, 'b, E>
where
E: TElement,
F: FnMut(&E, ElementSelectorFlags),
{
/// Trivially construct a new collector.
pub fn new(
@ -95,7 +93,6 @@ where
rule_inclusion: RuleInclusion,
rules: &'a mut ApplicableDeclarationList,
context: &'a mut MatchingContext<'b, E::Impl>,
flags_setter: &'a mut F,
) -> Self {
// When we're matching with matching_mode =
// `ForStatelessPseudoeElement`, the "target" for the rule hash is the
@ -125,7 +122,6 @@ where
animation_declarations,
rule_inclusion,
context,
flags_setter,
rules,
matches_user_and_author_rules,
matches_document_author_rules: matches_user_and_author_rules,
@ -227,9 +223,9 @@ where
part_rules,
&mut self.rules,
&mut self.context,
&mut self.flags_setter,
cascade_level,
cascade_data,
&self.stylist,
);
}
@ -246,9 +242,9 @@ where
self.rule_hash_target,
&mut self.rules,
&mut self.context,
&mut self.flags_setter,
cascade_level,
cascade_data,
&self.stylist,
);
}

View file

@ -203,9 +203,6 @@ impl RuleTree {
/// where it likely did not result from a rigorous performance analysis.)
const RULE_TREE_GC_INTERVAL: usize = 300;
/// Used for some size assertions.
pub const RULE_NODE_SIZE: usize = std::mem::size_of::<RuleNode>();
/// A node in the rule tree.
struct RuleNode {
/// The root node. Only the root has no root pointer, for obvious reasons.
@ -768,3 +765,8 @@ impl hash::Hash for StrongRuleNode {
(&*self.p as *const RuleNode).hash(state)
}
}
// Large pages generate thousands of RuleNode objects.
size_of_test!(RuleNode, 80);
// StrongRuleNode should be pointer-sized even inside an option.
size_of_test!(Option<StrongRuleNode>, 8);

View file

@ -20,7 +20,7 @@ mod map;
mod source;
mod unsafe_box;
pub use self::core::{RuleTree, StrongRuleNode, RULE_NODE_SIZE};
pub use self::core::{RuleTree, StrongRuleNode};
pub use self::level::{CascadeLevel, ShadowCascadeOrder};
pub use self::source::StyleSource;

View file

@ -10,11 +10,11 @@ use crate::context::QuirksMode;
use crate::dom::TElement;
use crate::rule_tree::CascadeLevel;
use crate::selector_parser::SelectorImpl;
use crate::stylist::{CascadeData, Rule};
use crate::stylist::{Stylist, CascadeData, Rule, ContainerConditionId};
use crate::AllocErr;
use crate::{Atom, LocalName, Namespace, ShrinkIfNeeded, WeakAtom};
use precomputed_hash::PrecomputedHash;
use selectors::matching::{matches_selector, ElementSelectorFlags, MatchingContext};
use selectors::matching::{matches_selector, MatchingContext};
use selectors::parser::{Combinator, Component, SelectorIter};
use smallvec::SmallVec;
use std::collections::hash_map;
@ -186,34 +186,33 @@ impl SelectorMap<Rule> {
///
/// Extract matching rules as per element's ID, classes, tag name, etc..
/// Sort the Rules at the end to maintain cascading order.
pub fn get_all_matching_rules<E, F>(
pub fn get_all_matching_rules<E>(
&self,
element: E,
rule_hash_target: E,
matching_rules_list: &mut ApplicableDeclarationList,
context: &mut MatchingContext<E::Impl>,
flags_setter: &mut F,
matching_context: &mut MatchingContext<E::Impl>,
cascade_level: CascadeLevel,
cascade_data: &CascadeData,
stylist: &Stylist,
) where
E: TElement,
F: FnMut(&E, ElementSelectorFlags),
{
if self.is_empty() {
return;
}
let quirks_mode = context.quirks_mode();
let quirks_mode = matching_context.quirks_mode();
if rule_hash_target.is_root() {
SelectorMap::get_matching_rules(
element,
&self.root,
matching_rules_list,
context,
flags_setter,
matching_context,
cascade_level,
cascade_data,
stylist,
);
}
@ -223,10 +222,10 @@ impl SelectorMap<Rule> {
element,
rules,
matching_rules_list,
context,
flags_setter,
matching_context,
cascade_level,
cascade_data,
stylist,
)
}
}
@ -237,10 +236,10 @@ impl SelectorMap<Rule> {
element,
rules,
matching_rules_list,
context,
flags_setter,
matching_context,
cascade_level,
cascade_data,
stylist,
)
}
});
@ -252,10 +251,10 @@ impl SelectorMap<Rule> {
element,
rules,
matching_rules_list,
context,
flags_setter,
matching_context,
cascade_level,
cascade_data,
stylist,
)
}
});
@ -266,10 +265,10 @@ impl SelectorMap<Rule> {
element,
rules,
matching_rules_list,
context,
flags_setter,
matching_context,
cascade_level,
cascade_data,
stylist,
)
}
@ -278,10 +277,10 @@ impl SelectorMap<Rule> {
element,
rules,
matching_rules_list,
context,
flags_setter,
matching_context,
cascade_level,
cascade_data,
stylist,
)
}
@ -289,38 +288,44 @@ impl SelectorMap<Rule> {
element,
&self.other,
matching_rules_list,
context,
flags_setter,
matching_context,
cascade_level,
cascade_data,
stylist,
);
}
/// Adds rules in `rules` that match `element` to the `matching_rules` list.
pub(crate) fn get_matching_rules<E, F>(
pub(crate) fn get_matching_rules<E>(
element: E,
rules: &[Rule],
matching_rules: &mut ApplicableDeclarationList,
context: &mut MatchingContext<E::Impl>,
flags_setter: &mut F,
matching_context: &mut MatchingContext<E::Impl>,
cascade_level: CascadeLevel,
cascade_data: &CascadeData,
stylist: &Stylist,
) where
E: TElement,
F: FnMut(&E, ElementSelectorFlags),
{
for rule in rules {
if matches_selector(
if !matches_selector(
&rule.selector,
0,
Some(&rule.hashes),
&element,
context,
flags_setter,
matching_context,
) {
matching_rules
.push(rule.to_applicable_declaration_block(cascade_level, cascade_data));
continue;
}
if rule.container_condition_id != ContainerConditionId::none() {
if !cascade_data.container_condition_matches(rule.container_condition_id, stylist, element) {
continue;
}
}
matching_rules
.push(rule.to_applicable_declaration_block(cascade_level, cascade_data));
}
}
}

View file

@ -7,13 +7,13 @@
use crate::context::QuirksMode;
use crate::custom_properties::CssEnvironment;
use crate::font_metrics::FontMetrics;
use crate::media_queries::media_feature::{AllowsRanges, ParsingRequirements};
use crate::media_queries::media_feature::{Evaluator, MediaFeatureDescription};
use crate::media_queries::media_feature_expression::RangeOrOperator;
use crate::queries::feature::{AllowsRanges, Evaluator, FeatureFlags, QueryFeatureDescription};
use crate::media_queries::MediaType;
use crate::properties::ComputedValues;
use crate::values::computed::CSSPixelLength;
use crate::values::computed::Context;
use crate::values::specified::font::FONT_MEDIUM_PX;
use crate::values::specified::ViewportVariant;
use crate::values::KeyframesName;
use app_units::Au;
use cssparser::RGBA;
@ -143,8 +143,13 @@ impl Device {
}
/// Like the above, but records that we've used viewport units.
pub fn au_viewport_size_for_viewport_unit_resolution(&self) -> UntypedSize2D<Au> {
pub fn au_viewport_size_for_viewport_unit_resolution(
&self,
_: ViewportVariant,
) -> UntypedSize2D<Au> {
self.used_viewport_units.store(true, Ordering::Relaxed);
// Servo doesn't have dynamic UA interfaces that affect the viewport,
// so we can just ignore the ViewportVariant.
self.au_viewport_size()
}
@ -229,16 +234,8 @@ impl Device {
}
/// https://drafts.csswg.org/mediaqueries-4/#width
fn eval_width(
device: &Device,
value: Option<CSSPixelLength>,
range_or_operator: Option<RangeOrOperator>,
) -> bool {
RangeOrOperator::evaluate(
range_or_operator,
value.map(Au::from),
device.au_viewport_size().width,
)
fn eval_width(context: &Context) -> CSSPixelLength {
CSSPixelLength::new(context.device().au_viewport_size().width.to_f32_px())
}
#[derive(Clone, Copy, Debug, FromPrimitive, Parse, ToCss)]
@ -249,7 +246,7 @@ enum Scan {
}
/// https://drafts.csswg.org/mediaqueries-4/#scan
fn eval_scan(_: &Device, _: Option<Scan>) -> bool {
fn eval_scan(_: &Context, _: Option<Scan>) -> bool {
// Since we doesn't support the 'tv' media type, the 'scan' feature never
// matches.
false
@ -257,18 +254,18 @@ fn eval_scan(_: &Device, _: Option<Scan>) -> bool {
lazy_static! {
/// A list with all the media features that Servo supports.
pub static ref MEDIA_FEATURES: [MediaFeatureDescription; 2] = [
pub static ref MEDIA_FEATURES: [QueryFeatureDescription; 2] = [
feature!(
atom!("width"),
AllowsRanges::Yes,
Evaluator::Length(eval_width),
ParsingRequirements::empty(),
FeatureFlags::empty(),
),
feature!(
atom!("scan"),
AllowsRanges::No,
keyword_evaluator!(eval_scan, Scan),
ParsingRequirements::empty(),
FeatureFlags::empty(),
),
];
}

View file

@ -7,7 +7,7 @@
//! elements can indeed share the same style.
use crate::bloom::StyleBloom;
use crate::context::{SelectorFlagsMap, SharedStyleContext};
use crate::context::SharedStyleContext;
use crate::dom::TElement;
use crate::sharing::{StyleSharingCandidate, StyleSharingTarget};
use selectors::NthIndexCache;
@ -120,7 +120,6 @@ pub fn revalidate<E>(
shared_context: &SharedStyleContext,
bloom: &StyleBloom<E>,
nth_index_cache: &mut NthIndexCache,
selector_flags_map: &mut SelectorFlagsMap<E>,
) -> bool
where
E: TElement,
@ -128,7 +127,7 @@ where
let stylist = &shared_context.stylist;
let for_element =
target.revalidation_match_results(stylist, bloom, nth_index_cache, selector_flags_map);
target.revalidation_match_results(stylist, bloom, nth_index_cache);
let for_candidate = candidate.revalidation_match_results(stylist, bloom, nth_index_cache);

View file

@ -66,9 +66,8 @@
use crate::applicable_declarations::ApplicableDeclarationBlock;
use crate::bloom::StyleBloom;
use crate::context::{SelectorFlagsMap, SharedStyleContext, StyleContext};
use crate::context::{SharedStyleContext, StyleContext};
use crate::dom::{SendElement, TElement};
use crate::matching::MatchMethods;
use crate::properties::ComputedValues;
use crate::rule_tree::StrongRuleNode;
use crate::style_resolver::{PrimaryStyle, ResolvedElementStyles};
@ -76,7 +75,7 @@ use crate::stylist::Stylist;
use crate::values::AtomIdent;
use atomic_refcell::{AtomicRefCell, AtomicRefMut};
use owning_ref::OwningHandle;
use selectors::matching::{ElementSelectorFlags, VisitedHandlingMode};
use selectors::matching::{VisitedHandlingMode, NeedsSelectorFlags};
use selectors::NthIndexCache;
use servo_arc::Arc;
use smallbitvec::SmallBitVec;
@ -223,18 +222,17 @@ impl ValidationData {
/// Computes the revalidation results if needed, and returns it.
/// Inline so we know at compile time what bloom_known_valid is.
#[inline]
fn revalidation_match_results<E, F>(
fn revalidation_match_results<E>(
&mut self,
element: E,
stylist: &Stylist,
bloom: &StyleBloom<E>,
nth_index_cache: &mut NthIndexCache,
bloom_known_valid: bool,
flags_setter: &mut F,
needs_selector_flags: NeedsSelectorFlags,
) -> &SmallBitVec
where
E: TElement,
F: FnMut(&E, ElementSelectorFlags),
{
self.revalidation_match_results.get_or_insert_with(|| {
// The bloom filter may already be set up for our element.
@ -257,7 +255,7 @@ impl ValidationData {
element,
bloom_to_use,
nth_index_cache,
flags_setter,
needs_selector_flags,
)
})
}
@ -327,7 +325,9 @@ impl<E: TElement> StyleSharingCandidate<E> {
bloom,
nth_index_cache,
/* bloom_known_valid = */ false,
&mut |_, _| {},
// The candidate must already have the right bits already, if
// needed.
NeedsSelectorFlags::No,
)
}
}
@ -384,7 +384,6 @@ impl<E: TElement> StyleSharingTarget<E> {
stylist: &Stylist,
bloom: &StyleBloom<E>,
nth_index_cache: &mut NthIndexCache,
selector_flags_map: &mut SelectorFlagsMap<E>,
) -> &SmallBitVec {
// It's important to set the selector flags. Otherwise, if we succeed in
// sharing the style, we may not set the slow selector flags for the
@ -401,18 +400,13 @@ impl<E: TElement> StyleSharingTarget<E> {
// The style sharing cache will get a hit for the second span. When the
// child span is subsequently removed from the DOM, missing selector
// flags would cause us to miss the restyle on the second span.
let element = self.element;
let mut set_selector_flags = |el: &E, flags: ElementSelectorFlags| {
element.apply_selector_flags(selector_flags_map, el, flags);
};
self.validation_data.revalidation_match_results(
self.element,
stylist,
bloom,
nth_index_cache,
/* bloom_known_valid = */ true,
&mut set_selector_flags,
NeedsSelectorFlags::Yes,
)
}
@ -423,7 +417,6 @@ impl<E: TElement> StyleSharingTarget<E> {
) -> Option<ResolvedElementStyles> {
let cache = &mut context.thread_local.sharing_cache;
let shared_context = &context.shared;
let selector_flags_map = &mut context.thread_local.selector_flags;
let bloom_filter = &context.thread_local.bloom_filter;
let nth_index_cache = &mut context.thread_local.nth_index_cache;
@ -443,7 +436,6 @@ impl<E: TElement> StyleSharingTarget<E> {
cache.share_style_if_possible(
shared_context,
selector_flags_map,
bloom_filter,
nth_index_cache,
self,
@ -631,13 +623,13 @@ impl<E: TElement> StyleSharingCache<E> {
//
// These are things we don't check in the candidate match because they
// are either uncommon or expensive.
let box_style = style.style().get_box();
if box_style.specifies_transitions() {
let ui_style = style.style().get_ui();
if ui_style.specifies_transitions() {
debug!("Failing to insert to the cache: transitions");
return;
}
if box_style.specifies_animations() {
if ui_style.specifies_animations() {
debug!("Failing to insert to the cache: animations");
return;
}
@ -667,7 +659,6 @@ impl<E: TElement> StyleSharingCache<E> {
fn share_style_if_possible(
&mut self,
shared_context: &SharedStyleContext,
selector_flags_map: &mut SelectorFlagsMap<E>,
bloom_filter: &StyleBloom<E>,
nth_index_cache: &mut NthIndexCache,
target: &mut StyleSharingTarget<E>,
@ -700,7 +691,6 @@ impl<E: TElement> StyleSharingCache<E> {
&shared_context,
bloom_filter,
nth_index_cache,
selector_flags_map,
shared_context,
)
})
@ -712,7 +702,6 @@ impl<E: TElement> StyleSharingCache<E> {
shared: &SharedStyleContext,
bloom: &StyleBloom<E>,
nth_index_cache: &mut NthIndexCache,
selector_flags_map: &mut SelectorFlagsMap<E>,
shared_context: &SharedStyleContext,
) -> Option<ResolvedElementStyles> {
debug_assert!(!target.is_in_native_anonymous_subtree());
@ -817,7 +806,6 @@ impl<E: TElement> StyleSharingCache<E> {
shared,
bloom,
nth_index_cache,
selector_flags_map,
) {
trace!("Miss: Revalidation");
return None;

View file

@ -15,7 +15,7 @@ use crate::rule_tree::StrongRuleNode;
use crate::selector_parser::{PseudoElement, SelectorImpl};
use crate::stylist::RuleInclusion;
use log::Level::Trace;
use selectors::matching::{ElementSelectorFlags, MatchingContext};
use selectors::matching::{NeedsSelectorFlags, MatchingContext};
use selectors::matching::{MatchingMode, VisitedHandlingMode};
use servo_arc::Arc;
@ -451,7 +451,6 @@ where
);
let mut applicable_declarations = ApplicableDeclarationList::new();
let map = &mut self.context.thread_local.selector_flags;
let bloom_filter = self.context.thread_local.bloom_filter.filter();
let nth_index_cache = &mut self.context.thread_local.nth_index_cache;
let mut matching_context = MatchingContext::new_for_visited(
@ -460,29 +459,22 @@ where
Some(nth_index_cache),
visited_handling,
self.context.shared.quirks_mode(),
NeedsSelectorFlags::Yes,
);
let stylist = &self.context.shared.stylist;
let implemented_pseudo = self.element.implemented_pseudo_element();
{
let resolving_element = self.element;
let mut set_selector_flags = |element: &E, flags: ElementSelectorFlags| {
resolving_element.apply_selector_flags(map, element, flags);
};
// Compute the primary rule node.
stylist.push_applicable_declarations(
self.element,
implemented_pseudo.as_ref(),
self.element.style_attribute(),
self.element.smil_override(),
self.element.animation_declarations(self.context.shared),
self.rule_inclusion,
&mut applicable_declarations,
&mut matching_context,
&mut set_selector_flags,
);
}
// Compute the primary rule node.
stylist.push_applicable_declarations(
self.element,
implemented_pseudo.as_ref(),
self.element.style_attribute(),
self.element.smil_override(),
self.element.animation_declarations(self.context.shared),
self.rule_inclusion,
&mut applicable_declarations,
&mut matching_context,
);
// FIXME(emilio): This is a hack for animations, and should go away.
self.element.unset_dirty_style_attribute();
@ -540,14 +532,9 @@ where
Some(nth_index_cache),
visited_handling,
self.context.shared.quirks_mode(),
NeedsSelectorFlags::Yes,
);
let map = &mut self.context.thread_local.selector_flags;
let resolving_element = self.element;
let mut set_selector_flags = |element: &E, flags: ElementSelectorFlags| {
resolving_element.apply_selector_flags(map, element, flags);
};
// NB: We handle animation rules for ::before and ::after when
// traversing them.
stylist.push_applicable_declarations(
@ -559,7 +546,6 @@ where
self.rule_inclusion,
&mut applicable_declarations,
&mut matching_context,
&mut set_selector_flags,
);
if applicable_declarations.is_empty() {

View 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()),
),
];

View file

@ -2,7 +2,7 @@
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
//! A [`@layer`][layer] urle.
//! A [`@layer`][layer] rule.
//!
//! [layer]: https://drafts.csswg.org/css-cascade-5/#layering
@ -61,17 +61,6 @@ impl LayerOrder {
}
}
/// The id of a given layer, a sequentially-increasing identifier.
#[derive(Clone, Copy, Debug, Eq, MallocSizeOf, PartialEq, PartialOrd, Ord)]
pub struct LayerId(pub u32);
impl LayerId {
/// The id of the root layer.
pub const fn root() -> Self {
Self(0)
}
}
/// A `<layer-name>`: https://drafts.csswg.org/css-cascade-5/#typedef-layer-name
#[derive(Clone, Debug, Eq, Hash, MallocSizeOf, PartialEq, ToShmem)]
pub struct LayerName(pub SmallVec<[AtomIdent; 1]>);

View file

@ -2,7 +2,7 @@
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
//! An [`@media`][media] urle.
//! An [`@media`][media] rule.
//!
//! [media]: https://drafts.csswg.org/css-conditional/#at-ruledef-media
@ -18,7 +18,7 @@ use servo_arc::Arc;
use std::fmt::{self, Write};
use style_traits::{CssWriter, ToCss};
/// An [`@media`][media] urle.
/// An [`@media`][media] rule.
///
/// [media]: https://drafts.csswg.org/css-conditional/#at-ruledef-media
#[derive(Debug, ToShmem)]

View file

@ -12,6 +12,7 @@ pub mod font_feature_values_rule;
pub mod import_rule;
pub mod keyframes_rule;
pub mod layer_rule;
pub mod container_rule;
mod loader;
mod media_rule;
mod namespace_rule;
@ -53,6 +54,7 @@ pub use self::import_rule::ImportRule;
pub use self::keyframes_rule::KeyframesRule;
pub use self::layer_rule::{LayerBlockRule, LayerStatementRule};
pub use self::loader::StylesheetLoader;
pub use self::container_rule::ContainerRule;
pub use self::media_rule::MediaRule;
pub use self::namespace_rule::NamespaceRule;
pub use self::origin::{Origin, OriginSet, OriginSetIterator, PerOrigin, PerOriginIter};
@ -253,6 +255,7 @@ pub enum CssRule {
Import(Arc<Locked<ImportRule>>),
Style(Arc<Locked<StyleRule>>),
Media(Arc<Locked<MediaRule>>),
Container(Arc<Locked<ContainerRule>>),
FontFace(Arc<Locked<FontFaceRule>>),
FontFeatureValues(Arc<Locked<FontFeatureValuesRule>>),
CounterStyle(Arc<Locked<CounterStyleRule>>),
@ -287,6 +290,10 @@ impl CssRule {
lock.unconditional_shallow_size_of(ops) + lock.read_with(guard).size_of(guard, ops)
},
CssRule::Container(ref lock) => {
lock.unconditional_shallow_size_of(ops) + lock.read_with(guard).size_of(guard, ops)
},
CssRule::FontFace(_) => 0,
CssRule::FontFeatureValues(_) => 0,
CssRule::CounterStyle(_) => 0,
@ -344,6 +351,7 @@ pub enum CssRuleType {
LayerBlock = 16,
LayerStatement = 17,
ScrollTimeline = 18,
Container = 19,
}
#[allow(missing_docs)]
@ -373,16 +381,7 @@ impl CssRule {
CssRule::LayerBlock(_) => CssRuleType::LayerBlock,
CssRule::LayerStatement(_) => CssRuleType::LayerStatement,
CssRule::ScrollTimeline(_) => CssRuleType::ScrollTimeline,
}
}
fn rule_state(&self) -> State {
match *self {
// CssRule::Charset(..) => State::Start,
CssRule::Import(..) => State::Imports,
CssRule::Namespace(..) => State::Namespaces,
// TODO(emilio): Do we need something for EarlyLayers?
_ => State::Body,
CssRule::Container(_) => CssRuleType::Container,
}
}
@ -460,6 +459,12 @@ impl DeepCloneWithLock for CssRule {
lock.wrap(rule.deep_clone_with_lock(lock, guard, params)),
))
},
CssRule::Container(ref arc) => {
let rule = arc.read_with(guard);
CssRule::Container(Arc::new(
lock.wrap(rule.deep_clone_with_lock(lock, guard, params)),
))
},
CssRule::Media(ref arc) => {
let rule = arc.read_with(guard);
CssRule::Media(Arc::new(
@ -545,6 +550,7 @@ impl ToCssWithGuard for CssRule {
CssRule::LayerBlock(ref lock) => lock.read_with(guard).to_css(guard, dest),
CssRule::LayerStatement(ref lock) => lock.read_with(guard).to_css(guard, dest),
CssRule::ScrollTimeline(ref lock) => lock.read_with(guard).to_css(guard, dest),
CssRule::Container(ref lock) => lock.read_with(guard).to_css(guard, dest),
}
}
}

View file

@ -153,21 +153,17 @@ impl CssRulesHelpers for RawOffsetArc<Locked<CssRules>> {
}
// Computes the parser state at the given index
let insert_rule_context = InsertRuleContext {
rule_list: &rules.0,
index,
};
let state = if nested {
State::Body
} else if index == 0 {
State::Start
} else {
rules
.0
.get(index - 1)
.map(CssRule::rule_state)
.unwrap_or(State::Body)
};
let insert_rule_context = InsertRuleContext {
rule_list: &rules.0,
index,
insert_rule_context.max_rule_state_at_index(index - 1)
};
// Steps 3, 4, 5, 6

View file

@ -13,6 +13,7 @@ use crate::properties::parse_property_declaration_list;
use crate::selector_parser::{SelectorImpl, SelectorParser};
use crate::shared_lock::{Locked, SharedRwLock};
use crate::str::starts_with_ignore_ascii_case;
use crate::stylesheets::container_rule::{ContainerRule, ContainerCondition};
use crate::stylesheets::document_rule::DocumentCondition;
use crate::stylesheets::font_feature_values_rule::parse_family_name_list;
use crate::stylesheets::import_rule::ImportLayer;
@ -45,6 +46,36 @@ pub struct InsertRuleContext<'a> {
pub index: usize,
}
impl<'a> InsertRuleContext<'a> {
/// Returns the max rule state allowable for insertion at a given index in
/// the rule list.
pub fn max_rule_state_at_index(&self, index: usize) -> State {
let rule = match self.rule_list.get(index) {
Some(rule) => rule,
None => return State::Body,
};
match rule {
CssRule::Import(..) => State::Imports,
CssRule::Namespace(..) => State::Namespaces,
CssRule::LayerStatement(..) => {
// If there are @import / @namespace after this layer, then
// we're in the early-layers phase, otherwise we're in the body
// and everything is fair game.
let next_non_layer_statement_rule = self.rule_list[index + 1..]
.iter()
.find(|r| !matches!(*r, CssRule::LayerStatement(..)));
if let Some(non_layer) = next_non_layer_statement_rule {
if matches!(*non_layer, CssRule::Import(..) | CssRule::Namespace(..)) {
return State::EarlyLayers;
}
}
State::Body
}
_ => State::Body,
}
}
}
/// The parser for the top-level rules in a stylesheet.
pub struct TopLevelRuleParser<'a> {
/// A reference to the lock we need to use to create rules.
@ -102,12 +133,8 @@ impl<'b> TopLevelRuleParser<'b> {
None => return true,
};
let next_rule_state = match ctx.rule_list.get(ctx.index) {
None => return true,
Some(rule) => rule.rule_state(),
};
if new_state > next_rule_state {
let max_rule_state = ctx.max_rule_state_at_index(ctx.index);
if new_state > max_rule_state {
self.dom_error = Some(RulesMutateError::HierarchyRequest);
return false;
}
@ -162,6 +189,8 @@ pub enum AtRulePrelude {
CounterStyle(CustomIdent),
/// A @media rule prelude, with its media queries.
Media(Arc<Locked<MediaList>>),
/// A @container rule prelude.
Container(Arc<ContainerCondition>),
/// An @supports rule, with its conditional
Supports(SupportsCondition),
/// A @viewport rule prelude.
@ -262,11 +291,23 @@ impl<'a, 'i> AtRuleParser<'i> for TopLevelRuleParser<'a> {
self.dom_error = Some(RulesMutateError::HierarchyRequest);
return Err(input.new_custom_error(StyleParseErrorKind::UnexpectedCharsetRule))
},
_ => {}
}
if !self.check_state(State::Body) {
return Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError));
"layer" => {
let state_to_check = if self.state <= State::EarlyLayers {
// The real state depends on whether there's a block or not.
// We don't know that yet, but the parse_block check deals
// with that.
State::EarlyLayers
} else {
State::Body
};
if !self.check_state(state_to_check) {
return Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError));
}
},
_ => {
// All other rules have blocks, so we do this check early in
// parse_block instead.
}
}
AtRuleParser::parse_prelude(&mut self.nested(), name, input)
@ -279,6 +320,9 @@ impl<'a, 'i> AtRuleParser<'i> for TopLevelRuleParser<'a> {
start: &ParserState,
input: &mut Parser<'i, 't>,
) -> Result<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)?;
self.state = State::Body;
Ok((start.position(), rule))
@ -410,6 +454,16 @@ impl<'a, 'b> NestedRuleParser<'a, 'b> {
}
}
fn container_queries_enabled() -> bool {
#[cfg(feature = "gecko")]
return static_prefs::pref!("layout.css.container-queries.enabled");
#[cfg(feature = "servo")]
return servo_config::prefs::pref_map()
.get("layout.container-queries.enabled")
.as_bool()
.unwrap_or(false);
}
impl<'a, 'b, 'i> AtRuleParser<'i> for NestedRuleParser<'a, 'b> {
type Prelude = AtRulePrelude;
type AtRule = CssRule;
@ -433,6 +487,10 @@ impl<'a, 'b, 'i> AtRuleParser<'i> for NestedRuleParser<'a, 'b> {
"font-face" => {
AtRulePrelude::FontFace
},
"container" if container_queries_enabled() => {
let condition = Arc::new(ContainerCondition::parse(self.context, input)?);
AtRulePrelude::Container(condition)
},
"layer" => {
let names = input.try_parse(|input| {
input.parse_comma_separated(|input| {
@ -614,6 +672,15 @@ impl<'a, 'b, 'i> AtRuleParser<'i> for NestedRuleParser<'a, 'b> {
},
))))
},
AtRulePrelude::Container(condition) => {
Ok(CssRule::Container(Arc::new(self.shared_lock.wrap(
ContainerRule {
condition,
rules: self.parse_nested_rules(input, CssRuleType::Container),
source_location: start.source_location(),
},
))))
},
AtRulePrelude::Layer(names) => {
let name = match names.len() {
0 | 1 => names.into_iter().next(),

View file

@ -88,6 +88,10 @@ where
}
Some(doc_rule.rules.read_with(guard).0.iter())
},
CssRule::Container(ref lock) => {
let container_rule = lock.read_with(guard);
Some(container_rule.rules.read_with(guard).0.iter())
},
CssRule::Media(ref lock) => {
let media_rule = lock.read_with(guard);
if !C::process_media(guard, device, quirks_mode, media_rule) {

View file

@ -206,7 +206,6 @@ impl Default for ScrollDirection {
}
}
// Avoid name collision in cbindgen with StyleOrientation.
pub use self::ScrollDirection as Orientation;
/// Scroll-timeline offsets. We treat None as an empty vector.

View file

@ -370,6 +370,7 @@ impl SanitizationKind {
CssRule::Media(..) |
CssRule::Supports(..) |
CssRule::Import(..) |
CssRule::Container(..) |
// TODO(emilio): Perhaps Layer should not be always sanitized? But
// we sanitize @media and co, so this seems safer for now.
CssRule::LayerStatement(..) |

View file

@ -671,7 +671,8 @@ impl MaybeNew for ViewportConstraints {
builder: StyleBuilder::for_inheritance(device, None, None),
cached_system_font: None,
in_media_query: false,
quirks_mode: quirks_mode,
quirks_mode,
container_info: None,
for_smil_animation: false,
for_non_inherited_property: None,
rule_cache_conditions: RefCell::new(&mut conditions),

View file

@ -28,8 +28,9 @@ use crate::selector_parser::{PerPseudoElementMap, PseudoElement, SelectorImpl, S
use crate::shared_lock::{Locked, SharedRwLockReadGuard, StylesheetGuards};
use crate::stylesheet_set::{DataValidity, DocumentStylesheetSet, SheetRebuildKind};
use crate::stylesheet_set::{DocumentStylesheetFlusher, SheetCollectionFlusher};
use crate::stylesheets::container_rule::ContainerCondition;
use crate::stylesheets::keyframes_rule::KeyframesAnimation;
use crate::stylesheets::layer_rule::{LayerId, LayerName, LayerOrder};
use crate::stylesheets::layer_rule::{LayerName, LayerOrder};
use crate::stylesheets::viewport_rule::{self, MaybeNew, ViewportRule};
#[cfg(feature = "gecko")]
use crate::stylesheets::{
@ -39,7 +40,6 @@ use crate::stylesheets::{
CssRule, EffectiveRulesIterator, Origin, OriginSet, PageRule, PerOrigin, PerOriginIter,
};
use crate::stylesheets::{StyleRule, StylesheetContents, StylesheetInDocument};
use crate::thread_state::{self, ThreadState};
use crate::AllocErr;
use crate::{Atom, LocalName, Namespace, ShrinkIfNeeded, WeakAtom};
use fxhash::FxHashMap;
@ -49,7 +49,7 @@ use malloc_size_of::MallocUnconditionalShallowSizeOf;
use selectors::attr::{CaseSensitivity, NamespaceConstraint};
use selectors::bloom::BloomFilter;
use selectors::matching::VisitedHandlingMode;
use selectors::matching::{matches_selector, ElementSelectorFlags, MatchingContext, MatchingMode};
use selectors::matching::{matches_selector, MatchingContext, MatchingMode, NeedsSelectorFlags};
use selectors::parser::{AncestorHashes, Combinator, Component, Selector, SelectorIter};
use selectors::visitor::SelectorVisitor;
use selectors::NthIndexCache;
@ -549,6 +549,47 @@ impl From<StyleRuleInclusion> for RuleInclusion {
}
}
/// A struct containing state from ancestor rules like @layer / @import /
/// @container.
struct ContainingRuleState {
layer_name: LayerName,
layer_id: LayerId,
container_condition_id: ContainerConditionId,
}
impl Default for ContainingRuleState {
fn default() -> Self {
Self {
layer_name: LayerName::new_empty(),
layer_id: LayerId::root(),
container_condition_id: ContainerConditionId::none(),
}
}
}
struct SavedContainingRuleState {
layer_name_len: usize,
layer_id: LayerId,
container_condition_id: ContainerConditionId,
}
impl ContainingRuleState {
fn save(&self) -> SavedContainingRuleState {
SavedContainingRuleState {
layer_name_len: self.layer_name.0.len(),
layer_id: self.layer_id,
container_condition_id: self.container_condition_id,
}
}
fn restore(&mut self, saved: &SavedContainingRuleState) {
debug_assert!(self.layer_name.0.len() >= saved.layer_name_len);
self.layer_name.0.truncate(saved.layer_name_len);
self.layer_id = saved.layer_id;
self.container_condition_id = saved.container_condition_id;
}
}
impl Stylist {
/// Construct a new `Stylist`, using given `Device` and `QuirksMode`.
/// If more members are added here, think about whether they should
@ -1073,38 +1114,12 @@ impl Stylist {
{
debug_assert!(pseudo.is_lazy());
// Apply the selector flags. We should be in sequential mode
// already, so we can directly apply the parent flags.
let mut set_selector_flags = |element: &E, flags: ElementSelectorFlags| {
if cfg!(feature = "servo") {
// Servo calls this function from the worker, but only for internal
// pseudos, so we should never generate selector flags here.
unreachable!("internal pseudo generated slow selector flags?");
}
// No need to bother setting the selector flags when we're computing
// default styles.
if rule_inclusion == RuleInclusion::DefaultOnly {
return;
}
// Gecko calls this from sequential mode, so we can directly apply
// the flags.
debug_assert_eq!(thread_state::get(), ThreadState::LAYOUT);
let self_flags = flags.for_self();
if !self_flags.is_empty() {
unsafe {
element.set_selector_flags(self_flags);
}
}
let parent_flags = flags.for_parent();
if !parent_flags.is_empty() {
if let Some(p) = element.parent_element() {
unsafe {
p.set_selector_flags(parent_flags);
}
}
}
// No need to bother setting the selector flags when we're computing
// default styles.
let needs_selector_flags = if rule_inclusion == RuleInclusion::DefaultOnly {
NeedsSelectorFlags::No
} else {
NeedsSelectorFlags::Yes
};
let mut declarations = ApplicableDeclarationList::new();
@ -1113,6 +1128,7 @@ impl Stylist {
None,
None,
self.quirks_mode,
needs_selector_flags,
);
matching_context.pseudo_element_matching_fn = matching_fn;
@ -1126,7 +1142,6 @@ impl Stylist {
rule_inclusion,
&mut declarations,
&mut matching_context,
&mut set_selector_flags,
);
if declarations.is_empty() && is_probe {
@ -1144,6 +1159,7 @@ impl Stylist {
None,
VisitedHandlingMode::RelevantLinkVisited,
self.quirks_mode,
needs_selector_flags,
);
matching_context.pseudo_element_matching_fn = matching_fn;
@ -1156,7 +1172,6 @@ impl Stylist {
rule_inclusion,
&mut declarations,
&mut matching_context,
&mut set_selector_flags,
);
if !declarations.is_empty() {
let rule_node = self.rule_tree.insert_ordered_rules_with_important(
@ -1269,7 +1284,7 @@ impl Stylist {
}
/// Returns the applicable CSS declarations for the given element.
pub fn push_applicable_declarations<E, F>(
pub fn push_applicable_declarations<E>(
&self,
element: E,
pseudo_element: Option<&PseudoElement>,
@ -1279,10 +1294,8 @@ impl Stylist {
rule_inclusion: RuleInclusion,
applicable_declarations: &mut ApplicableDeclarationList,
context: &mut MatchingContext<E::Impl>,
flags_setter: &mut F,
) where
E: TElement,
F: FnMut(&E, ElementSelectorFlags),
{
RuleCollector::new(
self,
@ -1294,7 +1307,6 @@ impl Stylist {
rule_inclusion,
applicable_declarations,
context,
flags_setter,
)
.collect_all();
}
@ -1374,16 +1386,15 @@ impl Stylist {
/// Computes the match results of a given element against the set of
/// revalidation selectors.
pub fn match_revalidation_selectors<E, F>(
pub fn match_revalidation_selectors<E>(
&self,
element: E,
bloom: Option<&BloomFilter>,
nth_index_cache: &mut NthIndexCache,
flags_setter: &mut F,
needs_selector_flags: NeedsSelectorFlags,
) -> SmallBitVec
where
E: TElement,
F: FnMut(&E, ElementSelectorFlags),
{
// NB: `MatchingMode` doesn't really matter, given we don't share style
// between pseudos.
@ -1392,6 +1403,7 @@ impl Stylist {
bloom,
Some(nth_index_cache),
self.quirks_mode,
needs_selector_flags,
);
// Note that, by the time we're revalidating, we're guaranteed that the
@ -1414,7 +1426,6 @@ impl Stylist {
Some(&selector_and_hashes.hashes),
&element,
matching_context,
flags_setter,
));
true
},
@ -1437,7 +1448,6 @@ impl Stylist {
Some(&selector_and_hashes.hashes),
&element,
&mut matching_context,
flags_setter,
));
true
},
@ -2061,6 +2071,17 @@ impl PartElementAndPseudoRules {
}
}
/// The id of a given layer, a sequentially-increasing identifier.
#[derive(Clone, Copy, Debug, Eq, MallocSizeOf, PartialEq, PartialOrd, Ord)]
pub struct LayerId(u16);
impl LayerId {
/// The id of the root layer.
pub const fn root() -> Self {
Self(0)
}
}
#[derive(Clone, Debug, MallocSizeOf)]
struct CascadeLayer {
id: LayerId,
@ -2078,6 +2099,35 @@ impl CascadeLayer {
}
}
/// The id of a given container condition, a sequentially-increasing identifier
/// for a given style set.
#[derive(Clone, Copy, Debug, Eq, MallocSizeOf, PartialEq, PartialOrd, Ord)]
pub struct ContainerConditionId(u16);
impl ContainerConditionId {
/// A special id that represents no container rule all.
pub const fn none() -> Self {
Self(0)
}
}
#[derive(Clone, Debug, MallocSizeOf)]
struct ContainerConditionReference {
parent: ContainerConditionId,
#[ignore_malloc_size_of = "Arc"]
condition: Option<Arc<ContainerCondition>>,
}
impl ContainerConditionReference {
const fn none() -> Self {
Self {
parent: ContainerConditionId::none(),
condition: None,
}
}
}
/// Data resulting from performing the CSS cascade that is specific to a given
/// origin.
///
@ -2149,6 +2199,9 @@ pub struct CascadeData {
/// The list of cascade layers, indexed by their layer id.
layers: SmallVec<[CascadeLayer; 1]>,
/// The list of container conditions, indexed by their id.
container_conditions: SmallVec<[ContainerConditionReference; 1]>,
/// Effective media query results cached from the last rebuild.
effective_media_query_results: EffectiveMediaQueryResults,
@ -2191,6 +2244,7 @@ impl CascadeData {
animations: Default::default(),
layer_id: Default::default(),
layers: smallvec::smallvec![CascadeLayer::root()],
container_conditions: smallvec::smallvec![ContainerConditionReference::none()],
extra_data: ExtraStyleData::default(),
effective_media_query_results: EffectiveMediaQueryResults::new(),
rules_source_order: 0,
@ -2305,6 +2359,23 @@ impl CascadeData {
self.layers[id.0 as usize].order
}
pub(crate) fn container_condition_matches<E>(&self, mut id: ContainerConditionId, stylist: &Stylist, element: E) -> bool
where
E: TElement,
{
loop {
let condition_ref = &self.container_conditions[id.0 as usize];
let condition = match condition_ref.condition {
None => return true,
Some(ref c) => c,
};
if !condition.matches(stylist.device(), element) {
return false;
}
id = condition_ref.parent;
}
}
fn did_finish_rebuild(&mut self) {
self.shrink_maps_if_needed();
self.compute_layer_order();
@ -2426,8 +2497,7 @@ impl CascadeData {
stylesheet: &S,
guard: &SharedRwLockReadGuard,
rebuild_kind: SheetRebuildKind,
mut current_layer: &mut LayerName,
current_layer_id: LayerId,
containing_rule_state: &mut ContainingRuleState,
mut precomputed_pseudo_element_decls: Option<&mut PrecomputedPseudoElementDeclarations>,
) -> Result<(), AllocErr>
where
@ -2450,7 +2520,7 @@ impl CascadeData {
if pseudo.is_precomputed() {
debug_assert!(selector.is_universal());
debug_assert_eq!(stylesheet.contents().origin, Origin::UserAgent);
debug_assert_eq!(current_layer_id, LayerId::root());
debug_assert_eq!(containing_rule_state.layer_id, LayerId::root());
precomputed_pseudo_element_decls
.as_mut()
@ -2477,7 +2547,8 @@ impl CascadeData {
hashes,
locked.clone(),
self.rules_source_order,
current_layer_id,
containing_rule_state.layer_id,
containing_rule_state.container_condition_id,
);
if rebuild_kind.should_rebuild_invalidation() {
@ -2555,7 +2626,7 @@ impl CascadeData {
self.animations.try_insert_with(
name,
animation,
current_layer_id,
containing_rule_state.layer_id,
compare_keyframes_in_same_layer,
)?;
},
@ -2564,25 +2635,35 @@ impl CascadeData {
// Note: Bug 1733260: we may drop @scroll-timeline rule once this spec issue
// https://github.com/w3c/csswg-drafts/issues/6674 gets landed.
self.extra_data
.add_scroll_timeline(guard, rule, current_layer_id)?;
.add_scroll_timeline(guard, rule, containing_rule_state.layer_id)?;
},
#[cfg(feature = "gecko")]
CssRule::FontFace(ref rule) => {
self.extra_data.add_font_face(rule, current_layer_id);
// NOTE(emilio): We don't care about container_condition_id
// because:
//
// Global, name-defining at-rules such as @keyframes or
// @font-face or @layer that are defined inside container
// queries are not constrained by the container query
// conditions.
//
// https://drafts.csswg.org/css-contain-3/#container-rule
// (Same elsewhere)
self.extra_data.add_font_face(rule, containing_rule_state.layer_id);
},
#[cfg(feature = "gecko")]
CssRule::FontFeatureValues(ref rule) => {
self.extra_data
.add_font_feature_values(rule, current_layer_id);
.add_font_feature_values(rule, containing_rule_state.layer_id);
},
#[cfg(feature = "gecko")]
CssRule::CounterStyle(ref rule) => {
self.extra_data
.add_counter_style(guard, rule, current_layer_id)?;
.add_counter_style(guard, rule, containing_rule_state.layer_id)?;
},
#[cfg(feature = "gecko")]
CssRule::Page(ref rule) => {
self.extra_data.add_page(guard, rule, current_layer_id)?;
self.extra_data.add_page(guard, rule, containing_rule_state.layer_id)?;
},
CssRule::Viewport(..) => {},
_ => {
@ -2623,7 +2704,7 @@ impl CascadeData {
if let Some(id) = data.layer_id.get(layer) {
return *id;
}
let id = LayerId(data.layers.len() as u32);
let id = LayerId(data.layers.len() as u16);
let parent_layer_id = if layer.layer_names().len() > 1 {
let mut parent = layer.clone();
@ -2654,9 +2735,8 @@ impl CascadeData {
fn maybe_register_layers(
data: &mut CascadeData,
name: Option<&LayerName>,
current_layer: &mut LayerName,
pushed_layers: &mut usize,
) -> LayerId {
containing_rule_state: &mut ContainingRuleState,
) {
let anon_name;
let name = match name {
Some(name) => name,
@ -2665,19 +2745,14 @@ impl CascadeData {
&anon_name
},
};
let mut id = LayerId::root();
for name in name.layer_names() {
current_layer.0.push(name.clone());
id = maybe_register_layer(data, &current_layer);
*pushed_layers += 1;
containing_rule_state.layer_name.0.push(name.clone());
containing_rule_state.layer_id = maybe_register_layer(data, &containing_rule_state.layer_name);
}
debug_assert_ne!(id, LayerId::root());
id
debug_assert_ne!(containing_rule_state.layer_id, LayerId::root());
}
let mut layer_names_to_pop = 0;
let mut children_layer_id = current_layer_id;
let saved_containing_rule_state = containing_rule_state.save();
match *rule {
CssRule::Import(ref lock) => {
let import_rule = lock.read_with(guard);
@ -2686,11 +2761,10 @@ impl CascadeData {
.saw_effective(import_rule);
}
if let Some(ref layer) = import_rule.layer {
children_layer_id = maybe_register_layers(
maybe_register_layers(
self,
layer.name.as_ref(),
&mut current_layer,
&mut layer_names_to_pop,
containing_rule_state
);
}
},
@ -2702,25 +2776,29 @@ impl CascadeData {
},
CssRule::LayerBlock(ref lock) => {
let layer_rule = lock.read_with(guard);
children_layer_id = maybe_register_layers(
maybe_register_layers(
self,
layer_rule.name.as_ref(),
&mut current_layer,
&mut layer_names_to_pop,
containing_rule_state,
);
},
CssRule::LayerStatement(ref lock) => {
let layer_rule = lock.read_with(guard);
for name in &*layer_rule.names {
let mut pushed = 0;
// There are no children, so we can ignore the
// return value.
maybe_register_layers(self, Some(name), &mut current_layer, &mut pushed);
for _ in 0..pushed {
current_layer.0.pop();
}
maybe_register_layers(self, Some(name), containing_rule_state);
// Register each layer individually.
containing_rule_state.restore(&saved_containing_rule_state);
}
},
CssRule::Container(ref lock) => {
let container_rule = lock.read_with(guard);
let id = ContainerConditionId(self.container_conditions.len() as u16);
self.container_conditions.push(ContainerConditionReference {
parent: containing_rule_state.container_condition_id,
condition: Some(container_rule.condition.clone()),
});
containing_rule_state.container_condition_id = id;
},
// We don't care about any other rule.
_ => {},
}
@ -2733,15 +2811,12 @@ impl CascadeData {
stylesheet,
guard,
rebuild_kind,
current_layer,
children_layer_id,
containing_rule_state,
precomputed_pseudo_element_decls.as_deref_mut(),
)?;
}
for _ in 0..layer_names_to_pop {
current_layer.0.pop();
}
containing_rule_state.restore(&saved_containing_rule_state);
}
Ok(())
@ -2770,7 +2845,7 @@ impl CascadeData {
self.effective_media_query_results.saw_effective(contents);
}
let mut current_layer = LayerName::new_empty();
let mut state = ContainingRuleState::default();
self.add_rule_list(
contents.rules(guard).iter(),
device,
@ -2778,8 +2853,7 @@ impl CascadeData {
stylesheet,
guard,
rebuild_kind,
&mut current_layer,
LayerId::root(),
&mut state,
precomputed_pseudo_element_decls.as_deref_mut(),
)?;
@ -2827,6 +2901,7 @@ impl CascadeData {
CssRule::Style(..) |
CssRule::Namespace(..) |
CssRule::FontFace(..) |
CssRule::Container(..) |
CssRule::CounterStyle(..) |
CssRule::Supports(..) |
CssRule::Keyframes(..) |
@ -2904,6 +2979,8 @@ impl CascadeData {
self.layer_id.clear();
self.layers.clear();
self.layers.push(CascadeLayer::root());
self.container_conditions.clear();
self.container_conditions.push(ContainerConditionReference::none());
#[cfg(feature = "gecko")]
{
self.extra_data.clear();
@ -2998,6 +3075,9 @@ pub struct Rule {
/// The current layer id of this style rule.
pub layer_id: LayerId,
/// The current @container rule id.
pub container_condition_id: ContainerConditionId,
/// The actual style rule.
#[cfg_attr(
feature = "gecko",
@ -3043,6 +3123,7 @@ impl Rule {
style_rule: Arc<Locked<StyleRule>>,
source_order: u32,
layer_id: LayerId,
container_condition_id: ContainerConditionId,
) -> Self {
Rule {
selector,
@ -3050,10 +3131,17 @@ impl Rule {
style_rule,
source_order,
layer_id,
container_condition_id,
}
}
}
// The size of this is critical to performance on the bloom-basic
// microbenchmark.
// When iterating over a large Rule array, we want to be able to fast-reject
// selectors (with the inline hashes) with as few cache misses as possible.
size_of_test!(Rule, 40);
/// A function to be able to test the revalidation stuff.
pub fn needs_revalidation_for_testing(s: &Selector<SelectorImpl>) -> bool {
let mut attribute_dependencies = Default::default();

View file

@ -16,6 +16,13 @@ use crate::stylist::RuleInclusion;
use crate::traversal_flags::TraversalFlags;
use selectors::NthIndexCache;
use smallvec::SmallVec;
use std::collections::HashMap;
/// A cache from element reference to known-valid computed style.
pub type UndisplayedStyleCache = HashMap<
selectors::OpaqueElement,
servo_arc::Arc<crate::properties::ComputedValues>,
>;
/// A per-traversal-level chunk of data. This is sent down by the traversal, and
/// currently only holds the dom depth for the bloom filter.
@ -294,6 +301,7 @@ pub fn resolve_style<E>(
element: E,
rule_inclusion: RuleInclusion,
pseudo: Option<&PseudoElement>,
mut undisplayed_style_cache: Option<&mut UndisplayedStyleCache>,
) -> ElementStyles
where
E: TElement,
@ -304,6 +312,11 @@ where
element.borrow_data().map_or(true, |d| !d.has_styles()),
"Why are we here?"
);
debug_assert!(
rule_inclusion == RuleInclusion::All || undisplayed_style_cache.is_none(),
"can't use the cache for default styles only"
);
let mut ancestors_requiring_style_resolution = SmallVec::<[E; 16]>::new();
// Clear the bloom filter, just in case the caller is reusing TLS.
@ -320,6 +333,12 @@ where
}
}
}
if let Some(ref mut cache) = undisplayed_style_cache {
if let Some(s) = cache.get(&current.opaque()) {
style = Some(s.clone());
break;
}
}
ancestors_requiring_style_resolution.push(current);
ancestor = current.traversal_parent();
}
@ -337,7 +356,9 @@ where
}
ancestor = ancestor.unwrap().traversal_parent();
layout_parent_style = ancestor.map(|a| a.borrow_data().unwrap().styles.primary().clone());
layout_parent_style = ancestor.and_then(|a| {
a.borrow_data().map(|data| data.styles.primary().clone())
});
}
for ancestor in ancestors_requiring_style_resolution.iter().rev() {
@ -360,18 +381,27 @@ where
layout_parent_style = style.clone();
}
if let Some(ref mut cache) = undisplayed_style_cache {
cache.insert(ancestor.opaque(), style.clone().unwrap());
}
context.thread_local.bloom_filter.push(*ancestor);
}
context.thread_local.bloom_filter.assert_complete(element);
StyleResolverForElement::new(
let styles: ElementStyles = StyleResolverForElement::new(
element,
context,
rule_inclusion,
PseudoElementResolution::Force,
)
.resolve_style(style.as_deref(), layout_parent_style.as_deref())
.into()
.into();
if let Some(ref mut cache) = undisplayed_style_cache {
cache.insert(element.opaque(), styles.primary().clone());
}
styles
}
/// Calculates the style for a single node.

File diff suppressed because it is too large Load diff

View file

@ -13,9 +13,11 @@ use crate::values::specified::box_ as specified;
pub use crate::values::specified::box_::{
AnimationName, AnimationTimeline, Appearance, BreakBetween, BreakWithin,
Clear as SpecifiedClear, Contain, ContentVisibility, Display, Float as SpecifiedFloat, Overflow,
OverflowAnchor, OverflowClipBox, OverscrollBehavior, ScrollSnapAlign, ScrollSnapAxis,
ScrollSnapStrictness, ScrollSnapType, ScrollbarGutter, TouchAction, TransitionProperty, WillChange,
Clear as SpecifiedClear, Contain, ContainerName, ContainerType, ContentVisibility, Display,
Float as SpecifiedFloat, Overflow, OverflowAnchor, OverflowClipBox,
OverscrollBehavior, ScrollSnapAlign, ScrollSnapAxis, ScrollSnapStop,
ScrollSnapStrictness, ScrollSnapType, ScrollbarGutter, TouchAction,
TransitionProperty, WillChange,
};
/// A computed value for the `vertical-align` property.

View file

@ -7,6 +7,7 @@
use crate::values::animated::color::RGBA as AnimatedRGBA;
use crate::values::animated::ToAnimatedValue;
use crate::values::generics::color::{GenericCaretColor, GenericColor, GenericColorOrAuto};
use crate::values::computed::percentage::Percentage;
use cssparser::{Color as CSSParserColor, RGBA};
use std::fmt;
use style_traits::{CssWriter, ToCss};
@ -20,7 +21,20 @@ pub type ColorPropertyValue = RGBA;
pub type MozFontSmoothingBackgroundColor = RGBA;
/// A computed value for `<color>`.
pub type Color = GenericColor<RGBA>;
pub type Color = GenericColor<RGBA, Percentage>;
impl ToCss for Color {
fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
where
W: fmt::Write,
{
match *self {
Self::Numeric(ref c) => c.to_css(dest),
Self::CurrentColor => CSSParserColor::CurrentColor.to_css(dest),
Self::ColorMix(ref m) => m.to_css(dest),
}
}
}
impl Color {
/// Returns a complex color value representing transparent.
@ -28,67 +42,21 @@ impl Color {
Color::rgba(RGBA::transparent())
}
/// Combine this complex color with the given foreground color into
/// a numeric RGBA color. It currently uses linear blending.
pub fn to_rgba(&self, fg_color: RGBA) -> RGBA {
// Common cases that the complex color is either pure numeric color or
// pure currentcolor.
if self.is_numeric() {
return self.color;
}
if self.is_currentcolor() {
return fg_color;
}
let ratios = &self.ratios;
let color = &self.color;
// For the more complicated case that the alpha value differs,
// we use the following formula to compute the components:
// alpha = self_alpha * bg_ratio + fg_alpha * fg_ratio
// color = (self_color * self_alpha * bg_ratio +
// fg_color * fg_alpha * fg_ratio) / alpha
let p1 = ratios.bg;
let a1 = color.alpha_f32();
let r1 = a1 * color.red_f32();
let g1 = a1 * color.green_f32();
let b1 = a1 * color.blue_f32();
let p2 = ratios.fg;
let a2 = fg_color.alpha_f32();
let r2 = a2 * fg_color.red_f32();
let g2 = a2 * fg_color.green_f32();
let b2 = a2 * fg_color.blue_f32();
let a = p1 * a1 + p2 * a2;
if a <= 0. {
return RGBA::transparent();
}
let a = a.min(1.);
let inv = 1. / a;
let r = (p1 * r1 + p2 * r2) * inv;
let g = (p1 * g1 + p2 * g2) * inv;
let b = (p1 * b1 + p2 * b2) * inv;
RGBA::from_floats(r, g, b, a)
/// Returns opaque black.
pub fn black() -> Color {
Color::rgba(RGBA::new(0, 0, 0, 255))
}
}
impl ToCss for Color {
fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
where
W: fmt::Write,
{
if self.is_currentcolor() {
return CSSParserColor::CurrentColor.to_css(dest);
}
if self.is_numeric() {
return self.color.to_css(dest);
}
Ok(())
/// Returns opaque white.
pub fn white() -> Color {
Color::rgba(RGBA::new(255, 255, 255, 255))
}
/// Combine this complex color with the given foreground color into
/// a numeric RGBA color.
pub fn into_rgba(mut self, current_color: RGBA) -> RGBA {
self.simplify(Some(&current_color));
*self.as_numeric().unwrap()
}
}

View file

@ -31,6 +31,9 @@ pub use specified::ImageRendering;
pub type Image =
generic::GenericImage<Gradient, MozImageRect, ComputedImageUrl, Color, Percentage, Resolution>;
// Images should remain small, see https://github.com/servo/servo/pull/18430
size_of_test!(Image, 40);
/// Computed values for a CSS gradient.
/// <https://drafts.csswg.org/css-images/#gradients>
pub type Gradient = generic::GenericGradient<
@ -47,8 +50,6 @@ pub type Gradient = generic::GenericGradient<
/// Computed values for CSS cross-fade
/// <https://drafts.csswg.org/css-images-4/#cross-fade-function>
pub type CrossFade = generic::CrossFade<Image, Color, Percentage>;
/// A computed percentage or nothing.
pub type PercentOrNone = generic::PercentOrNone<Percentage>;
/// A computed radial gradient ending shape.
pub type EndingShape = generic::GenericEndingShape<NonNegativeLength, NonNegativeLengthPercentage>;

View file

@ -40,7 +40,7 @@ impl ToComputedValue for specified::NoCalcLength {
context
.builder
.add_flags(ComputedValueFlags::USES_VIEWPORT_UNITS);
length.to_computed_value(context.viewport_size_for_viewport_unit_resolution())
length.to_computed_value(context)
},
specified::NoCalcLength::ServoCharacterWidth(length) => {
length.to_computed_value(context.style().get_font().clone_font_size().size())

View file

@ -16,6 +16,7 @@ use super::specified;
use super::{CSSFloat, CSSInteger};
use crate::computed_value_flags::ComputedValueFlags;
use crate::context::QuirksMode;
use crate::stylesheets::container_rule::ContainerInfo;
use crate::font_metrics::{FontMetrics, FontMetricsOrientation};
use crate::media_queries::Device;
#[cfg(feature = "gecko")]
@ -44,11 +45,12 @@ pub use self::basic_shape::FillRule;
pub use self::border::{BorderCornerRadius, BorderRadius, BorderSpacing};
pub use self::border::{BorderImageRepeat, BorderImageSideWidth};
pub use self::border::{BorderImageSlice, BorderImageWidth};
pub use self::box_::{AnimationIterationCount, AnimationName, AnimationTimeline, Contain};
pub use self::box_::{AnimationIterationCount, AnimationName, AnimationTimeline, Contain, ContainerName, ContainerType};
pub use self::box_::{Appearance, BreakBetween, BreakWithin, Clear, ContentVisibility, Float};
pub use self::box_::{Display, Overflow, OverflowAnchor, TransitionProperty};
pub use self::box_::{OverflowClipBox, OverscrollBehavior, Perspective, Resize, ScrollbarGutter};
pub use self::box_::{ScrollSnapAlign, ScrollSnapAxis, ScrollSnapStrictness, ScrollSnapType};
pub use self::box_::{ScrollSnapAlign, ScrollSnapAxis, ScrollSnapStop};
pub use self::box_::{ScrollSnapStrictness, ScrollSnapType};
pub use self::box_::{TouchAction, VerticalAlign, WillChange};
pub use self::color::{Color, ColorOrAuto, ColorPropertyValue, ColorScheme, PrintColorAdjust};
pub use self::column::ColumnCount;
@ -72,7 +74,7 @@ pub use self::list::ListStyleType;
pub use self::list::Quotes;
pub use self::motion::{OffsetPath, OffsetRotate};
pub use self::outline::OutlineStyle;
pub use self::page::{Orientation, PageName, PageSize, PaperSize};
pub use self::page::{PageOrientation, PageName, PageSize, PaperSize};
pub use self::percentage::{NonNegativePercentage, Percentage};
pub use self::position::AspectRatio;
pub use self::position::{
@ -97,6 +99,7 @@ pub use self::transform::{TransformOrigin, TransformStyle, Translate};
pub use self::ui::CursorImage;
pub use self::ui::{Cursor, MozForceBrokenImageIcon, UserSelect};
pub use super::specified::TextTransform;
pub use super::specified::ViewportVariant;
pub use super::specified::{BorderStyle, TextDecorationLine};
pub use super::{Auto, Either, None_};
pub use app_units::Au;
@ -169,6 +172,9 @@ pub struct Context<'a> {
/// values, which SMIL allows.
pub for_smil_animation: bool,
/// Returns the container information to evaluate a given container query.
pub container_info: Option<ContainerInfo>,
/// The property we are computing a value for, if it is a non-inherited
/// property. None if we are computed a value for an inherited property
/// or not computing for a property at all (e.g. in a media query
@ -197,6 +203,40 @@ impl<'a> Context<'a> {
in_media_query: true,
quirks_mode,
for_smil_animation: false,
container_info: None,
for_non_inherited_property: None,
rule_cache_conditions: RefCell::new(&mut conditions),
};
f(&context)
}
/// Creates a suitable context for container query evaluation for the style
/// specified.
pub fn for_container_query_evaluation<F, R>(
device: &Device,
container_info_and_style: Option<(ContainerInfo, Arc<ComputedValues>)>,
f: F,
) -> R
where
F: FnOnce(&Context) -> R,
{
let mut conditions = RuleCacheConditions::default();
let (container_info, style) = match container_info_and_style {
Some((ci, s)) => (Some(ci), Some(s)),
None => (None, None),
};
let style = style.as_ref().map(|s| &**s);
let quirks_mode = device.quirks_mode();
let context = Context {
builder: StyleBuilder::for_inheritance(device, style, None),
cached_system_font: None,
in_media_query: true,
quirks_mode,
for_smil_animation: false,
container_info,
for_non_inherited_property: None,
rule_cache_conditions: RefCell::new(&mut conditions),
};
@ -253,10 +293,13 @@ impl<'a> Context<'a> {
}
/// The current viewport size, used to resolve viewport units.
pub fn viewport_size_for_viewport_unit_resolution(&self) -> default::Size2D<Au> {
pub fn viewport_size_for_viewport_unit_resolution(
&self,
variant: ViewportVariant,
) -> default::Size2D<Au> {
self.builder
.device
.au_viewport_size_for_viewport_unit_resolution()
.au_viewport_size_for_viewport_unit_resolution(variant)
}
/// The default computed style we're getting our reset style from.
@ -300,8 +343,8 @@ impl<'a, 'cx, 'cx_a: 'cx, S: ToComputedValue + 'a> ComputedVecIter<'a, 'cx, 'cx_
/// Construct an iterator from a slice of specified values and a context
pub fn new(cx: &'cx Context<'cx_a>, values: &'a [S]) -> Self {
ComputedVecIter {
cx: cx,
values: values,
cx,
values,
}
}
}

View file

@ -11,7 +11,7 @@ use crate::values::generics::size::Size2D;
use crate::values::specified::page as specified;
pub use generics::page::GenericPageSize;
pub use generics::page::Orientation;
pub use generics::page::PageOrientation;
pub use generics::page::PaperSize;
pub use specified::PageName;
@ -26,7 +26,7 @@ pub enum PageSize {
/// Specified size, paper size, or paper size and orientation.
Size(Size2D<NonNegativeLength>),
/// `landscape` or `portrait` value, no specified size.
Orientation(Orientation),
Orientation(PageOrientation),
/// `auto` value
Auto,
}
@ -37,11 +37,11 @@ impl ToComputedValue for specified::PageSize {
fn to_computed_value(&self, ctx: &Context) -> Self::ComputedValue {
match &*self {
Self::Size(s) => PageSize::Size(s.to_computed_value(ctx)),
Self::PaperSize(p, Orientation::Landscape) => PageSize::Size(Size2D {
Self::PaperSize(p, PageOrientation::Landscape) => PageSize::Size(Size2D {
width: p.long_edge().to_computed_value(ctx),
height: p.short_edge().to_computed_value(ctx),
}),
Self::PaperSize(p, Orientation::Portrait) => PageSize::Size(Size2D {
Self::PaperSize(p, PageOrientation::Portrait) => PageSize::Size(Size2D {
width: p.short_edge().to_computed_value(ctx),
height: p.long_edge().to_computed_value(ctx),
}),

View file

@ -6,6 +6,7 @@
use crate::values::animated::ToAnimatedValue;
use crate::values::generics::NonNegative;
use crate::values::specified::percentage::ToPercentage;
use crate::values::{serialize_percentage, CSSFloat};
use crate::Zero;
use std::fmt;
@ -64,6 +65,12 @@ impl Zero for Percentage {
}
}
impl ToPercentage for Percentage {
fn to_percentage(&self) -> CSSFloat {
self.0
}
}
impl std::ops::AddAssign for Percentage {
fn add_assign(&mut self, other: Self) {
self.0 += other.0

View file

@ -72,6 +72,16 @@ impl ComputeSquaredDistance for Ratio {
}
}
impl Zero for Ratio {
fn zero() -> Self {
Self::new(Zero::zero(), One::one())
}
fn is_zero(&self) -> bool {
self.0.is_zero()
}
}
impl Ratio {
/// Returns a new Ratio.
#[inline]

View file

@ -8,7 +8,6 @@ use crate::values::computed::color::Color;
use crate::values::computed::url::ComputedUrl;
use crate::values::computed::{LengthPercentage, NonNegativeLengthPercentage, Opacity};
use crate::values::generics::svg as generic;
use crate::values::RGBA;
use crate::Zero;
pub use crate::values::specified::{DProperty, MozContextProperties, SVGPaintOrder};
@ -22,9 +21,8 @@ pub type SVGPaintKind = generic::GenericSVGPaintKind<Color, ComputedUrl>;
impl SVGPaint {
/// Opaque black color
pub fn black() -> Self {
let rgba = RGBA::from_floats(0., 0., 0., 1.).into();
SVGPaint {
kind: generic::SVGPaintKind::Color(rgba),
kind: generic::SVGPaintKind::Color(Color::black()),
fallback: generic::SVGPaintFallback::Unset,
}
}

View file

@ -45,13 +45,33 @@ pub enum SortKey {
Cap,
Ch,
Deg,
Dvb,
Dvh,
Dvi,
Dvmax,
Dvmin,
Dvw,
Em,
Ex,
Ic,
Lvb,
Lvh,
Lvi,
Lvmax,
Lvmin,
Lvw,
Px,
Rem,
Sec,
Svb,
Svh,
Svi,
Svmax,
Svmin,
Svw,
Vb,
Vh,
Vi,
Vmax,
Vmin,
Vw,

View file

@ -4,81 +4,268 @@
//! Generic types for color properties.
/// Ratios representing the contribution of color and currentcolor to
/// the final color value.
///
/// NOTE(emilio): For animated colors, the sum of these two might be more than
/// one (because the background color would've been scaled down already). So
/// beware that it is not generally safe to assume that if bg is 1 then fg is 0,
/// for example.
#[derive(Clone, Copy, Debug, MallocSizeOf, PartialEq, ToAnimatedValue, ToShmem)]
#[repr(C)]
pub struct ComplexColorRatios {
/// Numeric color contribution.
pub bg: f32,
/// currentcolor contribution.
pub fg: f32,
}
impl ComplexColorRatios {
/// Ratios representing a `Numeric` color.
pub const NUMERIC: ComplexColorRatios = ComplexColorRatios { bg: 1., fg: 0. };
/// Ratios representing the `CurrentColor` color.
pub const CURRENT_COLOR: ComplexColorRatios = ComplexColorRatios { bg: 0., fg: 1. };
}
use std::fmt::{self, Write};
use style_traits::{CssWriter, ParseError, ToCss};
use crate::values::{Parse, ParserContext, Parser};
use crate::values::specified::percentage::ToPercentage;
use crate::values::animated::ToAnimatedValue;
use crate::values::animated::color::RGBA as AnimatedRGBA;
/// This struct represents a combined color from a numeric color and
/// the current foreground color (currentcolor keyword).
#[derive(Clone, Copy, Debug, MallocSizeOf, PartialEq, ToAnimatedValue, ToShmem)]
#[derive(Clone, Debug, MallocSizeOf, PartialEq, ToAnimatedValue, ToShmem)]
#[repr(C)]
pub struct GenericColor<RGBA> {
pub enum GenericColor<RGBA, Percentage> {
/// The actual numeric color.
pub color: RGBA,
/// The ratios of mixing between numeric and currentcolor.
/// The formula is: `color * ratios.bg + currentcolor * ratios.fg`.
pub ratios: ComplexColorRatios,
Numeric(RGBA),
/// The `CurrentColor` keyword.
CurrentColor,
/// The color-mix() function.
ColorMix(Box<GenericColorMix<Self, Percentage>>),
}
/// A color space as defined in [1].
///
/// [1]: https://drafts.csswg.org/css-color-4/#typedef-color-space
#[derive(Clone, Copy, Debug, Eq, MallocSizeOf, Parse, PartialEq, ToAnimatedValue, ToComputedValue, ToCss, ToResolvedValue, ToShmem)]
#[repr(u8)]
pub enum ColorSpace {
/// The sRGB color space.
Srgb,
/// The linear-sRGB color space.
LinearSrgb,
/// The CIEXYZ color space.
#[parse(aliases = "xyz-d65")]
Xyz,
/// https://drafts.csswg.org/css-color-4/#valdef-color-xyz
XyzD50,
/// The CIELAB color space.
Lab,
/// https://drafts.csswg.org/css-color-4/#valdef-hsl-hsl
Hsl,
/// https://drafts.csswg.org/css-color-4/#valdef-hwb-hwb
Hwb,
/// The CIELAB color space, expressed in cylindrical coordinates.
Lch,
// TODO: Oklab, Lch
}
impl ColorSpace {
/// Returns whether this is a `<polar-color-space>`.
pub fn is_polar(self) -> bool {
match self {
Self::Srgb | Self::LinearSrgb | Self::Xyz | Self::XyzD50 | Self::Lab => false,
Self::Hsl | Self::Hwb | Self::Lch => true,
}
}
}
/// A hue-interpolation-method as defined in [1].
///
/// [1]: https://drafts.csswg.org/css-color-4/#typedef-hue-interpolation-method
#[derive(Clone, Copy, Debug, Eq, MallocSizeOf, Parse, PartialEq, ToAnimatedValue, ToComputedValue, ToCss, ToResolvedValue, ToShmem)]
#[repr(u8)]
pub enum HueInterpolationMethod {
/// https://drafts.csswg.org/css-color-4/#shorter
Shorter,
/// https://drafts.csswg.org/css-color-4/#longer
Longer,
/// https://drafts.csswg.org/css-color-4/#increasing
Increasing,
/// https://drafts.csswg.org/css-color-4/#decreasing
Decreasing,
/// https://drafts.csswg.org/css-color-4/#specified
Specified,
}
/// https://drafts.csswg.org/css-color-4/#color-interpolation-method
#[derive(Clone, Copy, Debug, Eq, MallocSizeOf, PartialEq, ToShmem, ToAnimatedValue, ToComputedValue, ToResolvedValue)]
#[repr(C)]
pub struct ColorInterpolationMethod {
/// The color-space the interpolation should be done in.
pub space: ColorSpace,
/// The hue interpolation method.
pub hue: HueInterpolationMethod,
}
impl ColorInterpolationMethod {
/// Returns the srgb interpolation method.
pub fn srgb() -> Self {
Self {
space: ColorSpace::Srgb,
hue: HueInterpolationMethod::Shorter,
}
}
}
impl Parse for ColorInterpolationMethod {
fn parse<'i, 't>(
_: &ParserContext,
input: &mut Parser<'i, 't>,
) -> Result<Self, ParseError<'i>> {
input.expect_ident_matching("in")?;
let space = ColorSpace::parse(input)?;
// https://drafts.csswg.org/css-color-4/#hue-interpolation
// Unless otherwise specified, if no specific hue interpolation
// algorithm is selected by the host syntax, the default is shorter.
let hue = if space.is_polar() {
input.try_parse(|input| -> Result<_, ParseError<'i>> {
let hue = HueInterpolationMethod::parse(input)?;
input.expect_ident_matching("hue")?;
Ok(hue)
}).unwrap_or(HueInterpolationMethod::Shorter)
} else {
HueInterpolationMethod::Shorter
};
Ok(Self { space, hue })
}
}
impl ToCss for ColorInterpolationMethod {
fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
where
W: Write,
{
dest.write_str("in ")?;
self.space.to_css(dest)?;
if self.hue != HueInterpolationMethod::Shorter {
dest.write_char(' ')?;
self.hue.to_css(dest)?;
dest.write_str(" hue")?;
}
Ok(())
}
}
/// A restricted version of the css `color-mix()` function, which only supports
/// percentages.
///
/// https://drafts.csswg.org/css-color-5/#color-mix
#[derive(Clone, Debug, MallocSizeOf, PartialEq, ToAnimatedValue, ToComputedValue, ToResolvedValue, ToShmem)]
#[allow(missing_docs)]
#[repr(C)]
pub struct GenericColorMix<Color, Percentage> {
pub interpolation: ColorInterpolationMethod,
pub left: Color,
pub left_percentage: Percentage,
pub right: Color,
pub right_percentage: Percentage,
pub normalize_weights: bool,
}
pub use self::GenericColorMix as ColorMix;
impl<Color: ToCss, Percentage: ToCss + ToPercentage> ToCss for ColorMix<Color, Percentage> {
fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
where
W: Write,
{
fn can_omit<Percentage: ToPercentage>(percent: &Percentage, other: &Percentage, is_left: bool) -> bool {
if percent.is_calc() {
return false;
}
if percent.to_percentage() == 0.5 {
return other.to_percentage() == 0.5;
}
if is_left {
return false;
}
(1.0 - percent.to_percentage() - other.to_percentage()).abs() <= f32::EPSILON
}
dest.write_str("color-mix(")?;
self.interpolation.to_css(dest)?;
dest.write_str(", ")?;
self.left.to_css(dest)?;
if !can_omit(&self.left_percentage, &self.right_percentage, true) {
dest.write_str(" ")?;
self.left_percentage.to_css(dest)?;
}
dest.write_str(", ")?;
self.right.to_css(dest)?;
if !can_omit(&self.right_percentage, &self.left_percentage, false) {
dest.write_str(" ")?;
self.right_percentage.to_css(dest)?;
}
dest.write_str(")")
}
}
impl<RGBA, Percentage> ColorMix<GenericColor<RGBA, Percentage>, Percentage> {
fn to_rgba(&self) -> Option<RGBA>
where
RGBA: Clone + ToAnimatedValue<AnimatedValue = AnimatedRGBA>,
Percentage: ToPercentage,
{
use crate::values::animated::color::Color as AnimatedColor;
let left = self.left.as_numeric()?.clone().to_animated_value();
let right = self.right.as_numeric()?.clone().to_animated_value();
Some(ToAnimatedValue::from_animated_value(AnimatedColor::mix(
&self.interpolation,
&left,
self.left_percentage.to_percentage(),
&right,
self.right_percentage.to_percentage(),
self.normalize_weights,
)))
}
}
pub use self::GenericColor as Color;
impl Color<cssparser::RGBA> {
/// Returns a color value representing currentcolor.
pub fn currentcolor() -> Self {
Color {
color: cssparser::RGBA::transparent(),
ratios: ComplexColorRatios::CURRENT_COLOR,
impl<RGBA, Percentage> Color<RGBA, Percentage> {
/// Returns the numeric rgba value if this color is numeric, or None
/// otherwise.
pub fn as_numeric(&self) -> Option<&RGBA> {
match *self {
Self::Numeric(ref rgba) => Some(rgba),
_ => None,
}
}
}
impl<RGBA> Color<RGBA> {
/// Create a color based upon the specified ratios.
pub fn new(color: RGBA, ratios: ComplexColorRatios) -> Self {
Self { color, ratios }
/// Simplifies the color-mix()es to the extent possible given a current
/// color (or not).
pub fn simplify(&mut self, current_color: Option<&RGBA>)
where
RGBA: Clone + ToAnimatedValue<AnimatedValue = AnimatedRGBA>,
Percentage: ToPercentage,
{
match *self {
Self::Numeric(..) => {},
Self::CurrentColor => {
if let Some(c) = current_color {
*self = Self::Numeric(c.clone());
}
},
Self::ColorMix(ref mut mix) => {
mix.left.simplify(current_color);
mix.right.simplify(current_color);
if let Some(mix) = mix.to_rgba() {
*self = Self::Numeric(mix);
}
},
}
}
/// Returns a color value representing currentcolor.
pub fn currentcolor() -> Self {
Self::CurrentColor
}
/// Returns a numeric color representing the given RGBA value.
pub fn rgba(color: RGBA) -> Self {
Self {
color,
ratios: ComplexColorRatios::NUMERIC,
}
}
/// Whether it is a numeric color (no currentcolor component).
pub fn is_numeric(&self) -> bool {
self.ratios == ComplexColorRatios::NUMERIC
Self::Numeric(color)
}
/// Whether it is a currentcolor value (no numeric color component).
pub fn is_currentcolor(&self) -> bool {
self.ratios == ComplexColorRatios::CURRENT_COLOR
matches!(*self, Self::CurrentColor)
}
}
impl<RGBA> From<RGBA> for Color<RGBA> {
fn from(color: RGBA) -> Self {
Self::rgba(color)
/// Whether it is a numeric color (no currentcolor component).
pub fn is_numeric(&self) -> bool {
matches!(*self, Self::Numeric(..))
}
}

View file

@ -8,6 +8,7 @@
use crate::custom_properties;
use crate::values::generics::position::PositionComponent;
use crate::values::generics::Optional;
use crate::values::serialize_atom_identifier;
use crate::Atom;
use crate::Zero;
@ -71,20 +72,6 @@ pub struct GenericCrossFade<Image, Color, Percentage> {
pub elements: crate::OwnedSlice<GenericCrossFadeElement<Image, Color, Percentage>>,
}
/// A `<percent> | none` value. Represents optional percentage values
/// assosicated with cross-fade images.
#[derive(
Clone, Debug, MallocSizeOf, PartialEq, ToComputedValue, ToResolvedValue, ToShmem, ToCss,
)]
#[repr(C, u8)]
pub enum PercentOrNone<Percentage> {
/// `none` variant.
#[css(skip)]
None,
/// A percentage variant.
Percent(Percentage),
}
/// An optional percent and a cross fade image.
#[derive(
Clone, Debug, MallocSizeOf, PartialEq, ToComputedValue, ToResolvedValue, ToShmem, ToCss,
@ -92,7 +79,7 @@ pub enum PercentOrNone<Percentage> {
#[repr(C)]
pub struct GenericCrossFadeElement<Image, Color, Percentage> {
/// The percent of the final image that `image` will be.
pub percent: PercentOrNone<Percentage>,
pub percent: Optional<Percentage>,
/// A color or image that will be blended when cross-fade is
/// evaluated.
pub image: GenericCrossFadeImage<Image, Color>,

View file

@ -310,3 +310,74 @@ impl<L> ClipRectOrAuto<L> {
}
pub use page::PageSize;
/// An optional value, much like `Option<T>`, but with a defined struct layout
/// to be able to use it from C++ as well.
///
/// Note that this is relatively inefficient, struct-layout-wise, as you have
/// one byte for the tag, but padding to the alignment of T. If you have
/// multiple optional values and care about struct compactness, you might be
/// better off "coalescing" the combinations into a parent enum. But that
/// shouldn't matter for most use cases.
#[allow(missing_docs)]
#[derive(
Animate,
Clone,
ComputeSquaredDistance,
Copy,
Debug,
MallocSizeOf,
Parse,
PartialEq,
SpecifiedValueInfo,
ToAnimatedValue,
ToAnimatedZero,
ToComputedValue,
ToCss,
ToResolvedValue,
ToShmem,
)]
#[repr(C, u8)]
pub enum Optional<T> {
#[css(skip)]
None,
Some(T),
}
impl<T> Optional<T> {
/// Returns whether this value is present.
pub fn is_some(&self) -> bool {
matches!(*self, Self::Some(..))
}
/// Returns whether this value is not present.
pub fn is_none(&self) -> bool {
matches!(*self, Self::None)
}
/// Turns this Optional<> into a regular rust Option<>.
pub fn into_rust(self) -> Option<T> {
match self {
Self::Some(v) => Some(v),
Self::None => None,
}
}
/// Return a reference to the containing value, if any, as a plain rust
/// Option<>.
pub fn as_ref(&self) -> Option<&T> {
match *self {
Self::Some(ref v) => Some(v),
Self::None => None,
}
}
}
impl<T> From<Option<T>> for Optional<T> {
fn from(rust: Option<T>) -> Self {
match rust {
Some(t) => Self::Some(t),
None => Self::None,
}
}
}

View file

@ -87,7 +87,7 @@ impl PaperSize {
ToShmem,
)]
#[repr(u8)]
pub enum Orientation {
pub enum PageOrientation {
/// Portrait orientation
Portrait,
/// Landscape orientation
@ -95,8 +95,8 @@ pub enum Orientation {
}
#[inline]
fn is_portrait(orientation: &Orientation) -> bool {
*orientation == Orientation::Portrait
fn is_portrait(orientation: &PageOrientation) -> bool {
*orientation == PageOrientation::Portrait
}
/// Page size property
@ -110,9 +110,9 @@ pub enum GenericPageSize<S> {
/// Page dimensions.
Size(S),
/// An orientation with no size.
Orientation(Orientation),
Orientation(PageOrientation),
/// Paper size by name
PaperSize(PaperSize, #[css(skip_if = "is_portrait")] Orientation),
PaperSize(PaperSize, #[css(skip_if = "is_portrait")] PageOrientation),
}
pub use self::GenericPageSize as PageSize;

View file

@ -16,7 +16,6 @@ pub use cssparser::{SourceLocation, Token, RGBA};
use precomputed_hash::PrecomputedHash;
use selectors::parser::SelectorParseErrorKind;
use std::fmt::{self, Debug, Write};
use std::hash;
use style_traits::{CssWriter, ParseError, StyleParseErrorKind, ToCss};
use to_shmem::impl_trivial_to_shmem;
@ -458,28 +457,33 @@ impl CustomIdent {
ident: &CowRcStr<'i>,
excluding: &[&str],
) -> Result<Self, ParseError<'i>> {
use crate::properties::CSSWideKeyword;
// https://drafts.csswg.org/css-values-4/#custom-idents:
//
// The CSS-wide keywords are not valid <custom-ident>s. The default
// keyword is reserved and is also not a valid <custom-ident>.
//
if CSSWideKeyword::from_ident(ident).is_ok() || ident.eq_ignore_ascii_case("default") {
if !Self::is_valid(ident, excluding) {
return Err(
location.new_custom_error(SelectorParseErrorKind::UnexpectedIdent(ident.clone()))
);
}
// https://drafts.csswg.org/css-values-4/#custom-idents:
//
// Excluded keywords are excluded in all ASCII case permutations.
//
if excluding.iter().any(|s| ident.eq_ignore_ascii_case(s)) {
Err(location.new_custom_error(StyleParseErrorKind::UnspecifiedError))
} else {
Ok(CustomIdent(Atom::from(ident.as_ref())))
}
}
fn is_valid(ident: &str, excluding: &[&str]) -> bool {
use crate::properties::CSSWideKeyword;
// https://drafts.csswg.org/css-values-4/#custom-idents:
//
// The CSS-wide keywords are not valid <custom-ident>s. The default
// keyword is reserved and is also not a valid <custom-ident>.
if CSSWideKeyword::from_ident(ident).is_ok() || ident.eq_ignore_ascii_case("default") {
return false;
}
// https://drafts.csswg.org/css-values-4/#custom-idents:
//
// Excluded keywords are excluded in all ASCII case permutations.
!excluding.iter().any(|s| ident.eq_ignore_ascii_case(s))
}
}
impl ToCss for CustomIdent {
@ -496,45 +500,72 @@ impl ToCss for CustomIdent {
///
/// <https://drafts.csswg.org/css-animations-2/#typedef-timeline-name>
/// <https://drafts.csswg.org/css-animations/#typedef-keyframes-name>
///
/// We use a single atom for these. Empty atom represents `none` animation.
#[repr(transparent)]
#[derive(
Clone, Debug, MallocSizeOf, SpecifiedValueInfo, ToComputedValue, ToResolvedValue, ToShmem,
Clone, Debug, Hash, PartialEq, MallocSizeOf, SpecifiedValueInfo, ToComputedValue, ToResolvedValue, ToShmem,
)]
#[repr(C, u8)]
pub enum TimelineOrKeyframesName {
/// <custom-ident>
Ident(CustomIdent),
/// <string>
QuotedString(Atom),
}
pub struct TimelineOrKeyframesName(Atom);
impl TimelineOrKeyframesName {
/// <https://drafts.csswg.org/css-animations/#dom-csskeyframesrule-name>
pub fn from_ident(value: &str) -> Self {
let location = SourceLocation { line: 0, column: 0 };
let custom_ident = CustomIdent::from_ident(location, &value.into(), &["none"]).ok();
match custom_ident {
Some(ident) => Self::Ident(ident),
None => Self::QuotedString(value.into()),
}
Self(Atom::from(value))
}
/// Returns the `none` value.
pub fn none() -> Self {
Self(atom!(""))
}
/// Returns whether this is the special `none` value.
pub fn is_none(&self) -> bool {
self.0 == atom!("")
}
/// Create a new TimelineOrKeyframesName from Atom.
#[cfg(feature = "gecko")]
pub fn from_atom(atom: Atom) -> Self {
debug_assert_ne!(atom, atom!(""));
// FIXME: We might want to preserve <string>, but currently Gecko
// stores both of <custom-ident> and <string> into nsAtom, so
// we can't tell it.
Self::Ident(CustomIdent(atom))
Self(atom)
}
/// The name as an Atom
pub fn as_atom(&self) -> &Atom {
match *self {
Self::Ident(ref ident) => &ident.0,
Self::QuotedString(ref atom) => atom,
&self.0
}
fn parse<'i, 't>(input: &mut Parser<'i, 't>, invalid: &[&str]) -> Result<Self, ParseError<'i>> {
debug_assert!(invalid.contains(&"none"));
let location = input.current_source_location();
Ok(match *input.next()? {
Token::Ident(ref s) => Self(CustomIdent::from_ident(location, s, invalid)?.0),
Token::QuotedString(ref s) => Self(Atom::from(s.as_ref())),
ref t => return Err(location.new_unexpected_token_error(t.clone())),
})
}
fn to_css<W>(&self, dest: &mut CssWriter<W>, invalid: &[&str]) -> fmt::Result
where
W: Write,
{
debug_assert!(invalid.contains(&"none"));
if self.0 == atom!("") {
return dest.write_str("none")
}
let mut serialize = |s: &_| {
if CustomIdent::is_valid(s, invalid) {
serialize_identifier(s, dest)
} else {
s.to_css(dest)
}
};
#[cfg(feature = "gecko")]
return self.0.with_str(|s| serialize(s));
#[cfg(feature = "servo")]
return serialize(self.0.as_ref());
}
}
@ -547,53 +578,70 @@ pub trait IsAuto {
fn is_auto(&self) -> bool;
}
impl PartialEq for TimelineOrKeyframesName {
fn eq(&self, other: &Self) -> bool {
self.as_atom() == other.as_atom()
/// The typedef of <timeline-name>.
#[repr(transparent)]
#[derive(
Clone, Debug, Deref, Hash, Eq, PartialEq, MallocSizeOf, SpecifiedValueInfo, ToComputedValue, ToResolvedValue, ToShmem,
)]
pub struct TimelineName(TimelineOrKeyframesName);
impl TimelineName {
/// Returns the `none` value.
pub fn none() -> Self {
Self(TimelineOrKeyframesName::none())
}
}
impl hash::Hash for TimelineOrKeyframesName {
fn hash<H>(&self, state: &mut H)
where
H: hash::Hasher,
{
self.as_atom().hash(state)
impl Parse for TimelineName {
fn parse<'i, 't>(_: &ParserContext, input: &mut Parser<'i, 't>) -> Result<Self, ParseError<'i>> {
Ok(Self(TimelineOrKeyframesName::parse(input, &["none", "auto"])?))
}
}
impl Parse for TimelineOrKeyframesName {
fn parse<'i, 't>(
_context: &ParserContext,
input: &mut Parser<'i, 't>,
) -> Result<Self, ParseError<'i>> {
let location = input.current_source_location();
match *input.next()? {
Token::Ident(ref s) => Ok(Self::Ident(CustomIdent::from_ident(
location,
s,
&["none"],
)?)),
Token::QuotedString(ref s) => Ok(Self::QuotedString(Atom::from(s.as_ref()))),
ref t => Err(location.new_unexpected_token_error(t.clone())),
}
}
}
impl ToCss for TimelineOrKeyframesName {
impl ToCss for TimelineName {
fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
where
W: Write,
{
match *self {
Self::Ident(ref ident) => ident.to_css(dest),
Self::QuotedString(ref atom) => atom.to_string().to_css(dest),
}
self.0.to_css(dest, &["none", "auto"])
}
}
/// The typedef of <timeline-name>.
pub type TimelineName = TimelineOrKeyframesName;
/// The typedef of <keyframes-name>.
pub type KeyframesName = TimelineOrKeyframesName;
#[derive(
Clone, Debug, Deref, Hash, Eq, PartialEq, MallocSizeOf, SpecifiedValueInfo, ToComputedValue, ToResolvedValue, ToShmem,
)]
pub struct KeyframesName(TimelineOrKeyframesName);
impl KeyframesName {
/// Create a new KeyframesName from Atom.
#[cfg(feature = "gecko")]
pub fn from_atom(atom: Atom) -> Self {
Self(TimelineOrKeyframesName::from_atom(atom))
}
/// <https://drafts.csswg.org/css-animations/#dom-csskeyframesrule-name>
pub fn from_ident(value: &str) -> Self {
Self(TimelineOrKeyframesName::from_ident(value))
}
/// Returns the `none` value.
pub fn none() -> Self {
Self(TimelineOrKeyframesName::none())
}
}
impl Parse for KeyframesName {
fn parse<'i, 't>(_: &ParserContext, input: &mut Parser<'i, 't>) -> Result<Self, ParseError<'i>> {
Ok(Self(TimelineOrKeyframesName::parse(input, &["none"])?))
}
}
impl ToCss for KeyframesName {
fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
where
W: Write,
{
self.0.to_css(dest, &["none"])
}
}

View file

@ -17,7 +17,6 @@ use crate::values::{CustomIdent, KeyframesName, TimelineName};
use crate::Atom;
use cssparser::Parser;
use num_traits::FromPrimitive;
use selectors::parser::SelectorParseErrorKind;
use std::fmt::{self, Debug, Formatter, Write};
use style_traits::{CssWriter, KeywordsCollectFn, ParseError};
use style_traits::{SpecifiedValueInfo, StyleParseErrorKind, ToCss};
@ -680,33 +679,30 @@ impl AnimationIterationCount {
PartialEq,
SpecifiedValueInfo,
ToComputedValue,
ToCss,
ToResolvedValue,
ToShmem,
)]
#[value_info(other_values = "none")]
pub struct AnimationName(pub Option<KeyframesName>);
pub struct AnimationName(pub KeyframesName);
impl AnimationName {
/// Get the name of the animation as an `Atom`.
pub fn as_atom(&self) -> Option<&Atom> {
self.0.as_ref().map(|n| n.as_atom())
if self.is_none() {
return None;
}
Some(self.0.as_atom())
}
/// Returns the `none` value.
pub fn none() -> Self {
AnimationName(None)
AnimationName(KeyframesName::none())
}
}
impl ToCss for AnimationName {
fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
where
W: Write,
{
match self.0 {
Some(ref name) => name.to_css(dest),
None => dest.write_str("none"),
}
/// Returns whether this is the none value.
pub fn is_none(&self) -> bool {
self.0.is_none()
}
}
@ -716,14 +712,88 @@ impl Parse for AnimationName {
input: &mut Parser<'i, 't>,
) -> Result<Self, ParseError<'i>> {
if let Ok(name) = input.try_parse(|input| KeyframesName::parse(context, input)) {
return Ok(AnimationName(Some(name)));
return Ok(AnimationName(name));
}
input.expect_ident_matching("none")?;
Ok(AnimationName(None))
Ok(AnimationName(KeyframesName::none()))
}
}
/// A value for the <Scroller> used in scroll().
///
/// https://drafts.csswg.org/scroll-animations-1/rewrite#typedef-scroller
#[derive(
Clone,
Debug,
Eq,
Hash,
MallocSizeOf,
Parse,
PartialEq,
SpecifiedValueInfo,
ToComputedValue,
ToCss,
ToResolvedValue,
ToShmem,
)]
#[repr(u8)]
pub enum Scroller {
/// The nearest ancestor scroll container. (Default.)
Nearest,
/// The document viewport as the scroll container.
Root,
// FIXME: Bug 1764450: Once we support container-name CSS property (Bug 1744224), we may add
// <custom-ident> here, based on the result of the spec issue:
// https://github.com/w3c/csswg-drafts/issues/7046
}
impl Default for Scroller {
fn default() -> Self {
Self::Nearest
}
}
/// A value for the <Axis> used in scroll().
///
/// https://drafts.csswg.org/scroll-animations-1/rewrite#typedef-axis
#[derive(
Clone,
Debug,
Eq,
Hash,
MallocSizeOf,
Parse,
PartialEq,
SpecifiedValueInfo,
ToComputedValue,
ToCss,
ToResolvedValue,
ToShmem,
)]
#[repr(u8)]
pub enum ScrollAxis {
/// The block axis of the scroll container. (Default.)
Block,
/// The inline axis of the scroll container.
Inline,
/// The vertical block axis of the scroll container.
Vertical,
/// The horizontal axis of the scroll container.
Horizontal,
}
impl Default for ScrollAxis {
fn default() -> Self {
Self::Block
}
}
#[inline]
fn is_default<T: Default + PartialEq>(value: &T) -> bool {
*value == Default::default()
}
/// A value for the <single-animation-timeline>.
///
/// https://drafts.csswg.org/css-animations-2/#typedef-single-animation-timeline
@ -745,10 +815,15 @@ impl Parse for AnimationName {
pub enum AnimationTimeline {
/// Use default timeline. The animations timeline is a DocumentTimeline.
Auto,
/// The animation is not associated with a timeline.
None,
/// The scroll-timeline name
Timeline(TimelineName),
/// The scroll() notation
/// https://drafts.csswg.org/scroll-animations-1/rewrite#scroll-notation
#[css(function)]
Scroll(
#[css(skip_if = "is_default")] ScrollAxis,
#[css(skip_if = "is_default")] Scroller,
),
}
impl AnimationTimeline {
@ -769,16 +844,30 @@ impl Parse for AnimationTimeline {
input: &mut Parser<'i, 't>,
) -> Result<Self, ParseError<'i>> {
// We are using the same parser for TimelineName and KeyframesName, but animation-timeline
// accepts "auto", so need to manually parse this. (We can not derive Parse because
// TimelineName excludes only "none" keyword.)
// accepts "auto", so need to manually parse this. (We can not derive
// Parse because TimelineName excludes only the "none" keyword).
//
// FIXME: Bug 1733260: we may drop None based on the spec issue:
// Note: https://github.com/w3c/csswg-drafts/issues/6674.
// https://github.com/w3c/csswg-drafts/issues/6674
//
// If `none` is removed, then we could potentially shrink this the same
// way we deal with animation-name.
if input.try_parse(|i| i.expect_ident_matching("auto")).is_ok() {
return Ok(Self::Auto);
}
if input.try_parse(|i| i.expect_ident_matching("none")).is_ok() {
return Ok(Self::None);
return Ok(AnimationTimeline::Timeline(TimelineName::none()));
}
// https://drafts.csswg.org/scroll-animations-1/rewrite#scroll-notation
if input.try_parse(|i| i.expect_function_matching("scroll")).is_ok() {
return input.parse_nested_block(|i| {
Ok(Self::Scroll(
i.try_parse(ScrollAxis::parse).unwrap_or(ScrollAxis::Block),
i.try_parse(Scroller::parse).unwrap_or(Scroller::Nearest),
))
});
}
TimelineName::parse(context, input).map(AnimationTimeline::Timeline)
@ -991,6 +1080,28 @@ impl ToCss for ScrollSnapAlign {
}
}
#[allow(missing_docs)]
#[cfg_attr(feature = "servo", derive(Deserialize, Serialize))]
#[derive(
Clone,
Copy,
Debug,
Eq,
MallocSizeOf,
Parse,
PartialEq,
SpecifiedValueInfo,
ToComputedValue,
ToCss,
ToResolvedValue,
ToShmem,
)]
#[repr(u8)]
pub enum ScrollSnapStop {
Normal,
Always,
}
#[allow(missing_docs)]
#[cfg_attr(feature = "servo", derive(Deserialize, Serialize))]
#[derive(
@ -1196,7 +1307,9 @@ impl Parse for WillChange {
&["will-change", "none", "all", "auto"],
)?;
if ident.0 == atom!("scroll-position") {
if context.in_ua_sheet() && ident.0 == atom!("-moz-fixed-pos-containing-block") {
bits |= WillChangeBits::FIXPOS_CB_NON_SVG;
} else if ident.0 == atom!("scroll-position") {
bits |= WillChangeBits::SCROLL;
} else {
bits |= change_bits_for_maybe_property(&parser_ident, context);
@ -1213,9 +1326,8 @@ impl Parse for WillChange {
bitflags! {
/// Values for the `touch-action` property.
#[derive(MallocSizeOf, SpecifiedValueInfo, ToComputedValue, ToResolvedValue, ToShmem)]
/// These constants match Gecko's `NS_STYLE_TOUCH_ACTION_*` constants.
#[value_info(other_values = "auto,none,manipulation,pan-x,pan-y,pinch-zoom")]
#[derive(MallocSizeOf, SpecifiedValueInfo, ToComputedValue, ToCss, ToResolvedValue, ToShmem, Parse)]
#[css(bitflags(single = "none,auto,manipulation", mixed = "pan-x,pan-y,pinch-zoom"))]
#[repr(C)]
pub struct TouchAction: u8 {
/// `none` variant
@ -1241,178 +1353,32 @@ impl TouchAction {
}
}
impl ToCss for TouchAction {
fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
where
W: Write,
{
if self.contains(TouchAction::AUTO) {
return dest.write_str("auto");
}
if self.contains(TouchAction::NONE) {
return dest.write_str("none");
}
if self.contains(TouchAction::MANIPULATION) {
return dest.write_str("manipulation");
}
let mut has_any = false;
macro_rules! maybe_write_value {
($ident:path => $str:expr) => {
if self.contains($ident) {
if has_any {
dest.write_str(" ")?;
}
has_any = true;
dest.write_str($str)?;
}
};
}
maybe_write_value!(TouchAction::PAN_X => "pan-x");
maybe_write_value!(TouchAction::PAN_Y => "pan-y");
maybe_write_value!(TouchAction::PINCH_ZOOM => "pinch-zoom");
debug_assert!(has_any);
Ok(())
}
}
impl Parse for TouchAction {
/// auto | none | [ pan-x || pan-y || pinch-zoom ] | manipulation
fn parse<'i, 't>(
_context: &ParserContext,
input: &mut Parser<'i, 't>,
) -> Result<TouchAction, ParseError<'i>> {
let mut result = TouchAction::empty();
while let Ok(name) = input.try_parse(|i| i.expect_ident_cloned()) {
let flag = match_ignore_ascii_case! { &name,
"pan-x" => Some(TouchAction::PAN_X),
"pan-y" => Some(TouchAction::PAN_Y),
"pinch-zoom" => Some(TouchAction::PINCH_ZOOM),
"none" if result.is_empty() => return Ok(TouchAction::NONE),
"manipulation" if result.is_empty() => return Ok(TouchAction::MANIPULATION),
"auto" if result.is_empty() => return Ok(TouchAction::AUTO),
_ => None
};
let flag = match flag {
Some(flag) if !result.contains(flag) => flag,
_ => {
return Err(
input.new_custom_error(SelectorParseErrorKind::UnexpectedIdent(name))
);
},
};
result.insert(flag);
}
if !result.is_empty() {
Ok(result)
} else {
Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError))
}
}
}
bitflags! {
#[derive(MallocSizeOf, SpecifiedValueInfo, ToComputedValue, ToResolvedValue, ToShmem)]
#[value_info(other_values = "none,strict,content,size,layout,paint")]
#[derive(MallocSizeOf, Parse, SpecifiedValueInfo, ToComputedValue, ToCss, ToResolvedValue, ToShmem)]
#[css(bitflags(single = "none,strict,content", mixed="size,layout,paint,inline-size", overlapping_bits))]
#[repr(C)]
/// Constants for contain: https://drafts.csswg.org/css-contain/#contain-property
pub struct Contain: u8 {
/// `none` variant, just for convenience.
const NONE = 0;
/// 'size' variant, turns on size containment
const SIZE = 1 << 0;
/// `inline-size` variant, turns on single-axis inline size containment
const INLINE_SIZE = 1 << 0;
/// `block-size` variant, turns on single-axis block size containment, internal only
const BLOCK_SIZE = 1 << 1;
/// `layout` variant, turns on layout containment
const LAYOUT = 1 << 1;
const LAYOUT = 1 << 2;
/// `paint` variant, turns on paint containment
const PAINT = 1 << 2;
const PAINT = 1 << 3;
/// 'size' variant, turns on size containment
const SIZE = 1 << 4 | Contain::INLINE_SIZE.bits | Contain::BLOCK_SIZE.bits;
/// `content` variant, turns on layout and paint containment
const CONTENT = 1 << 5 | Contain::LAYOUT.bits | Contain::PAINT.bits;
/// `strict` variant, turns on all types of containment
const STRICT = 1 << 3;
/// 'content' variant, turns on layout and paint containment
const CONTENT = 1 << 4;
/// variant with all the bits that contain: strict turns on
const STRICT_BITS = Contain::LAYOUT.bits | Contain::PAINT.bits | Contain::SIZE.bits;
/// variant with all the bits that contain: content turns on
const CONTENT_BITS = Contain::LAYOUT.bits | Contain::PAINT.bits;
const STRICT = 1 << 6 | Contain::LAYOUT.bits | Contain::PAINT.bits | Contain::SIZE.bits;
}
}
impl ToCss for Contain {
fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
where
W: Write,
{
if self.is_empty() {
return dest.write_str("none");
}
if self.contains(Contain::STRICT) {
return dest.write_str("strict");
}
if self.contains(Contain::CONTENT) {
return dest.write_str("content");
}
let mut has_any = false;
macro_rules! maybe_write_value {
($ident:path => $str:expr) => {
if self.contains($ident) {
if has_any {
dest.write_str(" ")?;
}
has_any = true;
dest.write_str($str)?;
}
};
}
maybe_write_value!(Contain::SIZE => "size");
maybe_write_value!(Contain::LAYOUT => "layout");
maybe_write_value!(Contain::PAINT => "paint");
debug_assert!(has_any);
Ok(())
}
}
impl Parse for Contain {
/// none | strict | content | [ size || layout || paint ]
fn parse<'i, 't>(
_context: &ParserContext,
input: &mut Parser<'i, 't>,
) -> Result<Contain, ParseError<'i>> {
let mut result = Contain::empty();
while let Ok(name) = input.try_parse(|i| i.expect_ident_cloned()) {
let flag = match_ignore_ascii_case! { &name,
"size" => Some(Contain::SIZE),
"layout" => Some(Contain::LAYOUT),
"paint" => Some(Contain::PAINT),
"strict" if result.is_empty() => return Ok(Contain::STRICT | Contain::STRICT_BITS),
"content" if result.is_empty() => return Ok(Contain::CONTENT | Contain::CONTENT_BITS),
"none" if result.is_empty() => return Ok(result),
_ => None
};
let flag = match flag {
Some(flag) if !result.contains(flag) => flag,
_ => {
return Err(
input.new_custom_error(SelectorParseErrorKind::UnexpectedIdent(name))
);
},
};
result.insert(flag);
}
if !result.is_empty() {
Ok(result)
} else {
Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError))
}
}
}
#[allow(missing_docs)]
/// https://drafts.csswg.org/css-contain-2/#content-visibility
#[cfg_attr(feature = "servo", derive(Deserialize, Serialize))]
#[derive(
Clone,
@ -1440,6 +1406,61 @@ pub enum ContentVisibility {
Visible,
}
bitflags! {
#[derive(MallocSizeOf, SpecifiedValueInfo, ToComputedValue, ToCss, Parse, ToResolvedValue, ToShmem)]
#[repr(C)]
#[allow(missing_docs)]
#[css(bitflags(single="none", mixed="style,size,inline-size", overlapping_bits))]
/// https://drafts.csswg.org/css-contain-3/#container-type
///
/// TODO: block-size is on the spec but it seems it was removed? WPTs don't
/// support it, see https://github.com/w3c/csswg-drafts/issues/7179.
pub struct ContainerType: u8 {
/// The `none` variant.
const NONE = 0;
/// The `style` variant.
const STYLE = 1 << 0;
/// The `inline-size` variant.
const INLINE_SIZE = 1 << 1;
/// The `size` variant, exclusive with `inline-size` (they sharing bits
/// guarantees this).
const SIZE = 1 << 2 | Self::INLINE_SIZE.bits;
}
}
/// https://drafts.csswg.org/css-contain-3/#container-name
#[repr(transparent)]
#[derive(Clone, Debug, MallocSizeOf, PartialEq, SpecifiedValueInfo, ToComputedValue, ToCss, ToResolvedValue, ToShmem)]
pub struct ContainerName(#[css(iterable, if_empty = "none")] pub crate::OwnedSlice<CustomIdent>);
impl ContainerName {
/// Return the `none` value.
pub fn none() -> Self {
Self(Default::default())
}
/// Returns whether this is the `none` value.
pub fn is_none(&self) -> bool {
self.0.is_empty()
}
}
impl Parse for ContainerName {
fn parse<'i, 't>( _: &ParserContext, input: &mut Parser<'i, 't>) -> Result<Self, ParseError<'i>> {
let mut idents = vec![];
let location = input.current_source_location();
let first = input.expect_ident()?;
if first.eq_ignore_ascii_case("none") {
return Ok(Self::none())
}
idents.push(CustomIdent::from_ident(location, first, &["none"])?);
while let Ok(ident) = input.try_parse(|input| input.expect_ident_cloned()) {
idents.push(CustomIdent::from_ident(location, &ident, &["none"])?);
}
Ok(ContainerName(idents.into()))
}
}
/// A specified value for the `perspective` property.
pub type Perspective = GenericPerspective<NonNegativeLength>;
@ -1890,10 +1911,6 @@ pub enum Appearance {
MozMacSourceList,
#[parse(condition = "ParserContext::in_ua_or_chrome_sheet")]
MozMacSourceListSelection,
#[parse(condition = "ParserContext::in_ua_or_chrome_sheet")]
MozMacVibrantTitlebarDark,
#[parse(condition = "ParserContext::in_ua_or_chrome_sheet")]
MozMacVibrantTitlebarLight,
/// A themed focus outline (for outline:auto).
///
@ -2101,9 +2118,9 @@ impl Overflow {
}
bitflags! {
#[derive(MallocSizeOf, SpecifiedValueInfo, ToComputedValue, ToResolvedValue, ToShmem)]
#[value_info(other_values = "auto,stable,both-edges")]
#[derive(MallocSizeOf, SpecifiedValueInfo, ToCss, ToComputedValue, ToResolvedValue, ToShmem, Parse)]
#[repr(C)]
#[css(bitflags(single = "auto", mixed = "stable,both-edges", validate_mixed="Self::has_stable"))]
/// Values for scrollbar-gutter:
/// <https://drafts.csswg.org/css-overflow-3/#scrollbar-gutter-property>
pub struct ScrollbarGutter: u8 {
@ -2116,56 +2133,9 @@ bitflags! {
}
}
impl ToCss for ScrollbarGutter {
fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
where
W: Write,
{
if self.is_empty() {
return dest.write_str("auto");
}
debug_assert!(
self.contains(ScrollbarGutter::STABLE),
"We failed to parse the syntax!"
);
dest.write_str("stable")?;
if self.contains(ScrollbarGutter::BOTH_EDGES) {
dest.write_str(" both-edges")?;
}
Ok(())
}
}
impl Parse for ScrollbarGutter {
/// auto | stable && both-edges?
fn parse<'i, 't>(
_context: &ParserContext,
input: &mut Parser<'i, 't>,
) -> Result<ScrollbarGutter, ParseError<'i>> {
if input.try_parse(|i| i.expect_ident_matching("auto")).is_ok() {
return Ok(ScrollbarGutter::AUTO);
}
let mut result = ScrollbarGutter::empty();
while let Ok(ident) = input.try_parse(|i| i.expect_ident_cloned()) {
let flag = match_ignore_ascii_case! { &ident,
"stable" => Some(ScrollbarGutter::STABLE),
"both-edges" => Some(ScrollbarGutter::BOTH_EDGES),
_ => None
};
match flag {
Some(flag) if !result.contains(flag) => result.insert(flag),
_ => return Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError)),
}
}
if result.contains(ScrollbarGutter::STABLE) {
Ok(result)
} else {
Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError))
}
impl ScrollbarGutter {
#[inline]
fn has_stable(self) -> bool {
self.intersects(Self::STABLE)
}
}

View file

@ -190,9 +190,29 @@ impl generic::CalcNodeLeaf for Leaf {
},
NoCalcLength::ViewportPercentage(ref vp) => match *vp {
ViewportPercentageLength::Vh(..) => SortKey::Vh,
ViewportPercentageLength::Svh(..) => SortKey::Svh,
ViewportPercentageLength::Lvh(..) => SortKey::Lvh,
ViewportPercentageLength::Dvh(..) => SortKey::Dvh,
ViewportPercentageLength::Vw(..) => SortKey::Vw,
ViewportPercentageLength::Svw(..) => SortKey::Svw,
ViewportPercentageLength::Lvw(..) => SortKey::Lvw,
ViewportPercentageLength::Dvw(..) => SortKey::Dvw,
ViewportPercentageLength::Vmax(..) => SortKey::Vmax,
ViewportPercentageLength::Svmax(..) => SortKey::Svmax,
ViewportPercentageLength::Lvmax(..) => SortKey::Lvmax,
ViewportPercentageLength::Dvmax(..) => SortKey::Dvmax,
ViewportPercentageLength::Vmin(..) => SortKey::Vmin,
ViewportPercentageLength::Svmin(..) => SortKey::Svmin,
ViewportPercentageLength::Lvmin(..) => SortKey::Lvmin,
ViewportPercentageLength::Dvmin(..) => SortKey::Dvmin,
ViewportPercentageLength::Vb(..) => SortKey::Vb,
ViewportPercentageLength::Svb(..) => SortKey::Svb,
ViewportPercentageLength::Lvb(..) => SortKey::Lvb,
ViewportPercentageLength::Dvb(..) => SortKey::Dvb,
ViewportPercentageLength::Vi(..) => SortKey::Vi,
ViewportPercentageLength::Svi(..) => SortKey::Svi,
ViewportPercentageLength::Lvi(..) => SortKey::Lvi,
ViewportPercentageLength::Dvi(..) => SortKey::Dvi,
},
NoCalcLength::ServoCharacterWidth(..) => unreachable!(),
},

View file

@ -7,7 +7,7 @@
use super::AllowQuirks;
use crate::parser::{Parse, ParserContext};
use crate::values::computed::{Color as ComputedColor, Context, ToComputedValue};
use crate::values::generics::color::{GenericCaretColor, GenericColorOrAuto};
use crate::values::generics::color::{ColorInterpolationMethod, GenericColorMix, GenericCaretColor, GenericColorOrAuto};
use crate::values::specified::calc::CalcNode;
use crate::values::specified::Percentage;
use crate::values::CustomIdent;
@ -19,52 +19,8 @@ use std::io::Write as IoWrite;
use style_traits::{CssType, CssWriter, KeywordsCollectFn, ParseError, StyleParseErrorKind};
use style_traits::{SpecifiedValueInfo, ToCss, ValueParseErrorKind};
/// A color space as defined in [1].
///
/// [1]: https://drafts.csswg.org/css-color-5/#typedef-colorspace
#[derive(Clone, Copy, Debug, Eq, MallocSizeOf, Parse, PartialEq, ToCss, ToShmem)]
pub enum ColorSpaceKind {
/// The sRGB color space.
Srgb,
/// The CIEXYZ color space.
Xyz,
/// The CIELAB color space.
Lab,
/// The CIELAB color space, expressed in cylindrical coordinates.
Lch,
}
/// A hue adjuster as defined in [1].
///
/// [1]: https://drafts.csswg.org/css-color-5/#typedef-hue-adjuster
#[derive(Clone, Copy, Debug, Eq, MallocSizeOf, Parse, PartialEq, ToCss, ToShmem)]
pub enum HueAdjuster {
/// The "shorter" angle adjustment.
Shorter,
/// The "longer" angle adjustment.
Longer,
/// The "increasing" angle adjustment.
Increasing,
/// The "decreasing" angle adjustment.
Decreasing,
/// The "specified" angle adjustment.
Specified,
}
/// A restricted version of the css `color-mix()` function, which only supports
/// percentages.
///
/// https://drafts.csswg.org/css-color-5/#color-mix
#[derive(Clone, Debug, MallocSizeOf, PartialEq, ToShmem)]
#[allow(missing_docs)]
pub struct ColorMix {
pub color_space: ColorSpaceKind,
pub left: Color,
pub left_percentage: Percentage,
pub right: Color,
pub right_percentage: Percentage,
pub hue_adjuster: HueAdjuster,
}
/// A specified color-mix().
pub type ColorMix = GenericColorMix<Color, Percentage>;
#[inline]
fn allow_color_mix() -> bool {
@ -74,18 +30,6 @@ fn allow_color_mix() -> bool {
return false;
}
#[inline]
fn allow_color_mix_color_spaces() -> bool {
#[cfg(feature = "gecko")]
return static_prefs::pref!("layout.css.color-mix.color-spaces.enabled");
#[cfg(feature = "servo")]
return false;
}
// NOTE(emilio): Syntax is still a bit in-flux, since [1] doesn't seem
// particularly complete, and disagrees with the examples.
//
// [1]: https://github.com/w3c/csswg-drafts/commit/a4316446112f9e814668c2caff7f826f512f8fed
impl Parse for ColorMix {
fn parse<'i, 't>(
context: &ParserContext,
@ -98,100 +42,58 @@ impl Parse for ColorMix {
return Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError));
}
let color_spaces_enabled = context.chrome_rules_enabled() ||
allow_color_mix_color_spaces();
input.expect_function_matching("color-mix")?;
// NOTE(emilio): This implements the syntax described here for now,
// might need to get updated in the future.
//
// https://github.com/w3c/csswg-drafts/issues/6066#issuecomment-789836765
input.parse_nested_block(|input| {
input.expect_ident_matching("in")?;
let color_space = if color_spaces_enabled {
ColorSpaceKind::parse(input)?
} else {
input.expect_ident_matching("srgb")?;
ColorSpaceKind::Srgb
};
let interpolation = ColorInterpolationMethod::parse(context, input)?;
input.expect_comma()?;
let try_parse_percentage = |input: &mut Parser| -> Option<Percentage> {
input
.try_parse(|input| Percentage::parse_zero_to_a_hundred(context, input))
.ok()
};
let mut left_percentage = try_parse_percentage(input);
let left = Color::parse(context, input)?;
let left_percentage = input
.try_parse(|input| Percentage::parse(context, input))
.ok();
if left_percentage.is_none() {
left_percentage = try_parse_percentage(input);
}
input.expect_comma()?;
let mut right_percentage = try_parse_percentage(input);
let right = Color::parse(context, input)?;
let right_percentage = input
.try_parse(|input| Percentage::parse(context, input))
.unwrap_or_else(|_| {
Percentage::new(1.0 - left_percentage.map_or(0.5, |p| p.get()))
});
if right_percentage.is_none() {
right_percentage = try_parse_percentage(input);
}
let right_percentage = right_percentage
.unwrap_or_else(|| Percentage::new(1.0 - left_percentage.map_or(0.5, |p| p.get())));
let left_percentage =
left_percentage.unwrap_or_else(|| Percentage::new(1.0 - right_percentage.get()));
let hue_adjuster = input
.try_parse(|input| HueAdjuster::parse(input))
.unwrap_or(HueAdjuster::Shorter);
if left_percentage.get() + right_percentage.get() <= 0.0 {
// If the percentages sum to zero, the function is invalid.
return Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError));
}
Ok(ColorMix {
color_space,
interpolation,
left,
left_percentage,
right,
right_percentage,
hue_adjuster,
normalize_weights: true,
})
})
}
}
impl ToCss for ColorMix {
fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
where
W: Write,
{
fn can_omit(percent: &Percentage, other: &Percentage, is_left: bool) -> bool {
if percent.is_calc() {
return false;
}
if percent.get() == 0.5 {
return other.get() == 0.5;
}
if is_left {
return false;
}
(1.0 - percent.get() - other.get()).abs() <= f32::EPSILON
}
dest.write_str("color-mix(in ")?;
self.color_space.to_css(dest)?;
dest.write_str(", ")?;
self.left.to_css(dest)?;
if !can_omit(&self.left_percentage, &self.right_percentage, true) {
dest.write_str(" ")?;
self.left_percentage.to_css(dest)?;
}
dest.write_str(", ")?;
self.right.to_css(dest)?;
if !can_omit(&self.right_percentage, &self.left_percentage, false) {
dest.write_str(" ")?;
self.right_percentage.to_css(dest)?;
}
if self.hue_adjuster != HueAdjuster::Shorter {
dest.write_str(" ")?;
self.hue_adjuster.to_css(dest)?;
}
dest.write_str(")")
}
}
/// Specified color value
#[derive(Clone, Debug, MallocSizeOf, PartialEq, ToShmem)]
pub enum Color {
@ -204,8 +106,6 @@ pub enum Color {
/// Authored representation
authored: Option<Box<str>>,
},
/// A complex color value from computed value
Complex(ComputedColor),
/// A system color.
#[cfg(feature = "gecko")]
System(SystemColor),
@ -387,10 +287,6 @@ pub enum SystemColor {
/// Inactive light hightlight
MozMacSecondaryhighlight,
/// Font smoothing background colors needed by the Mac OS X theme, based on
/// -moz-appearance names.
MozMacVibrantTitlebarLight,
MozMacVibrantTitlebarDark,
MozMacMenupopup,
MozMacMenuitem,
MozMacActiveMenuitem,
@ -597,8 +493,6 @@ impl ToCss for Color {
Color::Numeric {
parsed: ref rgba, ..
} => rgba.to_css(dest),
// TODO: Could represent this as a color-mix() instead.
Color::Complex(_) => Ok(()),
Color::ColorMix(ref mix) => mix.to_css(dest),
#[cfg(feature = "gecko")]
Color::System(system) => system.to_css(dest),
@ -744,23 +638,23 @@ impl Color {
/// the context to resolve, then `None` is returned.
pub fn to_computed_color(&self, context: Option<&Context>) -> Option<ComputedColor> {
Some(match *self {
Color::CurrentColor => ComputedColor::currentcolor(),
Color::Numeric { ref parsed, .. } => ComputedColor::rgba(*parsed),
Color::Complex(ref complex) => *complex,
Color::CurrentColor => ComputedColor::CurrentColor,
Color::Numeric { ref parsed, .. } => ComputedColor::Numeric(*parsed),
Color::ColorMix(ref mix) => {
use crate::values::animated::color::Color as AnimatedColor;
use crate::values::animated::ToAnimatedValue;
use crate::values::computed::percentage::Percentage;
let left = mix.left.to_computed_color(context)?.to_animated_value();
let right = mix.right.to_computed_color(context)?.to_animated_value();
ToAnimatedValue::from_animated_value(AnimatedColor::mix(
mix.color_space,
&left,
mix.left_percentage.get(),
&right,
mix.right_percentage.get(),
mix.hue_adjuster,
))
let left = mix.left.to_computed_color(context)?;
let right = mix.right.to_computed_color(context)?;
let mut color = ComputedColor::ColorMix(Box::new(GenericColorMix {
interpolation: mix.interpolation,
left,
left_percentage: Percentage(mix.left_percentage.get()),
right,
right_percentage: Percentage(mix.right_percentage.get()),
normalize_weights: mix.normalize_weights,
}));
color.simplify(None);
color
},
#[cfg(feature = "gecko")]
Color::System(system) => system.compute(context?),
@ -778,13 +672,13 @@ impl ToComputedValue for Color {
}
fn from_computed_value(computed: &ComputedColor) -> Self {
if computed.is_numeric() {
return Color::rgba(computed.color);
match *computed {
ComputedColor::Numeric(ref color) => Color::rgba(*color),
ComputedColor::CurrentColor => Color::CurrentColor,
ComputedColor::ColorMix(ref mix) => {
Color::ColorMix(Box::new(ToComputedValue::from_computed_value(&**mix)))
},
}
if computed.is_currentcolor() {
return Color::currentcolor();
}
Color::Complex(*computed)
}
}
@ -812,7 +706,7 @@ impl ToComputedValue for MozFontSmoothingBackgroundColor {
fn to_computed_value(&self, context: &Context) -> RGBA {
self.0
.to_computed_value(context)
.to_rgba(RGBA::transparent())
.into_rgba(RGBA::transparent())
}
fn from_computed_value(computed: &RGBA) -> Self {
@ -829,7 +723,15 @@ impl SpecifiedValueInfo for Color {
// should probably be handled that way as well.
// XXX `currentColor` should really be `currentcolor`. But let's
// keep it consistent with the old system for now.
f(&["rgb", "rgba", "hsl", "hsla", "hwb", "currentColor", "transparent"]);
f(&[
"rgb",
"rgba",
"hsl",
"hsla",
"hwb",
"currentColor",
"transparent",
]);
}
}
@ -846,7 +748,7 @@ impl ToComputedValue for ColorPropertyValue {
fn to_computed_value(&self, context: &Context) -> RGBA {
self.0
.to_computed_value(context)
.to_rgba(context.builder.get_parent_inherited_text().clone_color())
.into_rgba(context.builder.get_parent_inherited_text().clone_color())
}
#[inline]
@ -1008,7 +910,19 @@ impl ToCss for ColorScheme {
}
/// https://drafts.csswg.org/css-color-adjust/#print-color-adjust
#[derive(Clone, Copy, Debug, MallocSizeOf, Parse, PartialEq, SpecifiedValueInfo, ToCss, ToComputedValue, ToResolvedValue, ToShmem)]
#[derive(
Clone,
Copy,
Debug,
MallocSizeOf,
Parse,
PartialEq,
SpecifiedValueInfo,
ToCss,
ToComputedValue,
ToResolvedValue,
ToShmem,
)]
#[repr(u8)]
pub enum PrintColorAdjust {
/// Ignore backgrounds and darken text.

View file

@ -39,6 +39,9 @@ use style_traits::{SpecifiedValueInfo, StyleParseErrorKind, ToCss};
pub type Image =
generic::Image<Gradient, MozImageRect, SpecifiedImageUrl, Color, Percentage, Resolution>;
// Images should remain small, see https://github.com/servo/servo/pull/18430
size_of_test!(Image, 40);
/// Specified values for a CSS gradient.
/// <https://drafts.csswg.org/css-images/#gradients>
pub type Gradient = generic::Gradient<
@ -60,8 +63,6 @@ pub type CrossFade = generic::CrossFade<Image, Color, Percentage>;
pub type CrossFadeElement = generic::CrossFadeElement<Image, Color, Percentage>;
/// CrossFadeImage = image | color
pub type CrossFadeImage = generic::CrossFadeImage<Image, Color>;
/// A specified percentage or nothing.
pub type PercentOrNone = generic::PercentOrNone<Percentage>;
/// `image-set()`
pub type ImageSet = generic::ImageSet<Image, Resolution>;
@ -315,6 +316,16 @@ impl CrossFade {
}
impl CrossFadeElement {
fn parse_percentage<'i, 't>(context: &ParserContext, input: &mut Parser<'i, 't>) -> Option<Percentage> {
// We clamp our values here as this is the way that Safari and Chrome's
// implementation handle out-of-bounds percentages but whether or not
// this behavior follows the specification is still being discussed.
// See: <https://github.com/w3c/csswg-drafts/issues/5333>
input.try_parse(|input| Percentage::parse_non_negative(context, input))
.ok()
.map(|p| p.clamp_to_hundred())
}
/// <cf-image> = <percentage>? && [ <image> | <color> ]
fn parse<'i, 't>(
context: &ParserContext,
@ -322,14 +333,17 @@ impl CrossFadeElement {
cors_mode: CorsMode,
) -> Result<Self, ParseError<'i>> {
// Try and parse a leading percent sign.
let mut percent = PercentOrNone::parse_or_none(context, input);
let mut percent = Self::parse_percentage(context, input);
// Parse the image
let image = CrossFadeImage::parse(context, input, cors_mode)?;
// Try and parse a trailing percent sign.
if percent == PercentOrNone::None {
percent = PercentOrNone::parse_or_none(context, input);
if percent.is_none() {
percent = Self::parse_percentage(context, input);
}
Ok(Self { percent, image })
Ok(Self {
percent: percent.into(),
image,
})
}
}
@ -351,22 +365,6 @@ impl CrossFadeImage {
}
}
impl PercentOrNone {
fn parse_or_none<'i, 't>(context: &ParserContext, input: &mut Parser<'i, 't>) -> Self {
// We clamp our values here as this is the way that Safari and
// Chrome's implementation handle out-of-bounds percentages
// but whether or not this behavior follows the specification
// is still being discussed. See:
// <https://github.com/w3c/csswg-drafts/issues/5333>
if let Ok(percent) = input.try_parse(|input| Percentage::parse_non_negative(context, input))
{
Self::Percent(percent.clamp_to_hundred())
} else {
Self::None
}
}
}
impl ImageSet {
fn parse<'i, 't>(
context: &ParserContext,
@ -735,12 +733,12 @@ impl Gradient {
if items.is_empty() {
items = vec![
generic::GradientItem::ComplexColorStop {
color: Color::transparent().into(),
position: Percentage::zero().into(),
color: Color::transparent(),
position: LengthPercentage::zero_percent(),
},
generic::GradientItem::ComplexColorStop {
color: Color::transparent().into(),
position: Percentage::hundred().into(),
color: Color::transparent(),
position: LengthPercentage::hundred_percent(),
},
];
} else if items.len() == 1 {

View file

@ -21,7 +21,6 @@ use crate::values::CSSFloat;
use crate::Zero;
use app_units::Au;
use cssparser::{Parser, Token};
use euclid::default::Size2D;
use std::cmp;
use std::ops::{Add, Mul};
use style_traits::values::specified::AllowedNumericType;
@ -278,45 +277,249 @@ impl FontRelativeLength {
}
}
/// https://drafts.csswg.org/css-values/#viewport-variants
pub enum ViewportVariant {
/// https://drafts.csswg.org/css-values/#ua-default-viewport-size
UADefault,
/// https://drafts.csswg.org/css-values/#small-viewport-percentage-units
Small,
/// https://drafts.csswg.org/css-values/#large-viewport-percentage-units
Large,
/// https://drafts.csswg.org/css-values/#dynamic-viewport-percentage-units
Dynamic,
}
/// https://drafts.csswg.org/css-values/#viewport-relative-units
#[derive(PartialEq)]
enum ViewportUnit {
/// *vw units.
Vw,
/// *vh units.
Vh,
/// *vmin units.
Vmin,
/// *vmax units.
Vmax,
/// *vb units.
Vb,
/// *vi units.
Vi,
}
/// A viewport-relative length.
///
/// <https://drafts.csswg.org/css-values/#viewport-relative-lengths>
#[derive(Clone, Copy, Debug, MallocSizeOf, PartialEq, ToCss, ToShmem)]
pub enum ViewportPercentageLength {
/// A vw unit: https://drafts.csswg.org/css-values/#vw
/// <https://drafts.csswg.org/css-values/#valdef-length-vw>
#[css(dimension)]
Vw(CSSFloat),
/// A vh unit: https://drafts.csswg.org/css-values/#vh
/// <https://drafts.csswg.org/css-values/#valdef-length-svw>
#[css(dimension)]
Svw(CSSFloat),
/// <https://drafts.csswg.org/css-values/#valdef-length-lvw>
#[css(dimension)]
Lvw(CSSFloat),
/// <https://drafts.csswg.org/css-values/#valdef-length-dvw>
#[css(dimension)]
Dvw(CSSFloat),
/// <https://drafts.csswg.org/css-values/#valdef-length-vh>
#[css(dimension)]
Vh(CSSFloat),
/// <https://drafts.csswg.org/css-values/#vmin>
/// <https://drafts.csswg.org/css-values/#valdef-length-svh>
#[css(dimension)]
Svh(CSSFloat),
/// <https://drafts.csswg.org/css-values/#valdef-length-lvh>
#[css(dimension)]
Lvh(CSSFloat),
/// <https://drafts.csswg.org/css-values/#valdef-length-dvh>
#[css(dimension)]
Dvh(CSSFloat),
/// <https://drafts.csswg.org/css-values/#valdef-length-vmin>
#[css(dimension)]
Vmin(CSSFloat),
/// <https://drafts.csswg.org/css-values/#vmax>
/// <https://drafts.csswg.org/css-values/#valdef-length-svmin>
#[css(dimension)]
Svmin(CSSFloat),
/// <https://drafts.csswg.org/css-values/#valdef-length-lvmin>
#[css(dimension)]
Lvmin(CSSFloat),
/// <https://drafts.csswg.org/css-values/#valdef-length-dvmin>
#[css(dimension)]
Dvmin(CSSFloat),
/// <https://drafts.csswg.org/css-values/#valdef-length-vmax>
#[css(dimension)]
Vmax(CSSFloat),
/// <https://drafts.csswg.org/css-values/#valdef-length-svmax>
#[css(dimension)]
Svmax(CSSFloat),
/// <https://drafts.csswg.org/css-values/#valdef-length-lvmax>
#[css(dimension)]
Lvmax(CSSFloat),
/// <https://drafts.csswg.org/css-values/#valdef-length-dvmax>
#[css(dimension)]
Dvmax(CSSFloat),
/// <https://drafts.csswg.org/css-values/#valdef-length-vb>
#[css(dimension)]
Vb(CSSFloat),
/// <https://drafts.csswg.org/css-values/#valdef-length-svb>
#[css(dimension)]
Svb(CSSFloat),
/// <https://drafts.csswg.org/css-values/#valdef-length-lvb>
#[css(dimension)]
Lvb(CSSFloat),
/// <https://drafts.csswg.org/css-values/#valdef-length-dvb>
#[css(dimension)]
Dvb(CSSFloat),
/// <https://drafts.csswg.org/css-values/#valdef-length-vi>
#[css(dimension)]
Vi(CSSFloat),
/// <https://drafts.csswg.org/css-values/#valdef-length-svi>
#[css(dimension)]
Svi(CSSFloat),
/// <https://drafts.csswg.org/css-values/#valdef-length-lvi>
#[css(dimension)]
Lvi(CSSFloat),
/// <https://drafts.csswg.org/css-values/#valdef-length-dvi>
#[css(dimension)]
Dvi(CSSFloat),
}
impl ViewportPercentageLength {
/// Return true if this is a zero value.
fn is_zero(&self) -> bool {
match *self {
ViewportPercentageLength::Vw(v) |
ViewportPercentageLength::Vh(v) |
ViewportPercentageLength::Vmin(v) |
ViewportPercentageLength::Vmax(v) => v == 0.,
}
let (_, _, v) = self.unpack();
v == 0.
}
fn is_negative(&self) -> bool {
match *self {
ViewportPercentageLength::Vw(v) |
ViewportPercentageLength::Vh(v) |
ViewportPercentageLength::Vmin(v) |
ViewportPercentageLength::Vmax(v) => v < 0.,
}
let (_, _, v) = self.unpack();
v < 0.
}
fn unpack(&self) -> (ViewportVariant, ViewportUnit, CSSFloat) {
match *self {
ViewportPercentageLength::Vw(v) => (
ViewportVariant::UADefault,
ViewportUnit::Vw,
v,
),
ViewportPercentageLength::Svw(v) => (
ViewportVariant::Small,
ViewportUnit::Vw,
v,
),
ViewportPercentageLength::Lvw(v) => (
ViewportVariant::Large,
ViewportUnit::Vw,
v,
),
ViewportPercentageLength::Dvw(v) => (
ViewportVariant::Dynamic,
ViewportUnit::Vw,
v,
),
ViewportPercentageLength::Vh(v) => (
ViewportVariant::UADefault,
ViewportUnit::Vh,
v,
),
ViewportPercentageLength::Svh(v) => (
ViewportVariant::Small,
ViewportUnit::Vh,
v,
),
ViewportPercentageLength::Lvh(v) => (
ViewportVariant::Large,
ViewportUnit::Vh,
v),
ViewportPercentageLength::Dvh(v) => (
ViewportVariant::Dynamic,
ViewportUnit::Vh,
v,
),
ViewportPercentageLength::Vmin(v) => (
ViewportVariant::UADefault,
ViewportUnit::Vmin,
v,
),
ViewportPercentageLength::Svmin(v) => (
ViewportVariant::Small,
ViewportUnit::Vmin,
v,
),
ViewportPercentageLength::Lvmin(v) => (
ViewportVariant::Large,
ViewportUnit::Vmin,
v,
),
ViewportPercentageLength::Dvmin(v) => (
ViewportVariant::Dynamic,
ViewportUnit::Vmin,
v,
),
ViewportPercentageLength::Vmax(v) => (
ViewportVariant::UADefault,
ViewportUnit::Vmax,
v,
),
ViewportPercentageLength::Svmax(v) => (
ViewportVariant::Small,
ViewportUnit::Vmax,
v,
),
ViewportPercentageLength::Lvmax(v) => (
ViewportVariant::Large,
ViewportUnit::Vmax,
v,
),
ViewportPercentageLength::Dvmax(v) => (
ViewportVariant::Dynamic,
ViewportUnit::Vmax,
v,
),
ViewportPercentageLength::Vb(v) => (
ViewportVariant::UADefault,
ViewportUnit::Vb,
v,
),
ViewportPercentageLength::Svb(v) => (
ViewportVariant::Small,
ViewportUnit::Vb,
v,
),
ViewportPercentageLength::Lvb(v) => (
ViewportVariant::Large,
ViewportUnit::Vb,
v,
),
ViewportPercentageLength::Dvb(v) => (
ViewportVariant::Dynamic,
ViewportUnit::Vb,
v,
),
ViewportPercentageLength::Vi(v) => (
ViewportVariant::UADefault,
ViewportUnit::Vi,
v,
),
ViewportPercentageLength::Svi(v) => (
ViewportVariant::Small,
ViewportUnit::Vi,
v,
),
ViewportPercentageLength::Lvi(v) => (
ViewportVariant::Large,
ViewportUnit::Vi,
v,
),
ViewportPercentageLength::Dvi(v) => (
ViewportVariant::Dynamic,
ViewportUnit::Vi,
v,
),
}
}
fn try_sum(&self, other: &Self) -> Result<Self, ()> {
use self::ViewportPercentageLength::*;
@ -326,14 +529,39 @@ impl ViewportPercentageLength {
Ok(match (self, other) {
(&Vw(one), &Vw(other)) => Vw(one + other),
(&Svw(one), &Svw(other)) => Svw(one + other),
(&Lvw(one), &Lvw(other)) => Lvw(one + other),
(&Dvw(one), &Dvw(other)) => Dvw(one + other),
(&Vh(one), &Vh(other)) => Vh(one + other),
(&Svh(one), &Svh(other)) => Svh(one + other),
(&Lvh(one), &Lvh(other)) => Lvh(one + other),
(&Dvh(one), &Dvh(other)) => Dvh(one + other),
(&Vmin(one), &Vmin(other)) => Vmin(one + other),
(&Svmin(one), &Svmin(other)) => Svmin(one + other),
(&Lvmin(one), &Lvmin(other)) => Lvmin(one + other),
(&Dvmin(one), &Dvmin(other)) => Dvmin(one + other),
(&Vmax(one), &Vmax(other)) => Vmax(one + other),
(&Svmax(one), &Svmax(other)) => Svmax(one + other),
(&Lvmax(one), &Lvmax(other)) => Lvmax(one + other),
(&Dvmax(one), &Dvmax(other)) => Dvmax(one + other),
(&Vb(one), &Vb(other)) => Vb(one + other),
(&Svb(one), &Svb(other)) => Svb(one + other),
(&Lvb(one), &Lvb(other)) => Lvb(one + other),
(&Dvb(one), &Dvb(other)) => Dvb(one + other),
(&Vi(one), &Vi(other)) => Vi(one + other),
(&Svi(one), &Svi(other)) => Svi(one + other),
(&Lvi(one), &Lvi(other)) => Lvi(one + other),
(&Dvi(one), &Dvi(other)) => Dvi(one + other),
// See https://github.com/rust-lang/rust/issues/68867. rustc isn't
// able to figure it own on its own so we help.
_ => unsafe {
match *self {
Vw(..) | Vh(..) | Vmin(..) | Vmax(..) => {},
Vw(..) | Svw(..) | Lvw(..) | Dvw(..) |
Vh(..) | Svh(..) | Lvh(..) | Dvh(..) |
Vmin(..) | Svmin(..) | Lvmin(..) | Dvmin(..) |
Vmax(..) | Svmax(..) | Lvmax(..) | Dvmax(..) |
Vb(..) | Svb(..) | Lvb(..) | Dvb(..) |
Vi(..) | Svi(..) | Lvi(..) | Dvi(..) => {},
}
debug_unreachable!("Forgot to handle unit in try_sum()")
},
@ -341,15 +569,24 @@ impl ViewportPercentageLength {
}
/// Computes the given viewport-relative length for the given viewport size.
pub fn to_computed_value(&self, viewport_size: Size2D<Au>) -> CSSPixelLength {
let (factor, length) = match *self {
ViewportPercentageLength::Vw(length) => (length, viewport_size.width),
ViewportPercentageLength::Vh(length) => (length, viewport_size.height),
ViewportPercentageLength::Vmin(length) => {
(length, cmp::min(viewport_size.width, viewport_size.height))
},
ViewportPercentageLength::Vmax(length) => {
(length, cmp::max(viewport_size.width, viewport_size.height))
pub fn to_computed_value(&self, context: &Context) -> CSSPixelLength {
let (variant, unit, factor) = self.unpack();
let size = context.viewport_size_for_viewport_unit_resolution(variant);
let length = match unit {
ViewportUnit::Vw => size.width,
ViewportUnit::Vh => size.height,
ViewportUnit::Vmin => cmp::min(size.width, size.height),
ViewportUnit::Vmax => cmp::max(size.width, size.height),
ViewportUnit::Vi | ViewportUnit::Vb => {
context
.rule_cache_conditions
.borrow_mut()
.set_writing_mode_dependency(context.builder.writing_mode);
if (unit == ViewportUnit::Vb) == context.style().writing_mode.is_vertical() {
size.width
} else {
size.height
}
},
};
@ -578,15 +815,75 @@ impl NoCalcLength {
"vw" if !context.in_page_rule() => {
NoCalcLength::ViewportPercentage(ViewportPercentageLength::Vw(value))
},
"svw" if !context.in_page_rule() => {
NoCalcLength::ViewportPercentage(ViewportPercentageLength::Svw(value))
},
"lvw" if !context.in_page_rule() => {
NoCalcLength::ViewportPercentage(ViewportPercentageLength::Lvw(value))
},
"dvw" if !context.in_page_rule() => {
NoCalcLength::ViewportPercentage(ViewportPercentageLength::Dvw(value))
},
"vh" if !context.in_page_rule() => {
NoCalcLength::ViewportPercentage(ViewportPercentageLength::Vh(value))
},
"svh" if !context.in_page_rule() => {
NoCalcLength::ViewportPercentage(ViewportPercentageLength::Svh(value))
},
"lvh" if !context.in_page_rule() => {
NoCalcLength::ViewportPercentage(ViewportPercentageLength::Lvh(value))
},
"dvh" if !context.in_page_rule() => {
NoCalcLength::ViewportPercentage(ViewportPercentageLength::Dvh(value))
},
"vmin" if !context.in_page_rule() => {
NoCalcLength::ViewportPercentage(ViewportPercentageLength::Vmin(value))
},
"svmin" if !context.in_page_rule() => {
NoCalcLength::ViewportPercentage(ViewportPercentageLength::Svmin(value))
},
"lvmin" if !context.in_page_rule() => {
NoCalcLength::ViewportPercentage(ViewportPercentageLength::Lvmin(value))
},
"dvmin" if !context.in_page_rule() => {
NoCalcLength::ViewportPercentage(ViewportPercentageLength::Dvmin(value))
},
"vmax" if !context.in_page_rule() => {
NoCalcLength::ViewportPercentage(ViewportPercentageLength::Vmax(value))
},
"svmax" if !context.in_page_rule() => {
NoCalcLength::ViewportPercentage(ViewportPercentageLength::Svmax(value))
},
"lvmax" if !context.in_page_rule() => {
NoCalcLength::ViewportPercentage(ViewportPercentageLength::Lvmax(value))
},
"dvmax" if !context.in_page_rule() => {
NoCalcLength::ViewportPercentage(ViewportPercentageLength::Dvmax(value))
},
"vb" if !context.in_page_rule() => {
NoCalcLength::ViewportPercentage(ViewportPercentageLength::Vb(value))
},
"svb" if !context.in_page_rule() => {
NoCalcLength::ViewportPercentage(ViewportPercentageLength::Svb(value))
},
"lvb" if !context.in_page_rule() => {
NoCalcLength::ViewportPercentage(ViewportPercentageLength::Lvb(value))
},
"dvb" if !context.in_page_rule() => {
NoCalcLength::ViewportPercentage(ViewportPercentageLength::Dvb(value))
},
"vi" if !context.in_page_rule() => {
NoCalcLength::ViewportPercentage(ViewportPercentageLength::Vi(value))
},
"svi" if !context.in_page_rule() => {
NoCalcLength::ViewportPercentage(ViewportPercentageLength::Svi(value))
},
"lvi" if !context.in_page_rule() => {
NoCalcLength::ViewportPercentage(ViewportPercentageLength::Lvi(value))
},
"dvi" if !context.in_page_rule() => {
NoCalcLength::ViewportPercentage(ViewportPercentageLength::Dvi(value))
},
_ => return Err(()),
})
}
@ -770,9 +1067,29 @@ impl Mul<CSSFloat> for ViewportPercentageLength {
fn mul(self, scalar: CSSFloat) -> ViewportPercentageLength {
match self {
ViewportPercentageLength::Vw(v) => ViewportPercentageLength::Vw(v * scalar),
ViewportPercentageLength::Svw(v) => ViewportPercentageLength::Svw(v * scalar),
ViewportPercentageLength::Lvw(v) => ViewportPercentageLength::Lvw(v * scalar),
ViewportPercentageLength::Dvw(v) => ViewportPercentageLength::Dvw(v * scalar),
ViewportPercentageLength::Vh(v) => ViewportPercentageLength::Vh(v * scalar),
ViewportPercentageLength::Svh(v) => ViewportPercentageLength::Svh(v * scalar),
ViewportPercentageLength::Lvh(v) => ViewportPercentageLength::Lvh(v * scalar),
ViewportPercentageLength::Dvh(v) => ViewportPercentageLength::Dvh(v * scalar),
ViewportPercentageLength::Vmin(v) => ViewportPercentageLength::Vmin(v * scalar),
ViewportPercentageLength::Svmin(v) => ViewportPercentageLength::Svmin(v * scalar),
ViewportPercentageLength::Lvmin(v) => ViewportPercentageLength::Lvmin(v * scalar),
ViewportPercentageLength::Dvmin(v) => ViewportPercentageLength::Dvmin(v * scalar),
ViewportPercentageLength::Vmax(v) => ViewportPercentageLength::Vmax(v * scalar),
ViewportPercentageLength::Svmax(v) => ViewportPercentageLength::Svmax(v * scalar),
ViewportPercentageLength::Lvmax(v) => ViewportPercentageLength::Lvmax(v * scalar),
ViewportPercentageLength::Dvmax(v) => ViewportPercentageLength::Dvmax(v * scalar),
ViewportPercentageLength::Vb(v) => ViewportPercentageLength::Vb(v * scalar),
ViewportPercentageLength::Svb(v) => ViewportPercentageLength::Svb(v * scalar),
ViewportPercentageLength::Lvb(v) => ViewportPercentageLength::Lvb(v * scalar),
ViewportPercentageLength::Dvb(v) => ViewportPercentageLength::Dvb(v * scalar),
ViewportPercentageLength::Vi(v) => ViewportPercentageLength::Vi(v * scalar),
ViewportPercentageLength::Svi(v) => ViewportPercentageLength::Svi(v * scalar),
ViewportPercentageLength::Lvi(v) => ViewportPercentageLength::Lvi(v * scalar),
ViewportPercentageLength::Dvi(v) => ViewportPercentageLength::Dvi(v * scalar),
}
}
}
@ -787,14 +1104,39 @@ impl PartialOrd for ViewportPercentageLength {
match (self, other) {
(&Vw(ref one), &Vw(ref other)) => one.partial_cmp(other),
(&Svw(ref one), &Svw(ref other)) => one.partial_cmp(other),
(&Lvw(ref one), &Lvw(ref other)) => one.partial_cmp(other),
(&Dvw(ref one), &Dvw(ref other)) => one.partial_cmp(other),
(&Vh(ref one), &Vh(ref other)) => one.partial_cmp(other),
(&Svh(ref one), &Svh(ref other)) => one.partial_cmp(other),
(&Lvh(ref one), &Lvh(ref other)) => one.partial_cmp(other),
(&Dvh(ref one), &Dvh(ref other)) => one.partial_cmp(other),
(&Vmin(ref one), &Vmin(ref other)) => one.partial_cmp(other),
(&Svmin(ref one), &Svmin(ref other)) => one.partial_cmp(other),
(&Lvmin(ref one), &Lvmin(ref other)) => one.partial_cmp(other),
(&Dvmin(ref one), &Dvmin(ref other)) => one.partial_cmp(other),
(&Vmax(ref one), &Vmax(ref other)) => one.partial_cmp(other),
(&Svmax(ref one), &Svmax(ref other)) => one.partial_cmp(other),
(&Lvmax(ref one), &Lvmax(ref other)) => one.partial_cmp(other),
(&Dvmax(ref one), &Dvmax(ref other)) => one.partial_cmp(other),
(&Vb(ref one), &Vb(ref other)) => one.partial_cmp(other),
(&Svb(ref one), &Svb(ref other)) => one.partial_cmp(other),
(&Lvb(ref one), &Lvb(ref other)) => one.partial_cmp(other),
(&Dvb(ref one), &Dvb(ref other)) => one.partial_cmp(other),
(&Vi(ref one), &Vi(ref other)) => one.partial_cmp(other),
(&Svi(ref one), &Svi(ref other)) => one.partial_cmp(other),
(&Lvi(ref one), &Lvi(ref other)) => one.partial_cmp(other),
(&Dvi(ref one), &Dvi(ref other)) => one.partial_cmp(other),
// See https://github.com/rust-lang/rust/issues/68867. rustc isn't
// able to figure it own on its own so we help.
_ => unsafe {
match *self {
Vw(..) | Vh(..) | Vmin(..) | Vmax(..) => {},
Vw(..) | Svw(..) | Lvw(..) | Dvw(..) |
Vh(..) | Svh(..) | Lvh(..) | Dvh(..) |
Vmin(..) | Svmin(..) | Lvmin(..) | Dvmin(..) |
Vmax(..) | Svmax(..) | Lvmax(..) | Dvmax(..) |
Vb(..) | Svb(..) | Lvb(..) | Dvb(..) |
Vi(..) | Svi(..) | Lvi(..) | Dvi(..) => {},
}
debug_unreachable!("Forgot an arm in partial_cmp?")
},
@ -986,10 +1328,9 @@ impl From<NoCalcLength> for LengthPercentage {
impl From<Percentage> for LengthPercentage {
#[inline]
fn from(pc: Percentage) -> Self {
if pc.is_calc() {
// FIXME(emilio): Hard-coding the clamping mode is suspect.
if let Some(clamping_mode) = pc.calc_clamping_mode() {
LengthPercentage::Calc(Box::new(CalcLengthPercentage {
clamping_mode: AllowedNumericType::All,
clamping_mode,
node: CalcNode::Leaf(calc::Leaf::Percentage(pc.get())),
}))
} else {
@ -1022,6 +1363,12 @@ impl LengthPercentage {
LengthPercentage::Percentage(computed::Percentage::zero())
}
#[inline]
/// Returns a `100%` value.
pub fn hundred_percent() -> LengthPercentage {
LengthPercentage::Percentage(computed::Percentage::hundred())
}
fn parse_internal<'i, 't>(
context: &ParserContext,
input: &mut Parser<'i, 't>,

View file

@ -37,10 +37,11 @@ pub use self::border::{BorderCornerRadius, BorderImageSlice, BorderImageWidth};
pub use self::border::{BorderImageRepeat, BorderImageSideWidth};
pub use self::border::{BorderRadius, BorderSideWidth, BorderSpacing, BorderStyle};
pub use self::box_::{AnimationIterationCount, AnimationName, AnimationTimeline, Contain, Display};
pub use self::box_::{Appearance, BreakBetween, BreakWithin};
pub use self::box_::{Appearance, BreakBetween, BreakWithin, ContainerName, ContainerType};
pub use self::box_::{Clear, ContentVisibility, Float, Overflow, OverflowAnchor};
pub use self::box_::{OverflowClipBox, OverscrollBehavior, Perspective, Resize, ScrollbarGutter};
pub use self::box_::{ScrollSnapAlign, ScrollSnapAxis, ScrollSnapStrictness, ScrollSnapType};
pub use self::box_::{ScrollSnapAlign, ScrollSnapAxis, ScrollSnapStop};
pub use self::box_::{ScrollSnapStrictness, ScrollSnapType};
pub use self::box_::{TouchAction, TransitionProperty, VerticalAlign, WillChange};
pub use self::color::{Color, ColorOrAuto, ColorPropertyValue, ColorScheme, PrintColorAdjust};
pub use self::column::ColumnCount;
@ -60,7 +61,7 @@ pub use self::length::{AbsoluteLength, CalcLengthPercentage, CharacterWidth};
pub use self::length::{FontRelativeLength, Length, LengthOrNumber, NonNegativeLengthOrNumber};
pub use self::length::{LengthOrAuto, LengthPercentage, LengthPercentageOrAuto};
pub use self::length::{MaxSize, Size};
pub use self::length::{NoCalcLength, ViewportPercentageLength};
pub use self::length::{NoCalcLength, ViewportPercentageLength, ViewportVariant};
pub use self::length::{
NonNegativeLength, NonNegativeLengthPercentage, NonNegativeLengthPercentageOrAuto,
};
@ -69,7 +70,7 @@ pub use self::list::ListStyleType;
pub use self::list::Quotes;
pub use self::motion::{OffsetPath, OffsetRotate};
pub use self::outline::OutlineStyle;
pub use self::page::{Orientation, PageName, PageSize, PaperSize};
pub use self::page::{PageOrientation, PageName, PageSize, PaperSize};
pub use self::percentage::{NonNegativePercentage, Percentage};
pub use self::position::AspectRatio;
pub use self::position::{

View file

@ -11,7 +11,7 @@ use crate::values::{generics, CustomIdent};
use cssparser::Parser;
use style_traits::ParseError;
pub use generics::page::Orientation;
pub use generics::page::PageOrientation;
pub use generics::page::PaperSize;
/// Specified value of the @page size descriptor
pub type PageSize = generics::page::PageSize<Size2D<NonNegativeLength>>;
@ -24,12 +24,12 @@ impl Parse for PageSize {
// Try to parse as <page-size> [ <orientation> ]
if let Ok(paper_size) = input.try_parse(PaperSize::parse) {
let orientation = input
.try_parse(Orientation::parse)
.unwrap_or(Orientation::Portrait);
.try_parse(PageOrientation::parse)
.unwrap_or(PageOrientation::Portrait);
return Ok(PageSize::PaperSize(paper_size, orientation));
}
// Try to parse as <orientation> [ <page-size> ]
if let Ok(orientation) = input.try_parse(Orientation::parse) {
if let Ok(orientation) = input.try_parse(PageOrientation::parse) {
if let Ok(paper_size) = input.try_parse(PaperSize::parse) {
return Ok(PageSize::PaperSize(paper_size, orientation));
}

View file

@ -92,9 +92,9 @@ impl Percentage {
Number::new_with_clamping_mode(self.value, self.calc_clamping_mode)
}
/// Returns whether this percentage is a `calc()` value.
pub fn is_calc(&self) -> bool {
self.calc_clamping_mode.is_some()
/// Returns the calc() clamping mode for this percentage.
pub fn calc_clamping_mode(&self) -> Option<AllowedNumericType> {
self.calc_clamping_mode
}
/// Reverses this percentage, preserving calc-ness.
@ -138,6 +138,15 @@ impl Percentage {
Self::parse_with_clamping_mode(context, input, AllowedNumericType::NonNegative)
}
/// Parses a percentage token, but rejects it if it's negative or more than
/// 100%.
pub fn parse_zero_to_a_hundred<'i, 't>(
context: &ParserContext,
input: &mut Parser<'i, 't>,
) -> Result<Self, ParseError<'i>> {
Self::parse_with_clamping_mode(context, input, AllowedNumericType::ZeroToOne)
}
/// Clamp to 100% if the value is over 100%.
#[inline]
pub fn clamp_to_hundred(self) -> Self {
@ -174,6 +183,24 @@ impl ToComputedValue for Percentage {
impl SpecifiedValueInfo for Percentage {}
/// Turns the percentage into a plain float.
pub trait ToPercentage {
/// Returns whether this percentage used to be a calc().
fn is_calc(&self) -> bool { false }
/// Turns the percentage into a plain float.
fn to_percentage(&self) -> CSSFloat;
}
impl ToPercentage for Percentage {
fn is_calc(&self) -> bool {
self.calc_clamping_mode.is_some()
}
fn to_percentage(&self) -> CSSFloat {
self.get()
}
}
/// A wrapper of Percentage, whose value must be >= 0.
pub type NonNegativePercentage = NonNegative<Percentage>;

View file

@ -556,6 +556,7 @@ impl From<MasonryAutoFlow> for u8 {
}
}
// TODO: Can be derived with some care.
impl Parse for GridAutoFlow {
/// [ row | column ] || dense
fn parse<'i, 't>(

View file

@ -6,8 +6,9 @@
#[cfg(feature = "gecko")]
use crate::gecko_bindings::sugar::ownership::{HasBoxFFI, HasFFI, HasSimpleFFI};
use crate::media_queries::{Device, MediaCondition};
use crate::media_queries::Device;
use crate::parser::{Parse, ParserContext};
use crate::queries::{QueryCondition, FeatureType};
use crate::values::computed::{self, ToComputedValue};
use crate::values::specified::{Length, NoCalcLength, ViewportPercentageLength};
use app_units::Au;
@ -20,7 +21,7 @@ use style_traits::ParseError;
/// https://html.spec.whatwg.org/multipage/#source-size
#[derive(Debug)]
pub struct SourceSize {
condition: MediaCondition,
condition: QueryCondition,
value: Length,
}
@ -29,9 +30,8 @@ impl Parse for SourceSize {
context: &ParserContext,
input: &mut Parser<'i, 't>,
) -> Result<Self, ParseError<'i>> {
let condition = MediaCondition::parse(context, input)?;
let condition = QueryCondition::parse(context, input, FeatureType::Media)?;
let value = Length::parse_non_negative(context, input)?;
Ok(Self { condition, value })
}
}
@ -56,12 +56,12 @@ impl SourceSizeList {
/// Evaluate this <source-size-list> to get the final viewport length.
pub fn evaluate(&self, device: &Device, quirks_mode: QuirksMode) -> Au {
let matching_source_size = self
.source_sizes
.iter()
.find(|source_size| source_size.condition.matches(device, quirks_mode));
computed::Context::for_media_query_evaluation(device, quirks_mode, |context| {
let matching_source_size = self
.source_sizes
.iter()
.find(|source_size| source_size.condition.matches(context));
match matching_source_size {
Some(source_size) => source_size.value.to_computed_value(context),
None => match self.value {

View file

@ -232,8 +232,8 @@ impl ToComputedValue for TextOverflow {
}
bitflags! {
#[derive(MallocSizeOf, Serialize, SpecifiedValueInfo, ToComputedValue, ToResolvedValue, ToShmem)]
#[value_info(other_values = "none,underline,overline,line-through,blink")]
#[derive(MallocSizeOf, Parse, Serialize, SpecifiedValueInfo, ToCss, ToComputedValue, ToResolvedValue, ToShmem)]
#[css(bitflags(single = "none", mixed = "underline,overline,line-through,blink"))]
#[repr(C)]
/// Specified keyword values for the text-decoration-line property.
pub struct TextDecorationLine: u8 {
@ -265,94 +265,6 @@ impl Default for TextDecorationLine {
}
}
impl Parse for TextDecorationLine {
/// none | [ underline || overline || line-through || blink ]
fn parse<'i, 't>(
_context: &ParserContext,
input: &mut Parser<'i, 't>,
) -> Result<Self, ParseError<'i>> {
let mut result = TextDecorationLine::empty();
// NOTE(emilio): this loop has this weird structure because we run this
// code to parse the text-decoration shorthand as well, so we need to
// ensure we don't return an error if we don't consume the whole thing
// because we find an invalid identifier or other kind of token.
loop {
let flag: Result<_, ParseError<'i>> = input.try_parse(|input| {
let flag = try_match_ident_ignore_ascii_case! { input,
"none" if result.is_empty() => TextDecorationLine::NONE,
"underline" => TextDecorationLine::UNDERLINE,
"overline" => TextDecorationLine::OVERLINE,
"line-through" => TextDecorationLine::LINE_THROUGH,
"blink" => TextDecorationLine::BLINK,
};
Ok(flag)
});
let flag = match flag {
Ok(flag) => flag,
Err(..) => break,
};
if flag.is_empty() {
return Ok(TextDecorationLine::NONE);
}
if result.contains(flag) {
return Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError));
}
result.insert(flag)
}
if !result.is_empty() {
Ok(result)
} else {
Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError))
}
}
}
impl ToCss for TextDecorationLine {
fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
where
W: Write,
{
if self.is_empty() {
return dest.write_str("none");
}
#[cfg(feature = "gecko")]
{
if *self == TextDecorationLine::COLOR_OVERRIDE {
return Ok(());
}
}
let mut writer = SequenceWriter::new(dest, " ");
let mut any = false;
macro_rules! maybe_write {
($ident:ident => $str:expr) => {
if self.contains(TextDecorationLine::$ident) {
any = true;
writer.raw_item($str)?;
}
};
}
maybe_write!(UNDERLINE => "underline");
maybe_write!(OVERLINE => "overline");
maybe_write!(LINE_THROUGH => "line-through");
maybe_write!(BLINK => "blink");
debug_assert!(any);
Ok(())
}
}
impl TextDecorationLine {
#[inline]
/// Returns the initial value of text-decoration-line
@ -399,6 +311,7 @@ impl TextTransform {
}
}
// TODO: This can be simplified by deriving it.
impl Parse for TextTransform {
fn parse<'i, 't>(
_context: &ParserContext,
@ -615,11 +528,20 @@ pub enum TextAlign {
/// unlike other keywords.
#[cfg(feature = "gecko")]
MatchParent,
/// `MozCenterOrInherit` value of text-align property. It cannot be parsed,
/// only set directly on the elements and it has a different handling
/// unlike other values.
/// This is how we implement the following HTML behavior from
/// https://html.spec.whatwg.org/#tables-2:
///
/// User agents are expected to have a rule in their user agent style sheet
/// that matches th elements that have a parent node whose computed value
/// for the 'text-align' property is its initial value, whose declaration
/// block consists of just a single declaration that sets the 'text-align'
/// property to the value 'center'.
///
/// Since selectors can't depend on the ancestor styles, we implement it with a
/// magic value that computes to the right thing. Since this is an
/// implementation detail, it shouldn't be exposed to web content.
#[cfg(feature = "gecko")]
#[css(skip)]
#[parse(condition = "ParserContext::in_ua_or_chrome_sheet")]
MozCenterOrInherit,
}
@ -1194,6 +1116,7 @@ bitflags! {
}
}
// TODO: This can be derived with some care.
impl Parse for TextUnderlinePosition {
fn parse<'i, 't>(
_context: &ParserContext,