servo: Merge #13791 - Centralize construction of specified url() values in style (from emilio:lazy-url); r=SimonSapin
authorEmilio Cobos Álvarez <ecoal95@gmail.com>
Thu, 10 Nov 2016 08:28:39 -0600
changeset 340124 5524b3796fc753c012eb6f33986336acb50ae7c0
parent 340123 01d7ca83748ca4485c4da8776505a24ad12e9e24
child 340125 26b0d0023cc2fdb8da8a7698f5b96d7d21bae97b
push id31307
push usergszorc@mozilla.com
push dateSat, 04 Feb 2017 00:59:06 +0000
treeherdermozilla-central@94079d43835f [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersSimonSapin
servo: Merge #13791 - Centralize construction of specified url() values in style (from emilio:lazy-url); r=SimonSapin This reduces a decent amount of overhead in #13778. r? @SimonSapin --- <!-- Thank you for contributing to Servo! Please replace each `[ ]` by `[X]` when the step is complete, and replace `__` with appropriate data: --> - [x] `./mach build -d` does not report any errors - [x] `./mach test-tidy` does not report any errors <!-- Either: --> - [x] There are tests for these changes OR <!-- Pull requests that do not address these steps are welcome, but they will require additional verification as part of the review process. --> Source-Repo: https://github.com/servo/servo Source-Revision: 164e956944db0d1d873d2f6eeb9eb59e3f46dc5f
servo/components/layout/construct.rs
servo/components/layout/display_list_builder.rs
servo/components/script/dom/element.rs
servo/components/servo/Cargo.lock
servo/components/style/gecko/conversions.rs
servo/components/style/parser.rs
servo/components/style/properties/gecko.mako.rs
servo/components/style/properties/longhand/svg.mako.rs
servo/components/style/values/computed/basic_shape.rs
servo/components/style/values/computed/image.rs
servo/components/style/values/computed/length.rs
servo/components/style/values/computed/mod.rs
servo/components/style/values/specified/basic_shape.rs
servo/components/style/values/specified/image.rs
servo/components/style/values/specified/mod.rs
servo/components/style/values/specified/url.rs
servo/components/style_traits/Cargo.toml
servo/components/style_traits/lib.rs
servo/components/style_traits/values.rs
servo/ports/cef/Cargo.lock
servo/ports/geckolib/Cargo.lock
servo/tests/unit/style/properties/serialization.rs
--- a/servo/components/layout/construct.rs
+++ b/servo/components/layout/construct.rs
@@ -1200,19 +1200,19 @@ impl<'a, ConcreteThreadSafeLayoutNode: T
     /// Builds a flow for a node with `display: list-item`. This yields a `ListItemFlow` with
     /// possibly other `BlockFlow`s or `InlineFlow`s underneath it.
     fn build_flow_for_list_item(&mut self,
                                 node: &ConcreteThreadSafeLayoutNode,
                                 flotation: float::T)
                                 -> ConstructionResult {
         let flotation = FloatKind::from_property(flotation);
         let marker_fragments = match node.style(self.style_context()).get_list().list_style_image {
-            list_style_image::T::Url(ref url, ref _extra_data) => {
+            list_style_image::T::Url(ref url_value) => {
                 let image_info = box ImageFragmentInfo::new(node,
-                                                            Some((*url).clone()),
+                                                            url_value.url().map(|u| (**u).clone()),
                                                             &self.layout_context.shared);
                 vec![Fragment::new(node, SpecificFragmentInfo::Image(image_info), self.layout_context)]
             }
             list_style_image::T::None => {
                 match ListStyleTypeContent::from_list_style_type(node.style(self.style_context())
                                                                      .get_list()
                                                                      .list_style_type) {
                     ListStyleTypeContent::None => Vec::new(),
--- a/servo/components/layout/display_list_builder.rs
+++ b/servo/components/layout/display_list_builder.rs
@@ -607,24 +607,26 @@ impl FragmentDisplayListBuilding for Fra
                         self.build_display_list_for_background_gradient(state,
                                                                         display_list_section,
                                                                         &bounds,
                                                                         &clip,
                                                                         gradient,
                                                                         style);
                     }
                 }
-                Some(computed::Image::Url(ref image_url, ref _extra_data)) => {
-                    self.build_display_list_for_background_image(state,
-                                                                 style,
-                                                                 display_list_section,
-                                                                 &bounds,
-                                                                 &clip,
-                                                                 image_url,
-                                                                 i);
+                Some(computed::Image::Url(ref image_url)) => {
+                    if let Some(url) = image_url.url() {
+                        self.build_display_list_for_background_image(state,
+                                                                     style,
+                                                                     display_list_section,
+                                                                     &bounds,
+                                                                     &clip,
+                                                                     url,
+                                                                     i);
+                    }
                 }
             }
         }
     }
 
     fn compute_background_image_size(&self,
                                      style: &ServoComputedValues,
                                      bounds: &Rect<Au>,
--- a/servo/components/script/dom/element.rs
+++ b/servo/components/script/dom/element.rs
@@ -363,17 +363,17 @@ impl LayoutElementHelpers for LayoutJS<E
             None
         };
 
         if let Some(url) = background {
             hints.push(from_declaration(
                 PropertyDeclaration::BackgroundImage(DeclaredValue::Value(
                     background_image::SpecifiedValue(vec![
                         background_image::single_value::SpecifiedValue(Some(
-                            specified::Image::Url(url, specified::UrlExtraData { })
+                            specified::Image::for_cascade(Some(Arc::new(url)), specified::url::UrlExtraData { })
                         ))
                     ])))));
         }
 
         let color = if let Some(this) = self.downcast::<HTMLFontElement>() {
             this.get_color()
         } else if let Some(this) = self.downcast::<HTMLBodyElement>() {
             // https://html.spec.whatwg.org/multipage/#the-page:the-body-element-20
--- a/servo/components/servo/Cargo.lock
+++ b/servo/components/servo/Cargo.lock
@@ -2523,17 +2523,16 @@ dependencies = [
  "app_units 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "cssparser 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "euclid 0.10.2 (registry+https://github.com/rust-lang/crates.io-index)",
  "heapsize 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)",
  "heapsize_derive 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)",
  "rustc-serialize 0.3.19 (registry+https://github.com/rust-lang/crates.io-index)",
  "serde 0.8.17 (registry+https://github.com/rust-lang/crates.io-index)",
  "serde_derive 0.8.17 (registry+https://github.com/rust-lang/crates.io-index)",
- "url 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
 ]
 
 [[package]]
 name = "syn"
 version = "0.9.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 dependencies = [
  "quote 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)",
--- a/servo/components/style/gecko/conversions.rs
+++ b/servo/components/style/gecko/conversions.rs
@@ -115,21 +115,23 @@ impl From<nsStyleCoord_CalcValue> for Le
 }
 
 impl nsStyleImage {
     pub fn set(&mut self, image: Image, with_url: bool, cacheable: &mut bool) {
         match image {
             Image::Gradient(gradient) => {
                 self.set_gradient(gradient)
             },
-            Image::Url(ref url, ref extra_data) if with_url => {
+            Image::Url(ref url) if with_url => {
+                let (ptr, len) = url.as_slice_components();
+                let extra_data = url.extra_data();
                 unsafe {
                     Gecko_SetUrlImageValue(self,
-                                           url.as_str().as_ptr(),
-                                           url.as_str().len() as u32,
+                                           ptr,
+                                           len as u32,
                                            extra_data.base.get(),
                                            extra_data.referrer.get(),
                                            extra_data.principal.get());
                 }
                 // We unfortunately must make any url() value uncacheable, since
                 // the applicable declarations cache is not per document, but
                 // global, and the imgRequestProxy objects we store in the style
                 // structs don't like to be tracked by more than one document.
--- a/servo/components/style/parser.rs
+++ b/servo/components/style/parser.rs
@@ -61,25 +61,16 @@ impl<'a> ParserContext<'a> {
 
     pub fn new(stylesheet_origin: Origin, base_url: &'a Url, error_reporter: Box<ParseErrorReporter + Send>)
                -> ParserContext<'a> {
         let extra_data = ParserContextExtraData::default();
         ParserContext::new_with_extra_data(stylesheet_origin, base_url, error_reporter, extra_data)
     }
 }
 
-
-impl<'a> ParserContext<'a> {
-    pub fn parse_url(&self, input: &str) -> Url {
-        self.base_url.join(input)
-            .unwrap_or_else(|_| Url::parse("about:invalid").unwrap())
-    }
-}
-
-
 /// Defaults to a no-op.
 /// Set a `RUST_LOG=style::errors` environment variable
 /// to log CSS parse errors to stderr.
 pub fn log_css_error(input: &mut Parser, position: SourcePosition, message: &str, parsercontext: &ParserContext) {
     parsercontext.error_reporter.report_error(input, position, message);
 }
 
 // XXXManishearth Replace all specified value parse impls with impls of this
--- a/servo/components/style/properties/gecko.mako.rs
+++ b/servo/components/style/properties/gecko.mako.rs
@@ -1031,21 +1031,23 @@ fn static_assert() {
 
     <%call expr="impl_coord_copy('vertical_align', 'mVerticalAlign')"></%call>
 
     #[allow(non_snake_case)]
     pub fn set__moz_binding(&mut self, v: longhands::_moz_binding::computed_value::T) {
         use properties::longhands::_moz_binding::computed_value::T as BindingValue;
         match v {
             BindingValue::None => debug_assert!(self.gecko.mBinding.mRawPtr.is_null()),
-            BindingValue::Url(ref url, ref extra_data) => {
+            BindingValue::Url(ref url) => {
+                let extra_data = url.extra_data();
+                let (ptr, len) = url.as_slice_components();
                 unsafe {
                     Gecko_SetMozBinding(&mut self.gecko,
-                                        url.as_str().as_ptr(),
-                                        url.as_str().len() as u32,
+                                        ptr,
+                                        len as u32,
                                         extra_data.base.get(),
                                         extra_data.referrer.get(),
                                         extra_data.principal.get());
                 }
             }
         }
     }
     #[allow(non_snake_case)]
@@ -1436,21 +1438,23 @@ fn static_assert() {
     pub fn set_list_style_image(&mut self, image: longhands::list_style_image::computed_value::T) {
         use values::computed::UrlOrNone;
         match image {
             UrlOrNone::None => {
                 unsafe {
                     Gecko_SetListStyleImageNone(&mut self.gecko);
                 }
             }
-            UrlOrNone::Url(ref url, ref extra_data) => {
+            UrlOrNone::Url(ref url) => {
+                let (ptr, len) = url.as_slice_components();
+                let extra_data = url.extra_data();
                 unsafe {
                     Gecko_SetListStyleImage(&mut self.gecko,
-                                            url.as_str().as_ptr(),
-                                            url.as_str().len() as u32,
+                                            ptr,
+                                            len as u32,
                                             extra_data.base.get(),
                                             extra_data.referrer.get(),
                                             extra_data.principal.get());
                 }
                 // We don't need to record this struct as uncacheable, like when setting
                 // background-image to a url() value, since only properties in reset structs
                 // are re-used from the applicable declaration cache, and the List struct
                 // is an inherited struct.
--- a/servo/components/style/properties/longhand/svg.mako.rs
+++ b/servo/components/style/properties/longhand/svg.mako.rs
@@ -144,59 +144,60 @@
                          vector=True,
                          products="gecko",
                          animatable=False)}
 
 <%helpers:vector_longhand name="mask-image" products="gecko" animatable="False"
                           has_uncacheable_values="${product == 'gecko'}">
     use std::fmt;
     use style_traits::ToCss;
-    use url::Url;
-    use values::specified::{Image, UrlExtraData};
+    use std::sync::Arc;
+    use values::specified::Image;
+    use values::specified::url::SpecifiedUrl;
     use values::NoViewportPercentage;
 
     pub mod computed_value {
         use std::fmt;
         use style_traits::ToCss;
-        use url::Url;
         use values::computed;
+        use values::specified::url::SpecifiedUrl;
         #[derive(Debug, Clone, PartialEq)]
         #[cfg_attr(feature = "servo", derive(HeapSizeOf))]
         pub enum T {
             Image(computed::Image),
-            Url(Url, computed::UrlExtraData),
+            Url(SpecifiedUrl),
             None
         }
 
         impl ToCss for T {
             fn to_css<W>(&self, dest: &mut W) -> fmt::Result where W: fmt::Write {
                 match *self {
                     T::None => dest.write_str("none"),
                     T::Image(ref image) => image.to_css(dest),
-                    T::Url(ref url, _) => url.to_css(dest),
+                    T::Url(ref url) => url.to_css(dest),
                 }
             }
         }
     }
 
     impl NoViewportPercentage for SpecifiedValue {}
 
     #[derive(Debug, Clone, PartialEq)]
     #[cfg_attr(feature = "servo", derive(HeapSizeOf))]
     pub enum SpecifiedValue {
         Image(Image),
-        Url(Url, UrlExtraData),
+        Url(SpecifiedUrl),
         None
     }
 
     impl ToCss for SpecifiedValue {
         fn to_css<W>(&self, dest: &mut W) -> fmt::Result where W: fmt::Write {
             match *self {
                 SpecifiedValue::Image(ref image) => image.to_css(dest),
-                SpecifiedValue::Url(ref url, _) => url.to_css(dest),
+                SpecifiedValue::Url(ref url) => url.to_css(dest),
                 SpecifiedValue::None => dest.write_str("none"),
             }
         }
     }
 
     #[inline]
     pub fn get_initial_value() -> computed_value::T {
         computed_value::T::None
@@ -206,45 +207,50 @@
         SpecifiedValue::None
     }
     pub fn parse(context: &ParserContext, input: &mut Parser) -> Result<SpecifiedValue, ()> {
         if input.try(|input| input.expect_ident_matching("none")).is_ok() {
             Ok(SpecifiedValue::None)
         } else {
             let image = try!(Image::parse(context, input));
             match image {
-                Image::Url(url, data) => {
-                    if url.fragment().is_some() {
-                        Ok(SpecifiedValue::Url(url, data))
+                Image::Url(url_value) => {
+                    let has_valid_url = match url_value.url() {
+                        Some(url) => url.fragment().is_some(),
+                        None => false,
+                    };
+
+                    if has_valid_url {
+                        Ok(SpecifiedValue::Url(url_value))
                     } else {
-                        Ok(SpecifiedValue::Image(Image::Url(url, data)))
+                        Ok(SpecifiedValue::Image(Image::Url(url_value)))
                     }
                 }
                 image => Ok(SpecifiedValue::Image(image))
             }
         }
     }
     impl ToComputedValue for SpecifiedValue {
         type ComputedValue = computed_value::T;
 
         #[inline]
         fn to_computed_value(&self, context: &Context) -> computed_value::T {
             match *self {
                 SpecifiedValue::None => computed_value::T::None,
                 SpecifiedValue::Image(ref image) =>
                     computed_value::T::Image(image.to_computed_value(context)),
-                SpecifiedValue::Url(ref url, ref data) =>
-                    computed_value::T::Url(url.clone(), data.clone()),
+                SpecifiedValue::Url(ref url) =>
+                    computed_value::T::Url(url.clone()),
             }
         }
 
         #[inline]
         fn from_computed_value(computed: &computed_value::T) -> Self {
             match *computed {
                 computed_value::T::None => SpecifiedValue::None,
                 computed_value::T::Image(ref image) =>
                     SpecifiedValue::Image(ToComputedValue::from_computed_value(image)),
-                computed_value::T::Url(ref url, ref data) =>
-                    SpecifiedValue::Url(url.clone(), data.clone()),
+                computed_value::T::Url(ref url) =>
+                    SpecifiedValue::Url(url.clone()),
             }
         }
     }
 </%helpers:vector_longhand>
--- a/servo/components/style/values/computed/basic_shape.rs
+++ b/servo/components/style/values/computed/basic_shape.rs
@@ -5,42 +5,41 @@
 //! CSS handling for the computed value of
 //! [`basic-shape`][basic-shape]s
 //!
 //! [basic-shape]: https://drafts.csswg.org/css-shapes/#typedef-basic-shape
 
 use properties::shorthands::serialize_four_sides;
 use std::fmt;
 use style_traits::ToCss;
-use url::Url;
 use values::computed::{BorderRadiusSize, LengthOrPercentage};
-use values::computed::UrlExtraData;
 use values::computed::position::Position;
+use values::specified::url::SpecifiedUrl;
 
 pub use values::specified::basic_shape::{FillRule, GeometryBox, ShapeBox};
 
 #[derive(Clone, PartialEq, Debug)]
 #[cfg_attr(feature = "servo", derive(HeapSizeOf))]
 pub enum ShapeSource<T> {
-    Url(Url, UrlExtraData),
+    Url(SpecifiedUrl),
     Shape(BasicShape, Option<T>),
     Box(T),
     None,
 }
 
 impl<T> Default for ShapeSource<T> {
     fn default() -> Self {
         ShapeSource::None
     }
 }
 
 impl<T: ToCss> ToCss for ShapeSource<T> {
     fn to_css<W>(&self, dest: &mut W) -> fmt::Result where W: fmt::Write {
         match *self {
-            ShapeSource::Url(ref url, _) => url.to_css(dest),
+            ShapeSource::Url(ref url) => url.to_css(dest),
             ShapeSource::Shape(ref shape, Some(ref reference)) => {
                 try!(shape.to_css(dest));
                 try!(dest.write_str(" "));
                 reference.to_css(dest)
             }
             ShapeSource::Shape(ref shape, None) => shape.to_css(dest),
             ShapeSource::Box(ref reference) => reference.to_css(dest),
             ShapeSource::None => dest.write_str("none"),
--- a/servo/components/style/values/computed/image.rs
+++ b/servo/components/style/values/computed/image.rs
@@ -5,84 +5,82 @@
 //! CSS handling for the computed value of
 //! [`image`][image]s
 //!
 //! [image]: https://drafts.csswg.org/css-images/#image-values
 
 use cssparser::Color as CSSColor;
 use std::fmt;
 use style_traits::ToCss;
-use url::Url;
 use values::computed::{Context, Length, LengthOrPercentage, ToComputedValue};
 use values::computed::position::Position;
-use values::specified::{self, AngleOrCorner, SizeKeyword, UrlExtraData};
+use values::specified::{self, AngleOrCorner, SizeKeyword};
+use values::specified::url::SpecifiedUrl;
 
 
 impl ToComputedValue for specified::Image {
     type ComputedValue = Image;
 
     #[inline]
     fn to_computed_value(&self, context: &Context) -> Image {
         match *self {
-            specified::Image::Url(ref url, ref extra_data) => {
-                Image::Url(url.clone(), extra_data.clone())
+            specified::Image::Url(ref url_value) => {
+                Image::Url(url_value.clone())
             },
             specified::Image::Gradient(ref gradient) => {
                 Image::Gradient(gradient.to_computed_value(context))
             }
         }
     }
 
     #[inline]
     fn from_computed_value(computed: &Image) -> Self {
         match *computed {
-            Image::Url(ref url, ref extra_data) => {
-                specified::Image::Url(url.clone(), extra_data.clone())
+            Image::Url(ref url_value) => {
+                specified::Image::Url(url_value.clone())
             },
             Image::Gradient(ref linear_gradient) => {
                 specified::Image::Gradient(
                     ToComputedValue::from_computed_value(linear_gradient)
                 )
             }
         }
     }
 }
 
 /// Computed values for an image according to CSS-IMAGES.
 /// https://drafts.csswg.org/css-images/#image-values
 #[derive(Clone, PartialEq)]
 #[cfg_attr(feature = "servo", derive(HeapSizeOf))]
 pub enum Image {
-    Url(Url, UrlExtraData),
+    Url(SpecifiedUrl),
     Gradient(Gradient),
 }
 
 impl fmt::Debug for Image {
     fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
         match *self {
-            Image::Url(ref url, ref _extra_data) => write!(f, "url(\"{}\")", url),
+            Image::Url(ref url) => url.to_css(f),
             Image::Gradient(ref grad) => {
                 if grad.repeating {
                     let _ = write!(f, "repeating-");
                 }
                 match grad.gradient_kind {
                     GradientKind::Linear(_) => write!(f, "linear-gradient({:?})", grad),
                     GradientKind::Radial(_, _) => write!(f, "radial-gradient({:?})", grad),
                 }
             },
         }
     }
 }
 
 impl ToCss for Image {
     fn to_css<W>(&self, dest: &mut W) -> fmt::Result where W: fmt::Write {
         match *self {
-            Image::Url(ref url, _) => {
-                url.to_css(dest)
-            }
+            Image::Url(ref url) => url.to_css(dest),
             Image::Gradient(ref gradient) => gradient.to_css(dest)
         }
     }
 }
 
 /// Computed values for a CSS gradient.
 /// https://drafts.csswg.org/css-images/#gradients
 #[derive(Clone, PartialEq)]
--- a/servo/components/style/values/computed/length.rs
+++ b/servo/components/style/values/computed/length.rs
@@ -7,17 +7,17 @@ use ordered_float::NotNaN;
 use std::fmt;
 use style_traits::ToCss;
 use super::{Number, ToComputedValue, Context};
 use values::{CSSFloat, Either, None_, specified};
 
 pub use cssparser::Color as CSSColor;
 pub use super::image::{EndingShape as GradientShape, Gradient, GradientKind, Image};
 pub use super::image::{LengthOrKeyword, LengthOrPercentageOrKeyword};
-pub use values::specified::{Angle, BorderStyle, Time, UrlExtraData, UrlOrNone};
+pub use values::specified::{Angle, BorderStyle, Time, UrlOrNone};
 
 #[derive(Clone, PartialEq, Copy, Debug)]
 #[cfg_attr(feature = "servo", derive(HeapSizeOf))]
 pub struct CalcLengthOrPercentage {
     pub length: Option<Au>,
     pub percentage: Option<CSSFloat>,
 }
 
--- a/servo/components/style/values/computed/mod.rs
+++ b/servo/components/style/values/computed/mod.rs
@@ -8,17 +8,18 @@ use properties::ComputedValues;
 use std::fmt;
 use style_traits::ToCss;
 use super::{CSSFloat, specified};
 
 pub use cssparser::Color as CSSColor;
 pub use self::image::{EndingShape as GradientShape, Gradient, GradientKind, Image};
 pub use self::image::{LengthOrKeyword, LengthOrPercentageOrKeyword};
 pub use super::{Either, None_};
-pub use super::specified::{Angle, BorderStyle, Time, UrlExtraData, UrlOrNone};
+pub use super::specified::{Angle, BorderStyle, Time, UrlOrNone};
+pub use super::specified::url::UrlExtraData;
 pub use self::length::{CalcLengthOrPercentage, Length, LengthOrNumber, LengthOrPercentage, LengthOrPercentageOrAuto};
 pub use self::length::{LengthOrPercentageOrAutoOrContent, LengthOrPercentageOrNone, LengthOrNone};
 
 pub mod basic_shape;
 pub mod image;
 pub mod length;
 pub mod position;
 
--- a/servo/components/style/values/specified/basic_shape.rs
+++ b/servo/components/style/values/specified/basic_shape.rs
@@ -7,46 +7,45 @@
 //!
 //! [basic-shape]: https://drafts.csswg.org/css-shapes/#typedef-basic-shape
 
 use cssparser::Parser;
 use parser::{Parse, ParserContext};
 use properties::shorthands::{parse_four_sides, serialize_four_sides};
 use std::fmt;
 use style_traits::ToCss;
-use url::Url;
 use values::computed::{ComputedValueAsSpecified, Context, ToComputedValue};
 use values::computed::basic_shape as computed_basic_shape;
 use values::specified::{BorderRadiusSize, LengthOrPercentage, Percentage};
-use values::specified::UrlExtraData;
 use values::specified::position::{Keyword, Position};
+use values::specified::url::SpecifiedUrl;
 
 /// A shape source, for some reference box
 ///
 /// clip-path uses ShapeSource<GeometryBox>,
 /// shape-outside uses ShapeSource<ShapeBox>
 #[derive(Clone, PartialEq, Debug)]
 #[cfg_attr(feature = "servo", derive(HeapSizeOf))]
 pub enum ShapeSource<T> {
-    Url(Url, UrlExtraData),
+    Url(SpecifiedUrl),
     Shape(BasicShape, Option<T>),
     Box(T),
     None,
 }
 
 impl<T> Default for ShapeSource<T> {
     fn default() -> Self {
         ShapeSource::None
     }
 }
 
 impl<T: ToCss> ToCss for ShapeSource<T> {
     fn to_css<W>(&self, dest: &mut W) -> fmt::Result where W: fmt::Write {
         match *self {
-            ShapeSource::Url(ref url, _) => url.to_css(dest),
+            ShapeSource::Url(ref url) => url.to_css(dest),
             ShapeSource::Shape(ref shape, Some(ref reference)) => {
                 try!(shape.to_css(dest));
                 try!(dest.write_str(" "));
                 reference.to_css(dest)
             }
             ShapeSource::Shape(ref shape, None) => shape.to_css(dest),
             ShapeSource::Box(ref reference) => reference.to_css(dest),
             ShapeSource::None => dest.write_str("none"),
@@ -54,23 +53,18 @@ impl<T: ToCss> ToCss for ShapeSource<T> 
         }
     }
 }
 
 impl<T: Parse + PartialEq + Copy> ShapeSource<T> {
     pub fn parse(context: &ParserContext, input: &mut Parser) -> Result<Self, ()> {
         if let Ok(_) = input.try(|input| input.expect_ident_matching("none")) {
             Ok(ShapeSource::None)
-        } else if let Ok(url) = input.try(|input| input.expect_url()) {
-            match UrlExtraData::make_from(context) {
-                Some(extra_data) => {
-                    Ok(ShapeSource::Url(context.parse_url(&url), extra_data))
-                },
-                None => Err(()),
-            }
+        } else if let Ok(url) = input.try(|input| SpecifiedUrl::parse(context, input)) {
+            Ok(ShapeSource::Url(url))
         } else {
             fn parse_component<U: Parse>(input: &mut Parser, component: &mut Option<U>) -> bool {
                 if component.is_some() {
                     return false; // already parsed this component
                 }
                 *component = input.try(U::parse).ok();
                 component.is_some()
             }
@@ -93,36 +87,36 @@ impl<T: Parse + PartialEq + Copy> ShapeS
 }
 
 impl<T: ToComputedValue> ToComputedValue for ShapeSource<T> {
     type ComputedValue = computed_basic_shape::ShapeSource<T::ComputedValue>;
 
     #[inline]
     fn to_computed_value(&self, cx: &Context) -> Self::ComputedValue {
         match *self {
-            ShapeSource::Url(ref url, ref data) => {
-                computed_basic_shape::ShapeSource::Url(url.clone(), data.clone())
+            ShapeSource::Url(ref url) => {
+                computed_basic_shape::ShapeSource::Url(url.to_computed_value(cx))
             }
             ShapeSource::Shape(ref shape, ref reference) => {
                 computed_basic_shape::ShapeSource::Shape(
                     shape.to_computed_value(cx),
                     reference.as_ref().map(|ref r| r.to_computed_value(cx)))
             }
             ShapeSource::Box(ref reference) => {
                 computed_basic_shape::ShapeSource::Box(reference.to_computed_value(cx))
             }
             ShapeSource::None => computed_basic_shape::ShapeSource::None,
         }
     }
 
     #[inline]
     fn from_computed_value(computed: &Self::ComputedValue) -> Self {
         match *computed {
-            computed_basic_shape::ShapeSource::Url(ref url, ref data) => {
-                ShapeSource::Url(url.clone(), data.clone())
+            computed_basic_shape::ShapeSource::Url(ref url) => {
+                ShapeSource::Url(SpecifiedUrl::from_computed_value(url))
             }
             computed_basic_shape::ShapeSource::Shape(ref shape, ref reference) => {
                 ShapeSource::Shape(
                     ToComputedValue::from_computed_value(shape),
                     reference.as_ref().map(|r| ToComputedValue::from_computed_value(r)))
             }
             computed_basic_shape::ShapeSource::Box(ref reference) => {
                 ShapeSource::Box(ToComputedValue::from_computed_value(reference))
--- a/servo/components/style/values/specified/image.rs
+++ b/servo/components/style/values/specified/image.rs
@@ -6,59 +6,55 @@
 //! [`image`][image]s
 //!
 //! [image]: https://drafts.csswg.org/css-images/#image-values
 
 use cssparser::Parser;
 use parser::{Parse, ParserContext};
 use std::f32::consts::PI;
 use std::fmt;
+use std::sync::Arc;
 use style_traits::ToCss;
 use url::Url;
 use values::computed::ComputedValueAsSpecified;
-use values::specified::{Angle, CSSColor, Length, LengthOrPercentage, UrlExtraData};
+use values::specified::{Angle, CSSColor, Length, LengthOrPercentage};
 use values::specified::position::Position;
+use values::specified::url::{SpecifiedUrl, UrlExtraData};
 
 /// Specified values for an image according to CSS-IMAGES.
 /// https://drafts.csswg.org/css-images/#image-values
 #[derive(Clone, PartialEq, Debug)]
 #[cfg_attr(feature = "servo", derive(HeapSizeOf))]
 pub enum Image {
-    Url(Url, UrlExtraData),
+    Url(SpecifiedUrl),
     Gradient(Gradient),
 }
 
 impl ToCss for Image {
     fn to_css<W>(&self, dest: &mut W) -> fmt::Result where W: fmt::Write {
         match *self {
-            Image::Url(ref url, ref _extra_data) => {
-                url.to_css(dest)
-            }
-            Image::Gradient(ref gradient) => gradient.to_css(dest)
+            Image::Url(ref url_value) => url_value.to_css(dest),
+            Image::Gradient(ref gradient) => gradient.to_css(dest),
         }
     }
 }
 
 impl Image {
     pub fn parse(context: &ParserContext, input: &mut Parser) -> Result<Image, ()> {
-        if let Ok(url) = input.try(|input| input.expect_url()) {
-            match UrlExtraData::make_from(context) {
-                Some(extra_data) => {
-                    Ok(Image::Url(context.parse_url(&url), extra_data))
-                },
-                None => {
-                    // FIXME(heycam) should ensure we always have a principal, etc., when
-                    // parsing style attributes and re-parsing due to CSS Variables.
-                    println!("stylo: skipping declaration without ParserContextExtraData");
-                    Err(())
-                },
-            }
-        } else {
-            Ok(Image::Gradient(try!(Gradient::parse_function(input))))
+        if let Ok(url) = input.try(|input| SpecifiedUrl::parse(context, input)) {
+            return Ok(Image::Url(url));
         }
+
+        Ok(Image::Gradient(try!(Gradient::parse_function(input))))
+    }
+
+    /// Creates an already specified image value from an already resolved URL
+    /// for insertion in the cascade.
+    pub fn for_cascade(url: Option<Arc<Url>>, extra_data: UrlExtraData) -> Self {
+        Image::Url(SpecifiedUrl::for_cascade(url, extra_data))
     }
 }
 
 /// Specified values for a CSS gradient.
 /// https://drafts.csswg.org/css-images/#gradients
 #[derive(Clone, PartialEq, Debug)]
 #[cfg_attr(feature = "servo", derive(HeapSizeOf))]
 pub struct Gradient {
--- a/servo/components/style/values/specified/mod.rs
+++ b/servo/components/style/values/specified/mod.rs
@@ -1,40 +1,37 @@
 /* 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 app_units::Au;
 use cssparser::{self, Parser, Token};
 use euclid::size::Size2D;
-#[cfg(feature = "gecko")]
-use gecko_bindings::sugar::refptr::{GeckoArcPrincipal, GeckoArcURI};
 use parser::ParserContext;
-#[cfg(feature = "gecko")]
-use parser::ParserContextExtraData;
+use self::url::SpecifiedUrl;
 use std::ascii::AsciiExt;
 use std::f32::consts::PI;
 use std::fmt;
 use std::ops::Mul;
 use style_traits::ToCss;
 use super::{CSSFloat, HasViewportPercentage, NoViewportPercentage};
 use super::computed::{ComputedValueAsSpecified, Context, ToComputedValue};
-use url::Url;
 
 pub use self::image::{AngleOrCorner, ColorStop, EndingShape as GradientEndingShape, Gradient};
 pub use self::image::{GradientKind, HorizontalDirection, Image, LengthOrKeyword, LengthOrPercentageOrKeyword};
 pub use self::image::{SizeKeyword, VerticalDirection};
 pub use self::length::{FontRelativeLength, ViewportPercentageLength, CharacterWidth, Length, CalcLengthOrPercentage};
 pub use self::length::{Percentage, LengthOrNone, LengthOrNumber, LengthOrPercentage, LengthOrPercentageOrAuto};
 pub use self::length::{LengthOrPercentageOrNone, LengthOrPercentageOrAutoOrContent, CalcUnit};
 
 pub mod basic_shape;
 pub mod image;
 pub mod length;
 pub mod position;
+pub mod url;
 
 impl NoViewportPercentage for i32 {}  // For PropertyDeclaration::Order
 
 #[derive(Clone, PartialEq, Debug)]
 #[cfg_attr(feature = "servo", derive(HeapSizeOf))]
 pub struct CSSColor {
     pub parsed: cssparser::Color,
     pub authored: Option<String>,
@@ -258,52 +255,16 @@ impl Angle {
             "grad" => Ok(Angle(value * RAD_PER_GRAD)),
             "turn" => Ok(Angle(value * RAD_PER_TURN)),
             "rad" => Ok(Angle(value)),
              _ => Err(())
         }
     }
 }
 
-#[derive(PartialEq, Clone, Debug)]
-#[cfg_attr(feature = "servo", derive(HeapSizeOf))]
-pub struct UrlExtraData {
-    #[cfg(feature = "gecko")]
-    pub base: GeckoArcURI,
-    #[cfg(feature = "gecko")]
-    pub referrer: GeckoArcURI,
-    #[cfg(feature = "gecko")]
-    pub principal: GeckoArcPrincipal,
-}
-
-impl UrlExtraData {
-    #[cfg(feature = "servo")]
-    pub fn make_from(_: &ParserContext) -> Option<UrlExtraData> {
-        Some(UrlExtraData { })
-    }
-
-    #[cfg(feature = "gecko")]
-    pub fn make_from(context: &ParserContext) -> Option<UrlExtraData> {
-        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,
-        }
-    }
-}
-
 pub fn parse_border_radius(input: &mut Parser) -> Result<BorderRadiusSize, ()> {
     input.try(BorderRadiusSize::parse).or_else(|()| {
             match_ignore_ascii_case! { try!(input.expect_ident()),
                 "thin" => Ok(BorderRadiusSize::circle(
                                  LengthOrPercentage::Length(Length::from_px(1.)))),
                 "medium" => Ok(BorderRadiusSize::circle(
                                    LengthOrPercentage::Length(Length::from_px(3.)))),
                 "thick" => Ok(BorderRadiusSize::circle(
@@ -541,49 +502,33 @@ impl ToCss for Opacity {
     fn to_css<W>(&self, dest: &mut W) -> fmt::Result where W: fmt::Write {
         self.0.to_css(dest)
     }
 }
 
 #[derive(PartialEq, Clone, Debug)]
 #[cfg_attr(feature = "servo", derive(HeapSizeOf))]
 pub enum UrlOrNone {
-    Url(Url, UrlExtraData),
+    Url(SpecifiedUrl),
     None,
 }
 
 impl ComputedValueAsSpecified for UrlOrNone {}
 impl NoViewportPercentage for UrlOrNone {}
 
 impl ToCss for UrlOrNone {
     fn to_css<W>(&self, dest: &mut W) -> fmt::Result where W: fmt::Write {
         match *self {
-            UrlOrNone::Url(ref url, _) => {
-                url.to_css(dest)
-            }
-            UrlOrNone::None => {
-                try!(dest.write_str("none"));
-                Ok(())
-            }
+            UrlOrNone::Url(ref url) => url.to_css(dest),
+            UrlOrNone::None => dest.write_str("none"),
         }
     }
 }
 
 impl UrlOrNone {
     pub fn parse(context: &ParserContext, input: &mut Parser) -> Result<UrlOrNone, ()> {
         if input.try(|input| input.expect_ident_matching("none")).is_ok() {
             return Ok(UrlOrNone::None);
         }
 
-        let url = context.parse_url(&*try!(input.expect_url()));
-        match UrlExtraData::make_from(context) {
-            Some(extra_data) => {
-                Ok(UrlOrNone::Url(url, extra_data))
-            },
-            _ => {
-                // FIXME(heycam) should ensure we always have a principal, etc., when parsing
-                // style attributes and re-parsing due to CSS Variables.
-                println!("stylo: skipping UrlOrNone declaration without ParserContextExtraData");
-                Err(())
-            },
-        }
+        Ok(UrlOrNone::Url(try!(SpecifiedUrl::parse(context, input))))
     }
 }
new file mode 100644
--- /dev/null
+++ b/servo/components/style/values/specified/url.rs
@@ -0,0 +1,165 @@
+/* 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::sugar::refptr::{GeckoArcPrincipal, GeckoArcURI};
+use parser::ParserContext;
+#[cfg(feature = "gecko")]
+use parser::ParserContextExtraData;
+use std::fmt::{self, Write};
+use std::ptr;
+use std::sync::Arc;
+use style_traits::ToCss;
+use url::Url;
+use values::computed::ComputedValueAsSpecified;
+
+#[derive(PartialEq, Clone, Debug)]
+#[cfg_attr(feature = "servo", derive(HeapSizeOf))]
+pub struct UrlExtraData {
+    #[cfg(feature = "gecko")]
+    pub base: GeckoArcURI,
+    #[cfg(feature = "gecko")]
+    pub referrer: GeckoArcURI,
+    #[cfg(feature = "gecko")]
+    pub principal: GeckoArcPrincipal,
+}
+
+impl UrlExtraData {
+    #[cfg(feature = "servo")]
+    pub fn make_from(_: &ParserContext) -> Option<UrlExtraData> {
+        Some(UrlExtraData { })
+    }
+
+    #[cfg(feature = "gecko")]
+    pub fn make_from(context: &ParserContext) -> Option<UrlExtraData> {
+        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))]
+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<Arc<String>>,
+
+    /// The resolved value for the url, if valid.
+    resolved: Option<Arc<Url>>,
+
+    /// Extra data used for Stylo.
+    extra_data: UrlExtraData,
+}
+
+impl SpecifiedUrl {
+    pub fn parse(context: &ParserContext, input: &mut Parser) -> Result<Self, ()> {
+        let url = try!(input.expect_url());
+
+        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.
+                println!("stylo: skipping declaration without ParserContextExtraData");
+                return Err(())
+            },
+        };
+
+        let serialization = Arc::new(url.into_owned());
+        let resolved = context.base_url.join(&serialization).ok().map(Arc::new);
+        Ok(SpecifiedUrl {
+            original: Some(serialization),
+            resolved: resolved,
+            extra_data: extra_data,
+        })
+    }
+
+    pub fn extra_data(&self) -> &UrlExtraData {
+        &self.extra_data
+    }
+
+    pub fn url(&self) -> Option<&Arc<Url>> {
+        self.resolved.as_ref()
+    }
+
+    /// Little helper for Gecko's ffi.
+    pub fn as_slice_components(&self) -> (*const u8, usize) {
+        match self.resolved {
+            Some(ref url) => (url.as_str().as_ptr(), url.as_str().len()),
+            None => (ptr::null(), 0),
+        }
+    }
+
+    /// Creates an already specified url value from an already resolved URL
+    /// for insertion in the cascade.
+    pub fn for_cascade(url: Option<Arc<Url>>, extra_data: UrlExtraData) -> Self {
+        SpecifiedUrl {
+            original: None,
+            resolved: url,
+            extra_data: extra_data,
+        }
+    }
+
+    // Just for unit tests, don't use outside of them!
+    #[cfg(feature = "servo")]
+    pub fn new_for_testing(url: &str) -> Self {
+        SpecifiedUrl {
+            original: Some(Arc::new(url.into())),
+            resolved: Url::parse(url).ok().map(Arc::new),
+            extra_data: UrlExtraData {}
+        }
+    }
+}
+
+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 ToCss for SpecifiedUrl {
+    fn to_css<W>(&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 {}
--- a/servo/components/style_traits/Cargo.toml
+++ b/servo/components/style_traits/Cargo.toml
@@ -17,9 +17,8 @@ servo = ["heapsize", "heapsize_derive", 
 app_units = "0.3"
 cssparser = "0.7"
 euclid = "0.10.1"
 heapsize = {version = "0.3.0", optional = true}
 heapsize_derive = {version = "0.1", optional = true}
 rustc-serialize = "0.3"
 serde = {version = "0.8", optional = true}
 serde_derive = {version = "0.8", optional = true}
-url = "1.2"
--- a/servo/components/style_traits/lib.rs
+++ b/servo/components/style_traits/lib.rs
@@ -18,17 +18,16 @@ extern crate app_units;
 #[macro_use]
 extern crate cssparser;
 extern crate euclid;
 #[cfg(feature = "servo")] extern crate heapsize;
 #[cfg(feature = "servo")] #[macro_use] extern crate heapsize_derive;
 extern crate rustc_serialize;
 #[cfg(feature = "servo")] extern crate serde;
 #[cfg(feature = "servo")] #[macro_use] extern crate serde_derive;
-extern crate url;
 
 /// Opaque type stored in type-unsafe work queues for parallel layout.
 /// Must be transmutable to and from TNode.
 pub type UnsafeNode = (usize, usize);
 
 /// One CSS "px" in the coordinate system of the "initial viewport":
 /// http://www.w3.org/TR/css-device-adapt/#initial-viewport
 ///
--- a/servo/components/style_traits/values.rs
+++ b/servo/components/style_traits/values.rs
@@ -1,16 +1,14 @@
 /* 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 app_units::Au;
-use cssparser::CssStringWriter;
-use std::fmt::{self, Write};
-use url::Url;
+use std::fmt;
 
 /// The real ToCss trait can't be implemented for types in crates that don't
 /// depend on each other.
 pub trait ToCss {
     /// Serialize `self` in CSS syntax, writing to `dest`.
     fn to_css<W>(&self, dest: &mut W) -> fmt::Result where W: fmt::Write;
 
     /// Serialize `self` in CSS syntax and return a string.
@@ -25,25 +23,16 @@ pub trait ToCss {
 }
 
 impl ToCss for Au {
     fn to_css<W>(&self, dest: &mut W) -> fmt::Result where W: fmt::Write {
         write!(dest, "{}px", self.to_f64_px())
     }
 }
 
-impl ToCss for Url {
-    fn to_css<W>(&self, dest: &mut W) -> fmt::Result where W: fmt::Write {
-        try!(dest.write_str("url(\""));
-        try!(write!(CssStringWriter::new(dest), "{}", self));
-        try!(dest.write_str("\")"));
-        Ok(())
-    }
-}
-
 macro_rules! impl_to_css_for_predefined_type {
     ($name: ty) => {
         impl<'a> ToCss for $name {
             fn to_css<W>(&self, dest: &mut W) -> fmt::Result where W: fmt::Write {
                 ::cssparser::ToCss::to_css(self, dest)
             }
         }
     };
--- a/servo/ports/cef/Cargo.lock
+++ b/servo/ports/cef/Cargo.lock
@@ -2352,17 +2352,16 @@ dependencies = [
  "app_units 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "cssparser 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "euclid 0.10.2 (registry+https://github.com/rust-lang/crates.io-index)",
  "heapsize 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)",
  "heapsize_derive 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)",
  "rustc-serialize 0.3.19 (registry+https://github.com/rust-lang/crates.io-index)",
  "serde 0.8.17 (registry+https://github.com/rust-lang/crates.io-index)",
  "serde_derive 0.8.17 (registry+https://github.com/rust-lang/crates.io-index)",
- "url 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
 ]
 
 [[package]]
 name = "syn"
 version = "0.9.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 dependencies = [
  "quote 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)",
--- a/servo/ports/geckolib/Cargo.lock
+++ b/servo/ports/geckolib/Cargo.lock
@@ -368,17 +368,16 @@ dependencies = [
 [[package]]
 name = "style_traits"
 version = "0.0.1"
 dependencies = [
  "app_units 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "cssparser 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "euclid 0.10.2 (registry+https://github.com/rust-lang/crates.io-index)",
  "rustc-serialize 0.3.19 (registry+https://github.com/rust-lang/crates.io-index)",
- "url 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
 ]
 
 [[package]]
 name = "stylo_tests"
 version = "0.0.1"
 dependencies = [
  "app_units 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "cssparser 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)",
--- a/servo/tests/unit/style/properties/serialization.rs
+++ b/servo/tests/unit/style/properties/serialization.rs
@@ -4,19 +4,18 @@
 
 pub use std::sync::Arc;
 pub use style::computed_values::display::T::inline_block;
 pub use style::properties::{DeclaredValue, PropertyDeclaration, PropertyDeclarationBlock, Importance};
 pub use style::values::specified::{BorderStyle, BorderWidth, CSSColor, Length};
 pub use style::values::specified::{LengthOrPercentage, LengthOrPercentageOrAuto, LengthOrPercentageOrAutoOrContent};
 pub use style::properties::longhands::outline_color::computed_value::T as ComputedColor;
 pub use style::values::RGBA;
-pub use style::values::specified::UrlExtraData;
+pub use style::values::specified::url::{UrlExtraData, SpecifiedUrl};
 pub use style_traits::ToCss;
-pub use url::Url;
 
 #[test]
 fn property_declaration_block_should_serialize_correctly() {
     use style::properties::longhands::overflow_x::computed_value::T as OverflowXValue;
     use style::properties::longhands::overflow_y::computed_value::T as OverflowYContainer;
 
     let declarations = vec![
         (PropertyDeclaration::Width(
@@ -425,19 +424,17 @@ mod shorthand_serialization {
         use super::*;
 
         #[test]
         fn list_style_should_show_all_properties_when_values_are_set() {
             let mut properties = Vec::new();
 
             let position = DeclaredValue::Value(ListStylePosition::inside);
             let image = DeclaredValue::Value(ListStyleImage::Url(
-                Url::parse("http://servo/test.png").unwrap(),
-                UrlExtraData {},
-            ));
+                SpecifiedUrl::new_for_testing("http://servo/test.png")));
             let style_type = DeclaredValue::Value(ListStyleType::disc);
 
             properties.push(PropertyDeclaration::ListStylePosition(position));
             properties.push(PropertyDeclaration::ListStyleImage(image));
             properties.push(PropertyDeclaration::ListStyleType(style_type));
 
             let serialization = shorthand_properties_to_string(properties);
             assert_eq!(serialization, "list-style: inside url(\"http://servo/test.png\") disc;");
@@ -742,18 +739,17 @@ mod shorthand_serialization {
                     vert_position: Some(LengthOrPercentage::Length(Length::from_px(4f32)))
                 }
             );
 
             let repeat = single_vec_keyword_value!(repeat, repeat_x);
             let attachment = single_vec_keyword_value!(attachment, scroll);
 
             let image = single_vec_value!(image,
-                Some(Image::Url(Url::parse("http://servo/test.png").unwrap(),
-                                UrlExtraData {})));
+                Some(Image::Url(SpecifiedUrl::new_for_testing("http://servo/test.png"))));
 
             let size = single_vec_variant_value!(size,
                 size::single_value::SpecifiedValue::Explicit(
                     size::single_value::ExplicitSize {
                         width: LengthOrPercentageOrAuto::Length(Length::from_px(70f32)),
                         height: LengthOrPercentageOrAuto::Length(Length::from_px(50f32))
                     }
                 )
@@ -797,18 +793,17 @@ mod shorthand_serialization {
                     vert_position: Some(LengthOrPercentage::Length(Length::from_px(4f32)))
                 }
             );
 
             let repeat = single_vec_keyword_value!(repeat, repeat_x);
             let attachment = single_vec_keyword_value!(attachment, scroll);
 
             let image = single_vec_value!(image,
-                        Some(Image::Url(Url::parse("http://servo/test.png").unwrap(),
-                                        UrlExtraData {})));
+                        Some(Image::Url(SpecifiedUrl::new_for_testing("http://servo/test.png"))));
 
             let size = single_vec_variant_value!(size,
                 size::single_value::SpecifiedValue::Explicit(
                     size::single_value::ExplicitSize {
                         width: LengthOrPercentageOrAuto::Length(Length::from_px(70f32)),
                         height: LengthOrPercentageOrAuto::Length(Length::from_px(50f32))
                     }
                 )
@@ -912,18 +907,17 @@ mod shorthand_serialization {
         }
 
         #[test]
         fn mask_should_serialize_all_available_properties_when_specified() {
             let mut properties = Vec::new();
 
             let image = single_vec_value_typedef!(image,
                 image::single_value::SpecifiedValue::Image(
-                    Image::Url(Url::parse("http://servo/test.png").unwrap(),
-                    UrlExtraData {})));
+                    Image::Url(SpecifiedUrl::new_for_testing("http://servo/test.png"))));
 
             let mode = single_vec_keyword_value!(mode, luminance);
 
             let position = single_vec_value_typedef!(position,
                 Position {
                     horiz_keyword: None,
                     horiz_position: Some(LengthOrPercentage::Length(Length::from_px(7f32))),
                     vert_keyword: None,
@@ -963,18 +957,17 @@ mod shorthand_serialization {
         }
 
         #[test]
         fn mask_should_combine_origin_and_clip_properties_when_equal() {
             let mut properties = Vec::new();
 
             let image = single_vec_value_typedef!(image,
                 image::single_value::SpecifiedValue::Image(
-                    Image::Url(Url::parse("http://servo/test.png").unwrap(),
-                    UrlExtraData {})));
+                    Image::Url(SpecifiedUrl::new_for_testing("http://servo/test.png"))));
 
             let mode = single_vec_keyword_value!(mode, luminance);
 
             let position = single_vec_value_typedef!(position,
                 Position {
                     horiz_keyword: None,
                     horiz_position: Some(LengthOrPercentage::Length(Length::from_px(7f32))),
                     vert_keyword: None,