diff --git a/components/layout/display_list/background.rs b/components/layout/display_list/background.rs index 145c069cb85..4036c2efd2c 100644 --- a/components/layout/display_list/background.rs +++ b/components/layout/display_list/background.rs @@ -20,6 +20,7 @@ use style::computed_values::background_attachment::single_value::T as Background use style::computed_values::background_clip::single_value::T as BackgroundClip; use style::computed_values::background_origin::single_value::T as BackgroundOrigin; use style::computed_values::border_image_outset::T as BorderImageOutset; +use style::properties::ComputedValues; use style::properties::style_structs::{self, Background}; use style::values::Either; use style::values::computed::{Angle, GradientItem, BackgroundSize as ComputedBackgroundSize}; @@ -429,7 +430,11 @@ fn convert_ellipse_size_keyword( } } -fn convert_gradient_stops(gradient_items: &[GradientItem], total_length: Au) -> Vec { +fn convert_gradient_stops( + style: &ComputedValues, + gradient_items: &[GradientItem], + total_length: Au, +) -> Vec { // Determine the position of each stop per CSS-IMAGES § 3.4. // Only keep the color stops, discard the color interpolation hints. @@ -497,8 +502,8 @@ fn convert_gradient_stops(gradient_items: &[GradientItem], total_length: Au) -> .unwrap(); let end_offset = position_to_offset(end_stop.position.unwrap(), total_length); stop_run = Some(StopRun { - start_offset: start_offset, - end_offset: end_offset, + start_offset, + end_offset, start_index: i - 1, stop_count: end_index, }) @@ -518,7 +523,7 @@ fn convert_gradient_stops(gradient_items: &[GradientItem], total_length: Au) -> assert!(offset.is_finite()); stops.push(GradientStop { offset: offset, - color: stop.color.to_layout(), + color: style.resolve_color(stop.color).to_layout(), }) } stops @@ -533,6 +538,7 @@ fn as_gradient_extend_mode(repeating: bool) -> ExtendMode { } pub fn convert_linear_gradient( + style: &ComputedValues, size: Size2D, stops: &[GradientItem], direction: LineDirection, @@ -581,19 +587,20 @@ pub fn convert_linear_gradient( // This is the length of the gradient line. let length = Au::from_f32_px((delta.x.to_f32_px() * 2.0).hypot(delta.y.to_f32_px() * 2.0)); - let stops = convert_gradient_stops(stops, length); + let stops = convert_gradient_stops(style, stops, length); let center = Point2D::new(size.width / 2, size.height / 2); Gradient { start_point: (center - delta).to_layout(), end_point: (center + delta).to_layout(), - stops: stops, + stops, extend_mode: as_gradient_extend_mode(repeating), } } pub fn convert_radial_gradient( + style: &ComputedValues, size: Size2D, stops: &[GradientItem], shape: EndingShape, @@ -620,7 +627,7 @@ pub fn convert_radial_gradient( }, }; - let stops = convert_gradient_stops(stops, radius.width); + let stops = convert_gradient_stops(style, stops, radius.width); RadialGradient { center: center.to_layout(), diff --git a/components/layout/display_list/builder.rs b/components/layout/display_list/builder.rs index b5c252cc64e..16a1fc85770 100644 --- a/components/layout/display_list/builder.rs +++ b/components/layout/display_list/builder.rs @@ -1116,6 +1116,7 @@ impl FragmentDisplayListBuilding for Fragment { let display_item = match gradient.kind { GradientKind::Linear(angle_or_corner) => { let gradient = convert_linear_gradient( + style, placement.tile_size, &gradient.items[..], angle_or_corner, @@ -1130,6 +1131,7 @@ impl FragmentDisplayListBuilding for Fragment { }, GradientKind::Radial(shape, center, _angle) => { let gradient = convert_radial_gradient( + style, placement.tile_size, &gradient.items[..], shape, @@ -1298,6 +1300,7 @@ impl FragmentDisplayListBuilding for Fragment { Either::Second(Image::Gradient(ref gradient)) => Some(match gradient.kind { GradientKind::Linear(angle_or_corner) => BorderDetails::Gradient(GradientBorder { gradient: convert_linear_gradient( + style, bounds.size, &gradient.items[..], angle_or_corner, @@ -1308,6 +1311,7 @@ impl FragmentDisplayListBuilding for Fragment { GradientKind::Radial(shape, center, _angle) => { BorderDetails::RadialGradient(RadialGradientBorder { gradient: convert_radial_gradient( + style, bounds.size, &gradient.items[..], shape, diff --git a/components/script/dom/cssmediarule.rs b/components/script/dom/cssmediarule.rs index 11f05d125a8..0ecdf71a314 100644 --- a/components/script/dom/cssmediarule.rs +++ b/components/script/dom/cssmediarule.rs @@ -16,7 +16,7 @@ use dom::medialist::MediaList; use dom::window::Window; use dom_struct::dom_struct; use servo_arc::Arc; -use style::media_queries::parse_media_query_list; +use style::media_queries::MediaList as StyleMediaList; use style::parser::ParserContext; use style::shared_lock::{Locked, ToCssWithGuard}; use style::stylesheets::{CssRuleType, MediaRule}; @@ -79,8 +79,11 @@ impl CSSMediaRule { ParsingMode::DEFAULT, quirks_mode); - let new_medialist = parse_media_query_list(&context, &mut input, - window.css_error_reporter()); + let new_medialist = StyleMediaList::parse( + &context, + &mut input, + window.css_error_reporter(), + ); let mut guard = self.cssconditionrule.shared_lock().write(); // Clone an Arc because we can’t borrow `guard` twice at the same time. diff --git a/components/script/dom/htmllinkelement.rs b/components/script/dom/htmllinkelement.rs index f55a795ba0d..ab84906d223 100644 --- a/components/script/dom/htmllinkelement.rs +++ b/components/script/dom/htmllinkelement.rs @@ -29,7 +29,7 @@ use std::borrow::ToOwned; use std::cell::Cell; use std::default::Default; use style::attr::AttrValue; -use style::media_queries::parse_media_query_list; +use style::media_queries::MediaList; use style::parser::ParserContext as CssParserContext; use style::str::HTML_SPACE_CHARACTERS; use style::stylesheets::{CssRuleType, Stylesheet}; @@ -277,12 +277,21 @@ impl HTMLLinkElement { let mut input = ParserInput::new(&mq_str); let mut css_parser = CssParser::new(&mut input); let doc_url = document.url(); - let context = CssParserContext::new_for_cssom(&doc_url, Some(CssRuleType::Media), - ParsingMode::DEFAULT, - document.quirks_mode()); + // FIXME(emilio): This looks somewhat fishy, since we use the context + // only to parse the media query list, CssRuleType::Media doesn't make + // much sense. + let context = CssParserContext::new_for_cssom( + &doc_url, + Some(CssRuleType::Media), + ParsingMode::DEFAULT, + document.quirks_mode(), + ); let window = document.window(); - let media = parse_media_query_list(&context, &mut css_parser, - window.css_error_reporter()); + let media = MediaList::parse( + &context, + &mut css_parser, + window.css_error_reporter(), + ); let im_attribute = element.get_attribute(&ns!(), &local_name!("integrity")); let integrity_val = im_attribute.r().map(|a| a.value()); diff --git a/components/script/dom/htmlstyleelement.rs b/components/script/dom/htmlstyleelement.rs index 6a6bc416d2d..6778af92218 100644 --- a/components/script/dom/htmlstyleelement.rs +++ b/components/script/dom/htmlstyleelement.rs @@ -21,7 +21,7 @@ use html5ever::{LocalName, Prefix}; use net_traits::ReferrerPolicy; use servo_arc::Arc; use std::cell::Cell; -use style::media_queries::parse_media_query_list; +use style::media_queries::MediaList; use style::parser::ParserContext as CssParserContext; use style::stylesheets::{CssRuleType, Stylesheet, Origin}; use style_traits::ParsingMode; @@ -91,9 +91,11 @@ impl HTMLStyleElement { let shared_lock = node.owner_doc().style_shared_lock().clone(); let mut input = ParserInput::new(&mq_str); let css_error_reporter = window.css_error_reporter(); - let mq = Arc::new(shared_lock.wrap(parse_media_query_list(&context, - &mut CssParser::new(&mut input), - css_error_reporter))); + let mq = Arc::new(shared_lock.wrap(MediaList::parse( + &context, + &mut CssParser::new(&mut input), + css_error_reporter), + )); let loader = StylesheetLoader::for_element(self.upcast()); let sheet = Stylesheet::from_str(&data, window.get_url(), Origin::Author, mq, diff --git a/components/script/dom/medialist.rs b/components/script/dom/medialist.rs index 449c3c19ea6..2f5b33c1016 100644 --- a/components/script/dom/medialist.rs +++ b/components/script/dom/medialist.rs @@ -13,8 +13,8 @@ use dom::cssstylesheet::CSSStyleSheet; use dom::window::Window; use dom_struct::dom_struct; use servo_arc::Arc; -use style::media_queries::{MediaQuery, parse_media_query_list}; use style::media_queries::MediaList as StyleMediaList; +use style::media_queries::MediaQuery; use style::parser::ParserContext; use style::shared_lock::{SharedRwLock, Locked}; use style::stylesheets::CssRuleType; @@ -80,8 +80,11 @@ impl MediaListMethods for MediaList { let context = ParserContext::new_for_cssom(&url, Some(CssRuleType::Media), ParsingMode::DEFAULT, quirks_mode); - *media_queries = parse_media_query_list(&context, &mut parser, - window.css_error_reporter()); + *media_queries = StyleMediaList::parse( + &context, + &mut parser, + window.css_error_reporter(), + ); } // https://drafts.csswg.org/cssom/#dom-medialist-length diff --git a/components/script/dom/window.rs b/components/script/dom/window.rs index 2db7609bec4..0f259dd2ea7 100644 --- a/components/script/dom/window.rs +++ b/components/script/dom/window.rs @@ -1020,8 +1020,11 @@ impl WindowMethods for Window { let context = CssParserContext::new_for_cssom(&url, Some(CssRuleType::Media), ParsingMode::DEFAULT, quirks_mode); - let media_query_list = media_queries::parse_media_query_list(&context, &mut parser, - self.css_error_reporter()); + let media_query_list = media_queries::MediaList::parse( + &context, + &mut parser, + self.css_error_reporter(), + ); let document = self.Document(); let mql = MediaQueryList::new(&document, media_query_list); self.media_query_lists.push(&*mql); diff --git a/components/style/gecko/arc_types.rs b/components/style/gecko/arc_types.rs index 3ef3d07da13..7351cbfeac4 100644 --- a/components/style/gecko/arc_types.rs +++ b/components/style/gecko/arc_types.rs @@ -9,12 +9,12 @@ #![allow(non_snake_case, missing_docs)] use gecko_bindings::bindings::RawServoCounterStyleRule; -use gecko_bindings::bindings::RawServoDocumentRule; use gecko_bindings::bindings::RawServoFontFeatureValuesRule; use gecko_bindings::bindings::RawServoImportRule; use gecko_bindings::bindings::RawServoKeyframe; use gecko_bindings::bindings::RawServoKeyframesRule; use gecko_bindings::bindings::RawServoMediaRule; +use gecko_bindings::bindings::RawServoMozDocumentRule; use gecko_bindings::bindings::RawServoNamespaceRule; use gecko_bindings::bindings::RawServoPageRule; use gecko_bindings::bindings::RawServoRuleNode; @@ -98,7 +98,7 @@ impl_arc_ffi!(Locked => RawServoPageRule impl_arc_ffi!(Locked => RawServoSupportsRule [Servo_SupportsRule_AddRef, Servo_SupportsRule_Release]); -impl_arc_ffi!(Locked => RawServoDocumentRule +impl_arc_ffi!(Locked => RawServoMozDocumentRule [Servo_DocumentRule_AddRef, Servo_DocumentRule_Release]); impl_arc_ffi!(Locked => RawServoFontFeatureValuesRule diff --git a/components/style/gecko/conversions.rs b/components/style/gecko/conversions.rs index bbc2776daa9..14fee693a03 100644 --- a/components/style/gecko/conversions.rs +++ b/components/style/gecko/conversions.rs @@ -9,7 +9,7 @@ #![allow(unsafe_code)] use app_units::Au; -use gecko::values::{convert_rgba_to_nscolor, GeckoStyleCoordConvertible}; +use gecko::values::GeckoStyleCoordConvertible; use gecko_bindings::bindings; use gecko_bindings::structs::{self, nsCSSUnit, nsStyleCoord_CalcValue}; use gecko_bindings::structs::{nsresult, SheetType, nsStyleImage}; @@ -358,7 +358,7 @@ impl nsStyleImage { match *item { GradientItem::ColorStop(ref stop) => { - gecko_stop.mColor = convert_rgba_to_nscolor(&stop.color); + gecko_stop.mColor = stop.color.into(); gecko_stop.mIsInterpolationHint = false; coord.set(stop.position); }, @@ -433,7 +433,6 @@ impl nsStyleImage { } unsafe fn get_gradient(self: &nsStyleImage) -> Box { - use gecko::values::convert_nscolor_to_rgba; use self::structs::NS_STYLE_GRADIENT_SIZE_CLOSEST_CORNER as CLOSEST_CORNER; use self::structs::NS_STYLE_GRADIENT_SIZE_CLOSEST_SIDE as CLOSEST_SIDE; use self::structs::NS_STYLE_GRADIENT_SIZE_FARTHEST_CORNER as FARTHEST_CORNER; @@ -601,7 +600,7 @@ impl nsStyleImage { ) } else { GradientItem::ColorStop(ColorStop { - color: convert_nscolor_to_rgba(stop.mColor), + color: stop.mColor.into(), position: LengthOrPercentage::from_gecko_style_coord(&stop.mLocation), }) } diff --git a/components/style/gecko/media_queries.rs b/components/style/gecko/media_queries.rs index 554606bb9db..1c34762f286 100644 --- a/components/style/gecko/media_queries.rs +++ b/components/style/gecko/media_queries.rs @@ -23,7 +23,7 @@ use properties::ComputedValues; use servo_arc::Arc; use std::fmt::{self, Write}; use std::sync::atomic::{AtomicBool, AtomicIsize, AtomicUsize, Ordering}; -use str::starts_with_ignore_ascii_case; +use str::{starts_with_ignore_ascii_case, string_as_ascii_lowercase}; use string_cache::Atom; use style_traits::{CSSPixel, CssWriter, DevicePixel}; use style_traits::{ParseError, StyleParseErrorKind, ToCss}; @@ -118,8 +118,7 @@ impl Device { /// Set the font size of the root element (for rem) pub fn set_root_font_size(&self, size: Au) { - self.root_font_size - .store(size.0 as isize, Ordering::Relaxed) + self.root_font_size.store(size.0 as isize, Ordering::Relaxed) } /// Sets the body text color for the "inherit color from body" quirk. @@ -229,7 +228,7 @@ impl Device { } /// The kind of matching that should be performed on a media feature value. -#[derive(Clone, Copy, Debug, Eq, PartialEq)] +#[derive(Clone, Copy, Debug, Eq, MallocSizeOf, PartialEq)] pub enum Range { /// At least the specified value. Min, @@ -241,7 +240,7 @@ pub enum Range { /// A expression for gecko contains a reference to the media feature, the value /// the media query contained, and the range to evaluate. -#[derive(Clone, Debug)] +#[derive(Clone, Debug, MallocSizeOf)] pub struct Expression { feature: &'static nsMediaFeature, value: Option, @@ -294,7 +293,7 @@ impl PartialEq for Expression { /// If the first, this would need to store the relevant values. /// /// See: https://github.com/w3c/csswg-drafts/issues/1968 -#[derive(Clone, Debug, PartialEq)] +#[derive(Clone, Debug, MallocSizeOf, PartialEq)] pub enum MediaExpressionValue { /// A length. Length(Length), @@ -596,7 +595,7 @@ impl Expression { Range::Equal }; - let atom = Atom::from(feature_name); + let atom = Atom::from(string_as_ascii_lowercase(feature_name)); match find_feature(|f| atom.as_ptr() == unsafe { *f.mName as *mut _ }) { Some(f) => Ok((f, range)), None => Err(()), diff --git a/components/style/gecko/wrapper.rs b/components/style/gecko/wrapper.rs index 5475be1e3fc..0ce4a45d175 100644 --- a/components/style/gecko/wrapper.rs +++ b/components/style/gecko/wrapper.rs @@ -178,11 +178,13 @@ impl<'lr> TShadowRoot for GeckoShadowRoot<'lr> { as *const bindings::RawServoAuthorStyles) }; + let author_styles = AuthorStyles::::from_ffi(author_styles); debug_assert!( author_styles.quirks_mode == self.as_node().owner_doc().quirks_mode() || - author_styles.stylesheets.is_empty() + author_styles.stylesheets.is_empty() || + author_styles.stylesheets.dirty() ); &author_styles.data diff --git a/components/style/media_queries.rs b/components/style/media_queries.rs deleted file mode 100644 index 5742e794c3f..00000000000 --- a/components/style/media_queries.rs +++ /dev/null @@ -1,353 +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/. */ - -//! [Media queries][mq]. -//! -//! [mq]: https://drafts.csswg.org/mediaqueries/ - -use Atom; -use context::QuirksMode; -use cssparser::{Delimiter, Parser}; -use cssparser::{ParserInput, Token}; -use error_reporting::{ContextualParseError, ParseErrorReporter}; -use parser::{ParserContext, ParserErrorContext}; -use selectors::parser::SelectorParseErrorKind; -use std::fmt::{self, Write}; -use str::string_as_ascii_lowercase; -use style_traits::{CssWriter, ParseError, StyleParseErrorKind, ToCss}; -use values::CustomIdent; - -#[cfg(feature = "servo")] -pub use servo::media_queries::{Device, Expression}; -#[cfg(feature = "gecko")] -pub use gecko::media_queries::{Device, Expression}; - -/// A type that encapsulates a media query list. -#[cfg_attr(feature = "servo", derive(MallocSizeOf))] -#[css(comma)] -#[derive(Clone, Debug, ToCss)] -pub struct MediaList { - /// The list of media queries. - #[css(iterable)] - pub media_queries: Vec, -} - -impl MediaList { - /// Create an empty MediaList. - pub fn empty() -> Self { - MediaList { - media_queries: vec![], - } - } -} - -/// -#[cfg_attr(feature = "servo", derive(MallocSizeOf))] -#[derive(Clone, Copy, Debug, Eq, PartialEq, ToCss)] -pub enum Qualifier { - /// Hide a media query from legacy UAs: - /// - Only, - /// Negate a media query: - /// - Not, -} - -/// A [media query][mq]. -/// -/// [mq]: https://drafts.csswg.org/mediaqueries/ -#[derive(Clone, Debug, PartialEq)] -#[cfg_attr(feature = "servo", derive(MallocSizeOf))] -pub struct MediaQuery { - /// The qualifier for this query. - pub qualifier: Option, - /// 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, -} - -impl MediaQuery { - /// Return a media query that never matches, used for when we fail to parse - /// a given media query. - fn never_matching() -> Self { - Self { - qualifier: Some(Qualifier::Not), - media_type: MediaQueryType::All, - expressions: vec![], - } - } -} - -impl ToCss for MediaQuery { - fn to_css(&self, dest: &mut CssWriter) -> fmt::Result - where - W: Write, - { - if let Some(qual) = self.qualifier { - qual.to_css(dest)?; - dest.write_char(' ')?; - } - - match self.media_type { - MediaQueryType::All => { - // We need to print "all" if there's a qualifier, or there's - // just an empty list of expressions. - // - // 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() { - dest.write_str("all")?; - } - }, - MediaQueryType::Concrete(MediaType(ref desc)) => desc.to_css(dest)?, - } - - if self.expressions.is_empty() { - 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(()) - } -} - -/// -#[derive(Clone, Debug, Eq, PartialEq)] -#[cfg_attr(feature = "servo", derive(MallocSizeOf))] -pub enum MediaQueryType { - /// A media type that matches every device. - All, - /// A specific media type. - Concrete(MediaType), -} - -impl MediaQueryType { - fn parse(ident: &str) -> Result { - match_ignore_ascii_case! { ident, - "all" => return Ok(MediaQueryType::All), - _ => (), - }; - - // 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::Concrete(ref known_type) => *known_type == other, - } - } -} - -/// -#[derive(Clone, Debug, Eq, PartialEq)] -#[cfg_attr(feature = "servo", derive(MallocSizeOf))] -pub struct MediaType(pub CustomIdent); - -impl MediaType { - /// 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 { - /// Parse a media query given css input. - /// - /// Returns an error if any of the expressions is unknown. - pub fn parse<'i, 't>( - context: &ParserContext, - input: &mut Parser<'i, 't>, - ) -> Result> { - let mut expressions = vec![]; - - let qualifier = if input - .try(|input| input.expect_ident_matching("only")) - .is_ok() - { - Some(Qualifier::Only) - } else if input - .try(|input| input.expect_ident_matching("not")) - .is_ok() - { - Some(Qualifier::Not) - } else { - None - }; - - 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)); - } - - // Without a media type, require at least one expression. - expressions.push(Expression::parse(context, input)?); - - MediaQueryType::All - }, - }; - - // 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)?) - } - } -} - -/// Parse a media query list from CSS. -/// -/// Always returns a media query list. If any invalid media query is found, the -/// media query list is only filled with the equivalent of "not all", see: -/// -/// -pub fn parse_media_query_list( - context: &ParserContext, - input: &mut Parser, - error_reporter: &R, -) -> MediaList -where - R: ParseErrorReporter, -{ - if input.is_exhausted() { - return MediaList::empty(); - } - - let mut media_queries = vec![]; - loop { - let start_position = input.position(); - match input.parse_until_before(Delimiter::Comma, |i| MediaQuery::parse(context, i)) { - Ok(mq) => { - media_queries.push(mq); - }, - Err(err) => { - media_queries.push(MediaQuery::never_matching()); - let location = err.location; - let error = - ContextualParseError::InvalidMediaRule(input.slice_from(start_position), err); - let error_context = ParserErrorContext { error_reporter }; - context.log_css_error(&error_context, location, error); - }, - } - - match input.next() { - Ok(&Token::Comma) => {}, - Ok(_) => unreachable!(), - Err(_) => break, - } - } - - MediaList { - media_queries: media_queries, - } -} - -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) - // 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) - let query_match = media_match && - mq.expressions - .iter() - .all(|expression| expression.matches(&device, quirks_mode)); - - // Apply the logical NOT qualifier to the result - match mq.qualifier { - Some(Qualifier::Not) => !query_match, - _ => query_match, - } - }) - } - - /// Whether this `MediaList` contains no media queries. - pub fn is_empty(&self) -> bool { - self.media_queries.is_empty() - } - - /// Append a new media query item to the media list. - /// - /// - /// Returns true if added, false if fail to parse the medium string. - pub fn append_medium(&mut self, context: &ParserContext, new_medium: &str) -> bool { - let mut input = ParserInput::new(new_medium); - let mut parser = Parser::new(&mut input); - let new_query = match MediaQuery::parse(&context, &mut parser) { - Ok(query) => query, - Err(_) => { - return false; - }, - }; - // This algorithm doesn't actually matches the current spec, - // but it matches the behavior of Gecko and Edge. - // See https://github.com/w3c/csswg-drafts/issues/697 - self.media_queries.retain(|query| query != &new_query); - self.media_queries.push(new_query); - true - } - - /// Delete a media query from the media list. - /// - /// - /// Returns true if found and deleted, false otherwise. - pub fn delete_medium(&mut self, context: &ParserContext, old_medium: &str) -> bool { - let mut input = ParserInput::new(old_medium); - let mut parser = Parser::new(&mut input); - let old_query = match MediaQuery::parse(context, &mut parser) { - Ok(query) => query, - Err(_) => { - return false; - }, - }; - let old_len = self.media_queries.len(); - self.media_queries.retain(|query| query != &old_query); - old_len != self.media_queries.len() - } -} diff --git a/components/style/media_queries/media_list.rs b/components/style/media_queries/media_list.rs new file mode 100644 index 00000000000..5243174e07f --- /dev/null +++ b/components/style/media_queries/media_list.rs @@ -0,0 +1,143 @@ +/* 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 list: +//! +//! https://drafts.csswg.org/mediaqueries/#typedef-media-query-list + +use context::QuirksMode; +use cssparser::{Delimiter, Parser}; +use cssparser::{ParserInput, Token}; +use error_reporting::{ContextualParseError, ParseErrorReporter}; +use parser::{ParserContext, ParserErrorContext}; +use super::{Device, MediaQuery, Qualifier}; + +/// A type that encapsulates a media query list. +#[css(comma)] +#[derive(Clone, Debug, MallocSizeOf, ToCss)] +pub struct MediaList { + /// The list of media queries. + #[css(iterable)] + pub media_queries: Vec, +} + +impl MediaList { + /// Parse a media query list from CSS. + /// + /// Always returns a media query list. If any invalid media query is + /// found, the media query list is only filled with the equivalent of + /// "not all", see: + /// + /// + pub fn parse( + context: &ParserContext, + input: &mut Parser, + error_reporter: &R, + ) -> MediaList + where + R: ParseErrorReporter, + { + if input.is_exhausted() { + return Self::empty(); + } + + let mut media_queries = vec![]; + loop { + let start_position = input.position(); + match input.parse_until_before(Delimiter::Comma, |i| MediaQuery::parse(context, i)) { + Ok(mq) => { + media_queries.push(mq); + }, + Err(err) => { + media_queries.push(MediaQuery::never_matching()); + let location = err.location; + let error = + ContextualParseError::InvalidMediaRule(input.slice_from(start_position), err); + let error_context = ParserErrorContext { error_reporter }; + context.log_css_error(&error_context, location, error); + }, + } + + match input.next() { + Ok(&Token::Comma) => {}, + Ok(_) => unreachable!(), + Err(_) => break, + } + } + + MediaList { media_queries } + } + + /// Create an empty MediaList. + pub fn empty() -> Self { + MediaList { + media_queries: vec![], + } + } + + /// 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) + // 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) + let query_match = media_match && + mq.expressions + .iter() + .all(|expression| expression.matches(&device, quirks_mode)); + + // Apply the logical NOT qualifier to the result + match mq.qualifier { + Some(Qualifier::Not) => !query_match, + _ => query_match, + } + }) + } + + /// Whether this `MediaList` contains no media queries. + pub fn is_empty(&self) -> bool { + self.media_queries.is_empty() + } + + /// Append a new media query item to the media list. + /// + /// + /// Returns true if added, false if fail to parse the medium string. + pub fn append_medium(&mut self, context: &ParserContext, new_medium: &str) -> bool { + let mut input = ParserInput::new(new_medium); + let mut parser = Parser::new(&mut input); + let new_query = match MediaQuery::parse(&context, &mut parser) { + Ok(query) => query, + Err(_) => { + return false; + }, + }; + // This algorithm doesn't actually matches the current spec, + // but it matches the behavior of Gecko and Edge. + // See https://github.com/w3c/csswg-drafts/issues/697 + self.media_queries.retain(|query| query != &new_query); + self.media_queries.push(new_query); + true + } + + /// Delete a media query from the media list. + /// + /// + /// Returns true if found and deleted, false otherwise. + pub fn delete_medium(&mut self, context: &ParserContext, old_medium: &str) -> bool { + let mut input = ParserInput::new(old_medium); + let mut parser = Parser::new(&mut input); + let old_query = match MediaQuery::parse(context, &mut parser) { + Ok(query) => query, + Err(_) => { + return false; + }, + }; + let old_len = self.media_queries.len(); + self.media_queries.retain(|query| query != &old_query); + old_len != self.media_queries.len() + } +} diff --git a/components/style/media_queries/media_query.rs b/components/style/media_queries/media_query.rs new file mode 100644 index 00000000000..10fa84bd61a --- /dev/null +++ b/components/style/media_queries/media_query.rs @@ -0,0 +1,196 @@ +/* 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: +//! +//! https://drafts.csswg.org/mediaqueries/#typedef-media-query + +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 values::CustomIdent; + +/// +#[derive(Clone, Copy, Debug, Eq, MallocSizeOf, Parse, PartialEq, ToCss)] +pub enum Qualifier { + /// Hide a media query from legacy UAs: + /// + Only, + /// Negate a media query: + /// + Not, +} + +/// +#[derive(Clone, Debug, Eq, MallocSizeOf, PartialEq)] +pub struct MediaType(pub CustomIdent); + +impl MediaType { + /// 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))))), + } + } +} + +/// A [media query][mq]. +/// +/// [mq]: https://drafts.csswg.org/mediaqueries/ +#[derive(Clone, Debug, MallocSizeOf, PartialEq)] +pub struct MediaQuery { + /// The qualifier for this query. + pub qualifier: Option, + /// 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, +} + +impl ToCss for MediaQuery { + fn to_css(&self, dest: &mut CssWriter) -> fmt::Result + where + W: Write, + { + if let Some(qual) = self.qualifier { + qual.to_css(dest)?; + dest.write_char(' ')?; + } + + match self.media_type { + MediaQueryType::All => { + // We need to print "all" if there's a qualifier, or there's + // just an empty list of expressions. + // + // 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() { + dest.write_str("all")?; + } + }, + MediaQueryType::Concrete(MediaType(ref desc)) => desc.to_css(dest)?, + } + + if self.expressions.is_empty() { + 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(()) + } +} + +impl MediaQuery { + /// Return a media query that never matches, used for when we fail to parse + /// a given media query. + pub fn never_matching() -> Self { + Self { + qualifier: Some(Qualifier::Not), + media_type: MediaQueryType::All, + expressions: vec![], + } + } + + /// Parse a media query given css input. + /// + /// Returns an error if any of the expressions is unknown. + pub fn parse<'i, 't>( + context: &ParserContext, + input: &mut Parser<'i, 't>, + ) -> Result> { + let mut expressions = vec![]; + + 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)); + } + + // Without a media type, require at least one expression. + expressions.push(Expression::parse(context, input)?); + + MediaQueryType::All + }, + }; + + // 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)?) + } + } +} + +/// +#[derive(Clone, Debug, Eq, MallocSizeOf, PartialEq)] +pub enum MediaQueryType { + /// A media type that matches every device. + All, + /// A specific media type. + Concrete(MediaType), +} + +impl MediaQueryType { + fn parse(ident: &str) -> Result { + match_ignore_ascii_case! { ident, + "all" => return Ok(MediaQueryType::All), + _ => (), + }; + + // If parseable, accept this type as a concrete type. + MediaType::parse(ident).map(MediaQueryType::Concrete) + } + + /// Returns whether this media query type matches a MediaType. + pub fn matches(&self, other: MediaType) -> bool { + match *self { + MediaQueryType::All => true, + MediaQueryType::Concrete(ref known_type) => *known_type == other, + } + } +} diff --git a/components/style/media_queries/mod.rs b/components/style/media_queries/mod.rs new file mode 100644 index 00000000000..8da14fc67e5 --- /dev/null +++ b/components/style/media_queries/mod.rs @@ -0,0 +1,18 @@ +/* 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/. */ + +//! [Media queries][mq]. +//! +//! [mq]: https://drafts.csswg.org/mediaqueries/ + +mod media_list; +mod media_query; + +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}; +#[cfg(feature = "gecko")] +pub use gecko::media_queries::{Device, Expression}; diff --git a/components/style/properties/helpers/animated_properties.mako.rs b/components/style/properties/helpers/animated_properties.mako.rs index d935726eb48..de0e48c782d 100644 --- a/components/style/properties/helpers/animated_properties.mako.rs +++ b/components/style/properties/helpers/animated_properties.mako.rs @@ -1739,7 +1739,9 @@ impl Animate for Quaternion { let (this_weight, other_weight) = procedure.weights(); debug_assert!( - (this_weight + other_weight - 1.0f64).abs() <= f64::EPSILON || + // Doule EPSILON since both this_weight and other_weght have calculation errors + // which are approximately equal to EPSILON. + (this_weight + other_weight - 1.0f64).abs() <= f64::EPSILON * 2.0 || other_weight == 1.0f64 || other_weight == 0.0f64, "animate should only be used for interpolating or accumulating transforms" ); diff --git a/components/style/properties/longhand/box.mako.rs b/components/style/properties/longhand/box.mako.rs index 898f7391de0..aea51fed766 100644 --- a/components/style/properties/longhand/box.mako.rs +++ b/components/style/properties/longhand/box.mako.rs @@ -626,6 +626,7 @@ ${helpers.single_keyword("-moz-appearance", gecko_ffi_name="mAppearance", gecko_constant_prefix="ThemeWidgetType_NS_THEME", products="gecko", + alias="-webkit-appearance:layout.css.webkit-appearance.enabled", spec="Nonstandard (https://developer.mozilla.org/en-US/docs/Web/CSS/-moz-appearance)", animation_value_type="discrete")} diff --git a/components/style/stylesheets/rule_parser.rs b/components/style/stylesheets/rule_parser.rs index a446068faac..259130dd89b 100644 --- a/components/style/stylesheets/rule_parser.rs +++ b/components/style/stylesheets/rule_parser.rs @@ -10,7 +10,7 @@ use cssparser::{AtRuleParser, AtRuleType, Parser, QualifiedRuleParser, RuleListP use cssparser::{BasicParseError, BasicParseErrorKind, CowRcStr, SourceLocation}; use error_reporting::{ContextualParseError, ParseErrorReporter}; use font_face::parse_font_face_block; -use media_queries::{parse_media_query_list, MediaList}; +use media_queries::MediaList; use parser::{Parse, ParserContext, ParserErrorContext}; use properties::parse_property_declaration_list; use selector_parser::{SelectorImpl, SelectorParser}; @@ -197,8 +197,11 @@ impl<'a, 'i, R: ParseErrorReporter> AtRuleParser<'i> for TopLevelRuleParser<'a, let url_string = input.expect_url_or_string()?.as_ref().to_owned(); let url = CssUrl::parse_from_string(url_string, &self.context); - let media = parse_media_query_list(&self.context, input, - self.error_context.error_reporter); + let media = MediaList::parse( + &self.context, + input, + self.error_context.error_reporter, + ); let media = Arc::new(self.shared_lock.wrap(media)); let prelude = AtRuleNonBlockPrelude::Import(url, media, location); @@ -380,8 +383,11 @@ impl<'a, 'b, 'i, R: ParseErrorReporter> AtRuleParser<'i> for NestedRuleParser<'a match_ignore_ascii_case! { &*name, "media" => { - let media_queries = parse_media_query_list(self.context, input, - self.error_context.error_reporter); + let media_queries = MediaList::parse( + self.context, + input, + self.error_context.error_reporter, + ); let arc = Arc::new(self.shared_lock.wrap(media_queries)); Ok(AtRuleType::WithBlock(AtRuleBlockPrelude::Media(arc, location))) }, diff --git a/components/style/values/computed/image.rs b/components/style/values/computed/image.rs index 6976182a6f4..91e55c39910 100644 --- a/components/style/values/computed/image.rs +++ b/components/style/values/computed/image.rs @@ -7,12 +7,11 @@ //! //! [image]: https://drafts.csswg.org/css-images/#image-values -use cssparser::RGBA; use std::f32::consts::PI; use std::fmt::{self, Write}; use style_traits::{CssWriter, ToCss}; use values::{Either, None_}; -use values::computed::{Angle, Context}; +use values::computed::{Angle, Color, Context}; use values::computed::{Length, LengthOrPercentage, NumberOrPercentage, ToComputedValue}; #[cfg(feature = "gecko")] use values::computed::Percentage; @@ -32,7 +31,7 @@ pub type Image = generic::Image; /// Computed values for a CSS gradient. /// pub type Gradient = - generic::Gradient; + generic::Gradient; /// A computed gradient kind. pub type GradientKind = @@ -58,10 +57,10 @@ pub enum LineDirection { pub type EndingShape = generic::EndingShape; /// A computed gradient item. -pub type GradientItem = generic::GradientItem; +pub type GradientItem = generic::GradientItem; /// A computed color stop. -pub type ColorStop = generic::ColorStop; +pub type ColorStop = generic::ColorStop; /// Computed values for `-moz-image-rect(...)`. pub type MozImageRect = generic::MozImageRect; diff --git a/components/style/values/specified/image.rs b/components/style/values/specified/image.rs index 2390292253b..64d24573f4a 100644 --- a/components/style/values/specified/image.rs +++ b/components/style/values/specified/image.rs @@ -26,7 +26,7 @@ use values::generics::image::{self as generic, Circle, CompatMode, Ellipse, Shap use values::generics::image::PaintWorklet; use values::generics::position::Position as GenericPosition; use values::specified::{Angle, Color, Length, LengthOrPercentage}; -use values::specified::{Number, NumberOrPercentage, Percentage, RGBAColor}; +use values::specified::{Number, NumberOrPercentage, Percentage}; use values::specified::position::{LegacyPosition, Position, PositionComponent, Side, X, Y}; use values::specified::url::SpecifiedImageUrl; @@ -41,19 +41,13 @@ pub type Image = generic::Image; /// #[cfg(not(feature = "gecko"))] pub type Gradient = - generic::Gradient; + generic::Gradient; /// Specified values for a CSS gradient. /// #[cfg(feature = "gecko")] -pub type Gradient = generic::Gradient< - LineDirection, - Length, - LengthOrPercentage, - GradientPosition, - RGBAColor, - Angle, ->; +pub type Gradient = + generic::Gradient; impl SpecifiedValueInfo for Gradient { const SUPPORTED_TYPES: u8 = CssType::GRADIENT; @@ -121,10 +115,10 @@ pub enum GradientPosition { pub type EndingShape = generic::EndingShape; /// A specified gradient item. -pub type GradientItem = generic::GradientItem; +pub type GradientItem = generic::GradientItem; /// A computed color stop. -pub type ColorStop = generic::ColorStop; +pub type ColorStop = generic::ColorStop; /// Specified values for `moz-image-rect` /// -moz-image-rect(, top, right, bottom, left); @@ -957,7 +951,7 @@ impl Parse for ColorStop { input: &mut Parser<'i, 't>, ) -> Result> { Ok(ColorStop { - color: RGBAColor::parse(context, input)?, + color: Color::parse(context, input)?, position: input.try(|i| LengthOrPercentage::parse(context, i)).ok(), }) } diff --git a/components/style/values/specified/resolution.rs b/components/style/values/specified/resolution.rs index 52da8eaea34..77a269c251b 100644 --- a/components/style/values/specified/resolution.rs +++ b/components/style/values/specified/resolution.rs @@ -12,7 +12,7 @@ use style_traits::{ParseError, StyleParseErrorKind}; use values::CSSFloat; /// A specified resolution. -#[derive(Clone, Debug, PartialEq, ToCss)] +#[derive(Clone, Debug, MallocSizeOf, PartialEq, ToCss)] pub enum Resolution { /// Dots per inch. #[css(dimension)]