prepare for media query merging

This commit is contained in:
Connor Skees 2021-07-21 20:24:25 -04:00
parent be3edd8a62
commit 9ed2a8a984

View File

@ -1,3 +1,4 @@
#![allow(dead_code)]
use std::fmt;
use crate::{parse::Stmt, selector::Selector};
@ -9,7 +10,7 @@ pub(crate) struct MediaRule {
pub body: Vec<Stmt>,
}
#[derive(Debug, Eq, PartialEq, Clone)]
#[derive(Clone, Debug, Eq, PartialEq, Hash)]
pub(crate) struct MediaQuery {
/// The modifier, probably either "not" or "only".
///
@ -25,7 +26,6 @@ pub(crate) struct MediaQuery {
pub features: Vec<String>,
}
#[allow(dead_code)]
impl MediaQuery {
pub fn is_condition(&self) -> bool {
self.modifier.is_none() && self.media_type.is_none()
@ -47,9 +47,159 @@ impl MediaQuery {
}
}
#[allow(dead_code, unused_variables)]
pub fn merge(other: &Self) -> Self {
todo!()
fn merge(&self, other: &Self) -> MediaQueryMergeResult {
let this_modifier = self.modifier.as_ref().map(|m| m.to_ascii_lowercase());
let this_type = self.media_type.as_ref().map(|m| m.to_ascii_lowercase());
let other_modifier = other.modifier.as_ref().map(|m| m.to_ascii_lowercase());
let other_type = other.media_type.as_ref().map(|m| m.to_ascii_lowercase());
if this_type.is_none() && other_type.is_none() {
return MediaQueryMergeResult::Success(Self::condition(
self.features
.iter()
.chain(&other.features)
.cloned()
.collect(),
));
}
let modifier;
let media_type;
let features;
if (this_modifier.as_deref() == Some("not")) != (other_modifier.as_deref() == Some("not")) {
if this_modifier == other_modifier {
let negative_features = if this_modifier.as_deref() == Some("not") {
&self.features
} else {
&other.features
};
let positive_features = if this_modifier.as_deref() == Some("not") {
&other.features
} else {
&self.features
};
// If the negative features are a subset of the positive features, the
// query is empty. For example, `not screen and (color)` has no
// intersection with `screen and (color) and (grid)`.
//
// However, `not screen and (color)` *does* intersect with `screen and
// (grid)`, because it means `not (screen and (color))` and so it allows
// a screen with no color but with a grid.
if negative_features
.iter()
.all(|feat| positive_features.contains(&feat))
{
return MediaQueryMergeResult::Empty;
} else {
return MediaQueryMergeResult::Unrepresentable;
}
} else if self.matches_all_types() || other.matches_all_types() {
return MediaQueryMergeResult::Unrepresentable;
}
if this_modifier.as_deref() == Some("not") {
modifier = &other_modifier;
media_type = &other_type;
features = other.features.clone();
} else {
modifier = &this_modifier;
media_type = &this_type;
features = self.features.clone();
}
} else if this_modifier.as_deref() == Some("not") {
debug_assert_eq!(other_modifier.as_deref(), Some("not"));
// CSS has no way of representing "neither screen nor print".
if this_type != other_type {
return MediaQueryMergeResult::Unrepresentable;
}
let more_features = if self.features.len() > other.features.len() {
&self.features
} else {
&other.features
};
let fewer_features = if self.features.len() > other.features.len() {
&other.features
} else {
&self.features
};
// If one set of features is a superset of the other, use those features
// because they're strictly narrower.
if fewer_features
.iter()
.all(|feat| more_features.contains(feat))
{
modifier = &this_modifier; // "not"
media_type = &this_type;
features = more_features.clone();
} else {
// Otherwise, there's no way to represent the intersection.
return MediaQueryMergeResult::Unrepresentable;
}
} else if self.matches_all_types() {
modifier = &other_modifier;
// Omit the type if either input query did, since that indicates that they
// aren't targeting a browser that requires "all and".
media_type = if other.matches_all_types() && this_type.is_none() {
&None
} else {
&other_type
};
features = self
.features
.iter()
.chain(&other.features)
.cloned()
.collect();
} else if other.matches_all_types() {
modifier = &this_modifier;
media_type = &this_type;
features = self
.features
.iter()
.chain(&other.features)
.cloned()
.collect();
} else if this_type != other_type {
return MediaQueryMergeResult::Empty;
} else {
if this_modifier.is_some() {
modifier = &this_modifier
} else {
modifier = &other_modifier;
}
media_type = &this_type;
features = self
.features
.iter()
.chain(&other.features)
.cloned()
.collect();
}
MediaQueryMergeResult::Success(MediaQuery {
media_type: if media_type == &this_type {
self.media_type.clone()
} else {
other.media_type.clone()
},
modifier: if modifier == &this_modifier {
self.modifier.clone()
} else {
other.modifier.clone()
},
features,
})
}
}
@ -67,3 +217,10 @@ impl fmt::Display for MediaQuery {
f.write_str(&self.features.join(" and "))
}
}
#[derive(Clone, Debug, Eq, PartialEq, Hash)]
enum MediaQueryMergeResult {
Empty,
Unrepresentable,
Success(MediaQuery),
}