Auto merge of #21109 - emilio:gecko-sync, r=SimonSapin

style: sync changes from mozilla-central.

See each individual commit for details..

<!-- Reviewable:start -->
---
This change is [<img src="https://reviewable.io/review_button.svg" height="34" align="absmiddle" alt="Reviewable"/>](https://reviewable.io/reviews/servo/servo/21109)
<!-- Reviewable:end -->
This commit is contained in:
bors-servo 2018-07-01 05:14:21 -04:00 committed by GitHub
commit c71c55e542
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
54 changed files with 872 additions and 1077 deletions

29
Cargo.lock generated
View file

@ -271,7 +271,7 @@ dependencies = [
"azure 0.29.0 (git+https://github.com/servo/rust-azure)",
"canvas_traits 0.0.1",
"compositing 0.0.1",
"cssparser 0.23.10 (registry+https://github.com/rust-lang/crates.io-index)",
"cssparser 0.24.0 (registry+https://github.com/rust-lang/crates.io-index)",
"euclid 0.17.3 (registry+https://github.com/rust-lang/crates.io-index)",
"fnv 1.0.5 (registry+https://github.com/rust-lang/crates.io-index)",
"gleam 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)",
@ -289,7 +289,7 @@ dependencies = [
name = "canvas_traits"
version = "0.0.1"
dependencies = [
"cssparser 0.23.10 (registry+https://github.com/rust-lang/crates.io-index)",
"cssparser 0.24.0 (registry+https://github.com/rust-lang/crates.io-index)",
"euclid 0.17.3 (registry+https://github.com/rust-lang/crates.io-index)",
"gleam 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)",
"ipc-channel 0.10.0 (registry+https://github.com/rust-lang/crates.io-index)",
@ -561,7 +561,7 @@ dependencies = [
[[package]]
name = "cssparser"
version = "0.23.10"
version = "0.24.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"cssparser-macros 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)",
@ -569,12 +569,12 @@ dependencies = [
"itoa 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)",
"matches 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)",
"phf 0.7.21 (registry+https://github.com/rust-lang/crates.io-index)",
"proc-macro2 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)",
"proc-macro2 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)",
"procedural-masquerade 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)",
"quote 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)",
"quote 0.6.3 (registry+https://github.com/rust-lang/crates.io-index)",
"serde 1.0.66 (registry+https://github.com/rust-lang/crates.io-index)",
"smallvec 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)",
"syn 0.13.1 (registry+https://github.com/rust-lang/crates.io-index)",
"syn 0.14.2 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
@ -1610,7 +1610,7 @@ name = "malloc_size_of"
version = "0.0.1"
dependencies = [
"app_units 0.6.1 (registry+https://github.com/rust-lang/crates.io-index)",
"cssparser 0.23.10 (registry+https://github.com/rust-lang/crates.io-index)",
"cssparser 0.24.0 (registry+https://github.com/rust-lang/crates.io-index)",
"euclid 0.17.3 (registry+https://github.com/rust-lang/crates.io-index)",
"hashglobe 0.1.0",
"hyper 0.10.13 (registry+https://github.com/rust-lang/crates.io-index)",
@ -2470,7 +2470,7 @@ dependencies = [
"chrono 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)",
"cmake 0.1.29 (registry+https://github.com/rust-lang/crates.io-index)",
"cookie 0.10.1 (registry+https://github.com/rust-lang/crates.io-index)",
"cssparser 0.23.10 (registry+https://github.com/rust-lang/crates.io-index)",
"cssparser 0.24.0 (registry+https://github.com/rust-lang/crates.io-index)",
"deny_public_fields 0.0.1",
"devtools_traits 0.0.1",
"dom_struct 0.0.1",
@ -2547,7 +2547,7 @@ dependencies = [
"app_units 0.6.1 (registry+https://github.com/rust-lang/crates.io-index)",
"atomic_refcell 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
"canvas_traits 0.0.1",
"cssparser 0.23.10 (registry+https://github.com/rust-lang/crates.io-index)",
"cssparser 0.24.0 (registry+https://github.com/rust-lang/crates.io-index)",
"euclid 0.17.3 (registry+https://github.com/rust-lang/crates.io-index)",
"gfx_traits 0.0.1",
"html5ever 0.22.3 (registry+https://github.com/rust-lang/crates.io-index)",
@ -2590,7 +2590,6 @@ dependencies = [
"msg 0.0.1",
"script 0.0.1",
"servo_url 0.0.1",
"style 0.0.1",
]
[[package]]
@ -2629,7 +2628,7 @@ name = "selectors"
version = "0.19.0"
dependencies = [
"bitflags 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)",
"cssparser 0.23.10 (registry+https://github.com/rust-lang/crates.io-index)",
"cssparser 0.24.0 (registry+https://github.com/rust-lang/crates.io-index)",
"fnv 1.0.5 (registry+https://github.com/rust-lang/crates.io-index)",
"log 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)",
"matches 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)",
@ -2967,7 +2966,7 @@ dependencies = [
"bitflags 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)",
"byteorder 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
"cfg-if 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)",
"cssparser 0.23.10 (registry+https://github.com/rust-lang/crates.io-index)",
"cssparser 0.24.0 (registry+https://github.com/rust-lang/crates.io-index)",
"encoding_rs 0.7.1 (registry+https://github.com/rust-lang/crates.io-index)",
"euclid 0.17.3 (registry+https://github.com/rust-lang/crates.io-index)",
"fallible 0.0.1",
@ -3028,7 +3027,7 @@ version = "0.0.1"
dependencies = [
"app_units 0.6.1 (registry+https://github.com/rust-lang/crates.io-index)",
"byteorder 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
"cssparser 0.23.10 (registry+https://github.com/rust-lang/crates.io-index)",
"cssparser 0.24.0 (registry+https://github.com/rust-lang/crates.io-index)",
"euclid 0.17.3 (registry+https://github.com/rust-lang/crates.io-index)",
"html5ever 0.22.3 (registry+https://github.com/rust-lang/crates.io-index)",
"parking_lot 0.5.4 (registry+https://github.com/rust-lang/crates.io-index)",
@ -3050,7 +3049,7 @@ version = "0.0.1"
dependencies = [
"app_units 0.6.1 (registry+https://github.com/rust-lang/crates.io-index)",
"bitflags 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)",
"cssparser 0.23.10 (registry+https://github.com/rust-lang/crates.io-index)",
"cssparser 0.24.0 (registry+https://github.com/rust-lang/crates.io-index)",
"euclid 0.17.3 (registry+https://github.com/rust-lang/crates.io-index)",
"malloc_size_of 0.0.1",
"malloc_size_of_derive 0.0.1",
@ -3760,7 +3759,7 @@ dependencies = [
"checksum crossbeam-deque 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "f739f8c5363aca78cfb059edf753d8f0d36908c348f3d8d1503f03d8b75d9cf3"
"checksum crossbeam-epoch 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "927121f5407de9956180ff5e936fe3cf4324279280001cd56b669d28ee7e9150"
"checksum crossbeam-utils 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "2760899e32a1d58d5abb31129f8fae5de75220bc2176e77ff7c627ae45c918d9"
"checksum cssparser 0.23.10 (registry+https://github.com/rust-lang/crates.io-index)" = "8f1c74d99b0f489cc546336b911452562ebfd4aec034b0c526cb77a3a02d3790"
"checksum cssparser 0.24.0 (registry+https://github.com/rust-lang/crates.io-index)" = "495beddc39b1987b8e9f029354eccbd5ef88eb5f1cd24badb764dce338acf2e0"
"checksum cssparser-macros 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "f3a5383ae18dbfdeb569ed62019f5bddb2a95cd2d3833313c475a0d014777805"
"checksum darling 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "2a78af487e4eb8f4421a1770687b328af6bb4494ca93435210678c6eea875c11"
"checksum darling_core 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "b315f49c7b6db3708bca6e6913c194581a44ec619b7a39e131d4dd63733a3698"

View file

@ -13,7 +13,7 @@ path = "lib.rs"
azure = {git = "https://github.com/servo/rust-azure"}
canvas_traits = {path = "../canvas_traits"}
compositing = {path = "../compositing"}
cssparser = "0.23.0"
cssparser = "0.24"
euclid = "0.17"
fnv = "1.0"
gleam = "0.5"

View file

@ -10,7 +10,7 @@ name = "canvas_traits"
path = "lib.rs"
[dependencies]
cssparser = "0.23.0"
cssparser = "0.24.0"
euclid = "0.17"
ipc-channel = "0.10"
gleam = "0.5.1"

View file

@ -1219,11 +1219,7 @@ impl FragmentDisplayListBuilding for Fragment {
state.add_display_item(DisplayItem::BoxShadow(Box::new(BoxShadowDisplayItem {
base: base,
box_bounds: absolute_bounds.to_layout(),
color: box_shadow
.base
.color
.unwrap_or(style.get_color().color)
.to_layout(),
color: style.resolve_color(box_shadow.base.color).to_layout(),
offset: LayoutVector2D::new(
box_shadow.base.horizontal.px(),
box_shadow.base.vertical.px(),
@ -2038,10 +2034,7 @@ impl FragmentDisplayListBuilding for Fragment {
base: base.clone(),
shadow: webrender_api::Shadow {
offset: LayoutVector2D::new(shadow.horizontal.px(), shadow.vertical.px()),
color: shadow
.color
.unwrap_or(self.style().get_color().color)
.to_layout(),
color: self.style.resolve_color(shadow.color).to_layout(),
blur_radius: shadow.blur.px(),
},
},

View file

@ -24,7 +24,7 @@ servo = [
[dependencies]
app_units = "0.6"
cssparser = "0.23.0"
cssparser = "0.24.0"
euclid = "0.17"
hashglobe = { path = "../hashglobe" }
hyper = { version = "0.10", optional = true }

View file

@ -37,7 +37,7 @@ canvas_traits = {path = "../canvas_traits"}
caseless = "0.2"
cookie = "0.10"
chrono = "0.4"
cssparser = "0.23.0"
cssparser = "0.24"
deny_public_fields = {path = "../deny_public_fields"}
devtools_traits = {path = "../devtools_traits"}
dom_struct = {path = "../dom_struct"}

View file

@ -3,7 +3,6 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
use app_units::{Au, AU_PER_PX};
use cssparser::{Parser, ParserInput};
use document_loader::{LoadType, LoadBlocker};
use dom::activation::Activatable;
use dom::attr::Attr;
@ -58,13 +57,7 @@ use std::default::Default;
use std::i32;
use std::sync::{Arc, Mutex};
use style::attr::{AttrValue, LengthOrPercentageOrAuto, parse_double, parse_unsigned_integer};
use style::context::QuirksMode;
use style::media_queries::MediaQuery;
use style::parser::ParserContext;
use style::str::is_ascii_digit;
use style::values::specified::{Length, ViewportPercentageLength};
use style::values::specified::length::NoCalcLength;
use style_traits::ParsingMode;
use task_source::TaskSource;
enum ParseState {
@ -94,12 +87,6 @@ enum State {
Broken,
}
#[derive(Debug, PartialEq)]
pub struct Size {
pub query: Option<MediaQuery>,
pub length: Length,
}
#[derive(Clone, Copy, JSTraceable, MallocSizeOf)]
enum ImageRequestPhase {
Pending,
@ -758,63 +745,6 @@ impl LayoutHTMLImageElementHelpers for LayoutDom<HTMLImageElement> {
}
}
//https://html.spec.whatwg.org/multipage/#parse-a-sizes-attribute
pub fn parse_a_sizes_attribute(input: DOMString, width: Option<u32>) -> Vec<Size> {
let mut sizes = Vec::<Size>::new();
for unparsed_size in input.split(',') {
let whitespace = unparsed_size.chars().rev().take_while(|c| char::is_whitespace(*c)).count();
let trimmed: String = unparsed_size.chars().take(unparsed_size.chars().count() - whitespace).collect();
if trimmed.is_empty() {
continue;
}
let mut input = ParserInput::new(&trimmed);
let url = ServoUrl::parse("about:blank").unwrap();
let context = ParserContext::new_for_cssom(
&url,
None,
ParsingMode::empty(),
QuirksMode::NoQuirks,
None,
);
let mut parser = Parser::new(&mut input);
let length = parser.try(|i| Length::parse_non_negative(&context, i));
match length {
Ok(len) => sizes.push(Size {
length: len,
query: None
}),
Err(_) => {
let mut media_query_parser = parser;
let media_query = media_query_parser.try(|i| MediaQuery::parse(&context, i));
if let Ok(query) = media_query {
let length = Length::parse_non_negative(&context, &mut media_query_parser);
if let Ok(length) = length {
sizes.push(Size {
length: length,
query: Some(query)
})
}
}
},
}
}
if sizes.is_empty() {
let size = match width {
Some(w) => Size {
length: Length::from_px(w as f32),
query: None
},
None => Size {
length: Length::NoCalc(NoCalcLength::ViewportPercentage(ViewportPercentageLength::Vw(100.))),
query: None
},
};
sizes.push(size);
}
sizes
}
impl HTMLImageElementMethods for HTMLImageElement {
// https://html.spec.whatwg.org/multipage/#dom-img-alt
make_getter!(Alt, "alt");

View file

@ -350,6 +350,14 @@ partial interface CSSStyleDeclaration {
[CEReactions, SetterThrows, TreatNullAs=EmptyString] attribute DOMString offsetInlineStart;
[CEReactions, SetterThrows, TreatNullAs=EmptyString] attribute DOMString offset-inline-end;
[CEReactions, SetterThrows, TreatNullAs=EmptyString] attribute DOMString offsetInlineEnd;
[CEReactions, SetterThrows, TreatNullAs=EmptyString] attribute DOMString inset-block-start;
[CEReactions, SetterThrows, TreatNullAs=EmptyString] attribute DOMString insetBlockStart;
[CEReactions, SetterThrows, TreatNullAs=EmptyString] attribute DOMString inset-block-end;
[CEReactions, SetterThrows, TreatNullAs=EmptyString] attribute DOMString insetBlockEnd;
[CEReactions, SetterThrows, TreatNullAs=EmptyString] attribute DOMString inset-inline-start;
[CEReactions, SetterThrows, TreatNullAs=EmptyString] attribute DOMString insetInlineStart;
[CEReactions, SetterThrows, TreatNullAs=EmptyString] attribute DOMString inset-inline-end;
[CEReactions, SetterThrows, TreatNullAs=EmptyString] attribute DOMString insetInlineEnd;
[CEReactions, SetterThrows, TreatNullAs=EmptyString] attribute DOMString height;
[CEReactions, SetterThrows, TreatNullAs=EmptyString] attribute DOMString minHeight;

View file

@ -15,10 +15,6 @@ pub mod area {
pub use dom::htmlareaelement::{Area, Shape};
}
pub mod sizes {
pub use dom::htmlimageelement::{parse_a_sizes_attribute, Size};
}
pub mod size_of {
use dom::characterdata::CharacterData;
use dom::element::Element;

View file

@ -13,7 +13,7 @@ path = "lib.rs"
app_units = "0.6"
atomic_refcell = "0.1"
canvas_traits = {path = "../canvas_traits"}
cssparser = "0.23.0"
cssparser = "0.24"
euclid = "0.17"
gfx_traits = {path = "../gfx_traits"}
html5ever = "0.22"

View file

@ -22,7 +22,7 @@ bench = []
[dependencies]
bitflags = "1.0"
matches = "0.1"
cssparser = "0.23.0"
cssparser = "0.24.0"
log = "0.4"
fnv = "1.0"
phf = "0.7.18"

View file

@ -23,7 +23,7 @@ use sink::Push;
use smallvec::{self, SmallVec};
use std::cmp;
use std::iter;
use std::ops::Add;
use std::ops::{AddAssign, Add};
use std::ptr;
use std::slice;
@ -228,6 +228,16 @@ struct Specificity {
element_selectors: u32,
}
impl AddAssign for Specificity {
#[inline]
fn add_assign(&mut self, rhs: Self) {
self.id_selectors += rhs.id_selectors;
self.class_like_selectors += rhs.class_like_selectors;
self.element_selectors += rhs.element_selectors;
}
}
impl Add for Specificity {
type Output = Specificity;
@ -251,6 +261,7 @@ impl Default for Specificity {
}
impl From<u32> for Specificity {
#[inline]
fn from(value: u32) -> Specificity {
assert!(value <= MAX_10BIT << 20 | MAX_10BIT << 10 | MAX_10BIT);
Specificity {
@ -262,6 +273,7 @@ impl From<u32> for Specificity {
}
impl From<Specificity> for u32 {
#[inline]
fn from(specificity: Specificity) -> u32 {
cmp::min(specificity.id_selectors, MAX_10BIT) << 20 |
cmp::min(specificity.class_like_selectors, MAX_10BIT) << 10 |
@ -298,17 +310,29 @@ where
builder,
);
}
// FIXME(emilio): Spec doesn't define any particular specificity for
// ::slotted(), so apply the general rule for pseudos per:
//
// https://github.com/w3c/csswg-drafts/issues/1915
//
// Though other engines compute it dynamically, so maybe we should
// do that instead, eventually.
Component::Slotted(..) | Component::PseudoElement(..) | Component::LocalName(..) => {
Component::PseudoElement(..) | Component::LocalName(..) => {
specificity.element_selectors += 1
},
Component::ID(..) => specificity.id_selectors += 1,
Component::Slotted(ref selector) => {
specificity.element_selectors += 1;
// Note that due to the way ::slotted works we only compete with
// other ::slotted rules, so the above rule doesn't really
// matter, but we do it still for consistency with other
// pseudo-elements.
//
// See: https://github.com/w3c/csswg-drafts/issues/1915
*specificity += Specificity::from(selector.specificity());
},
Component::Host(ref selector) => {
specificity.class_like_selectors += 1;
if let Some(ref selector) = *selector {
// See: https://github.com/w3c/csswg-drafts/issues/1915
*specificity += Specificity::from(selector.specificity());
}
}
Component::ID(..) => {
specificity.id_selectors += 1;
},
Component::Class(..) |
Component::AttributeInNoNamespace { .. } |
Component::AttributeInNoNamespaceExists { .. } |
@ -319,7 +343,6 @@ where
Component::Root |
Component::Empty |
Component::Scope |
Component::Host(..) |
Component::NthChild(..) |
Component::NthLastChild(..) |
Component::NthOfType(..) |
@ -327,7 +350,9 @@ where
Component::FirstOfType |
Component::LastOfType |
Component::OnlyOfType |
Component::NonTSPseudoClass(..) => specificity.class_like_selectors += 1,
Component::NonTSPseudoClass(..) => {
specificity.class_like_selectors += 1;
},
Component::ExplicitUniversalType |
Component::ExplicitAnyNamespace |
Component::ExplicitNoNamespace |

View file

@ -576,7 +576,7 @@ where
_ => {},
}
if element.is_link() || combinator.is_sibling() {
if element.is_link() {
visited_handling = VisitedHandlingMode::AllLinksUnvisited;
}

View file

@ -995,8 +995,7 @@ impl<Impl: SelectorImpl> ToCss for Selector<Impl> {
let mut combinators = self.iter_raw_match_order()
.rev()
.filter(|x| x.is_combinator())
.peekable();
.filter_map(|x| x.as_combinator());
let compound_selectors = self.iter_raw_match_order()
.as_slice()
.split(|x| x.is_combinator())
@ -1007,8 +1006,10 @@ impl<Impl: SelectorImpl> ToCss for Selector<Impl> {
debug_assert!(!combinators_exhausted);
// https://drafts.csswg.org/cssom/#serializing-selectors
if compound.is_empty() {
continue;
}
if !compound.is_empty() {
// 1. If there is only one simple selector in the compound selectors
// which is a universal selector, append the result of
// serializing the universal selector to s.
@ -1019,16 +1020,17 @@ impl<Impl: SelectorImpl> ToCss for Selector<Impl> {
//
// If we are in this case, after we have serialized the universal
// selector, we skip Step 2 and continue with the algorithm.
let (can_elide_namespace, first_non_namespace) = match &compound[0] {
&Component::ExplicitAnyNamespace |
&Component::ExplicitNoNamespace |
&Component::Namespace(_, _) => (false, 1),
&Component::DefaultNamespace(_) => (true, 1),
let (can_elide_namespace, first_non_namespace) = match compound[0] {
Component::ExplicitAnyNamespace |
Component::ExplicitNoNamespace |
Component::Namespace(..) => (false, 1),
Component::DefaultNamespace(..) => (true, 1),
_ => (true, 0),
};
let mut perform_step_2 = true;
let next_combinator = combinators.next();
if first_non_namespace == compound.len() - 1 {
match (combinators.peek(), &compound[first_non_namespace]) {
match (next_combinator, &compound[first_non_namespace]) {
// We have to be careful here, because if there is a
// pseudo element "combinator" there isn't really just
// the one simple selector. Technically this compound
@ -1036,8 +1038,8 @@ impl<Impl: SelectorImpl> ToCss for Selector<Impl> {
// -- Combinator::PseudoElement, just like
// Combinator::SlotAssignment, don't exist in the
// spec.
(Some(&&Component::Combinator(Combinator::PseudoElement)), _) |
(Some(&&Component::Combinator(Combinator::SlotAssignment)), _) => (),
(Some(Combinator::PseudoElement), _) |
(Some(Combinator::SlotAssignment), _) => (),
(_, &Component::ExplicitUniversalType) => {
// Iterate over everything so we serialize the namespace
// too.
@ -1047,7 +1049,7 @@ impl<Impl: SelectorImpl> ToCss for Selector<Impl> {
// Skip step 2, which is an "otherwise".
perform_step_2 = false;
},
(_, _) => (),
_ => (),
}
}
@ -1074,14 +1076,13 @@ impl<Impl: SelectorImpl> ToCss for Selector<Impl> {
simple.to_css(dest)?;
}
}
}
// 3. If this is not the last part of the chain of the selector
// append a single SPACE (U+0020), followed by the combinator
// ">", "+", "~", ">>", "||", as appropriate, followed by another
// single SPACE (U+0020) if the combinator was not whitespace, to
// s.
match combinators.next() {
match next_combinator {
Some(c) => c.to_css(dest)?,
None => combinators_exhausted = true,
};

View file

@ -31,7 +31,7 @@ atomic_refcell = "0.1"
bitflags = "1.0"
byteorder = "1.0"
cfg-if = "0.1.0"
cssparser = "0.23.0"
cssparser = "0.24.0"
new_debug_unreachable = "1.0"
encoding_rs = {version = "0.7", optional = true}
euclid = "0.17"

View file

@ -182,14 +182,21 @@ mod bindings {
// Disable rust unions, because we replace some types inside of
// them.
let mut builder = Builder::default().rust_target(RustTarget::Stable_1_0);
let rustfmt_path = env::var_os("MOZ_AUTOMATION")
.and_then(|_| env::var_os("TOOLTOOL_DIR").or_else(|| env::var_os("MOZ_SRC")))
.map(PathBuf::from);
builder = match rustfmt_path {
Some(path) => builder.with_rustfmt(path.join("rustc").join("bin").join("rustfmt")),
None => builder.rustfmt_bindings(env::var_os("STYLO_RUSTFMT_BINDINGS").is_some()),
};
let rustfmt_path = env::var_os("RUSTFMT")
// This can be replaced with
// > .filter(|p| !p.is_empty()).map(PathBuf::from)
// once we can use 1.27+.
.and_then(|p| {
if p.is_empty() {
None
} else {
Some(PathBuf::from(p))
}
});
if let Some(path) = rustfmt_path {
builder = builder.with_rustfmt(path);
}
for dir in SEARCH_PATHS.iter() {
builder = builder.clang_arg("-I").clang_arg(dir.to_str().unwrap());

View file

@ -71,7 +71,8 @@ bitflags! {
const IN_OPTIONAL_STATE = 1 << 22;
/// <https://html.spec.whatwg.org/multipage/#selector-read-write>
const IN_READ_WRITE_STATE = 1 << 22;
/// There is a free bit at 23.
/// <https://html.spec.whatwg.org/multipage/#selector-defined>
const IN_DEFINED_STATE = 1 << 23;
/// <https://html.spec.whatwg.org/multipage/#selector-visited>
const IN_VISITED_STATE = 1 << 24;
/// <https://html.spec.whatwg.org/multipage/#selector-link>

View file

@ -7,7 +7,7 @@
use app_units::AU_PER_PX;
use app_units::Au;
use context::QuirksMode;
use cssparser::{BasicParseErrorKind, Parser, RGBA};
use cssparser::{Parser, RGBA, Token};
use euclid::Size2D;
use euclid::TypedScale;
use gecko::values::{convert_nscolor_to_rgba, convert_rgba_to_nscolor};
@ -255,20 +255,53 @@ pub enum Range {
Min,
/// At most the specified value.
Max,
/// Exactly the specified value.
Equal,
}
/// A expression for gecko contains a reference to the media feature, the value
/// the media query contained, and the range to evaluate.
/// The operator that was specified in this media feature.
#[derive(Clone, Copy, Debug, Eq, MallocSizeOf, PartialEq)]
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)]
enum RangeOrOperator {
Range(Range),
Operator(Operator),
}
/// A feature expression for gecko contains a reference to the media feature,
/// the value the media query contained, and the range to evaluate.
#[derive(Clone, Debug, MallocSizeOf)]
pub struct Expression {
pub struct MediaFeatureExpression {
feature: &'static nsMediaFeature,
value: Option<MediaExpressionValue>,
range: Range,
range_or_operator: Option<RangeOrOperator>,
}
impl ToCss for Expression {
impl ToCss for MediaFeatureExpression {
fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
where
W: fmt::Write,
@ -279,10 +312,12 @@ impl ToCss for Expression {
{
dest.write_str("-webkit-")?;
}
match self.range {
if let Some(RangeOrOperator::Range(range)) = self.range_or_operator {
match range {
Range::Min => dest.write_str("min-")?,
Range::Max => dest.write_str("max-")?,
Range::Equal => {},
}
}
// NB: CssStringWriter not needed, feature names are under control.
@ -290,8 +325,15 @@ impl ToCss for Expression {
Atom::from_static(*self.feature.mName)
})?;
if let Some(ref val) = self.value {
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)?;
}
@ -299,10 +341,10 @@ impl ToCss for Expression {
}
}
impl PartialEq for Expression {
fn eq(&self, other: &Expression) -> bool {
impl PartialEq for MediaFeatureExpression {
fn eq(&self, other: &Self) -> bool {
self.feature.mName == other.feature.mName && self.value == other.value &&
self.range == other.range
self.range_or_operator == other.range_or_operator
}
}
@ -337,7 +379,10 @@ pub enum MediaExpressionValue {
}
impl MediaExpressionValue {
fn from_css_value(for_expr: &Expression, css_value: &nsCSSValue) -> Option<Self> {
fn from_css_value(
for_expr: &MediaFeatureExpression,
css_value: &nsCSSValue,
) -> Option<Self> {
// NB: If there's a null value, that means that we don't support the
// feature.
if css_value.mUnit == nsCSSUnit::eCSSUnit_Null {
@ -395,7 +440,7 @@ impl MediaExpressionValue {
}
impl MediaExpressionValue {
fn to_css<W>(&self, dest: &mut CssWriter<W>, for_expr: &Expression) -> fmt::Result
fn to_css<W>(&self, dest: &mut CssWriter<W>, for_expr: &MediaFeatureExpression) -> fmt::Result
where
W: fmt::Write,
{
@ -537,17 +582,53 @@ fn parse_feature_value<'i, 't>(
Ok(value)
}
impl Expression {
/// 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(()),
}
};
Ok(Some(match first_delim {
'=' => Operator::Equal,
'>' => {
if input.try(|i| i.expect_delim('=')).is_ok() {
Operator::GreaterThanEqual
} else {
Operator::GreaterThan
}
}
'<' => {
if input.try(|i| i.expect_delim('=')).is_ok() {
Operator::LessThanEqual
} else {
Operator::LessThan
}
}
_ => return Err(()),
}))
}
impl MediaFeatureExpression {
/// Trivially construct a new expression.
fn new(
feature: &'static nsMediaFeature,
value: Option<MediaExpressionValue>,
range: Range,
range_or_operator: Option<RangeOrOperator>,
) -> Self {
Self {
feature,
value,
range,
range_or_operator,
}
}
@ -560,29 +641,24 @@ impl Expression {
context: &ParserContext,
input: &mut Parser<'i, 't>,
) -> Result<Self, ParseError<'i>> {
input.expect_parenthesis_block().map_err(|err| {
err.location.new_custom_error(match err.kind {
BasicParseErrorKind::UnexpectedToken(t) => {
StyleParseErrorKind::ExpectedIdentifier(t)
},
_ => StyleParseErrorKind::UnspecifiedError,
})
})?;
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>> {
// FIXME: remove extra indented block when lifetimes are non-lexical
let feature;
let range;
{
let location = input.current_source_location();
let ident = input.expect_ident().map_err(|err| {
err.location.new_custom_error(match err.kind {
BasicParseErrorKind::UnexpectedToken(t) => {
StyleParseErrorKind::ExpectedIdentifier(t)
},
_ => StyleParseErrorKind::UnspecifiedError,
})
})?;
let ident = input.expect_ident()?;
let mut flags = 0;
@ -608,12 +684,12 @@ impl Expression {
let range = if starts_with_ignore_ascii_case(feature_name, "min-") {
feature_name = &feature_name[4..];
Range::Min
Some(Range::Min)
} else if starts_with_ignore_ascii_case(feature_name, "max-") {
feature_name = &feature_name[4..];
Range::Max
Some(Range::Max)
} else {
Range::Equal
None
};
let atom = Atom::from(string_as_ascii_lowercase(feature_name));
@ -641,7 +717,7 @@ impl Expression {
));
}
if range != Range::Equal &&
if range.is_some() &&
feature.mRangeType != nsMediaFeature_RangeType::eMinMaxAllowed
{
return Err(location.new_custom_error(
@ -650,17 +726,52 @@ impl Expression {
}
}
// If there's no colon, this is a media query of the form
// '(<feature>)', that is, there's no value specified.
let feature_allows_ranges =
feature.mRangeType == nsMediaFeature_RangeType::eMinMaxAllowed;
let operator = input.try(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 input.try(|i| i.expect_colon()).is_err() {
if range != Range::Equal {
return Err(input.new_custom_error(StyleParseErrorKind::RangedExpressionWithNoValue));
// 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(Expression::new(feature, None, range));
return Ok(Self::new(feature, 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 =
parse_feature_value(feature, feature.mValueType, context, input).map_err(|err| {
@ -668,8 +779,7 @@ impl Expression {
.new_custom_error(StyleParseErrorKind::MediaQueryExpectedFeatureValue)
})?;
Ok(Expression::new(feature, Some(value), range))
})
Ok(Self::new(feature, Some(value), range_or_operator))
}
/// Returns whether this media query evaluates to true for the given device.
@ -704,8 +814,8 @@ impl Expression {
use std::cmp::Ordering;
debug_assert!(
self.range == Range::Equal ||
self.feature.mRangeType == nsMediaFeature_RangeType::eMinMaxAllowed,
self.feature.mRangeType == nsMediaFeature_RangeType::eMinMaxAllowed ||
self.range_or_operator.is_none(),
"Whoops, wrong range"
);
@ -730,7 +840,7 @@ impl Expression {
};
// FIXME(emilio): Handle the possible floating point errors?
let cmp = match (required_value, actual_value) {
let cmp = match (actual_value, required_value) {
(&Length(ref one), &Length(ref other)) => {
computed::Context::for_media_query_evaluation(device, quirks_mode, |context| {
one.to_computed_value(&context)
@ -750,11 +860,11 @@ impl Expression {
if (*device.pres_context).mOverrideDPPX > 0.0 {
self::Resolution::Dppx((*device.pres_context).mOverrideDPPX).to_dpi()
} else {
other.to_dpi()
one.to_dpi()
}
};
one.to_dpi().partial_cmp(&actual_dpi).unwrap()
actual_dpi.partial_cmp(&other.to_dpi()).unwrap()
},
(&Ident(ref one), &Ident(ref other)) => {
debug_assert_ne!(
@ -773,10 +883,31 @@ impl Expression {
_ => unreachable!(),
};
cmp == Ordering::Equal || match self.range {
Range::Min => cmp == Ordering::Less,
Range::Equal => false,
Range::Max => cmp == Ordering::Greater,
let range_or_op = match self.range_or_operator {
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
}
}
}
}
}
}

View file

@ -47,6 +47,7 @@ macro_rules! apply_non_ts_list {
("visited", Visited, visited, IN_VISITED_STATE, _),
("active", Active, active, IN_ACTIVE_STATE, _),
("checked", Checked, checked, IN_CHECKED_STATE, _),
("defined", Defined, defined, IN_DEFINED_STATE, PSEUDO_CLASS_ENABLED_IN_UA_SHEETS_AND_CHROME),
("disabled", Disabled, disabled, IN_DISABLED_STATE, _),
("enabled", Enabled, enabled, IN_ENABLED_STATE, _),
("focus", Focus, focus, IN_FOCUS_STATE, _),

View file

@ -187,6 +187,9 @@ impl NonTSPseudoClass {
NonTSPseudoClass::Fullscreen => unsafe {
mozilla::StaticPrefs_sVarCache_full_screen_api_unprefix_enabled
},
NonTSPseudoClass::Defined => unsafe {
structs::nsContentUtils_sIsCustomElementsEnabled
},
// Otherwise, a pseudo-class is enabled in content when it
// doesn't have any enabled flag.
_ => !self.has_any_flag(

View file

@ -37,6 +37,7 @@ use gecko_bindings::bindings::Gecko_ElementHasAnimations;
use gecko_bindings::bindings::Gecko_ElementHasCSSAnimations;
use gecko_bindings::bindings::Gecko_ElementHasCSSTransitions;
use gecko_bindings::bindings::Gecko_GetActiveLinkAttrDeclarationBlock;
use gecko_bindings::bindings::Gecko_GetAnimationEffectCount;
use gecko_bindings::bindings::Gecko_GetAnimationRule;
use gecko_bindings::bindings::Gecko_GetExtraContentStyleDeclarations;
use gecko_bindings::bindings::Gecko_GetHTMLPresentationAttrDeclarationBlock;
@ -650,7 +651,10 @@ impl<'le> GeckoElement<'le> {
#[inline]
fn extended_slots(&self) -> Option<&structs::FragmentOrElement_nsExtendedDOMSlots> {
self.dom_slots().and_then(|s| unsafe {
(s._base.mExtendedSlots.mPtr as *const structs::FragmentOrElement_nsExtendedDOMSlots)
// For the bit usage, see nsContentSlots::GetExtendedSlots.
let e_slots = s._base.mExtendedSlots &
!structs::nsIContent_nsContentSlots_sNonOwningExtendedSlotsFlag;
(e_slots as *const structs::FragmentOrElement_nsExtendedDOMSlots)
.as_ref()
})
}
@ -948,8 +952,16 @@ fn get_animation_rule(
cascade_level: CascadeLevel,
) -> Option<Arc<Locked<PropertyDeclarationBlock>>> {
use gecko_bindings::sugar::ownership::HasSimpleFFI;
use properties::longhands::ANIMATABLE_PROPERTY_COUNT;
// There's a very rough correlation between the number of effects
// (animations) on an element and the number of properties it is likely to
// animate, so we use that as an initial guess for the size of the
// AnimationValueMap in order to reduce the number of re-allocations needed.
let effect_count = unsafe { Gecko_GetAnimationEffectCount(element.0) };
// Also, we should try to reuse the PDB, to avoid creating extra rule nodes.
let mut animation_values = AnimationValueMap::default();
let mut animation_values = AnimationValueMap::with_capacity_and_hasher(
effect_count.min(ANIMATABLE_PROPERTY_COUNT), Default::default());
if unsafe {
Gecko_GetAnimationRule(
element.0,
@ -2115,6 +2127,7 @@ impl<'le> ::selectors::Element for GeckoElement<'le> {
{
use selectors::matching::*;
match *pseudo_class {
NonTSPseudoClass::Defined |
NonTSPseudoClass::Focus |
NonTSPseudoClass::Enabled |
NonTSPseudoClass::Disabled |

View file

@ -5,9 +5,7 @@
//! Rust helpers for Gecko's `nsCSSShadowItem`.
use app_units::Au;
use gecko::values::{convert_nscolor_to_rgba, convert_rgba_to_nscolor};
use gecko_bindings::structs::nsCSSShadowItem;
use values::computed::RGBAColor;
use values::computed::effects::{BoxShadow, SimpleShadow};
impl nsCSSShadowItem {
@ -37,31 +35,14 @@ impl nsCSSShadowItem {
self.mRadius = shadow.blur.0.to_i32_au();
self.mSpread = 0;
self.mInset = false;
if let Some(color) = shadow.color {
self.mHasColor = true;
self.mColor = convert_rgba_to_nscolor(&color);
} else {
// TODO handle currentColor
// https://bugzilla.mozilla.org/show_bug.cgi?id=760345
self.mHasColor = false;
self.mColor = 0;
}
}
#[inline]
fn extract_color(&self) -> Option<RGBAColor> {
if self.mHasColor {
Some(convert_nscolor_to_rgba(self.mColor))
} else {
None
}
self.mColor = shadow.color.into();
}
/// Gets a simple shadow from this item.
#[inline]
fn extract_simple_shadow(&self) -> SimpleShadow {
SimpleShadow {
color: self.extract_color(),
color: self.mColor.into(),
horizontal: Au(self.mXOffset).into(),
vertical: Au(self.mYOffset).into(),
blur: Au(self.mRadius).into(),

View file

@ -0,0 +1,192 @@
/* 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 http://mozilla.org/MPL/2.0/. */
//! A media query condition:
//!
//! https://drafts.csswg.org/mediaqueries-4/#typedef-media-condition
use context::QuirksMode;
use cssparser::{Parser, Token};
use parser::ParserContext;
use std::fmt::{self, Write};
use style_traits::{CssWriter, ParseError, StyleParseErrorKind, ToCss};
use super::{Device, MediaFeatureExpression};
/// A binary `and` or `or` operator.
#[derive(Clone, Copy, Debug, Eq, MallocSizeOf, Parse, PartialEq, ToCss)]
#[allow(missing_docs)]
pub enum Operator {
And,
Or,
}
/// Whether to allow an `or` condition or not during parsing.
#[derive(Clone, Copy, Debug, Eq, MallocSizeOf, PartialEq, ToCss)]
enum AllowOr {
Yes,
No,
}
/// Represents a media condition.
#[derive(Clone, Debug, MallocSizeOf, PartialEq)]
pub enum MediaCondition {
/// A simple media feature expression, implicitly parenthesized.
Feature(MediaFeatureExpression),
/// A negation of a condition.
Not(Box<MediaCondition>),
/// A set of joint operations.
Operation(Box<[MediaCondition]>, Operator),
/// A condition wrapped in parenthesis.
InParens(Box<MediaCondition>),
}
impl ToCss for MediaCondition {
fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
where
W: fmt::Write,
{
match *self {
// NOTE(emilio): MediaFeatureExpression already includes the
// parenthesis.
MediaCondition::Feature(ref f) => f.to_css(dest),
MediaCondition::Not(ref c) => {
dest.write_str("not ")?;
c.to_css(dest)
}
MediaCondition::InParens(ref c) => {
dest.write_char('(')?;
c.to_css(dest)?;
dest.write_char(')')
}
MediaCondition::Operation(ref list, op) => {
let mut iter = list.iter();
iter.next().unwrap().to_css(dest)?;
for item in iter {
dest.write_char(' ')?;
op.to_css(dest)?;
dest.write_char(' ')?;
item.to_css(dest)?;
}
Ok(())
}
}
}
}
impl MediaCondition {
/// Parse a single media condition.
pub fn parse<'i, 't>(
context: &ParserContext,
input: &mut Parser<'i, 't>,
) -> Result<Self, ParseError<'i>> {
Self::parse_internal(context, input, AllowOr::Yes)
}
/// Parse a single media condition, disallowing `or` expressions.
///
/// To be used from the legacy media query syntax.
pub fn parse_disallow_or<'i, 't>(
context: &ParserContext,
input: &mut Parser<'i, 't>,
) -> Result<Self, ParseError<'i>> {
Self::parse_internal(context, input, AllowOr::No)
}
fn parse_internal<'i, 't>(
context: &ParserContext,
input: &mut Parser<'i, 't>,
allow_or: AllowOr,
) -> Result<Self, ParseError<'i>> {
let location = input.current_source_location();
// FIXME(emilio): This can be cleaner with nll.
let is_negation = match *input.next()? {
Token::ParenthesisBlock => false,
Token::Ident(ref ident) if ident.eq_ignore_ascii_case("not") => true,
ref t => {
return Err(location.new_unexpected_token_error(t.clone()))
}
};
if is_negation {
let inner_condition = Self::parse_in_parens(context, input)?;
return Ok(MediaCondition::Not(Box::new(inner_condition)))
}
// ParenthesisBlock.
let first_condition = Self::parse_paren_block(context, input)?;
let operator = match input.try(Operator::parse) {
Ok(op) => op,
Err(..) => return Ok(first_condition),
};
if allow_or == AllowOr::No && operator == Operator::Or {
return Err(location.new_custom_error(StyleParseErrorKind::UnspecifiedError));
}
let mut conditions = vec![];
conditions.push(first_condition);
conditions.push(Self::parse_in_parens(context, input)?);
let delim = match operator {
Operator::And => "and",
Operator::Or => "or",
};
loop {
if input.try(|i| i.expect_ident_matching(delim)).is_err() {
return Ok(MediaCondition::Operation(
conditions.into_boxed_slice(),
operator,
));
}
conditions.push(Self::parse_in_parens(context, input)?);
}
}
/// Parse a media condition in parentheses.
pub fn parse_in_parens<'i, 't>(
context: &ParserContext,
input: &mut Parser<'i, 't>,
) -> Result<Self, ParseError<'i>> {
input.expect_parenthesis_block()?;
Self::parse_paren_block(context, input)
}
fn parse_paren_block<'i, 't>(
context: &ParserContext,
input: &mut Parser<'i, 't>,
) -> Result<Self, ParseError<'i>> {
input.parse_nested_block(|input| {
// Base case.
if let Ok(inner) = input.try(|i| Self::parse(context, i)) {
return Ok(MediaCondition::InParens(Box::new(inner)))
}
let expr = MediaFeatureExpression::parse_in_parenthesis_block(context, input)?;
Ok(MediaCondition::Feature(expr))
})
}
/// Whether this condition matches the device and quirks mode.
pub fn matches(&self, device: &Device, quirks_mode: QuirksMode) -> 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) => {
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))
}
}
}
}
}
}

View file

@ -73,16 +73,14 @@ impl MediaList {
/// Evaluate a whole `MediaList` against `Device`.
pub fn evaluate(&self, device: &Device, quirks_mode: QuirksMode) -> bool {
// Check if it is an empty media query list or any queries match (OR condition)
// 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() || self.media_queries.iter().any(|mq| {
let media_match = mq.media_type.matches(device.media_type());
// Check if all conditions match (AND condition)
// Check if the media condition match.
let query_match = media_match &&
mq.expressions
.iter()
.all(|expression| expression.matches(&device, quirks_mode));
mq.condition.as_ref().map_or(true, |c| c.matches(device, quirks_mode));
// Apply the logical NOT qualifier to the result
match mq.qualifier {

View file

@ -9,13 +9,13 @@
use Atom;
use cssparser::Parser;
use parser::ParserContext;
use selectors::parser::SelectorParseErrorKind;
use std::fmt::{self, Write};
use str::string_as_ascii_lowercase;
use style_traits::{CssWriter, ParseError, StyleParseErrorKind, ToCss};
use super::Expression;
use style_traits::{CssWriter, ParseError, ToCss};
use super::media_condition::MediaCondition;
use values::CustomIdent;
/// <https://drafts.csswg.org/mediaqueries/#mq-prefix>
#[derive(Clone, Copy, Debug, Eq, MallocSizeOf, Parse, PartialEq, ToCss)]
pub enum Qualifier {
@ -65,8 +65,9 @@ pub struct MediaQuery {
pub qualifier: Option<Qualifier>,
/// The media type for this query, that can be known, unknown, or "all".
pub media_type: MediaQueryType,
/// The set of expressions that this media query contains.
pub expressions: Vec<Expression>,
/// The condition that this media query contains. This cannot have `or`
/// in the first level.
pub condition: Option<MediaCondition>,
}
impl ToCss for MediaQuery {
@ -86,28 +87,23 @@ impl ToCss for MediaQuery {
//
// Otherwise, we'd serialize media queries like "(min-width:
// 40px)" in "all (min-width: 40px)", which is unexpected.
if self.qualifier.is_some() || self.expressions.is_empty() {
if self.qualifier.is_some() || self.condition.is_none() {
dest.write_str("all")?;
}
},
MediaQueryType::Concrete(MediaType(ref desc)) => desc.to_css(dest)?,
}
if self.expressions.is_empty() {
return Ok(());
}
let condition = match self.condition {
Some(ref c) => c,
None => return Ok(()),
};
if self.media_type != MediaQueryType::All || self.qualifier.is_some() {
dest.write_str(" and ")?;
}
self.expressions[0].to_css(dest)?;
for expr in self.expressions.iter().skip(1) {
dest.write_str(" and ")?;
expr.to_css(dest)?;
}
Ok(())
condition.to_css(dest)
}
}
@ -118,7 +114,7 @@ impl MediaQuery {
Self {
qualifier: Some(Qualifier::Not),
media_type: MediaQueryType::All,
expressions: vec![],
condition: None,
}
}
@ -128,41 +124,24 @@ impl MediaQuery {
pub fn parse<'i, 't>(
context: &ParserContext,
input: &mut Parser<'i, 't>,
) -> Result<MediaQuery, ParseError<'i>> {
let mut expressions = vec![];
) -> Result<Self, ParseError<'i>> {
let (qualifier, explicit_media_type) = input.try(|input| -> Result<_, ()> {
let qualifier = input.try(Qualifier::parse).ok();
let media_type = match input.try(|i| i.expect_ident_cloned()) {
Ok(ident) => MediaQueryType::parse(&*ident).map_err(|()| {
input.new_custom_error(SelectorParseErrorKind::UnexpectedIdent(ident.clone()))
})?,
Err(_) => {
// Media type is only optional if qualifier is not specified.
if qualifier.is_some() {
return Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError));
}
let ident = input.expect_ident().map_err(|_| ())?;
let media_type = MediaQueryType::parse(&ident)?;
Ok((qualifier, Some(media_type)))
}).unwrap_or_default();
// Without a media type, require at least one expression.
expressions.push(Expression::parse(context, input)?);
MediaQueryType::All
},
let condition = if explicit_media_type.is_none() {
Some(MediaCondition::parse(context, input)?)
} else if input.try(|i| i.expect_ident_matching("and")).is_ok() {
Some(MediaCondition::parse_disallow_or(context, input)?)
} else {
None
};
// Parse any subsequent expressions
loop {
if input
.try(|input| input.expect_ident_matching("and"))
.is_err()
{
return Ok(MediaQuery {
qualifier,
media_type,
expressions,
});
}
expressions.push(Expression::parse(context, input)?)
}
let media_type = explicit_media_type.unwrap_or(MediaQueryType::All);
Ok(Self { qualifier, media_type, condition })
}
}

View file

@ -6,13 +6,15 @@
//!
//! [mq]: https://drafts.csswg.org/mediaqueries/
mod media_condition;
mod media_list;
mod media_query;
pub use self::media_condition::MediaCondition;
pub use self::media_list::MediaList;
pub use self::media_query::{MediaQuery, MediaQueryType, MediaType, Qualifier};
#[cfg(feature = "servo")]
pub use servo::media_queries::{Device, Expression};
pub use servo::media_queries::{Device, MediaFeatureExpression};
#[cfg(feature = "gecko")]
pub use gecko::media_queries::{Device, Expression};
pub use gecko::media_queries::{Device, MediaFeatureExpression};

View file

@ -5489,8 +5489,7 @@ clip-path
use values::generics::CounterStyleOrNone;
use gecko_bindings::structs::nsStyleContentData;
use gecko_bindings::structs::nsStyleContentAttr;
use gecko_bindings::structs::nsStyleContentType;
use gecko_bindings::structs::nsStyleContentType::*;
use gecko_bindings::structs::StyleContentType;
use gecko_bindings::bindings::Gecko_ClearAndResizeStyleContents;
// Converts a string as utf16, and returns an owned, zero-terminated raw buffer.
@ -5505,19 +5504,19 @@ clip-path
fn set_counter_function(
data: &mut nsStyleContentData,
content_type: nsStyleContentType,
content_type: StyleContentType,
name: &CustomIdent,
sep: &str,
style: CounterStyleOrNone,
device: &Device,
) {
debug_assert!(content_type == eStyleContentType_Counter ||
content_type == eStyleContentType_Counters);
debug_assert!(content_type == StyleContentType::Counter ||
content_type == StyleContentType::Counters);
let counter_func = unsafe {
bindings::Gecko_SetCounterFunction(data, content_type).as_mut().unwrap()
};
counter_func.mIdent.assign(name.0.as_slice());
if content_type == eStyleContentType_Counters {
if content_type == StyleContentType::Counters {
counter_func.mSeparator.assign_utf8(sep);
}
style.to_gecko_value(&mut counter_func.mCounterStyle, device);
@ -5538,7 +5537,7 @@ clip-path
Gecko_ClearAndResizeStyleContents(&mut self.gecko, 1);
*self.gecko.mContents[0].mContent.mString.as_mut() = ptr::null_mut();
}
self.gecko.mContents[0].mType = eStyleContentType_AltContent;
self.gecko.mContents[0].mType = StyleContentType::AltContent;
},
Content::Items(items) => {
unsafe {
@ -5554,7 +5553,7 @@ clip-path
}
match *item {
ContentItem::String(ref value) => {
self.gecko.mContents[i].mType = eStyleContentType_String;
self.gecko.mContents[i].mType = StyleContentType::String;
unsafe {
// NB: we share allocators, so doing this is fine.
*self.gecko.mContents[i].mContent.mString.as_mut() =
@ -5562,7 +5561,7 @@ clip-path
}
}
ContentItem::Attr(ref attr) => {
self.gecko.mContents[i].mType = eStyleContentType_Attr;
self.gecko.mContents[i].mType = StyleContentType::Attr;
unsafe {
// NB: we share allocators, so doing this is fine.
let maybe_ns = attr.namespace.clone();
@ -5581,17 +5580,17 @@ clip-path
}
}
ContentItem::OpenQuote
=> self.gecko.mContents[i].mType = eStyleContentType_OpenQuote,
=> self.gecko.mContents[i].mType = StyleContentType::OpenQuote,
ContentItem::CloseQuote
=> self.gecko.mContents[i].mType = eStyleContentType_CloseQuote,
=> self.gecko.mContents[i].mType = StyleContentType::CloseQuote,
ContentItem::NoOpenQuote
=> self.gecko.mContents[i].mType = eStyleContentType_NoOpenQuote,
=> self.gecko.mContents[i].mType = StyleContentType::NoOpenQuote,
ContentItem::NoCloseQuote
=> self.gecko.mContents[i].mType = eStyleContentType_NoCloseQuote,
=> self.gecko.mContents[i].mType = StyleContentType::NoCloseQuote,
ContentItem::Counter(ref name, ref style) => {
set_counter_function(
&mut self.gecko.mContents[i],
eStyleContentType_Counter,
StyleContentType::Counter,
&name,
"",
style.clone(),
@ -5601,7 +5600,7 @@ clip-path
ContentItem::Counters(ref name, ref sep, ref style) => {
set_counter_function(
&mut self.gecko.mContents[i],
eStyleContentType_Counters,
StyleContentType::Counters,
&name,
&sep,
style.clone(),
@ -5636,7 +5635,7 @@ clip-path
pub fn clone_content(&self) -> longhands::content::computed_value::T {
use {Atom, Namespace};
use gecko::conversions::string_from_chars_pointer;
use gecko_bindings::structs::nsStyleContentType::*;
use gecko_bindings::structs::StyleContentType;
use values::generics::counters::{Content, ContentItem};
use values::computed::url::ComputedImageUrl;
use values::{CustomIdent, Either};
@ -5644,27 +5643,27 @@ clip-path
use values::specified::Attr;
if self.gecko.mContents.is_empty() {
return Content::Normal;
return Content::None;
}
if self.gecko.mContents.len() == 1 &&
self.gecko.mContents[0].mType == eStyleContentType_AltContent {
self.gecko.mContents[0].mType == StyleContentType::AltContent {
return Content::MozAltContent;
}
Content::Items(
self.gecko.mContents.iter().map(|gecko_content| {
match gecko_content.mType {
eStyleContentType_OpenQuote => ContentItem::OpenQuote,
eStyleContentType_CloseQuote => ContentItem::CloseQuote,
eStyleContentType_NoOpenQuote => ContentItem::NoOpenQuote,
eStyleContentType_NoCloseQuote => ContentItem::NoCloseQuote,
eStyleContentType_String => {
StyleContentType::OpenQuote => ContentItem::OpenQuote,
StyleContentType::CloseQuote => ContentItem::CloseQuote,
StyleContentType::NoOpenQuote => ContentItem::NoOpenQuote,
StyleContentType::NoCloseQuote => ContentItem::NoCloseQuote,
StyleContentType::String => {
let gecko_chars = unsafe { gecko_content.mContent.mString.as_ref() };
let string = unsafe { string_from_chars_pointer(*gecko_chars) };
ContentItem::String(string.into_boxed_str())
},
eStyleContentType_Attr => {
StyleContentType::Attr => {
let (namespace, attribute) = unsafe {
let s = &**gecko_content.mContent.mAttr.as_ref();
let ns = if s.mNamespaceURL.mRawPtr.is_null() {
@ -5678,7 +5677,7 @@ clip-path
};
ContentItem::Attr(Attr { namespace, attribute })
},
eStyleContentType_Counter | eStyleContentType_Counters => {
StyleContentType::Counter | StyleContentType::Counters => {
let gecko_function =
unsafe { &**gecko_content.mContent.mCounters.as_ref() };
let ident = CustomIdent(Atom::from(&*gecko_function.mIdent));
@ -5689,14 +5688,14 @@ clip-path
Either::Second(_) =>
unreachable!("counter function shouldn't have single string type"),
};
if gecko_content.mType == eStyleContentType_Counter {
if gecko_content.mType == StyleContentType::Counter {
ContentItem::Counter(ident, style)
} else {
let separator = gecko_function.mSeparator.to_string();
ContentItem::Counters(ident, separator.into_boxed_str(), style)
}
},
eStyleContentType_Image => {
StyleContentType::Image => {
unsafe {
let gecko_image_request =
&**gecko_content.mContent.mImage.as_ref();

View file

@ -856,7 +856,7 @@
elif len(maybe_size) == 1:
size = maybe_size[0]
def phys_ident(side, phy_side):
return to_rust_ident(name.replace(side, phy_side).replace("offset-", ""))
return to_rust_ident(name.replace(side, phy_side).replace("inset-", ""))
%>
% if side is not None:
use logical_geometry::PhysicalSide;

View file

@ -21,14 +21,15 @@
servo_restyle_damage="reflow_out_of_flow"
)}
% endfor
// offset-* logical properties, map to "top" / "left" / "bottom" / "right"
// inset-* logical properties, map to "top" / "left" / "bottom" / "right"
% for side in LOGICAL_SIDES:
${helpers.predefined_type(
"offset-%s" % side,
"inset-%s" % side,
"LengthOrPercentageOrAuto",
"computed::LengthOrPercentageOrAuto::Auto",
spec="https://drafts.csswg.org/css-logical-props/#propdef-offset-%s" % side,
spec="https://drafts.csswg.org/css-logical-props/#propdef-inset-%s" % side,
flags="GETCS_NEEDS_LAYOUT_FLUSH",
alias="offset-%s:layout.css.offset-logical-properties.enabled" % side,
animation_value_type="ComputedValue",
logical=True,
)}

View file

@ -100,6 +100,7 @@ pub mod longhands {
% for style_struct in data.style_structs:
include!("${repr(os.path.join(OUT_DIR, 'longhands/{}.rs'.format(style_struct.name_lower)))[1:-1]}");
% endfor
pub const ANIMATABLE_PROPERTY_COUNT: usize = ${sum(1 for prop in data.longhands if prop.animatable)};
}
macro_rules! unwrap_or_initial {
@ -177,7 +178,6 @@ pub mod shorthands {
data.declare_shorthand(
"all",
logical_longhands + other_longhands,
gecko_pref="layout.css.all-shorthand.enabled",
spec="https://drafts.csswg.org/css-cascade-3/#all-shorthand"
)
%>
@ -410,6 +410,7 @@ pub struct NonCustomPropertyId(usize);
impl NonCustomPropertyId {
#[cfg(feature = "gecko")]
#[inline]
fn to_nscsspropertyid(self) -> nsCSSPropertyID {
static MAP: [nsCSSPropertyID; ${len(data.longhands) + len(data.shorthands) + len(data.all_aliases())}] = [
% for property in data.longhands + data.shorthands + data.all_aliases():
@ -835,6 +836,8 @@ bitflags! {
* they can be checked in the C++ side via ServoCSSPropList.h. */
/// This property can be animated on the compositor.
const CAN_ANIMATE_ON_COMPOSITOR = 0;
/// This shorthand property is accessible from getComputedStyle.
const SHORTHAND_IN_GETCS = 0;
}
}
@ -1708,6 +1711,9 @@ impl PropertyId {
}
/// Returns a property id from Gecko's nsCSSPropertyID.
///
/// TODO(emilio): We should be able to make this a single integer cast to
/// `NonCustomPropertyId`.
#[cfg(feature = "gecko")]
#[allow(non_upper_case_globals)]
pub fn from_nscsspropertyid(id: nsCSSPropertyID) -> Result<Self, ()> {

View file

@ -196,6 +196,7 @@
</%helpers:shorthand>
<%helpers:shorthand name="background-position"
flags="SHORTHAND_IN_GETCS"
sub_properties="background-position-x background-position-y"
spec="https://drafts.csswg.org/css-backgrounds-4/#the-background-position">
use properties::longhands::{background_position_x, background_position_y};

View file

@ -6,6 +6,7 @@
<%helpers:shorthand
name="overflow"
flags="SHORTHAND_IN_GETCS"
sub_properties="overflow-x overflow-y"
spec="https://drafts.csswg.org/css-overflow/#propdef-overflow"
>

View file

@ -285,6 +285,7 @@
</%helpers:shorthand>
<%helpers:shorthand name="font-variant"
flags="SHORTHAND_IN_GETCS"
sub_properties="font-variant-caps
${'font-variant-alternates' if product == 'gecko' else ''}
${'font-variant-east-asian' if product == 'gecko' else ''}

View file

@ -5,6 +5,7 @@
<%namespace name="helpers" file="/helpers.mako.rs" />
<%helpers:shorthand name="mask" products="gecko" extra_prefixes="webkit"
flags="SHORTHAND_IN_GETCS"
sub_properties="mask-mode mask-repeat mask-clip mask-origin mask-composite mask-position-x
mask-position-y mask-size mask-image"
spec="https://drafts.fxtf.org/css-masking/#propdef-mask">
@ -182,6 +183,7 @@
</%helpers:shorthand>
<%helpers:shorthand name="mask-position" products="gecko" extra_prefixes="webkit"
flags="SHORTHAND_IN_GETCS"
sub_properties="mask-position-x mask-position-y"
spec="https://drafts.csswg.org/css-masks-4/#the-mask-position">
use properties::longhands::{mask_position_x,mask_position_y};

View file

@ -5,6 +5,7 @@
<%namespace name="helpers" file="/helpers.mako.rs" />
<%helpers:shorthand name="text-decoration"
flags="SHORTHAND_IN_GETCS"
sub_properties="text-decoration-line
${' text-decoration-style text-decoration-color' if product == 'gecko' else ''}"
spec="https://drafts.csswg.org/css-text-decor/#propdef-text-decoration">

View file

@ -5,7 +5,7 @@
//! A data structure to efficiently index structs containing selectors by local
//! name, ids and hash.
use {Atom, LocalName, WeakAtom};
use {Atom, LocalName, Namespace, WeakAtom};
use applicable_declarations::ApplicableDeclarationList;
use context::QuirksMode;
use dom::TElement;
@ -102,6 +102,8 @@ pub struct SelectorMap<T: 'static> {
pub class_hash: MaybeCaseInsensitiveHashMap<Atom, SmallVec<[T; 1]>>,
/// A hash from local name to rules which contain that local name selector.
pub local_name_hash: PrecomputedHashMap<LocalName, SmallVec<[T; 1]>>,
/// A hash from namespace to rules which contain that namespace selector.
pub namespace_hash: PrecomputedHashMap<Namespace, SmallVec<[T; 1]>>,
/// Rules that don't have ID, class, or element selectors.
pub other: SmallVec<[T; 1]>,
/// The number of entries in this map.
@ -125,6 +127,7 @@ impl<T: 'static> SelectorMap<T> {
id_hash: MaybeCaseInsensitiveHashMap::new(),
class_hash: MaybeCaseInsensitiveHashMap::new(),
local_name_hash: HashMap::default(),
namespace_hash: HashMap::default(),
other: SmallVec::new(),
count: 0,
}
@ -135,6 +138,7 @@ impl<T: 'static> SelectorMap<T> {
self.id_hash.clear();
self.class_hash.clear();
self.local_name_hash.clear();
self.namespace_hash.clear();
self.other.clear();
self.count = 0;
}
@ -217,6 +221,18 @@ impl SelectorMap<Rule> {
)
}
if let Some(rules) = self.namespace_hash.get(rule_hash_target.namespace()) {
SelectorMap::get_matching_rules(
element,
rules,
matching_rules_list,
context,
flags_setter,
cascade_level,
shadow_cascade_order,
)
}
SelectorMap::get_matching_rules(
element,
&self.other,
@ -261,7 +277,8 @@ impl SelectorMap<Rule> {
}
impl<T: SelectorMapEntry> SelectorMap<T> {
/// Inserts into the correct hash, trying id, class, and localname.
/// Inserts into the correct hash, trying id, class, localname and
/// namespace.
pub fn insert(
&mut self,
entry: T,
@ -298,13 +315,17 @@ impl<T: SelectorMapEntry> SelectorMap<T> {
.try_entry(name.clone())?
.or_insert_with(SmallVec::new)
},
Bucket::Namespace(url) => self.namespace_hash
.try_entry(url.clone())?
.or_insert_with(SmallVec::new),
Bucket::Universal => &mut self.other,
};
vector.try_push(entry)
}
/// Looks up entries by id, class, local name, and other (in order).
/// Looks up entries by id, class, local name, namespace, and other (in
/// order).
///
/// Each entry is passed to the callback, which returns true to continue
/// iterating entries, or false to terminate the lookup.
@ -319,7 +340,6 @@ impl<T: SelectorMapEntry> SelectorMap<T> {
E: TElement,
F: FnMut(&'a T) -> bool,
{
// Id.
if let Some(id) = element.id() {
if let Some(v) = self.id_hash.get(id, quirks_mode) {
for entry in v.iter() {
@ -330,7 +350,6 @@ impl<T: SelectorMapEntry> SelectorMap<T> {
}
}
// Class.
let mut done = false;
element.each_class(|class| {
if !done {
@ -348,7 +367,6 @@ impl<T: SelectorMapEntry> SelectorMap<T> {
return false;
}
// Local name.
if let Some(v) = self.local_name_hash.get(element.local_name()) {
for entry in v.iter() {
if !f(&entry) {
@ -357,7 +375,14 @@ impl<T: SelectorMapEntry> SelectorMap<T> {
}
}
// Other.
if let Some(v) = self.namespace_hash.get(element.namespace()) {
for entry in v.iter() {
if !f(&entry) {
return false;
}
}
}
for entry in self.other.iter() {
if !f(&entry) {
return false;
@ -425,6 +450,7 @@ enum Bucket<'a> {
name: &'a LocalName,
lower_name: &'a LocalName,
},
Namespace(&'a Namespace),
Universal,
}
@ -436,6 +462,8 @@ fn specific_bucket_for<'a>(component: &'a Component<SelectorImpl>) -> Bucket<'a>
name: &selector.name,
lower_name: &selector.lower_name,
},
Component::Namespace(_, ref url) |
Component::DefaultNamespace(ref url) => Bucket::Namespace(url),
// ::slotted(..) isn't a normal pseudo-element, so we can insert it on
// the rule hash normally without much problem. For example, in a
// selector like:
@ -470,7 +498,7 @@ fn find_bucket<'a>(mut iter: SelectorIter<'a, SelectorImpl>) -> Bucket<'a> {
// We basically want to find the most specific bucket,
// where:
//
// id > class > local name > universal.
// id > class > local name > namespace > universal.
//
for ss in &mut iter {
let new_bucket = specific_bucket_for(ss);
@ -480,10 +508,15 @@ fn find_bucket<'a>(mut iter: SelectorIter<'a, SelectorImpl>) -> Bucket<'a> {
current_bucket = new_bucket;
},
Bucket::LocalName { .. } => {
if matches!(current_bucket, Bucket::Universal) {
if matches!(current_bucket, Bucket::Universal | Bucket::Namespace(..)) {
current_bucket = new_bucket;
}
},
Bucket::Namespace(..) => {
if matches!(current_bucket, Bucket::Universal) {
current_bucket = new_bucket;
}
}
Bucket::Universal => {},
}
}

View file

@ -170,9 +170,9 @@ pub enum ExpressionKind {
/// <http://dev.w3.org/csswg/mediaqueries-3/#media1>
#[derive(Clone, Debug, PartialEq)]
#[cfg_attr(feature = "servo", derive(MallocSizeOf))]
pub struct Expression(pub ExpressionKind);
pub struct MediaFeatureExpression(pub ExpressionKind);
impl Expression {
impl MediaFeatureExpression {
/// The kind of expression we're, just for unit testing.
///
/// Eventually this will become servo-only.
@ -183,20 +183,30 @@ impl Expression {
/// Parse a media expression of the form:
///
/// ```
/// (media-feature: media-value)
/// media-feature: media-value
/// ```
///
/// Only supports width and width ranges for now.
/// Only supports width ranges for now.
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 range 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 name = input.expect_ident_cloned()?;
input.expect_colon()?;
// TODO: Handle other media features
Ok(Expression(match_ignore_ascii_case! { &name,
Ok(MediaFeatureExpression(match_ignore_ascii_case! { &name,
"min-width" => {
ExpressionKind::Width(Range::Min(specified::Length::parse_non_negative(context, input)?))
},
@ -208,7 +218,6 @@ impl Expression {
},
_ => return Err(input.new_custom_error(SelectorParseErrorKind::UnexpectedIdent(name.clone())))
}))
})
}
/// Evaluate this expression and return whether it matches the current
@ -228,7 +237,7 @@ impl Expression {
}
}
impl ToCss for Expression {
impl ToCss for MediaFeatureExpression {
fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
where
W: Write,
@ -246,8 +255,8 @@ impl ToCss for Expression {
/// An enumeration that represents a ranged value.
///
/// Only public for testing, implementation details of `Expression` may change
/// for Stylo.
/// Only public for testing, implementation details of `MediaFeatureExpression`
/// may change for Stylo.
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
#[cfg_attr(feature = "servo", derive(MallocSizeOf))]
pub enum Range<T> {

View file

@ -428,6 +428,7 @@ macro_rules! font_feature_values_blocks {
fn parse_block<'t>(
&mut self,
prelude: BlockType,
_location: SourceLocation,
input: &mut Parser<'i, 't>
) -> Result<Self::AtRule, ParseError<'i>> {
debug_assert_eq!(self.context.rule_type(), CssRuleType::FontFeatureValues);

View file

@ -509,14 +509,8 @@ impl<'a, 'i> AtRuleParser<'i> for KeyframeListParser<'a> {
type Error = StyleParseErrorKind<'i>;
}
/// A wrapper to wraps the KeyframeSelector with its source location
struct KeyframeSelectorParserPrelude {
selector: KeyframeSelector,
source_location: SourceLocation,
}
impl<'a, 'i> QualifiedRuleParser<'i> for KeyframeListParser<'a> {
type Prelude = KeyframeSelectorParserPrelude;
type Prelude = KeyframeSelector;
type QualifiedRule = Arc<Locked<Keyframe>>;
type Error = StyleParseErrorKind<'i>;
@ -525,27 +519,21 @@ impl<'a, 'i> QualifiedRuleParser<'i> for KeyframeListParser<'a> {
input: &mut Parser<'i, 't>,
) -> Result<Self::Prelude, ParseError<'i>> {
let start_position = input.position();
let start_location = input.current_source_location();
match KeyframeSelector::parse(input) {
Ok(sel) => Ok(KeyframeSelectorParserPrelude {
selector: sel,
source_location: start_location,
}),
Err(e) => {
KeyframeSelector::parse(input).map_err(|e| {
let location = e.location;
let error = ContextualParseError::InvalidKeyframeRule(
input.slice_from(start_position),
e.clone(),
);
self.context.log_css_error(location, error);
Err(e)
},
}
e
})
}
fn parse_block<'t>(
&mut self,
prelude: Self::Prelude,
selector: Self::Prelude,
source_location: SourceLocation,
input: &mut Parser<'i, 't>,
) -> Result<Self::QualifiedRule, ParseError<'i>> {
let context = ParserContext::new_with_rule_type(
@ -580,9 +568,9 @@ impl<'a, 'i> QualifiedRuleParser<'i> for KeyframeListParser<'a> {
// `parse_important` is not called here, `!important` is not allowed in keyframe blocks.
}
Ok(Arc::new(self.shared_lock.wrap(Keyframe {
selector: prelude.selector,
selector,
block: Arc::new(self.shared_lock.wrap(block)),
source_location: prelude.source_location,
source_location,
})))
}
}

View file

@ -146,31 +146,31 @@ pub enum VendorPrefix {
/// A rule prelude for at-rule with block.
pub enum AtRuleBlockPrelude {
/// A @font-face rule prelude.
FontFace(SourceLocation),
FontFace,
/// A @font-feature-values rule prelude, with its FamilyName list.
FontFeatureValues(Vec<FamilyName>, SourceLocation),
FontFeatureValues(Vec<FamilyName>),
/// A @counter-style rule prelude, with its counter style name.
CounterStyle(CustomIdent, SourceLocation),
CounterStyle(CustomIdent),
/// A @media rule prelude, with its media queries.
Media(Arc<Locked<MediaList>>, SourceLocation),
Media(Arc<Locked<MediaList>>),
/// An @supports rule, with its conditional
Supports(SupportsCondition, SourceLocation),
Supports(SupportsCondition),
/// A @viewport rule prelude.
Viewport,
/// A @keyframes rule, with its animation name and vendor prefix if exists.
Keyframes(KeyframesName, Option<VendorPrefix>, SourceLocation),
Keyframes(KeyframesName, Option<VendorPrefix>),
/// A @page rule prelude.
Page(SourceLocation),
Page,
/// A @document rule, with its conditional.
Document(DocumentCondition, SourceLocation),
Document(DocumentCondition),
}
/// A rule prelude for at-rule without block.
pub enum AtRuleNonBlockPrelude {
/// A @import rule prelude.
Import(CssUrl, Arc<Locked<MediaList>>, SourceLocation),
Import(CssUrl, Arc<Locked<MediaList>>),
/// A @namespace rule prelude.
Namespace(Option<Prefix>, Namespace, SourceLocation),
Namespace(Option<Prefix>, Namespace),
}
impl<'a, 'i> AtRuleParser<'i> for TopLevelRuleParser<'a> {
@ -184,7 +184,6 @@ impl<'a, 'i> AtRuleParser<'i> for TopLevelRuleParser<'a> {
name: CowRcStr<'i>,
input: &mut Parser<'i, 't>,
) -> Result<AtRuleType<AtRuleNonBlockPrelude, AtRuleBlockPrelude>, ParseError<'i>> {
let location = input.current_source_location();
match_ignore_ascii_case! { &*name,
"import" => {
if !self.check_state(State::Imports) {
@ -197,7 +196,7 @@ impl<'a, 'i> AtRuleParser<'i> for TopLevelRuleParser<'a> {
let media = MediaList::parse(&self.context, input);
let media = Arc::new(self.shared_lock.wrap(media));
let prelude = AtRuleNonBlockPrelude::Import(url, media, location);
let prelude = AtRuleNonBlockPrelude::Import(url, media);
return Ok(AtRuleType::WithoutBlock(prelude));
},
"namespace" => {
@ -215,7 +214,7 @@ impl<'a, 'i> AtRuleParser<'i> for TopLevelRuleParser<'a> {
Err(e) => return Err(e.into()),
};
let url = Namespace::from(maybe_namespace.as_ref());
let prelude = AtRuleNonBlockPrelude::Namespace(prefix, url, location);
let prelude = AtRuleNonBlockPrelude::Namespace(prefix, url);
return Ok(AtRuleType::WithoutBlock(prelude));
},
// @charset is removed by rust-cssparser if its the first rule in the stylesheet
@ -238,24 +237,29 @@ impl<'a, 'i> AtRuleParser<'i> for TopLevelRuleParser<'a> {
fn parse_block<'t>(
&mut self,
prelude: AtRuleBlockPrelude,
location: SourceLocation,
input: &mut Parser<'i, 't>,
) -> Result<CssRule, ParseError<'i>> {
AtRuleParser::parse_block(&mut self.nested(), prelude, input).map(|rule| {
AtRuleParser::parse_block(&mut self.nested(), prelude, location, input).map(|rule| {
self.state = State::Body;
rule
})
}
#[inline]
fn rule_without_block(&mut self, prelude: AtRuleNonBlockPrelude) -> CssRule {
fn rule_without_block(
&mut self,
prelude: AtRuleNonBlockPrelude,
source_location: SourceLocation,
) -> CssRule {
match prelude {
AtRuleNonBlockPrelude::Import(url, media, location) => {
AtRuleNonBlockPrelude::Import(url, media) => {
let loader = self.loader
.expect("Expected a stylesheet loader for @import");
let import_rule = loader.request_stylesheet(
url,
location,
source_location,
&self.context,
&self.shared_lock,
media,
@ -264,7 +268,7 @@ impl<'a, 'i> AtRuleParser<'i> for TopLevelRuleParser<'a> {
self.state = State::Imports;
CssRule::Import(import_rule)
},
AtRuleNonBlockPrelude::Namespace(prefix, url, source_location) => {
AtRuleNonBlockPrelude::Namespace(prefix, url) => {
let prefix = if let Some(prefix) = prefix {
self.namespaces.prefixes.insert(prefix.clone(), url.clone());
Some(prefix)
@ -284,13 +288,8 @@ impl<'a, 'i> AtRuleParser<'i> for TopLevelRuleParser<'a> {
}
}
pub struct QualifiedRuleParserPrelude {
selectors: SelectorList<SelectorImpl>,
source_location: SourceLocation,
}
impl<'a, 'i> QualifiedRuleParser<'i> for TopLevelRuleParser<'a> {
type Prelude = QualifiedRuleParserPrelude;
type Prelude = SelectorList<SelectorImpl>;
type QualifiedRule = CssRule;
type Error = StyleParseErrorKind<'i>;
@ -298,7 +297,7 @@ impl<'a, 'i> QualifiedRuleParser<'i> for TopLevelRuleParser<'a> {
fn parse_prelude<'t>(
&mut self,
input: &mut Parser<'i, 't>,
) -> Result<QualifiedRuleParserPrelude, ParseError<'i>> {
) -> Result<Self::Prelude, ParseError<'i>> {
if !self.check_state(State::Body) {
return Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError))
}
@ -309,10 +308,16 @@ impl<'a, 'i> QualifiedRuleParser<'i> for TopLevelRuleParser<'a> {
#[inline]
fn parse_block<'t>(
&mut self,
prelude: QualifiedRuleParserPrelude,
prelude: Self::Prelude,
location: SourceLocation,
input: &mut Parser<'i, 't>,
) -> Result<CssRule, ParseError<'i>> {
QualifiedRuleParser::parse_block(&mut self.nested(), prelude, input).map(|result| {
QualifiedRuleParser::parse_block(
&mut self.nested(),
prelude,
location,
input,
).map(|result| {
self.state = State::Body;
result
})
@ -373,20 +378,18 @@ impl<'a, 'b, 'i> AtRuleParser<'i> for NestedRuleParser<'a, 'b> {
name: CowRcStr<'i>,
input: &mut Parser<'i, 't>,
) -> Result<AtRuleType<AtRuleNonBlockPrelude, AtRuleBlockPrelude>, ParseError<'i>> {
let location = input.current_source_location();
match_ignore_ascii_case! { &*name,
"media" => {
let media_queries = MediaList::parse(self.context, input);
let arc = Arc::new(self.shared_lock.wrap(media_queries));
Ok(AtRuleType::WithBlock(AtRuleBlockPrelude::Media(arc, location)))
Ok(AtRuleType::WithBlock(AtRuleBlockPrelude::Media(arc)))
},
"supports" => {
let cond = SupportsCondition::parse(input)?;
Ok(AtRuleType::WithBlock(AtRuleBlockPrelude::Supports(cond, location)))
Ok(AtRuleType::WithBlock(AtRuleBlockPrelude::Supports(cond)))
},
"font-face" => {
Ok(AtRuleType::WithBlock(AtRuleBlockPrelude::FontFace(location)))
Ok(AtRuleType::WithBlock(AtRuleBlockPrelude::FontFace))
},
"font-feature-values" => {
if !cfg!(feature = "gecko") {
@ -394,7 +397,7 @@ impl<'a, 'b, 'i> AtRuleParser<'i> for NestedRuleParser<'a, 'b> {
return Err(input.new_custom_error(StyleParseErrorKind::UnsupportedAtRule(name.clone())))
}
let family_names = parse_family_name_list(self.context, input)?;
Ok(AtRuleType::WithBlock(AtRuleBlockPrelude::FontFeatureValues(family_names, location)))
Ok(AtRuleType::WithBlock(AtRuleBlockPrelude::FontFeatureValues(family_names)))
},
"counter-style" => {
if !cfg!(feature = "gecko") {
@ -402,7 +405,7 @@ impl<'a, 'b, 'i> AtRuleParser<'i> for NestedRuleParser<'a, 'b> {
return Err(input.new_custom_error(StyleParseErrorKind::UnsupportedAtRule(name.clone())))
}
let name = parse_counter_style_name_definition(input)?;
Ok(AtRuleType::WithBlock(AtRuleBlockPrelude::CounterStyle(name, location)))
Ok(AtRuleType::WithBlock(AtRuleBlockPrelude::CounterStyle(name)))
},
"viewport" => {
if viewport_rule::enabled() {
@ -426,11 +429,11 @@ impl<'a, 'b, 'i> AtRuleParser<'i> for NestedRuleParser<'a, 'b> {
}
let name = KeyframesName::parse(self.context, input)?;
Ok(AtRuleType::WithBlock(AtRuleBlockPrelude::Keyframes(name, prefix, location)))
Ok(AtRuleType::WithBlock(AtRuleBlockPrelude::Keyframes(name, prefix)))
},
"page" => {
if cfg!(feature = "gecko") {
Ok(AtRuleType::WithBlock(AtRuleBlockPrelude::Page(location)))
Ok(AtRuleType::WithBlock(AtRuleBlockPrelude::Page))
} else {
Err(input.new_custom_error(StyleParseErrorKind::UnsupportedAtRule(name.clone())))
}
@ -443,7 +446,7 @@ impl<'a, 'b, 'i> AtRuleParser<'i> for NestedRuleParser<'a, 'b> {
}
let cond = DocumentCondition::parse(self.context, input)?;
Ok(AtRuleType::WithBlock(AtRuleBlockPrelude::Document(cond, location)))
Ok(AtRuleType::WithBlock(AtRuleBlockPrelude::Document(cond)))
},
_ => Err(input.new_custom_error(StyleParseErrorKind::UnsupportedAtRule(name.clone())))
}
@ -452,10 +455,11 @@ impl<'a, 'b, 'i> AtRuleParser<'i> for NestedRuleParser<'a, 'b> {
fn parse_block<'t>(
&mut self,
prelude: AtRuleBlockPrelude,
source_location: SourceLocation,
input: &mut Parser<'i, 't>,
) -> Result<CssRule, ParseError<'i>> {
match prelude {
AtRuleBlockPrelude::FontFace(location) => {
AtRuleBlockPrelude::FontFace => {
let context = ParserContext::new_with_rule_type(
self.context,
CssRuleType::FontFace,
@ -463,10 +467,10 @@ impl<'a, 'b, 'i> AtRuleParser<'i> for NestedRuleParser<'a, 'b> {
);
Ok(CssRule::FontFace(Arc::new(self.shared_lock.wrap(
parse_font_face_block(&context, input, location).into(),
parse_font_face_block(&context, input, source_location).into(),
))))
},
AtRuleBlockPrelude::FontFeatureValues(family_names, location) => {
AtRuleBlockPrelude::FontFeatureValues(family_names) => {
let context = ParserContext::new_with_rule_type(
self.context,
CssRuleType::FontFeatureValues,
@ -478,11 +482,11 @@ impl<'a, 'b, 'i> AtRuleParser<'i> for NestedRuleParser<'a, 'b> {
&context,
input,
family_names,
location,
source_location,
),
))))
},
AtRuleBlockPrelude::CounterStyle(name, location) => {
AtRuleBlockPrelude::CounterStyle(name) => {
let context = ParserContext::new_with_rule_type(
self.context,
CssRuleType::CounterStyle,
@ -495,19 +499,19 @@ impl<'a, 'b, 'i> AtRuleParser<'i> for NestedRuleParser<'a, 'b> {
name,
&context,
input,
location,
source_location,
)?.into(),
),
)))
},
AtRuleBlockPrelude::Media(media_queries, source_location) => {
AtRuleBlockPrelude::Media(media_queries) => {
Ok(CssRule::Media(Arc::new(self.shared_lock.wrap(MediaRule {
media_queries,
rules: self.parse_nested_rules(input, CssRuleType::Media),
source_location,
}))))
},
AtRuleBlockPrelude::Supports(condition, source_location) => {
AtRuleBlockPrelude::Supports(condition) => {
let eval_context = ParserContext::new_with_rule_type(
self.context,
CssRuleType::Style,
@ -535,7 +539,7 @@ impl<'a, 'b, 'i> AtRuleParser<'i> for NestedRuleParser<'a, 'b> {
ViewportRule::parse(&context, input)?,
))))
},
AtRuleBlockPrelude::Keyframes(name, vendor_prefix, source_location) => {
AtRuleBlockPrelude::Keyframes(name, vendor_prefix) => {
let context = ParserContext::new_with_rule_type(
self.context,
CssRuleType::Keyframes,
@ -555,7 +559,7 @@ impl<'a, 'b, 'i> AtRuleParser<'i> for NestedRuleParser<'a, 'b> {
},
))))
},
AtRuleBlockPrelude::Page(source_location) => {
AtRuleBlockPrelude::Page => {
let context = ParserContext::new_with_rule_type(
self.context,
CssRuleType::Page,
@ -569,7 +573,7 @@ impl<'a, 'b, 'i> AtRuleParser<'i> for NestedRuleParser<'a, 'b> {
source_location,
}))))
},
AtRuleBlockPrelude::Document(condition, source_location) => {
AtRuleBlockPrelude::Document(condition) => {
if !cfg!(feature = "gecko") {
unreachable!()
}
@ -586,39 +590,37 @@ impl<'a, 'b, 'i> AtRuleParser<'i> for NestedRuleParser<'a, 'b> {
}
impl<'a, 'b, 'i> QualifiedRuleParser<'i> for NestedRuleParser<'a, 'b> {
type Prelude = QualifiedRuleParserPrelude;
type Prelude = SelectorList<SelectorImpl>;
type QualifiedRule = CssRule;
type Error = StyleParseErrorKind<'i>;
fn parse_prelude<'t>(
&mut self,
input: &mut Parser<'i, 't>,
) -> Result<QualifiedRuleParserPrelude, ParseError<'i>> {
) -> Result<Self::Prelude, ParseError<'i>> {
let selector_parser = SelectorParser {
stylesheet_origin: self.stylesheet_origin,
namespaces: self.namespaces,
url_data: Some(self.context.url_data),
};
let source_location = input.current_source_location();
let selectors = SelectorList::parse(&selector_parser, input)?;
Ok(QualifiedRuleParserPrelude { selectors, source_location, })
SelectorList::parse(&selector_parser, input)
}
fn parse_block<'t>(
&mut self,
prelude: QualifiedRuleParserPrelude,
selectors: Self::Prelude,
source_location: SourceLocation,
input: &mut Parser<'i, 't>,
) -> Result<CssRule, ParseError<'i>> {
let context =
ParserContext::new_with_rule_type(self.context, CssRuleType::Style, self.namespaces);
let declarations = parse_property_declaration_list(&context, input);
let block = Arc::new(self.shared_lock.wrap(declarations));
Ok(CssRule::Style(Arc::new(self.shared_lock.wrap(StyleRule {
selectors: prelude.selectors,
block: Arc::new(self.shared_lock.wrap(declarations)),
source_location: prelude.source_location,
selectors,
block,
source_location,
}))))
}
}

View file

@ -186,7 +186,7 @@ impl ComputeSquaredDistance for Color {
(Foreground, Foreground) => SquaredDistance::from_sqrt(0.),
(Numeric(c1), Numeric(c2)) => c1.compute_squared_distance(&c2)?,
(Foreground, Numeric(color)) | (Numeric(color), Foreground) => {
// `computed_squared_distance` is symmetic.
// `computed_squared_distance` is symmetric.
color.compute_squared_distance(&RGBA::transparent())?
+ SquaredDistance::from_sqrt(1.)
}
@ -207,7 +207,6 @@ impl ComputeSquaredDistance for Color {
impl ToAnimatedZero for Color {
#[inline]
fn to_animated_zero(&self) -> Result<Self, ()> {
// FIXME(nox): This does not look correct to me.
Err(())
Ok(RGBA::transparent().into())
}
}

View file

@ -6,7 +6,7 @@
#[cfg(not(feature = "gecko"))]
use values::Impossible;
use values::animated::color::RGBA;
use values::animated::color::Color;
use values::computed::{Angle, Number};
use values::computed::length::Length;
#[cfg(feature = "gecko")]
@ -17,7 +17,7 @@ use values::generics::effects::Filter as GenericFilter;
use values::generics::effects::SimpleShadow as GenericSimpleShadow;
/// An animated value for a single `box-shadow`.
pub type BoxShadow = GenericBoxShadow<Option<RGBA>, Length, Length, Length>;
pub type BoxShadow = GenericBoxShadow<Color, Length, Length, Length>;
/// An animated value for a single `filter`.
#[cfg(feature = "gecko")]
@ -28,7 +28,7 @@ pub type Filter = GenericFilter<Angle, Number, Length, SimpleShadow, ComputedUrl
pub type Filter = GenericFilter<Angle, Number, Length, Impossible, Impossible>;
/// An animated value for the `drop-shadow()` filter.
pub type SimpleShadow = GenericSimpleShadow<Option<RGBA>, Length, Length>;
pub type SimpleShadow = GenericSimpleShadow<Color, Length, Length>;
impl ComputeSquaredDistance for BoxShadow {
#[inline]

View file

@ -7,7 +7,7 @@
#[cfg(not(feature = "gecko"))]
use values::Impossible;
use values::computed::{Angle, NonNegativeNumber};
use values::computed::color::RGBAColor;
use values::computed::color::Color;
use values::computed::length::{Length, NonNegativeLength};
#[cfg(feature = "gecko")]
use values::computed::url::ComputedUrl;
@ -16,7 +16,7 @@ use values::generics::effects::Filter as GenericFilter;
use values::generics::effects::SimpleShadow as GenericSimpleShadow;
/// A computed value for a single shadow of the `box-shadow` property.
pub type BoxShadow = GenericBoxShadow<Option<RGBAColor>, Length, NonNegativeLength, Length>;
pub type BoxShadow = GenericBoxShadow<Color, Length, NonNegativeLength, Length>;
/// A computed value for a single `filter`.
#[cfg(feature = "gecko")]
@ -27,4 +27,4 @@ pub type Filter = GenericFilter<Angle, NonNegativeNumber, NonNegativeLength, Sim
pub type Filter = GenericFilter<Angle, NonNegativeNumber, NonNegativeLength, Impossible, Impossible>;
/// A computed value for the `drop-shadow()` filter.
pub type SimpleShadow = GenericSimpleShadow<Option<RGBAColor>, Length, NonNegativeLength>;
pub type SimpleShadow = GenericSimpleShadow<Color, Length, NonNegativeLength>;

View file

@ -122,7 +122,6 @@ impl<ImageUrl> Content<ImageUrl> {
pub fn normal() -> Self {
Content::Normal
}
}
/// Items for the `content` property.

View file

@ -17,14 +17,14 @@ use values::generics::effects::BoxShadow as GenericBoxShadow;
use values::generics::effects::Filter as GenericFilter;
use values::generics::effects::SimpleShadow as GenericSimpleShadow;
use values::specified::{Angle, NumberOrPercentage};
use values::specified::color::RGBAColor;
use values::specified::color::Color;
use values::specified::length::{Length, NonNegativeLength};
#[cfg(feature = "gecko")]
use values::specified::url::SpecifiedUrl;
/// A specified value for a single shadow of the `box-shadow` property.
pub type BoxShadow =
GenericBoxShadow<Option<RGBAColor>, Length, Option<NonNegativeLength>, Option<Length>>;
GenericBoxShadow<Option<Color>, Length, Option<NonNegativeLength>, Option<Length>>;
/// A specified value for a single `filter`.
#[cfg(feature = "gecko")]
@ -93,7 +93,7 @@ impl ToComputedValue for Factor {
}
/// A specified value for the `drop-shadow()` filter.
pub type SimpleShadow = GenericSimpleShadow<Option<RGBAColor>, Length, Option<NonNegativeLength>>;
pub type SimpleShadow = GenericSimpleShadow<Option<Color>, Length, Option<NonNegativeLength>>;
impl Parse for BoxShadow {
fn parse<'i, 't>(
@ -135,7 +135,7 @@ impl Parse for BoxShadow {
}
}
if color.is_none() {
if let Ok(value) = input.try(|i| RGBAColor::parse(context, i)) {
if let Ok(value) = input.try(|i| Color::parse(context, i)) {
color = Some(value);
continue;
}
@ -249,16 +249,18 @@ impl Parse for SimpleShadow {
context: &ParserContext,
input: &mut Parser<'i, 't>,
) -> Result<Self, ParseError<'i>> {
let color = input.try(|i| RGBAColor::parse(context, i)).ok();
let color = input.try(|i| Color::parse(context, i)).ok();
let horizontal = Length::parse(context, input)?;
let vertical = Length::parse(context, input)?;
let blur = input.try(|i| Length::parse_non_negative(context, i)).ok();
let color = color.or_else(|| input.try(|i| RGBAColor::parse(context, i)).ok());
let blur = blur.map(NonNegative::<Length>);
let color = color.or_else(|| input.try(|i| Color::parse(context, i)).ok());
Ok(SimpleShadow {
color: color,
horizontal: horizontal,
vertical: vertical,
blur: blur.map(NonNegative::<Length>),
color,
horizontal,
vertical,
blur,
})
}
}
@ -269,7 +271,10 @@ impl ToComputedValue for SimpleShadow {
#[inline]
fn to_computed_value(&self, context: &Context) -> Self::ComputedValue {
ComputedSimpleShadow {
color: self.color.to_computed_value(context),
color: self.color
.as_ref()
.unwrap_or(&Color::currentcolor())
.to_computed_value(context),
horizontal: self.horizontal.to_computed_value(context),
vertical: self.vertical.to_computed_value(context),
blur: self.blur
@ -282,7 +287,7 @@ impl ToComputedValue for SimpleShadow {
#[inline]
fn from_computed_value(computed: &Self::ComputedValue) -> Self {
SimpleShadow {
color: ToComputedValue::from_computed_value(&computed.color),
color: Some(ToComputedValue::from_computed_value(&computed.color)),
horizontal: ToComputedValue::from_computed_value(&computed.horizontal),
vertical: ToComputedValue::from_computed_value(&computed.vertical),
blur: Some(ToComputedValue::from_computed_value(&computed.blur)),

View file

@ -8,7 +8,7 @@ use app_units::Au;
use cssparser::{Delimiter, Parser, Token};
#[cfg(feature = "gecko")]
use gecko_bindings::sugar::ownership::{HasBoxFFI, HasFFI, HasSimpleFFI};
use media_queries::{Device, Expression as MediaExpression};
use media_queries::{Device, MediaCondition};
use parser::{Parse, ParserContext};
use selectors::context::QuirksMode;
use style_traits::ParseError;
@ -19,9 +19,7 @@ use values::specified::{Length, NoCalcLength, ViewportPercentageLength};
///
/// https://html.spec.whatwg.org/multipage/#source-size
pub struct SourceSize {
// FIXME(emilio): This should be a `MediaCondition`, and support `and` and
// `or`.
condition: MediaExpression,
condition: MediaCondition,
value: Length,
}
@ -30,7 +28,7 @@ impl Parse for SourceSize {
context: &ParserContext,
input: &mut Parser<'i, 't>,
) -> Result<Self, ParseError<'i>> {
let condition = MediaExpression::parse(context, input)?;
let condition = MediaCondition::parse(context, input)?;
let value = Length::parse_non_negative(context, input)?;
Ok(Self { condition, value })

View file

@ -15,7 +15,7 @@ gecko = []
[dependencies]
app_units = "0.6"
cssparser = "0.23.0"
cssparser = "0.24.0"
bitflags = "1.0"
euclid = "0.17"
malloc_size_of = { path = "../malloc_size_of" }

View file

@ -106,12 +106,12 @@ pub enum StyleParseErrorKind<'i> {
PropertyDeclarationValueNotExhausted,
/// An unexpected dimension token was encountered.
UnexpectedDimension(CowRcStr<'i>),
/// Expected identifier not found.
ExpectedIdentifier(Token<'i>),
/// Missing or invalid media feature name.
MediaQueryExpectedFeatureName(CowRcStr<'i>),
/// Missing or invalid media feature value.
MediaQueryExpectedFeatureValue,
/// A media feature range operator was not expected.
MediaQueryUnexpectedOperator,
/// min- or max- properties must have a value.
RangedExpressionWithNoValue,
/// A function was encountered that was not expected.

View file

@ -13,4 +13,3 @@ euclid = "0.17"
msg = {path = "../../../components/msg"}
script = {path = "../../../components/script"}
servo_url = {path = "../../../components/url"}
style = {path = "../../../components/style"}

View file

@ -2,104 +2,7 @@
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
use script::test::DOMString;
use script::test::sizes::{parse_a_sizes_attribute, Size};
use script::test::srcset::{Descriptor, ImageSource, parse_a_srcset_attribute};
use style::media_queries::{MediaQuery, MediaQueryType};
use style::media_queries::Expression;
use style::servo::media_queries::{ExpressionKind, Range};
use style::values::specified::{Length, NoCalcLength, AbsoluteLength, ViewportPercentageLength};
pub fn test_length_for_no_default_provided(len: f32) -> Length {
let length = Length::NoCalc(NoCalcLength::ViewportPercentage(ViewportPercentageLength::Vw(len)));
return length;
}
#[test]
fn no_default_provided() {
let mut a = vec![];
let length = test_length_for_no_default_provided(100f32);
let size = Size { query: None, length: length };
a.push(size);
assert_eq!(parse_a_sizes_attribute(DOMString::new(), None), a);
}
pub fn test_length_for_default_provided(len: f32) -> Length {
let length = Length::NoCalc(NoCalcLength::Absolute(AbsoluteLength::Px(len)));
return length;
}
#[test]
fn default_provided() {
let mut a = vec![];
let length = test_length_for_default_provided(2f32);
let size = Size { query: None, length: length };
a.push(size);
assert_eq!(parse_a_sizes_attribute(DOMString::new(), Some(2)), a);
}
pub fn test_media_query(len: f32) -> MediaQuery {
let length = Length::NoCalc(NoCalcLength::Absolute(AbsoluteLength::Px(len)));
let expr = Expression(ExpressionKind::Width(Range::Max(length)));
let media_query = MediaQuery {
qualifier: None,
media_type: MediaQueryType::All,
expressions: vec![expr]
};
media_query
}
pub fn test_length(len: f32) -> Length {
let length = Length::NoCalc(NoCalcLength::Absolute(AbsoluteLength::Px(len)));
return length;
}
#[test]
fn one_value() {
let mut a = vec![];
let media_query = test_media_query(200f32);
let length = test_length(545f32);
let size = Size { query: Some(media_query), length: length };
a.push(size);
assert_eq!(parse_a_sizes_attribute(DOMString::from("(max-width: 200px) 545px"), None), a);
}
#[test]
fn more_then_one_value() {
let media_query = test_media_query(900f32);
let length = test_length(1000f32);
let size = Size { query: Some(media_query), length: length };
let media_query1 = test_media_query(900f32);
let length1 = test_length(50f32);
let size1 = Size { query: Some(media_query1), length: length1 };
let a = &[size, size1];
assert_eq!(parse_a_sizes_attribute(DOMString::from("(max-width: 900px) 1000px, (max-width: 900px) 50px"),
None), a);
}
#[test]
fn no_extra_whitespace() {
let mut a = vec![];
let media_query = test_media_query(200f32);
let length = test_length(545f32);
let size = Size { query: Some(media_query), length: length };
a.push(size);
assert_eq!(parse_a_sizes_attribute(DOMString::from("(max-width: 200px) 545px"), None), a);
}
#[test]
fn extra_whitespace() {
let media_query = test_media_query(900f32);
let length = test_length(1000f32);
let size = Size { query: Some(media_query), length: length };
let media_query1 = test_media_query(900f32);
let length1 = test_length(50f32);
let size1 = Size { query: Some(media_query1), length: length1 };
let a = &[size, size1];
assert_eq!(parse_a_sizes_attribute(
DOMString::from("(max-width: 900px) 1000px, (max-width: 900px) 50px"),
None), a);
}
#[test]
fn no_value() {

View file

@ -6,7 +6,6 @@
#[cfg(test)] extern crate msg;
#[cfg(test)] extern crate script;
#[cfg(test)] extern crate servo_url;
#[cfg(test)] extern crate style;
#[cfg(test)] mod origin;
#[cfg(all(test, target_pointer_width = "64"))] mod size_of;

View file

@ -12,7 +12,7 @@ doctest = false
[dependencies]
byteorder = "1.0"
app_units = "0.6"
cssparser = "0.23.0"
cssparser = "0.24.0"
euclid = "0.17"
html5ever = "0.22"
parking_lot = "0.5"

View file

@ -27,7 +27,6 @@ mod attr;
mod custom_properties;
mod keyframes;
mod logical_geometry;
mod media_queries;
mod parsing;
mod properties;
mod rule_tree;
@ -37,16 +36,3 @@ mod str;
mod stylesheets;
mod stylist;
mod viewport;
mod writing_modes {
use style::logical_geometry::WritingMode;
use style::properties::INITIAL_SERVO_VALUES;
#[test]
fn initial_writing_mode_is_empty() {
assert_eq!(
WritingMode::new(INITIAL_SERVO_VALUES.get_inherited_box()),
WritingMode::empty(),
)
}
}

View file

@ -1,398 +0,0 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
use euclid::TypedScale;
use euclid::TypedSize2D;
use servo_arc::Arc;
use servo_url::ServoUrl;
use std::borrow::ToOwned;
use style::Atom;
use style::context::QuirksMode;
use style::media_queries::*;
use style::servo::media_queries::*;
use style::shared_lock::SharedRwLock;
use style::stylesheets::{AllRules, Stylesheet, StylesheetInDocument, Origin, CssRule};
use style::values::{CustomIdent, specified};
use style_traits::ToCss;
fn test_media_rule<F>(css: &str, callback: F)
where
F: Fn(&MediaList, &str),
{
let url = ServoUrl::parse("http://localhost").unwrap();
let css_str = css.to_owned();
let lock = SharedRwLock::new();
let media_list = Arc::new(lock.wrap(MediaList::empty()));
let stylesheet = Stylesheet::from_str(
css, url, Origin::Author, media_list, lock,
None, None, QuirksMode::NoQuirks, 0);
let dummy = Device::new(MediaType::screen(), TypedSize2D::new(200.0, 100.0), TypedScale::new(1.0));
let mut rule_count = 0;
let guard = stylesheet.shared_lock.read();
for rule in stylesheet.iter_rules::<AllRules>(&dummy, &guard) {
if let CssRule::Media(ref lock) = *rule {
rule_count += 1;
callback(&lock.read_with(&guard).media_queries.read_with(&guard), css);
}
}
assert!(rule_count > 0, css_str);
}
fn media_query_test(device: &Device, css: &str, expected_rule_count: usize) {
let url = ServoUrl::parse("http://localhost").unwrap();
let lock = SharedRwLock::new();
let media_list = Arc::new(lock.wrap(MediaList::empty()));
let ss = Stylesheet::from_str(
css, url, Origin::Author, media_list, lock,
None, None, QuirksMode::NoQuirks, 0);
let mut rule_count = 0;
ss.effective_style_rules(device, &ss.shared_lock.read(), |_| rule_count += 1);
assert!(rule_count == expected_rule_count, css.to_owned());
}
#[test]
fn test_mq_empty() {
test_media_rule("@media { }", |list, css| {
assert!(list.media_queries.len() == 0, css.to_owned());
});
}
#[test]
fn test_mq_screen() {
test_media_rule("@media screen { }", |list, css| {
assert!(list.media_queries.len() == 1, css.to_owned());
let q = &list.media_queries[0];
assert!(q.qualifier == None, css.to_owned());
assert!(q.media_type == MediaQueryType::Concrete(MediaType::screen()), css.to_owned());
assert!(q.expressions.len() == 0, css.to_owned());
});
test_media_rule("@media only screen { }", |list, css| {
assert!(list.media_queries.len() == 1, css.to_owned());
let q = &list.media_queries[0];
assert!(q.qualifier == Some(Qualifier::Only), css.to_owned());
assert!(q.media_type == MediaQueryType::Concrete(MediaType::screen()), css.to_owned());
assert!(q.expressions.len() == 0, css.to_owned());
});
test_media_rule("@media not screen { }", |list, css| {
assert!(list.media_queries.len() == 1, css.to_owned());
let q = &list.media_queries[0];
assert!(q.qualifier == Some(Qualifier::Not), css.to_owned());
assert!(q.media_type == MediaQueryType::Concrete(MediaType::screen()), css.to_owned());
assert!(q.expressions.len() == 0, css.to_owned());
});
}
#[test]
fn test_mq_print() {
test_media_rule("@media print { }", |list, css| {
assert!(list.media_queries.len() == 1, css.to_owned());
let q = &list.media_queries[0];
assert!(q.qualifier == None, css.to_owned());
assert!(q.media_type == MediaQueryType::Concrete(MediaType::print()), css.to_owned());
assert!(q.expressions.len() == 0, css.to_owned());
});
test_media_rule("@media only print { }", |list, css| {
assert!(list.media_queries.len() == 1, css.to_owned());
let q = &list.media_queries[0];
assert!(q.qualifier == Some(Qualifier::Only), css.to_owned());
assert!(q.media_type == MediaQueryType::Concrete(MediaType::print()), css.to_owned());
assert!(q.expressions.len() == 0, css.to_owned());
});
test_media_rule("@media not print { }", |list, css| {
assert!(list.media_queries.len() == 1, css.to_owned());
let q = &list.media_queries[0];
assert!(q.qualifier == Some(Qualifier::Not), css.to_owned());
assert!(q.media_type == MediaQueryType::Concrete(MediaType::print()), css.to_owned());
assert!(q.expressions.len() == 0, css.to_owned());
});
}
#[test]
fn test_mq_unknown() {
test_media_rule("@media fridge { }", |list, css| {
assert!(list.media_queries.len() == 1, css.to_owned());
let q = &list.media_queries[0];
assert!(q.qualifier == None, css.to_owned());
assert!(q.media_type == MediaQueryType::Concrete(
MediaType(CustomIdent(Atom::from("fridge")))), css.to_owned());
assert!(q.expressions.len() == 0, css.to_owned());
});
test_media_rule("@media only glass { }", |list, css| {
assert!(list.media_queries.len() == 1, css.to_owned());
let q = &list.media_queries[0];
assert!(q.qualifier == Some(Qualifier::Only), css.to_owned());
assert!(q.media_type == MediaQueryType::Concrete(
MediaType(CustomIdent(Atom::from("glass")))), css.to_owned());
assert!(q.expressions.len() == 0, css.to_owned());
});
test_media_rule("@media not wood { }", |list, css| {
assert!(list.media_queries.len() == 1, css.to_owned());
let q = &list.media_queries[0];
assert!(q.qualifier == Some(Qualifier::Not), css.to_owned());
assert!(q.media_type == MediaQueryType::Concrete(
MediaType(CustomIdent(Atom::from("wood")))), css.to_owned());
assert!(q.expressions.len() == 0, css.to_owned());
});
}
#[test]
fn test_mq_all() {
test_media_rule("@media all { }", |list, css| {
assert!(list.media_queries.len() == 1, css.to_owned());
let q = &list.media_queries[0];
assert!(q.qualifier == None, css.to_owned());
assert!(q.media_type == MediaQueryType::All, css.to_owned());
assert!(q.expressions.len() == 0, css.to_owned());
});
test_media_rule("@media only all { }", |list, css| {
assert!(list.media_queries.len() == 1, css.to_owned());
let q = &list.media_queries[0];
assert!(q.qualifier == Some(Qualifier::Only), css.to_owned());
assert!(q.media_type == MediaQueryType::All, css.to_owned());
assert!(q.expressions.len() == 0, css.to_owned());
});
test_media_rule("@media not all { }", |list, css| {
assert!(list.media_queries.len() == 1, css.to_owned());
let q = &list.media_queries[0];
assert!(q.qualifier == Some(Qualifier::Not), css.to_owned());
assert!(q.media_type == MediaQueryType::All, css.to_owned());
assert!(q.expressions.len() == 0, css.to_owned());
});
}
#[test]
fn test_mq_or() {
test_media_rule("@media screen, print { }", |list, css| {
assert!(list.media_queries.len() == 2, css.to_owned());
let q0 = &list.media_queries[0];
assert!(q0.qualifier == None, css.to_owned());
assert!(q0.media_type == MediaQueryType::Concrete(MediaType::screen()), css.to_owned());
assert!(q0.expressions.len() == 0, css.to_owned());
let q1 = &list.media_queries[1];
assert!(q1.qualifier == None, css.to_owned());
assert!(q1.media_type == MediaQueryType::Concrete(MediaType::print()), css.to_owned());
assert!(q1.expressions.len() == 0, css.to_owned());
});
}
#[test]
fn test_mq_default_expressions() {
test_media_rule("@media (min-width: 100px) { }", |list, css| {
assert!(list.media_queries.len() == 1, css.to_owned());
let q = &list.media_queries[0];
assert!(q.qualifier == None, css.to_owned());
assert!(q.media_type == MediaQueryType::All, css.to_owned());
assert!(q.expressions.len() == 1, css.to_owned());
match *q.expressions[0].kind_for_testing() {
ExpressionKind::Width(Range::Min(ref w)) => assert!(*w == specified::Length::from_px(100.)),
_ => panic!("wrong expression type"),
}
});
test_media_rule("@media (max-width: 43px) { }", |list, css| {
assert!(list.media_queries.len() == 1, css.to_owned());
let q = &list.media_queries[0];
assert!(q.qualifier == None, css.to_owned());
assert!(q.media_type == MediaQueryType::All, css.to_owned());
assert!(q.expressions.len() == 1, css.to_owned());
match *q.expressions[0].kind_for_testing() {
ExpressionKind::Width(Range::Max(ref w)) => assert!(*w == specified::Length::from_px(43.)),
_ => panic!("wrong expression type"),
}
});
}
#[test]
fn test_mq_expressions() {
test_media_rule("@media screen and (min-width: 100px) { }", |list, css| {
assert!(list.media_queries.len() == 1, css.to_owned());
let q = &list.media_queries[0];
assert!(q.qualifier == None, css.to_owned());
assert!(q.media_type == MediaQueryType::Concrete(MediaType::screen()), css.to_owned());
assert!(q.expressions.len() == 1, css.to_owned());
match *q.expressions[0].kind_for_testing() {
ExpressionKind::Width(Range::Min(ref w)) => assert!(*w == specified::Length::from_px(100.)),
_ => panic!("wrong expression type"),
}
});
test_media_rule("@media print and (max-width: 43px) { }", |list, css| {
assert!(list.media_queries.len() == 1, css.to_owned());
let q = &list.media_queries[0];
assert!(q.qualifier == None, css.to_owned());
assert!(q.media_type == MediaQueryType::Concrete(MediaType::print()), css.to_owned());
assert!(q.expressions.len() == 1, css.to_owned());
match *q.expressions[0].kind_for_testing() {
ExpressionKind::Width(Range::Max(ref w)) => assert!(*w == specified::Length::from_px(43.)),
_ => panic!("wrong expression type"),
}
});
test_media_rule("@media print and (width: 43px) { }", |list, css| {
assert!(list.media_queries.len() == 1, css.to_owned());
let q = &list.media_queries[0];
assert!(q.qualifier == None, css.to_owned());
assert!(q.media_type == MediaQueryType::Concrete(MediaType::print()), css.to_owned());
assert!(q.expressions.len() == 1, css.to_owned());
match *q.expressions[0].kind_for_testing() {
ExpressionKind::Width(Range::Eq(ref w)) => assert!(*w == specified::Length::from_px(43.)),
_ => panic!("wrong expression type"),
}
});
test_media_rule("@media fridge and (max-width: 52px) { }", |list, css| {
assert!(list.media_queries.len() == 1, css.to_owned());
let q = &list.media_queries[0];
assert!(q.qualifier == None, css.to_owned());
assert!(q.media_type == MediaQueryType::Concrete(
MediaType(CustomIdent(Atom::from("fridge")))), css.to_owned());
assert!(q.expressions.len() == 1, css.to_owned());
match *q.expressions[0].kind_for_testing() {
ExpressionKind::Width(Range::Max(ref w)) => assert!(*w == specified::Length::from_px(52.)),
_ => panic!("wrong expression type"),
}
});
}
#[test]
fn test_to_css() {
test_media_rule("@media print and (width: 43px) { }", |list, _| {
let q = &list.media_queries[0];
let dest = q.to_css_string();
assert_eq!(dest, "print and (width: 43px)");
});
}
#[test]
fn test_mq_multiple_expressions() {
test_media_rule("@media (min-width: 100px) and (max-width: 200px) { }", |list, css| {
assert!(list.media_queries.len() == 1, css.to_owned());
let q = &list.media_queries[0];
assert!(q.qualifier == None, css.to_owned());
assert!(q.media_type == MediaQueryType::All, css.to_owned());
assert!(q.expressions.len() == 2, css.to_owned());
match *q.expressions[0].kind_for_testing() {
ExpressionKind::Width(Range::Min(ref w)) => assert!(*w == specified::Length::from_px(100.)),
_ => panic!("wrong expression type"),
}
match *q.expressions[1].kind_for_testing() {
ExpressionKind::Width(Range::Max(ref w)) => assert!(*w == specified::Length::from_px(200.)),
_ => panic!("wrong expression type"),
}
});
test_media_rule("@media not screen and (min-width: 100px) and (max-width: 200px) { }", |list, css| {
assert!(list.media_queries.len() == 1, css.to_owned());
let q = &list.media_queries[0];
assert!(q.qualifier == Some(Qualifier::Not), css.to_owned());
assert!(q.media_type == MediaQueryType::Concrete(MediaType::screen()), css.to_owned());
assert!(q.expressions.len() == 2, css.to_owned());
match *q.expressions[0].kind_for_testing() {
ExpressionKind::Width(Range::Min(ref w)) => assert!(*w == specified::Length::from_px(100.)),
_ => panic!("wrong expression type"),
}
match *q.expressions[1].kind_for_testing() {
ExpressionKind::Width(Range::Max(ref w)) => assert!(*w == specified::Length::from_px(200.)),
_ => panic!("wrong expression type"),
}
});
}
#[test]
fn test_mq_malformed_expressions() {
fn check_malformed_expr(list: &MediaList, css: &str) {
assert!(!list.media_queries.is_empty(), css.to_owned());
for mq in &list.media_queries {
assert!(mq.qualifier == Some(Qualifier::Not), css.to_owned());
assert!(mq.media_type == MediaQueryType::All, css.to_owned());
assert!(mq.expressions.is_empty(), css.to_owned());
}
}
for rule in &[
"@media (min-width: 100blah) and (max-width: 200px) { }",
"@media screen and (height: 200px) { }",
"@media (min-width: 30em foo bar) {}",
"@media not {}",
"@media not (min-width: 300px) {}",
"@media , {}",
] {
test_media_rule(rule, check_malformed_expr);
}
}
#[test]
fn test_matching_simple() {
let device = Device::new(MediaType::screen(), TypedSize2D::new(200.0, 100.0), TypedScale::new(1.0));
media_query_test(&device, "@media not all { a { color: red; } }", 0);
media_query_test(&device, "@media not screen { a { color: red; } }", 0);
media_query_test(&device, "@media not print { a { color: red; } }", 1);
media_query_test(&device, "@media unknown { a { color: red; } }", 0);
media_query_test(&device, "@media not unknown { a { color: red; } }", 1);
media_query_test(&device, "@media { a { color: red; } }", 1);
media_query_test(&device, "@media screen { a { color: red; } }", 1);
media_query_test(&device, "@media print { a { color: red; } }", 0);
}
#[test]
fn test_matching_width() {
let device = Device::new(MediaType::screen(), TypedSize2D::new(200.0, 100.0), TypedScale::new(1.0));
media_query_test(&device, "@media { a { color: red; } }", 1);
media_query_test(&device, "@media (min-width: 50px) { a { color: red; } }", 1);
media_query_test(&device, "@media (min-width: 150px) { a { color: red; } }", 1);
media_query_test(&device, "@media (min-width: 300px) { a { color: red; } }", 0);
media_query_test(&device, "@media screen and (min-width: 50px) { a { color: red; } }", 1);
media_query_test(&device, "@media screen and (min-width: 150px) { a { color: red; } }", 1);
media_query_test(&device, "@media screen and (min-width: 300px) { a { color: red; } }", 0);
media_query_test(&device, "@media not screen and (min-width: 50px) { a { color: red; } }", 0);
media_query_test(&device, "@media not screen and (min-width: 150px) { a { color: red; } }", 0);
media_query_test(&device, "@media not screen and (min-width: 300px) { a { color: red; } }", 1);
media_query_test(&device, "@media (max-width: 50px) { a { color: red; } }", 0);
media_query_test(&device, "@media (max-width: 150px) { a { color: red; } }", 0);
media_query_test(&device, "@media (max-width: 300px) { a { color: red; } }", 1);
media_query_test(&device, "@media screen and (min-width: 50px) and (max-width: 100px) { a { color: red; } }", 0);
media_query_test(&device, "@media screen and (min-width: 250px) and (max-width: 300px) { a { color: red; } }", 0);
media_query_test(&device, "@media screen and (min-width: 50px) and (max-width: 250px) { a { color: red; } }", 1);
media_query_test(
&device, "@media not screen and (min-width: 50px) and (max-width: 100px) { a { color: red; } }", 1);
media_query_test(
&device, "@media not screen and (min-width: 250px) and (max-width: 300px) { a { color: red; } }", 1);
media_query_test(
&device, "@media not screen and (min-width: 50px) and (max-width: 250px) { a { color: red; } }", 0);
media_query_test(
&device, "@media not screen and (min-width: 3.1em) and (max-width: 6em) { a { color: red; } }", 1);
media_query_test(
&device, "@media not screen and (min-width: 16em) and (max-width: 19.75em) { a { color: red; } }", 1);
media_query_test(
&device, "@media not screen and (min-width: 3em) and (max-width: 250px) { a { color: red; } }", 0);
}
#[test]
fn test_matching_invalid() {
let device = Device::new(MediaType::screen(), TypedSize2D::new(200.0, 100.0), TypedScale::new(1.0));
media_query_test(&device, "@media fridge { a { color: red; } }", 0);
media_query_test(&device, "@media screen and (height: 100px) { a { color: red; } }", 0);
media_query_test(&device, "@media not print and (width: 100) { a { color: red; } }", 0);
}