diff --git a/components/script/dom/element.rs b/components/script/dom/element.rs index 8bf86b604f7..59cc1587560 100644 --- a/components/script/dom/element.rs +++ b/components/script/dom/element.rs @@ -429,7 +429,7 @@ impl LayoutElementHelpers for LayoutJS { PropertyDeclaration::BackgroundImage( background_image::SpecifiedValue(vec![ background_image::single_value::SpecifiedValue(Some( - specified::Image::for_cascade(url.into(), specified::url::UrlExtraData { }) + specified::Image::for_cascade(url.into()) )) ])))); } diff --git a/components/style/build_gecko.rs b/components/style/build_gecko.rs index 6cb3c77a8a6..7ca5530e9a3 100644 --- a/components/style/build_gecko.rs +++ b/components/style/build_gecko.rs @@ -353,6 +353,7 @@ mod bindings { "nsCursorImage", "nsFont", "nsIAtom", + "nsIURI", "nsMainThreadPtrHandle", "nsMainThreadPtrHolder", "nsMargin", @@ -613,6 +614,7 @@ mod bindings { "nsCursorImage", "nsFont", "nsIAtom", + "nsIURI", "nsMediaFeature", "nsRestyleHint", "nsStyleBackground", diff --git a/components/style/gecko/mod.rs b/components/style/gecko/mod.rs index 48d50c5fa80..1e950fe2ad1 100644 --- a/components/style/gecko/mod.rs +++ b/components/style/gecko/mod.rs @@ -17,5 +17,6 @@ pub mod selector_parser; pub mod snapshot; pub mod snapshot_helpers; pub mod traversal; +pub mod url; pub mod values; pub mod wrapper; diff --git a/components/style/gecko/url.rs b/components/style/gecko/url.rs new file mode 100644 index 00000000000..b1550486cdf --- /dev/null +++ b/components/style/gecko/url.rs @@ -0,0 +1,104 @@ +/* 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/. */ + +//! Common handling for the specified value CSS url() values. + +use cssparser::CssStringWriter; +use gecko_bindings::structs::ServoBundledURI; +use gecko_bindings::sugar::refptr::{GeckoArcPrincipal, GeckoArcURI}; +use parser::ParserContext; +use std::borrow::Cow; +use std::fmt::{self, Write}; +use std::sync::Arc; +use style_traits::ToCss; + +/// A specified url() value for gecko. Gecko does not eagerly resolve SpecifiedUrls. +#[derive(Clone, Debug, PartialEq)] +pub struct SpecifiedUrl { + /// The URL in unresolved string form. + /// + /// Refcounted since cloning this should be cheap and data: uris can be + /// really large. + serialization: Arc, + + /// The base URI. + pub base: GeckoArcURI, + /// The referrer. + pub referrer: GeckoArcURI, + /// The principal that originated this URI. + pub principal: GeckoArcPrincipal, +} + +impl SpecifiedUrl { + /// Try to parse a URL from a string value that is a valid CSS token for a + /// URL. + /// + /// Returns `Err` in the case that extra_data is incomplete. + pub fn parse_from_string<'a>(url: Cow<'a, str>, + context: &ParserContext) + -> Result { + let extra = &context.extra_data; + if extra.base.is_none() || extra.referrer.is_none() || extra.principal.is_none() { + // FIXME(heycam) should ensure we always have a principal, etc., + // when parsing style attributes and re-parsing due to CSS + // Variables. + warn!("stylo: skipping declaration without ParserContextExtraData"); + return Err(()) + } + + Ok(SpecifiedUrl { + serialization: Arc::new(url.into_owned()), + base: extra.base.as_ref().unwrap().clone(), + referrer: extra.referrer.as_ref().unwrap().clone(), + principal: extra.principal.as_ref().unwrap().clone(), + }) + } + + /// Returns true if the URL is definitely invalid. We don't eagerly resolve + /// URLs in gecko, so we just return false here. + /// use its |resolved| status. + pub fn is_invalid(&self) -> bool { + false + } + + /// Returns true if this URL looks like a fragment. + /// See https://drafts.csswg.org/css-values/#local-urls + pub fn is_fragment(&self) -> bool { + self.as_str().chars().next().map_or(false, |c| c == '#') + } + + /// Return the resolved url as string, or the empty string if it's invalid. + /// + /// FIXME(bholley): This returns the unresolved URL while the servo version + /// returns the resolved URL. + pub fn as_str(&self) -> &str { + &*self.serialization + } + + /// Little helper for Gecko's ffi. + pub fn as_slice_components(&self) -> (*const u8, usize) { + (self.serialization.as_str().as_ptr(), self.serialization.as_str().len()) + } + + /// Create a bundled URI suitable for sending to Gecko + /// to be constructed into a css::URLValue + pub fn for_ffi(&self) -> ServoBundledURI { + let (ptr, len) = self.as_slice_components(); + ServoBundledURI { + mURLString: ptr, + mURLStringLength: len as u32, + mBaseURI: self.base.get(), + mReferrer: self.referrer.get(), + mPrincipal: self.principal.get(), + } + } +} + +impl ToCss for SpecifiedUrl { + fn to_css(&self, dest: &mut W) -> fmt::Result where W: fmt::Write { + try!(dest.write_str("url(\"")); + try!(CssStringWriter::new(dest).write_str(&*self.serialization)); + dest.write_str("\")") + } +} diff --git a/components/style/gecko_bindings/bindings.rs b/components/style/gecko_bindings/bindings.rs index 1cf68c0ae59..360766befcc 100644 --- a/components/style/gecko_bindings/bindings.rs +++ b/components/style/gecko_bindings/bindings.rs @@ -40,6 +40,7 @@ use gecko_bindings::structs::nsChangeHint; use gecko_bindings::structs::nsCursorImage; use gecko_bindings::structs::nsFont; use gecko_bindings::structs::nsIAtom; +use gecko_bindings::structs::nsIURI; use gecko_bindings::structs::nsMediaFeature; use gecko_bindings::structs::nsRestyleHint; use gecko_bindings::structs::nsStyleBackground; @@ -437,8 +438,9 @@ extern "C" { pub fn Gecko_LoadStyleSheet(loader: *mut Loader, parent: *mut ServoStyleSheet, import_rule: RawServoImportRuleBorrowed, - url_bytes: *const u8, url_length: u32, - media_bytes: *const u8, media_length: u32); + base_uri: *mut nsIURI, url_bytes: *const u8, + url_length: u32, media_bytes: *const u8, + media_length: u32); } extern "C" { pub fn Gecko_MaybeCreateStyleChildrenIterator(node: RawGeckoNodeBorrowed) diff --git a/components/style/properties/longhand/svg.mako.rs b/components/style/properties/longhand/svg.mako.rs index 45da055559f..97f64cb0229 100644 --- a/components/style/properties/longhand/svg.mako.rs +++ b/components/style/properties/longhand/svg.mako.rs @@ -259,12 +259,7 @@ ${helpers.single_keyword("mask-composite", let image = try!(Image::parse(context, input)); match image { Image::Url(url_value) => { - let has_valid_url = match url_value.url() { - Some(url) => url.fragment().is_some(), - None => false, - }; - - if has_valid_url { + if url_value.is_fragment() { Ok(SpecifiedValue::Url(url_value)) } else { Ok(SpecifiedValue::Image(Image::Url(url_value))) diff --git a/components/style/servo/mod.rs b/components/style/servo/mod.rs index ad741616eeb..329e5b9a37d 100644 --- a/components/style/servo/mod.rs +++ b/components/style/servo/mod.rs @@ -9,3 +9,4 @@ pub mod media_queries; pub mod restyle_damage; pub mod selector_parser; +pub mod url; diff --git a/components/style/servo/url.rs b/components/style/servo/url.rs new file mode 100644 index 00000000000..e96a4777dfe --- /dev/null +++ b/components/style/servo/url.rs @@ -0,0 +1,127 @@ +/* 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/. */ + +//! Common handling for the specified value CSS url() values. + +use cssparser::CssStringWriter; +use parser::ParserContext; +use servo_url::ServoUrl; +use std::borrow::Cow; +use std::fmt::{self, Write}; +use std::sync::Arc; +use style_traits::ToCss; + +/// A specified url() value for servo. +/// +/// Servo eagerly resolves SpecifiedUrls, which it can then take advantage of +/// when computing values. In contrast, Gecko uses a different URL backend, so +/// eagerly resolving with rust-url would be duplicated work. +/// +/// However, this approach is still not necessarily optimal: See +/// https://bugzilla.mozilla.org/show_bug.cgi?id=1347435#c6 +#[derive(Clone, Debug, HeapSizeOf, Serialize, Deserialize)] +pub struct SpecifiedUrl { + /// The original URI. This might be optional since we may insert computed + /// values of images into the cascade directly, and we don't bother to + /// convert their serialization. + /// + /// Refcounted since cloning this should be cheap and data: uris can be + /// really large. + original: Option>, + + /// The resolved value for the url, if valid. + resolved: Option, +} + +impl SpecifiedUrl { + /// Try to parse a URL from a string value that is a valid CSS token for a + /// URL. Never fails - the API is only fallible to be compatible with the + /// gecko version. + pub fn parse_from_string<'a>(url: Cow<'a, str>, + context: &ParserContext) + -> Result { + let serialization = Arc::new(url.into_owned()); + let resolved = context.base_url.join(&serialization).ok(); + Ok(SpecifiedUrl { + original: Some(serialization), + resolved: resolved, + }) + } + + /// Returns true if the URL is definitely invalid. For Servo URLs, we can + /// use its |resolved| status. + pub fn is_invalid(&self) -> bool { + self.resolved.is_none() + } + + /// Returns true if this URL looks like a fragment. + /// See https://drafts.csswg.org/css-values/#local-urls + /// + /// Since Servo currently stores resolved URLs, this is hard to implement. We + /// either need to change servo to lazily resolve (like Gecko), or note this + /// information in the tokenizer. + pub fn is_fragment(&self) -> bool { + error!("Can't determine whether the url is a fragment."); + false + } + + /// Returns the resolved url if it was valid. + pub fn url(&self) -> Option<&ServoUrl> { + self.resolved.as_ref() + } + + /// Return the resolved url as string, or the empty string if it's invalid. + /// + /// TODO(emilio): Should we return the original one if needed? + pub fn as_str(&self) -> &str { + match self.resolved { + Some(ref url) => url.as_str(), + None => "", + } + } + + /// Creates an already specified url value from an already resolved URL + /// for insertion in the cascade. + pub fn for_cascade(url: ServoUrl) -> Self { + SpecifiedUrl { + original: None, + resolved: Some(url), + } + } + + /// Gets a new url from a string for unit tests. + pub fn new_for_testing(url: &str) -> Self { + SpecifiedUrl { + original: Some(Arc::new(url.into())), + resolved: ServoUrl::parse(url).ok(), + } + } +} + +impl PartialEq for SpecifiedUrl { + fn eq(&self, other: &Self) -> bool { + // TODO(emilio): maybe we care about equality of the specified values if + // present? Seems not. + self.resolved == other.resolved + } +} + +impl ToCss for SpecifiedUrl { + fn to_css(&self, dest: &mut W) -> fmt::Result where W: fmt::Write { + try!(dest.write_str("url(\"")); + let string = match self.original { + Some(ref original) => &**original, + None => match self.resolved { + Some(ref url) => url.as_str(), + // This can only happen if the url wasn't specified by the + // user *and* it's an invalid url that has been transformed + // back to specified value via the "uncompute" functionality. + None => "about:invalid", + } + }; + + try!(CssStringWriter::new(dest).write_str(string)); + dest.write_str("\")") + } +} diff --git a/components/style/stylesheets.rs b/components/style/stylesheets.rs index 202c31f7416..e5b82161133 100644 --- a/components/style/stylesheets.rs +++ b/components/style/stylesheets.rs @@ -839,8 +839,7 @@ impl<'a> AtRuleParser for TopLevelRuleParser<'a> { let media = parse_media_query_list(input); let noop_loader = NoOpLoader; - let is_valid_url = specified_url.url().is_some(); - let loader = if is_valid_url { + let loader = if !specified_url.is_invalid() { self.loader.expect("Expected a stylesheet loader for @import") } else { &noop_loader diff --git a/components/style/values/computed/mod.rs b/components/style/values/computed/mod.rs index be26554948f..6a3caa4acaa 100644 --- a/components/style/values/computed/mod.rs +++ b/components/style/values/computed/mod.rs @@ -21,7 +21,7 @@ pub use super::{Auto, Either, None_}; #[cfg(feature = "gecko")] pub use super::specified::{AlignItems, AlignJustifyContent, AlignJustifySelf, JustifyItems}; pub use super::specified::{Angle, BorderStyle, GridLine, Time, UrlOrNone}; -pub use super::specified::url::{SpecifiedUrl, UrlExtraData}; +pub use super::specified::url::SpecifiedUrl; pub use self::length::{CalcLengthOrPercentage, Length, LengthOrNumber, LengthOrPercentage, LengthOrPercentageOrAuto}; pub use self::length::{LengthOrPercentageOrAutoOrContent, LengthOrPercentageOrNone, LengthOrNone}; pub use self::length::{MaxLength, MinLength}; diff --git a/components/style/values/specified/image.rs b/components/style/values/specified/image.rs index 7cd0fc1f51d..ef1df93f9fa 100644 --- a/components/style/values/specified/image.rs +++ b/components/style/values/specified/image.rs @@ -9,12 +9,13 @@ use cssparser::Parser; use parser::{Parse, ParserContext}; +#[cfg(feature = "servo")] use servo_url::ServoUrl; use std::fmt; use style_traits::ToCss; use values::specified::{Angle, CSSColor, Length, LengthOrPercentage}; use values::specified::position::Position; -use values::specified::url::{SpecifiedUrl, UrlExtraData}; +use values::specified::url::SpecifiedUrl; /// Specified values for an image according to CSS-IMAGES. /// https://drafts.csswg.org/css-images/#image-values @@ -48,8 +49,9 @@ impl Image { /// Creates an already specified image value from an already resolved URL /// for insertion in the cascade. - pub fn for_cascade(url: ServoUrl, extra_data: UrlExtraData) -> Self { - Image::Url(SpecifiedUrl::for_cascade(url, extra_data)) + #[cfg(feature = "servo")] + pub fn for_cascade(url: ServoUrl) -> Self { + Image::Url(SpecifiedUrl::for_cascade(url)) } } diff --git a/components/style/values/specified/mod.rs b/components/style/values/specified/mod.rs index 9970be49ae4..46924f87820 100644 --- a/components/style/values/specified/mod.rs +++ b/components/style/values/specified/mod.rs @@ -42,7 +42,33 @@ pub mod grid; pub mod image; pub mod length; pub mod position; -pub mod url; + +/// Common handling for the specified value CSS url() values. +pub mod url { +use cssparser::Parser; +use parser::{Parse, ParserContext}; +use values::HasViewportPercentage; +use values::computed::ComputedValueAsSpecified; + +#[cfg(feature = "servo")] +pub use ::servo::url::*; +#[cfg(feature = "gecko")] +pub use ::gecko::url::*; + +impl Parse for SpecifiedUrl { + fn parse(context: &ParserContext, input: &mut Parser) -> Result { + let url = try!(input.expect_url()); + Self::parse_from_string(url, context) + } +} + +impl Eq for SpecifiedUrl {} + +// TODO(emilio): Maybe consider ComputedUrl to save a word in style structs? +impl ComputedValueAsSpecified for SpecifiedUrl {} + +no_viewport_percentage!(SpecifiedUrl); +} no_viewport_percentage!(i32); // For PropertyDeclaration::Order diff --git a/components/style/values/specified/url.rs b/components/style/values/specified/url.rs deleted file mode 100644 index 733d4199361..00000000000 --- a/components/style/values/specified/url.rs +++ /dev/null @@ -1,226 +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/. */ - -//! Common handling for the specified value CSS url() values. - -use cssparser::{CssStringWriter, Parser}; -#[cfg(feature = "gecko")] -use gecko_bindings::structs::ServoBundledURI; -#[cfg(feature = "gecko")] -use gecko_bindings::sugar::refptr::{GeckoArcPrincipal, GeckoArcURI}; -use parser::{Parse, ParserContext}; -#[cfg(feature = "gecko")] -use parser::ParserContextExtraData; -use servo_url::ServoUrl; -use std::borrow::Cow; -use std::fmt::{self, Write}; -use std::sync::Arc; -use style_traits::ToCss; -use values::HasViewportPercentage; -use values::computed::ComputedValueAsSpecified; - -/// A set of data needed in Gecko to represent a URL. -#[derive(PartialEq, Clone, Debug)] -#[cfg_attr(feature = "servo", derive(HeapSizeOf, Serialize, Deserialize, Eq))] -pub struct UrlExtraData { - /// The base URI. - #[cfg(feature = "gecko")] - pub base: GeckoArcURI, - /// The referrer. - #[cfg(feature = "gecko")] - pub referrer: GeckoArcURI, - /// The principal that originated this URI. - #[cfg(feature = "gecko")] - pub principal: GeckoArcPrincipal, -} - -impl UrlExtraData { - /// Constructs a `UrlExtraData`. - #[cfg(feature = "servo")] - pub fn make_from(_: &ParserContext) -> Option { - Some(UrlExtraData { }) - } - - /// Constructs a `UrlExtraData`. - #[cfg(feature = "gecko")] - pub fn make_from(context: &ParserContext) -> Option { - match context.extra_data { - ParserContextExtraData { - base: Some(ref base), - referrer: Some(ref referrer), - principal: Some(ref principal), - } => { - Some(UrlExtraData { - base: base.clone(), - referrer: referrer.clone(), - principal: principal.clone(), - }) - }, - _ => None, - } - } -} - -/// A specified url() value. -#[derive(Clone, Debug)] -#[cfg_attr(feature = "servo", derive(HeapSizeOf, Serialize, Deserialize))] -pub struct SpecifiedUrl { - /// The original URI. This might be optional since we may insert computed - /// values of images into the cascade directly, and we don't bother to - /// convert their serialization. - /// - /// Refcounted since cloning this should be cheap and data: uris can be - /// really large. - original: Option>, - - /// The resolved value for the url, if valid. - resolved: Option, - - /// Extra data used for Stylo. - extra_data: UrlExtraData, -} - -impl Parse for SpecifiedUrl { - fn parse(context: &ParserContext, input: &mut Parser) -> Result { - let url = try!(input.expect_url()); - Self::parse_from_string(url, context) - } -} - -impl SpecifiedUrl { - /// Try to parse a URL from a string value that is a valid CSS token for a - /// URL. - /// - /// Only returns `Err` for Gecko, in the case we can't construct a - /// `URLExtraData`. - pub fn parse_from_string<'a>(url: Cow<'a, str>, - context: &ParserContext) - -> Result { - let extra_data = match UrlExtraData::make_from(context) { - Some(extra_data) => extra_data, - None => { - // FIXME(heycam) should ensure we always have a principal, etc., - // when parsing style attributes and re-parsing due to CSS - // Variables. - warn!("stylo: skipping declaration without ParserContextExtraData"); - return Err(()) - }, - }; - - let serialization = Arc::new(url.into_owned()); - let resolved = context.base_url.join(&serialization).ok(); - Ok(SpecifiedUrl { - original: Some(serialization), - resolved: resolved, - extra_data: extra_data, - }) - } - - /// Get this URL's extra data. - pub fn extra_data(&self) -> &UrlExtraData { - &self.extra_data - } - - /// Returns the resolved url if it was valid. - pub fn url(&self) -> Option<&ServoUrl> { - self.resolved.as_ref() - } - - /// Return the resolved url as string, or the empty string if it's invalid. - /// - /// TODO(emilio): Should we return the original one if needed? - pub fn as_str(&self) -> &str { - match self.resolved { - Some(ref url) => url.as_str(), - None => "", - } - } - - /// Little helper for Gecko's ffi. - #[cfg(feature = "gecko")] - pub fn as_slice_components(&self) -> Result<(*const u8, usize), (*const u8, usize)> { - match self.resolved { - Some(ref url) => Ok((url.as_str().as_ptr(), url.as_str().len())), - None => { - let url = self.original.as_ref() - .expect("We should always have either the original or the resolved value"); - Err((url.as_str().as_ptr(), url.as_str().len())) - } - } - } - - /// Creates an already specified url value from an already resolved URL - /// for insertion in the cascade. - pub fn for_cascade(url: ServoUrl, extra_data: UrlExtraData) -> Self { - SpecifiedUrl { - original: None, - resolved: Some(url), - extra_data: extra_data, - } - } - - /// Gets a new url from a string for unit tests. - #[cfg(feature = "servo")] - pub fn new_for_testing(url: &str) -> Self { - SpecifiedUrl { - original: Some(Arc::new(url.into())), - resolved: ServoUrl::parse(url).ok(), - extra_data: UrlExtraData {} - } - } - - /// Create a bundled URI suitable for sending to Gecko - /// to be constructed into a css::URLValue - #[cfg(feature = "gecko")] - pub fn for_ffi(&self) -> ServoBundledURI { - let extra_data = self.extra_data(); - let (ptr, len) = match self.as_slice_components() { - Ok(value) => value, - // we're okay with passing down the unresolved relative URI - Err(value) => value, - }; - ServoBundledURI { - mURLString: ptr, - mURLStringLength: len as u32, - mBaseURI: extra_data.base.get(), - mReferrer: extra_data.referrer.get(), - mPrincipal: extra_data.principal.get(), - } - } -} - -impl PartialEq for SpecifiedUrl { - fn eq(&self, other: &Self) -> bool { - // TODO(emilio): maybe we care about equality of the specified values if - // present? Seems not. - self.resolved == other.resolved && - self.extra_data == other.extra_data - } -} - -impl Eq for SpecifiedUrl {} - -impl ToCss for SpecifiedUrl { - fn to_css(&self, dest: &mut W) -> fmt::Result where W: fmt::Write { - try!(dest.write_str("url(\"")); - let string = match self.original { - Some(ref original) => &**original, - None => match self.resolved { - Some(ref url) => url.as_str(), - // This can only happen if the url wasn't specified by the - // user *and* it's an invalid url that has been transformed - // back to specified value via the "uncompute" functionality. - None => "about:invalid", - } - }; - - try!(CssStringWriter::new(dest).write_str(string)); - dest.write_str("\")") - } -} - -// TODO(emilio): Maybe consider ComputedUrl to save a word in style structs? -impl ComputedValueAsSpecified for SpecifiedUrl {} - -no_viewport_percentage!(SpecifiedUrl); diff --git a/ports/geckolib/stylesheet_loader.rs b/ports/geckolib/stylesheet_loader.rs index d904f1b9870..cc81a267b48 100644 --- a/ports/geckolib/stylesheet_loader.rs +++ b/ports/geckolib/stylesheet_loader.rs @@ -42,14 +42,15 @@ impl StyleStylesheetLoader for StylesheetLoader { // and so the Arc pointer inside will also move, // but the Url it points to or the allocating backing the String inside that Url won’t, // so this raw pointer will still be valid. - let (spec_bytes, spec_len): (*const u8, usize) = import.url.as_slice_components() - .expect("Import only loads valid URLs"); + let (spec_bytes, spec_len): (*const u8, usize) = import.url.as_slice_components(); + let base_uri = import.url.base.mRawPtr; let arc = make_arc(import); unsafe { Gecko_LoadStyleSheet(self.0, self.1, HasArcFFI::arc_as_borrowed(&arc), + base_uri, spec_bytes, spec_len as u32, media_string.as_bytes().as_ptr(),