diff --git a/components/atoms/static_atoms.txt b/components/atoms/static_atoms.txt index 56d949eff5d9..9a878b09017b 100644 --- a/components/atoms/static_atoms.txt +++ b/components/atoms/static_atoms.txt @@ -70,3 +70,6 @@ gattserverdisconnected onchange reftest-wait + +screen +print diff --git a/components/layout_thread/lib.rs b/components/layout_thread/lib.rs index d94b4ef6094d..26d1ef7bb412 100644 --- a/components/layout_thread/lib.rs +++ b/components/layout_thread/lib.rs @@ -481,7 +481,7 @@ impl LayoutThread { // The device pixel ratio is incorrect (it does not have the hidpi value), // but it will be set correctly when the initial reflow takes place. let device = Device::new( - MediaType::Screen, + MediaType::screen(), opts::get().initial_window_size.to_f32() * ScaleFactor::new(1.0), ScaleFactor::new(opts::get().device_pixels_per_px.unwrap_or(1.0))); @@ -1156,7 +1156,7 @@ impl LayoutThread { let document_shared_lock = document.style_shared_lock(); self.document_shared_lock = Some(document_shared_lock.clone()); let author_guard = document_shared_lock.read(); - let device = Device::new(MediaType::Screen, initial_viewport, device_pixel_ratio); + let device = Device::new(MediaType::screen(), initial_viewport, device_pixel_ratio); self.stylist.set_device(device, &author_guard, &data.document_stylesheets); self.viewport_size = diff --git a/components/script/dom/mediaquerylist.rs b/components/script/dom/mediaquerylist.rs index 1765ef88bce5..60cb875163e6 100644 --- a/components/script/dom/mediaquerylist.rs +++ b/components/script/dom/mediaquerylist.rs @@ -77,7 +77,7 @@ impl MediaQueryList { if let Some(window_size) = self.document.window().window_size() { let viewport_size = window_size.initial_viewport; let device_pixel_ratio = window_size.device_pixel_ratio; - let device = Device::new(MediaType::Screen, viewport_size, device_pixel_ratio); + let device = Device::new(MediaType::screen(), viewport_size, device_pixel_ratio); self.media_query_list.evaluate(&device, self.document.quirks_mode()) } else { false diff --git a/components/style/gecko/media_queries.rs b/components/style/gecko/media_queries.rs index a7ba27d8414b..3bd1a85f442a 100644 --- a/components/style/gecko/media_queries.rs +++ b/components/style/gecko/media_queries.rs @@ -18,6 +18,7 @@ use gecko_bindings::structs::{nsCSSKeyword, nsCSSProps_KTableEntry, nsCSSValue, use gecko_bindings::structs::{nsMediaExpression_Range, nsMediaFeature}; use gecko_bindings::structs::{nsMediaFeature_ValueType, nsMediaFeature_RangeType, nsMediaFeature_RequirementFlags}; use gecko_bindings::structs::{nsPresContext, RawGeckoPresContextOwned}; +use gecko_bindings::structs::nsIAtom; use media_queries::MediaType; use parser::ParserContext; use properties::{ComputedValues, StyleBuilder}; @@ -31,7 +32,7 @@ use string_cache::Atom; use style_traits::{CSSPixel, DevicePixel}; use style_traits::{ToCss, ParseError, StyleParseError}; use style_traits::viewport::ViewportConstraints; -use values::{CSSFloat, specified}; +use values::{CSSFloat, specified, CustomIdent}; use values::computed::{self, ToComputedValue}; /// The `Device` in Gecko wraps a pres context, has a default values computed, @@ -140,15 +141,16 @@ impl Device { /// Returns the current media type of the device. pub fn media_type(&self) -> MediaType { unsafe { - // FIXME(emilio): Gecko allows emulating random media with - // mIsEmulatingMedia / mMediaEmulated . Refactor both sides so that - // is supported (probably just making MediaType an Atom). - if self.pres_context().mMedium == atom!("screen").as_ptr() { - MediaType::Screen + // Gecko allows emulating random media with mIsEmulatingMedia and + // mMediaEmulated. + let context = self.pres_context(); + let medium_to_use = if context.mIsEmulatingMedia() != 0 { + context.mMediaEmulated.raw::() } else { - debug_assert!(self.pres_context().mMedium == atom!("print").as_ptr()); - MediaType::Print - } + context.mMedium + }; + + MediaType(CustomIdent(Atom::from(medium_to_use))) } } diff --git a/components/style/media_queries.rs b/components/style/media_queries.rs index b6bde057b3c1..9d3f113f0a98 100644 --- a/components/style/media_queries.rs +++ b/components/style/media_queries.rs @@ -13,7 +13,9 @@ use parser::ParserContext; use selectors::parser::SelectorParseError; use serialize_comma_separated_list; use std::fmt; +use str::string_as_ascii_lowercase; use style_traits::{ToCss, ParseError, StyleParseError}; +use values::CustomIdent; #[cfg(feature = "servo")] pub use servo::media_queries::{Device, Expression}; @@ -108,9 +110,7 @@ impl ToCss for MediaQuery { write!(dest, "all")?; } }, - MediaQueryType::Known(MediaType::Screen) => write!(dest, "screen")?, - MediaQueryType::Known(MediaType::Print) => write!(dest, "print")?, - MediaQueryType::Unknown(ref desc) => write!(dest, "{}", desc)?, + MediaQueryType::Concrete(MediaType(ref desc)) => desc.to_css(dest)?, } if self.expressions.is_empty() { @@ -137,35 +137,25 @@ impl ToCss for MediaQuery { pub enum MediaQueryType { /// A media type that matches every device. All, - /// A known media type, that we parse and understand. - Known(MediaType), - /// An unknown media type. - Unknown(Atom), + /// A specific media type. + Concrete(MediaType), } impl MediaQueryType { fn parse(ident: &str) -> Result { match_ignore_ascii_case! { ident, "all" => return Ok(MediaQueryType::All), - // From https://drafts.csswg.org/mediaqueries/#mq-syntax: - // - // The production does not include the keywords only, - // not, and, and or. - "not" | "or" | "and" | "only" => return Err(()), _ => (), }; - Ok(match MediaType::parse(ident) { - Some(media_type) => MediaQueryType::Known(media_type), - None => MediaQueryType::Unknown(Atom::from(ident)), - }) + // If parseable, accept this type as a concrete type. + MediaType::parse(ident).map(MediaQueryType::Concrete) } fn matches(&self, other: MediaType) -> bool { match *self { MediaQueryType::All => true, - MediaQueryType::Known(ref known_type) => *known_type == other, - MediaQueryType::Unknown(..) => false, + MediaQueryType::Concrete(ref known_type) => *known_type == other, } } } @@ -173,20 +163,30 @@ impl MediaQueryType { /// https://drafts.csswg.org/mediaqueries/#media-types #[derive(PartialEq, Eq, Clone, Debug)] #[cfg_attr(feature = "servo", derive(HeapSizeOf))] -pub enum MediaType { - /// The "screen" media type. - Screen, - /// The "print" media type. - Print, -} +pub struct MediaType(pub CustomIdent); impl MediaType { - fn parse(name: &str) -> Option { - Some(match_ignore_ascii_case! { name, - "screen" => MediaType::Screen, - "print" => MediaType::Print, - _ => return None - }) + /// The `screen` media type. + pub fn screen() -> Self { + MediaType(CustomIdent(atom!("screen"))) + } + + /// The `print` media type. + pub fn print() -> Self { + MediaType(CustomIdent(atom!("print"))) + } + + fn parse(name: &str) -> Result { + // From https://drafts.csswg.org/mediaqueries/#mq-syntax: + // + // The production does not include the keywords not, or, and, and only. + // + // Here we also perform the to-ascii-lowercase part of the serialization + // algorithm: https://drafts.csswg.org/cssom/#serializing-media-queries + match_ignore_ascii_case! { name, + "not" | "or" | "and" | "only" => Err(()), + _ => Ok(MediaType(CustomIdent(Atom::from(string_as_ascii_lowercase(name))))), + } } } impl MediaQuery { diff --git a/components/style/str.rs b/components/style/str.rs index 92febb408249..a76bf98caf5b 100644 --- a/components/style/str.rs +++ b/components/style/str.rs @@ -8,6 +8,7 @@ use num_traits::ToPrimitive; use std::ascii::AsciiExt; +use std::borrow::Cow; use std::convert::AsRef; use std::iter::{Filter, Peekable}; use std::str::Split; @@ -151,3 +152,13 @@ pub fn starts_with_ignore_ascii_case(string: &str, prefix: &str) -> bool { string.len() >= prefix.len() && string.as_bytes()[0..prefix.len()].eq_ignore_ascii_case(prefix.as_bytes()) } + +/// Returns an ascii lowercase version of a string, only allocating if needed. +pub fn string_as_ascii_lowercase<'a>(input: &'a str) -> Cow<'a, str> { + if input.bytes().any(|c| matches!(c, b'A'...b'Z')) { + input.to_ascii_lowercase().into() + } else { + // Already ascii lowercase. + Cow::Borrowed(input) + } +} diff --git a/tests/unit/style/media_queries.rs b/tests/unit/style/media_queries.rs index 0fa770f4ae68..d4a542d00dc1 100644 --- a/tests/unit/style/media_queries.rs +++ b/tests/unit/style/media_queries.rs @@ -15,7 +15,7 @@ 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::specified; +use style::values::{CustomIdent, specified}; use style_traits::ToCss; pub struct CSSErrorReporterTest; @@ -40,7 +40,7 @@ fn test_media_rule(css: &str, callback: F) let stylesheet = Stylesheet::from_str( css, url, Origin::Author, media_list, lock, None, &CSSErrorReporterTest, QuirksMode::NoQuirks, 0u64); - let dummy = Device::new(MediaType::Screen, TypedSize2D::new(200.0, 100.0), ScaleFactor::new(1.0)); + let dummy = Device::new(MediaType::screen(), TypedSize2D::new(200.0, 100.0), ScaleFactor::new(1.0)); let mut rule_count = 0; let guard = stylesheet.shared_lock.read(); for rule in stylesheet.iter_rules::(&dummy, &guard) { @@ -77,7 +77,7 @@ fn test_mq_screen() { 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::Known(MediaType::Screen), css.to_owned()); + assert!(q.media_type == MediaQueryType::Concrete(MediaType::screen()), css.to_owned()); assert!(q.expressions.len() == 0, css.to_owned()); }); @@ -85,7 +85,7 @@ fn test_mq_screen() { 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::Known(MediaType::Screen), css.to_owned()); + assert!(q.media_type == MediaQueryType::Concrete(MediaType::screen()), css.to_owned()); assert!(q.expressions.len() == 0, css.to_owned()); }); @@ -93,7 +93,7 @@ fn test_mq_screen() { 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::Known(MediaType::Screen), css.to_owned()); + assert!(q.media_type == MediaQueryType::Concrete(MediaType::screen()), css.to_owned()); assert!(q.expressions.len() == 0, css.to_owned()); }); } @@ -104,7 +104,7 @@ fn test_mq_print() { 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::Known(MediaType::Print), css.to_owned()); + assert!(q.media_type == MediaQueryType::Concrete(MediaType::print()), css.to_owned()); assert!(q.expressions.len() == 0, css.to_owned()); }); @@ -112,7 +112,7 @@ fn test_mq_print() { 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::Known(MediaType::Print), css.to_owned()); + assert!(q.media_type == MediaQueryType::Concrete(MediaType::print()), css.to_owned()); assert!(q.expressions.len() == 0, css.to_owned()); }); @@ -120,7 +120,7 @@ fn test_mq_print() { 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::Known(MediaType::Print), css.to_owned()); + assert!(q.media_type == MediaQueryType::Concrete(MediaType::print()), css.to_owned()); assert!(q.expressions.len() == 0, css.to_owned()); }); } @@ -131,7 +131,8 @@ fn test_mq_unknown() { 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::Unknown(Atom::from("fridge")), 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()); }); @@ -139,7 +140,8 @@ fn test_mq_unknown() { 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::Unknown(Atom::from("glass")), 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()); }); @@ -147,7 +149,8 @@ fn test_mq_unknown() { 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::Unknown(Atom::from("wood")), 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()); }); } @@ -185,12 +188,12 @@ fn test_mq_or() { 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::Known(MediaType::Screen), 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::Known(MediaType::Print), css.to_owned()); + assert!(q1.media_type == MediaQueryType::Concrete(MediaType::print()), css.to_owned()); assert!(q1.expressions.len() == 0, css.to_owned()); }); } @@ -228,7 +231,7 @@ fn test_mq_expressions() { 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::Known(MediaType::Screen), 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.)), @@ -240,7 +243,7 @@ fn test_mq_expressions() { 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::Known(MediaType::Print), 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.)), @@ -252,7 +255,7 @@ fn test_mq_expressions() { 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::Known(MediaType::Print), 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.)), @@ -264,7 +267,8 @@ fn test_mq_expressions() { 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::Unknown(Atom::from("fridge")), 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.)), @@ -305,7 +309,7 @@ fn test_mq_multiple_expressions() { 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::Known(MediaType::Screen), 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.)), @@ -343,7 +347,7 @@ fn test_mq_malformed_expressions() { #[test] fn test_matching_simple() { - let device = Device::new(MediaType::Screen, TypedSize2D::new(200.0, 100.0), ScaleFactor::new(1.0)); + let device = Device::new(MediaType::screen(), TypedSize2D::new(200.0, 100.0), ScaleFactor::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); @@ -359,7 +363,7 @@ fn test_matching_simple() { #[test] fn test_matching_width() { - let device = Device::new(MediaType::Screen, TypedSize2D::new(200.0, 100.0), ScaleFactor::new(1.0)); + let device = Device::new(MediaType::screen(), TypedSize2D::new(200.0, 100.0), ScaleFactor::new(1.0)); media_query_test(&device, "@media { a { color: red; } }", 1); @@ -400,7 +404,7 @@ fn test_matching_width() { #[test] fn test_matching_invalid() { - let device = Device::new(MediaType::Screen, TypedSize2D::new(200.0, 100.0), ScaleFactor::new(1.0)); + let device = Device::new(MediaType::screen(), TypedSize2D::new(200.0, 100.0), ScaleFactor::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); diff --git a/tests/unit/style/stylist.rs b/tests/unit/style/stylist.rs index bec4c136b777..5f49a7223d74 100644 --- a/tests/unit/style/stylist.rs +++ b/tests/unit/style/stylist.rs @@ -223,7 +223,7 @@ fn test_insert() { } fn mock_stylist() -> Stylist { - let device = Device::new(MediaType::Screen, TypedSize2D::new(0f32, 0f32), ScaleFactor::new(1.0)); + let device = Device::new(MediaType::screen(), TypedSize2D::new(0f32, 0f32), ScaleFactor::new(1.0)); Stylist::new(device, QuirksMode::NoQuirks) } @@ -231,9 +231,9 @@ fn mock_stylist() -> Stylist { fn test_stylist_device_accessors() { thread_state::initialize(thread_state::LAYOUT); let stylist = mock_stylist(); - assert_eq!(stylist.device().media_type(), MediaType::Screen); + assert_eq!(stylist.device().media_type(), MediaType::screen()); let mut stylist_mut = mock_stylist(); - assert_eq!(stylist_mut.device_mut().media_type(), MediaType::Screen); + assert_eq!(stylist_mut.device_mut().media_type(), MediaType::screen()); } #[test] diff --git a/tests/unit/style/viewport.rs b/tests/unit/style/viewport.rs index 3affe7434c48..35e0ad13e5ef 100644 --- a/tests/unit/style/viewport.rs +++ b/tests/unit/style/viewport.rs @@ -97,7 +97,7 @@ macro_rules! viewport_length { #[test] fn empty_viewport_rule() { - let device = Device::new(MediaType::Screen, TypedSize2D::new(800., 600.), ScaleFactor::new(1.0)); + let device = Device::new(MediaType::screen(), TypedSize2D::new(800., 600.), ScaleFactor::new(1.0)); test_viewport_rule("@viewport {}", &device, |declarations, css| { println!("{}", css); @@ -120,7 +120,7 @@ macro_rules! assert_decl_eq { #[test] fn simple_viewport_rules() { - let device = Device::new(MediaType::Screen, TypedSize2D::new(800., 600.), ScaleFactor::new(1.0)); + let device = Device::new(MediaType::screen(), TypedSize2D::new(800., 600.), ScaleFactor::new(1.0)); test_viewport_rule("@viewport { width: auto; height: auto;\ zoom: auto; min-zoom: 0; max-zoom: 200%;\ @@ -192,7 +192,7 @@ fn simple_meta_viewport_contents() { #[test] fn cascading_within_viewport_rule() { - let device = Device::new(MediaType::Screen, TypedSize2D::new(800., 600.), ScaleFactor::new(1.0)); + let device = Device::new(MediaType::screen(), TypedSize2D::new(800., 600.), ScaleFactor::new(1.0)); // normal order of appearance test_viewport_rule("@viewport { min-width: 200px; min-width: auto; }", @@ -258,7 +258,7 @@ fn cascading_within_viewport_rule() { #[test] fn multiple_stylesheets_cascading() { PREFS.set("layout.viewport.enabled", PrefValue::Boolean(true)); - let device = Device::new(MediaType::Screen, TypedSize2D::new(800., 600.), ScaleFactor::new(1.0)); + let device = Device::new(MediaType::screen(), TypedSize2D::new(800., 600.), ScaleFactor::new(1.0)); let error_reporter = CSSErrorReporterTest; let shared_lock = SharedRwLock::new(); let stylesheets = vec![ @@ -314,7 +314,7 @@ fn constrain_viewport() { } let initial_viewport = TypedSize2D::new(800., 600.); - let device = Device::new(MediaType::Screen, initial_viewport, ScaleFactor::new(1.0)); + let device = Device::new(MediaType::screen(), initial_viewport, ScaleFactor::new(1.0)); let mut input = ParserInput::new(""); assert_eq!(ViewportConstraints::maybe_new(&device, from_css!(input), QuirksMode::NoQuirks), None); @@ -363,7 +363,7 @@ fn constrain_viewport() { })); let initial_viewport = TypedSize2D::new(200., 150.); - let device = Device::new(MediaType::Screen, initial_viewport, ScaleFactor::new(1.0)); + let device = Device::new(MediaType::screen(), initial_viewport, ScaleFactor::new(1.0)); let mut input = ParserInput::new("width: 320px auto"); assert_eq!(ViewportConstraints::maybe_new(&device, from_css!(input), QuirksMode::NoQuirks), Some(ViewportConstraints {