servo: Merge #20238 - Construct URLValue eagerly and share it between specified value and style structs (from upsuper:url-value); r=emilio
authorXidorn Quan <me@upsuper.org>
Thu, 08 Mar 2018 08:06:35 -0500
changeset 462197 815594262be005de3594174206ccbb4aa9cedf90
parent 462196 00c1789cdd6e6ae437589cd6e6c12f670f61d1a1
child 462198 78cff5f101db66654250ca355e14e0b36adf2715
push id1683
push usersfraser@mozilla.com
push dateThu, 26 Apr 2018 16:43:40 +0000
treeherdermozilla-release@5af6cb21869d [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersemilio
bugs20238, 1443046
milestone60.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
servo: Merge #20238 - Construct URLValue eagerly and share it between specified value and style structs (from upsuper:url-value); r=emilio This is the Servo side change of [bug 1443046](https://bugzilla.mozilla.org/show_bug.cgi?id=1443046). Source-Repo: https://github.com/servo/servo Source-Revision: 1d122c250c906358a91b607b0fcc720546d04134
servo/components/script/stylesheet_loader.rs
servo/components/style/gecko/conversions.rs
servo/components/style/gecko/generated/bindings.rs
servo/components/style/gecko/url.rs
servo/components/style/gecko_bindings/sugar/ns_css_value.rs
servo/components/style/properties/gecko.mako.rs
servo/components/style/properties/longhand/box.mako.rs
servo/components/style/properties/longhand/inherited_svg.mako.rs
servo/components/style/properties/longhand/list.mako.rs
servo/components/style/servo/url.rs
servo/components/style/stylesheets/document_rule.rs
servo/components/style/stylesheets/import_rule.rs
servo/components/style/stylesheets/loader.rs
servo/components/style/stylesheets/rule_parser.rs
servo/components/style/values/computed/counters.rs
servo/components/style/values/computed/image.rs
servo/components/style/values/computed/mod.rs
servo/components/style/values/computed/pointing.rs
servo/components/style/values/mod.rs
servo/components/style/values/specified/counters.rs
servo/components/style/values/specified/image.rs
servo/components/style/values/specified/list.rs
servo/components/style/values/specified/mod.rs
servo/components/style/values/specified/pointing.rs
servo/ports/geckolib/glue.rs
servo/ports/geckolib/stylesheet_loader.rs
servo/ports/geckolib/tests/size_of.rs
--- a/servo/components/script/stylesheet_loader.rs
+++ b/servo/components/script/stylesheet_loader.rs
@@ -29,17 +29,17 @@ use std::mem;
 use std::sync::Mutex;
 use std::sync::atomic::AtomicBool;
 use style::media_queries::MediaList;
 use style::parser::ParserContext;
 use style::shared_lock::{Locked, SharedRwLock};
 use style::stylesheets::{CssRules, ImportRule, Namespaces, Stylesheet, StylesheetContents, Origin};
 use style::stylesheets::StylesheetLoader as StyleStylesheetLoader;
 use style::stylesheets::import_rule::ImportSheet;
-use style::values::specified::url::SpecifiedUrl;
+use style::values::CssUrl;
 
 pub trait StylesheetOwner {
     /// Returns whether this element was inserted by the parser (i.e., it should
     /// trigger a document-load-blocking load).
     fn parser_inserted(&self) -> bool;
 
     /// Which referrer policy should loads triggered by this owner follow, or
     /// `None` for the default.
@@ -271,17 +271,17 @@ impl<'a> StylesheetLoader<'a> {
     }
 }
 
 impl<'a> StyleStylesheetLoader for StylesheetLoader<'a> {
     /// Request a stylesheet after parsing a given `@import` rule, and return
     /// the constructed `@import` rule.
     fn request_stylesheet(
         &self,
-        url: SpecifiedUrl,
+        url: CssUrl,
         source_location: SourceLocation,
         context: &ParserContext,
         lock: &SharedRwLock,
         media: Arc<Locked<MediaList>>,
     ) -> Arc<Locked<ImportRule>> {
         let sheet = Arc::new(Stylesheet {
             contents: StylesheetContents {
                 rules: CssRules::new(Vec::new(), lock),
--- a/servo/components/style/gecko/conversions.rs
+++ b/servo/components/style/gecko/conversions.rs
@@ -12,17 +12,17 @@ use app_units::Au;
 use gecko::values::{convert_rgba_to_nscolor, GeckoStyleCoordConvertible};
 use gecko_bindings::bindings::{Gecko_CreateGradient, Gecko_SetGradientImageValue, Gecko_SetLayerImageImageValue};
 use gecko_bindings::bindings::{Gecko_InitializeImageCropRect, Gecko_SetImageElement};
 use gecko_bindings::structs::{self, nsCSSUnit, nsStyleCoord_CalcValue};
 use gecko_bindings::structs::{nsStyleImage, nsresult, SheetType};
 use gecko_bindings::sugar::ns_style_coord::{CoordDataValue, CoordData, CoordDataMut};
 use std::f32::consts::PI;
 use stylesheets::{Origin, RulesMutateError};
-use values::computed::{Angle, CalcLengthOrPercentage, ComputedUrl, Gradient, Image};
+use values::computed::{Angle, CalcLengthOrPercentage, ComputedImageUrl, Gradient, Image};
 use values::computed::{Integer, LengthOrPercentage, LengthOrPercentageOrAuto, Percentage, TextAlign};
 use values::generics::box_::VerticalAlign;
 use values::generics::grid::{TrackListValue, TrackSize};
 use values::generics::image::{CompatMode, Image as GenericImage, GradientItem};
 use values::generics::rect::Rect;
 
 impl From<CalcLengthOrPercentage> for nsStyleCoord_CalcValue {
     fn from(other: CalcLengthOrPercentage) -> nsStyleCoord_CalcValue {
@@ -150,22 +150,22 @@ impl nsStyleImage {
     /// Set a given Servo `Image` value into this `nsStyleImage`.
     pub fn set(&mut self, image: Image) {
         match image {
             GenericImage::Gradient(boxed_gradient) => {
                 self.set_gradient(*boxed_gradient)
             },
             GenericImage::Url(ref url) => {
                 unsafe {
-                    Gecko_SetLayerImageImageValue(self, url.image_value.clone().unwrap().get());
+                    Gecko_SetLayerImageImageValue(self, url.image_value.get());
                 }
             },
             GenericImage::Rect(ref image_rect) => {
                 unsafe {
-                    Gecko_SetLayerImageImageValue(self, image_rect.url.image_value.clone().unwrap().get());
+                    Gecko_SetLayerImageImageValue(self, image_rect.url.image_value.get());
                     Gecko_InitializeImageCropRect(self);
 
                     // Set CropRect
                     let ref mut rect = *self.mCropRect.mPtr;
                     image_rect.top.to_gecko_style_coord(&mut rect.data_at_mut(0));
                     image_rect.right.to_gecko_style_coord(&mut rect.data_at_mut(1));
                     image_rect.bottom.to_gecko_style_coord(&mut rect.data_at_mut(2));
                     image_rect.left.to_gecko_style_coord(&mut rect.data_at_mut(3));
@@ -414,23 +414,21 @@ impl nsStyleImage {
                 use gecko_string_cache::Atom;
                 let atom = Gecko_GetImageElement(self);
                 Some(GenericImage::Element(Atom::from(atom)))
             },
             _ => panic!("Unexpected image type")
         }
     }
 
-    unsafe fn get_image_url(self: &nsStyleImage) -> ComputedUrl {
+    unsafe fn get_image_url(self: &nsStyleImage) -> ComputedImageUrl {
         use gecko_bindings::bindings::Gecko_GetURLValue;
         let url_value = Gecko_GetURLValue(self);
-        let mut url = ComputedUrl::from_url_value_data(url_value.as_ref().unwrap())
-                                    .expect("Could not convert to ComputedUrl");
-        url.build_image_value();
-        url
+        ComputedImageUrl::from_url_value_data(url_value.as_ref().unwrap())
+            .expect("Could not convert to ComputedUrl")
     }
 
     unsafe fn get_gradient(self: &nsStyleImage) -> Box<Gradient> {
         use gecko::values::convert_nscolor_to_rgba;
         use gecko_bindings::bindings::Gecko_GetGradientImageValue;
         use gecko_bindings::structs::{NS_STYLE_GRADIENT_SHAPE_CIRCULAR, NS_STYLE_GRADIENT_SHAPE_ELLIPTICAL};
         use gecko_bindings::structs::{NS_STYLE_GRADIENT_SHAPE_LINEAR, NS_STYLE_GRADIENT_SIZE_CLOSEST_CORNER};
         use gecko_bindings::structs::{NS_STYLE_GRADIENT_SIZE_CLOSEST_SIDE, NS_STYLE_GRADIENT_SIZE_EXPLICIT_SIZE};
--- a/servo/components/style/gecko/generated/bindings.rs
+++ b/servo/components/style/gecko/generated/bindings.rs
@@ -1373,32 +1373,32 @@ extern "C" {
 }
 extern "C" {
     pub fn Gecko_NewBasicShape(shape: *mut StyleShapeSource, type_: StyleBasicShapeType);
 }
 extern "C" {
     pub fn Gecko_NewShapeImage(shape: *mut StyleShapeSource);
 }
 extern "C" {
-    pub fn Gecko_StyleShapeSource_SetURLValue(shape: *mut StyleShapeSource, uri: ServoBundledURI);
+    pub fn Gecko_StyleShapeSource_SetURLValue(shape: *mut StyleShapeSource, uri: *mut URLValue);
 }
 extern "C" {
     pub fn Gecko_ResetFilters(effects: *mut nsStyleEffects, new_len: usize);
 }
 extern "C" {
     pub fn Gecko_CopyFiltersFrom(aSrc: *mut nsStyleEffects, aDest: *mut nsStyleEffects);
 }
 extern "C" {
-    pub fn Gecko_nsStyleFilter_SetURLValue(effects: *mut nsStyleFilter, uri: ServoBundledURI);
+    pub fn Gecko_nsStyleFilter_SetURLValue(effects: *mut nsStyleFilter, uri: *mut URLValue);
 }
 extern "C" {
     pub fn Gecko_nsStyleSVGPaint_CopyFrom(dest: *mut nsStyleSVGPaint, src: *const nsStyleSVGPaint);
 }
 extern "C" {
-    pub fn Gecko_nsStyleSVGPaint_SetURLValue(paint: *mut nsStyleSVGPaint, uri: ServoBundledURI);
+    pub fn Gecko_nsStyleSVGPaint_SetURLValue(paint: *mut nsStyleSVGPaint, uri: *mut URLValue);
 }
 extern "C" {
     pub fn Gecko_nsStyleSVGPaint_Reset(paint: *mut nsStyleSVGPaint);
 }
 extern "C" {
     pub fn Gecko_nsStyleSVG_SetDashArrayLength(svg: *mut nsStyleSVG, len: u32);
 }
 extern "C" {
@@ -1409,16 +1409,19 @@ extern "C" {
 }
 extern "C" {
     pub fn Gecko_nsStyleSVG_CopyContextProperties(dst: *mut nsStyleSVG, src: *const nsStyleSVG);
 }
 extern "C" {
     pub fn Gecko_NewURLValue(uri: ServoBundledURI) -> *mut URLValue;
 }
 extern "C" {
+    pub fn Gecko_URLValue_SizeOfIncludingThis(url: *mut URLValue) -> usize;
+}
+extern "C" {
     pub fn Gecko_AddRefCSSURLValueArbitraryThread(aPtr: *mut URLValue);
 }
 extern "C" {
     pub fn Gecko_ReleaseCSSURLValueArbitraryThread(aPtr: *mut URLValue);
 }
 extern "C" {
     pub fn Gecko_AddRefURLExtraDataArbitraryThread(aPtr: *mut RawGeckoURLExtraData);
 }
@@ -1517,17 +1520,17 @@ extern "C" {
 }
 extern "C" {
     pub fn Gecko_CSSValue_SetAtomIdent(css_value: nsCSSValueBorrowedMut, atom: *mut nsAtom);
 }
 extern "C" {
     pub fn Gecko_CSSValue_SetArray(css_value: nsCSSValueBorrowedMut, len: i32);
 }
 extern "C" {
-    pub fn Gecko_CSSValue_SetURL(css_value: nsCSSValueBorrowedMut, uri: ServoBundledURI);
+    pub fn Gecko_CSSValue_SetURL(css_value: nsCSSValueBorrowedMut, uri: *mut URLValue);
 }
 extern "C" {
     pub fn Gecko_CSSValue_SetInt(css_value: nsCSSValueBorrowedMut, integer: i32, unit: nsCSSUnit);
 }
 extern "C" {
     pub fn Gecko_CSSValue_SetFloat(css_value: nsCSSValueBorrowedMut, value: f32, unit: nsCSSUnit);
 }
 extern "C" {
--- a/servo/components/style/gecko/url.rs
+++ b/servo/components/style/gecko/url.rs
@@ -1,98 +1,78 @@
 /* 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::Parser;
+use gecko_bindings::bindings;
 use gecko_bindings::structs::{ServoBundledURI, URLExtraData};
 use gecko_bindings::structs::mozilla::css::URLValueData;
 use gecko_bindings::structs::root::{nsStyleImageRequest, RustString};
-use gecko_bindings::structs::root::mozilla::css::ImageValue;
+use gecko_bindings::structs::root::mozilla::css::{ImageValue, URLValue};
 use gecko_bindings::sugar::refptr::RefPtr;
 use malloc_size_of::{MallocSizeOf, MallocSizeOfOps};
-use parser::ParserContext;
+use parser::{Parse, ParserContext};
 use servo_arc::{Arc, RawOffsetArc};
 use std::mem;
 use style_traits::ParseError;
 
-/// A specified url() value for gecko. Gecko does not eagerly resolve SpecifiedUrls.
+/// A CSS url() value for gecko.
 #[css(function = "url")]
 #[derive(Clone, Debug, PartialEq, ToCss)]
-pub struct SpecifiedUrl {
+pub struct CssUrl {
     /// The URL in unresolved string form.
     ///
     /// Refcounted since cloning this should be cheap and data: uris can be
     /// really large.
     serialization: Arc<String>,
 
     /// The URL extra data.
     #[css(skip)]
     pub extra_data: RefPtr<URLExtraData>,
+}
 
-    /// Cache ImageValue, if any, so that we can reuse it while rematching a
-    /// a property with this specified url value.
-    #[css(skip)]
-    pub image_value: Option<RefPtr<ImageValue>>,
-}
-trivial_to_computed_value!(SpecifiedUrl);
-
-impl SpecifiedUrl {
+impl CssUrl {
     /// 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: String,
                                  context: &ParserContext)
                                  -> Result<Self, ParseError<'a>> {
-        Ok(SpecifiedUrl {
+        Ok(CssUrl {
             serialization: Arc::new(url),
             extra_data: context.url_data.clone(),
-            image_value: None,
         })
     }
 
     /// 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
     }
 
     /// Convert from URLValueData to SpecifiedUrl.
-    pub unsafe fn from_url_value_data(url: &URLValueData)
-                                       -> Result<SpecifiedUrl, ()> {
-        Ok(SpecifiedUrl {
+    unsafe fn from_url_value_data(url: &URLValueData) -> Result<Self, ()> {
+        Ok(CssUrl {
             serialization: if url.mUsingRustString {
                 let arc_type = url.mStrings.mRustString.as_ref()
                     as *const _ as
                     *const RawOffsetArc<String>;
                 Arc::from_raw_offset((*arc_type).clone())
             } else {
                 Arc::new(url.mStrings.mString.as_ref().to_string())
             },
             extra_data: url.mExtraData.to_safe(),
-            image_value: None,
         })
     }
 
-    /// Convert from nsStyleImageRequest to SpecifiedUrl.
-    pub unsafe fn from_image_request(image_request: &nsStyleImageRequest) -> Result<SpecifiedUrl, ()> {
-        if image_request.mImageValue.mRawPtr.is_null() {
-            return Err(());
-        }
-
-        let image_value = image_request.mImageValue.mRawPtr.as_ref().unwrap();
-        let ref url_value_data = image_value._base;
-        let mut result = Self::from_url_value_data(url_value_data)?;
-        result.build_image_value();
-        Ok(result)
-    }
-
     /// 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.
     ///
@@ -113,46 +93,163 @@ impl SpecifiedUrl {
         let arc_offset = Arc::into_raw_offset(self.serialization.clone());
         ServoBundledURI {
             mURLString: unsafe {
                 mem::transmute::<_, RawOffsetArc<RustString>>(arc_offset)
             },
             mExtraData: self.extra_data.get(),
         }
     }
-
-    /// Build and carry an image value on request.
-    pub fn build_image_value(&mut self) {
-        use gecko_bindings::bindings::Gecko_ImageValue_Create;
+}
 
-        debug_assert_eq!(self.image_value, None);
-        self.image_value = {
-            unsafe {
-                let ptr = Gecko_ImageValue_Create(self.for_ffi());
-                // We do not expect Gecko_ImageValue_Create returns null.
-                debug_assert!(!ptr.is_null());
-                Some(RefPtr::from_addrefed(ptr))
-            }
-        }
+impl Parse for CssUrl {
+    fn parse<'i, 't>(context: &ParserContext, input: &mut Parser<'i, 't>) -> Result<Self, ParseError<'i>> {
+        let url = input.expect_url()?;
+        Self::parse_from_string(url.as_ref().to_owned(), context)
     }
 }
 
-impl MallocSizeOf for SpecifiedUrl {
+impl Eq for CssUrl {}
+
+impl MallocSizeOf for CssUrl {
     fn size_of(&self, _ops: &mut MallocSizeOfOps) -> usize {
-        use gecko_bindings::bindings::Gecko_ImageValue_SizeOfIncludingThis;
-
-        let mut n = 0;
-
         // XXX: measure `serialization` once bug 1397971 lands
 
         // We ignore `extra_data`, because RefPtr is tricky, and there aren't
         // many of them in practise (sharing is common).
 
-        if let Some(ref image_value) = self.image_value {
-            // Although this is a RefPtr, this is the primary reference because
-            // SpecifiedUrl is responsible for creating the image_value. So we
-            // measure unconditionally here.
-            n += unsafe { Gecko_ImageValue_SizeOfIncludingThis(image_value.clone().get()) };
-        }
+        0
+    }
+}
+
+/// A specified url() value for general usage.
+#[derive(Clone, Debug, ToCss)]
+pub struct SpecifiedUrl {
+    /// The specified url value.
+    pub url: CssUrl,
+    /// Gecko's URLValue so that we can reuse it while rematching a
+    /// property with this specified value.
+    #[css(skip)]
+    pub url_value: RefPtr<URLValue>,
+}
+trivial_to_computed_value!(SpecifiedUrl);
+
+impl SpecifiedUrl {
+    fn from_css_url(url: CssUrl) -> Self {
+        let url_value = unsafe {
+            let ptr = bindings::Gecko_NewURLValue(url.for_ffi());
+            // We do not expect Gecko_NewURLValue returns null.
+            debug_assert!(!ptr.is_null());
+            RefPtr::from_addrefed(ptr)
+        };
+        SpecifiedUrl { url, url_value }
+    }
 
+    /// Convert from URLValueData to SpecifiedUrl.
+    pub unsafe fn from_url_value_data(url: &URLValueData) -> Result<Self, ()> {
+        CssUrl::from_url_value_data(url).map(Self::from_css_url)
+    }
+}
+
+impl PartialEq for SpecifiedUrl {
+    fn eq(&self, other: &Self) -> bool {
+        self.url.eq(&other.url)
+    }
+}
+
+impl Eq for SpecifiedUrl {}
+
+impl Parse for SpecifiedUrl {
+    fn parse<'i, 't>(context: &ParserContext, input: &mut Parser<'i, 't>) -> Result<Self, ParseError<'i>> {
+        CssUrl::parse(context, input).map(Self::from_css_url)
+    }
+}
+
+impl MallocSizeOf for SpecifiedUrl {
+    fn size_of(&self, ops: &mut MallocSizeOfOps) -> usize {
+        let mut n = self.url.size_of(ops);
+        // Although this is a RefPtr, this is the primary reference because
+        // SpecifiedUrl is responsible for creating the url_value. So we
+        // measure unconditionally here.
+        n += unsafe { bindings::Gecko_URLValue_SizeOfIncludingThis(self.url_value.get()) };
         n
     }
 }
+
+/// A specified url() value for image.
+///
+/// This exists so that we can construct `ImageValue` and reuse it.
+#[derive(Clone, Debug, ToCss)]
+pub struct SpecifiedImageUrl {
+    /// The specified url value.
+    pub url: CssUrl,
+    /// Gecko's ImageValue so that we can reuse it while rematching a
+    /// property with this specified value.
+    #[css(skip)]
+    pub image_value: RefPtr<ImageValue>,
+}
+trivial_to_computed_value!(SpecifiedImageUrl);
+
+impl SpecifiedImageUrl {
+    fn from_css_url(url: CssUrl) -> Self {
+        let image_value = unsafe {
+            let ptr = bindings::Gecko_ImageValue_Create(url.for_ffi());
+            // We do not expect Gecko_ImageValue_Create returns null.
+            debug_assert!(!ptr.is_null());
+            RefPtr::from_addrefed(ptr)
+        };
+        SpecifiedImageUrl { url, image_value }
+    }
+
+    /// Parse a URL from a string value. See SpecifiedUrl::parse_from_string.
+    pub fn parse_from_string<'a>(
+        url: String,
+        context: &ParserContext
+    ) -> Result<Self, ParseError<'a>> {
+        CssUrl::parse_from_string(url, context).map(Self::from_css_url)
+    }
+
+    /// Convert from URLValueData to SpecifiedUrl.
+    pub unsafe fn from_url_value_data(url: &URLValueData) -> Result<Self, ()> {
+        CssUrl::from_url_value_data(url).map(Self::from_css_url)
+    }
+
+    /// Convert from nsStyleImageRequest to SpecifiedUrl.
+    pub unsafe fn from_image_request(image_request: &nsStyleImageRequest) -> Result<Self, ()> {
+        if image_request.mImageValue.mRawPtr.is_null() {
+            return Err(());
+        }
+
+        let image_value = image_request.mImageValue.mRawPtr.as_ref().unwrap();
+        let url_value_data = &image_value._base;
+        Self::from_url_value_data(url_value_data)
+    }
+}
+
+impl Parse for SpecifiedImageUrl {
+    fn parse<'i, 't>(context: &ParserContext, input: &mut Parser<'i, 't>) -> Result<Self, ParseError<'i>> {
+        CssUrl::parse(context, input).map(Self::from_css_url)
+    }
+}
+
+impl PartialEq for SpecifiedImageUrl {
+    fn eq(&self, other: &Self) -> bool {
+        self.url.eq(&other.url)
+    }
+}
+
+impl Eq for SpecifiedImageUrl {}
+
+impl MallocSizeOf for SpecifiedImageUrl {
+    fn size_of(&self, ops: &mut MallocSizeOfOps) -> usize {
+        let mut n = self.url.size_of(ops);
+        // Although this is a RefPtr, this is the primary reference because
+        // SpecifiedUrl is responsible for creating the image_value. So we
+        // measure unconditionally here.
+        n += unsafe { bindings::Gecko_ImageValue_SizeOfIncludingThis(self.image_value.get()) };
+        n
+    }
+}
+
+/// The computed value of a CSS `url()`.
+pub type ComputedUrl = SpecifiedUrl;
+/// The computed value of a CSS `url()` for image.
+pub type ComputedImageUrl = SpecifiedImageUrl;
--- a/servo/components/style/gecko_bindings/sugar/ns_css_value.rs
+++ b/servo/components/style/gecko_bindings/sugar/ns_css_value.rs
@@ -207,17 +207,17 @@ impl nsCSSValue {
 
     /// Set to a number value
     pub fn set_number(&mut self, number: f32) {
         unsafe { bindings::Gecko_CSSValue_SetFloat(self, number, nsCSSUnit::eCSSUnit_Number) }
     }
 
     /// Set to a url value
     pub fn set_url(&mut self, url: &SpecifiedUrl) {
-        unsafe { bindings::Gecko_CSSValue_SetURL(self, url.for_ffi()) }
+        unsafe { bindings::Gecko_CSSValue_SetURL(self, url.url_value.get()) }
     }
 
     /// Set to an array of given length
     pub fn set_array(&mut self, len: i32) -> &mut nsCSSValue_Array {
         unsafe { bindings::Gecko_CSSValue_SetArray(self, len) }
         unsafe { self.mValue.mArray.as_mut().as_mut() }.unwrap()
     }
 
--- a/servo/components/style/properties/gecko.mako.rs
+++ b/servo/components/style/properties/gecko.mako.rs
@@ -691,17 +691,17 @@ def set_gecko_property(ffi_name, expr):
             SVGPaintKind::ContextFill => {
                 paint.mType = nsStyleSVGPaintType::eStyleSVGPaintType_ContextFill;
             }
             SVGPaintKind::ContextStroke => {
                 paint.mType = nsStyleSVGPaintType::eStyleSVGPaintType_ContextStroke;
             }
             SVGPaintKind::PaintServer(url) => {
                 unsafe {
-                    bindings::Gecko_nsStyleSVGPaint_SetURLValue(paint, url.for_ffi());
+                    bindings::Gecko_nsStyleSVGPaint_SetURLValue(paint, url.url_value.get());
                 }
             }
             SVGPaintKind::Color(color) => {
                 paint.mType = nsStyleSVGPaintType::eStyleSVGPaintType_Color;
                 unsafe {
                     *paint.mPaint.mColor.as_mut() = convert_rgba_to_nscolor(&color);
                 }
             }
@@ -931,28 +931,19 @@ def set_gecko_property(ffi_name, expr):
                         .expect("Failed to clone ${ident}");
         BorderCornerRadius::new(width, height)
     }
 </%def>
 
 <%def name="impl_css_url(ident, gecko_ffi_name)">
     #[allow(non_snake_case)]
     pub fn set_${ident}(&mut self, v: longhands::${ident}::computed_value::T) {
-        use gecko_bindings::sugar::refptr::RefPtr;
         match v {
             Either::First(url) => {
-                let refptr = unsafe {
-                    let ptr = bindings::Gecko_NewURLValue(url.for_ffi());
-                    if ptr.is_null() {
-                        self.gecko.${gecko_ffi_name}.clear();
-                        return;
-                    }
-                    RefPtr::from_addrefed(ptr)
-                };
-                self.gecko.${gecko_ffi_name}.set_move(refptr)
+                self.gecko.${gecko_ffi_name}.set_move(url.url_value.clone())
             }
             Either::Second(_none) => {
                 unsafe {
                     self.gecko.${gecko_ffi_name}.clear();
                 }
             }
         }
     }
@@ -4067,18 +4058,17 @@ fn static_assert() {
         match image {
             longhands::list_style_image::computed_value::T(Either::Second(_none)) => {
                 unsafe {
                     Gecko_SetListStyleImageNone(&mut self.gecko);
                 }
             }
             longhands::list_style_image::computed_value::T(Either::First(ref url)) => {
                 unsafe {
-                    Gecko_SetListStyleImageImageValue(&mut self.gecko,
-                                                      url.image_value.clone().unwrap().get());
+                    Gecko_SetListStyleImageImageValue(&mut self.gecko, url.image_value.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.
             }
         }
     }
@@ -4087,27 +4077,27 @@ fn static_assert() {
         unsafe { Gecko_CopyListStyleImageFrom(&mut self.gecko, &other.gecko); }
     }
 
     pub fn reset_list_style_image(&mut self, other: &Self) {
         self.copy_list_style_image_from(other)
     }
 
     pub fn clone_list_style_image(&self) -> longhands::list_style_image::computed_value::T {
-        use values::specified::url::SpecifiedUrl;
+        use values::specified::url::SpecifiedImageUrl;
         use values::{Either, None_};
 
         longhands::list_style_image::computed_value::T(
             match self.gecko.mListStyleImage.mRawPtr.is_null() {
                 true => Either::Second(None_),
                 false => {
                     unsafe {
                         let ref gecko_image_request = *self.gecko.mListStyleImage.mRawPtr;
-                        Either::First(SpecifiedUrl::from_image_request(gecko_image_request)
-                                      .expect("mListStyleImage could not convert to SpecifiedUrl"))
+                        Either::First(SpecifiedImageUrl::from_image_request(gecko_image_request)
+                                      .expect("mListStyleImage could not convert to SpecifiedImageUrl"))
                     }
                 }
             }
         )
     }
 
     pub fn set_list_style_type(&mut self, v: longhands::list_style_type::computed_value::T, device: &Device) {
         use gecko_bindings::bindings::Gecko_SetCounterStyleToString;
@@ -4439,17 +4429,17 @@ fn static_assert() {
                         }
                     }
 
                     let gecko_shadow = init_shadow(gecko_filter);
                     gecko_shadow.mArray[0].set_from_simple_shadow(shadow);
                 },
                 Url(ref url) => {
                     unsafe {
-                        bindings::Gecko_nsStyleFilter_SetURLValue(gecko_filter, url.for_ffi());
+                        bindings::Gecko_nsStyleFilter_SetURLValue(gecko_filter, url.url_value.get());
                     }
                 },
             }
         }
     }
 
     pub fn copy_filter_from(&mut self, other: &Self) {
         unsafe {
@@ -4965,17 +4955,17 @@ fn static_assert() {
         // clean up existing struct
         unsafe { Gecko_DestroyShapeSource(${ident}) };
         ${ident}.mType = StyleShapeSourceType::None;
 
         match v {
             % if ident == "clip_path":
             ShapeSource::ImageOrUrl(ref url) => {
                 unsafe {
-                    bindings::Gecko_StyleShapeSource_SetURLValue(${ident}, url.for_ffi())
+                    bindings::Gecko_StyleShapeSource_SetURLValue(${ident}, url.url_value.get())
                 }
             }
             % elif ident == "shape_outside":
             ShapeSource::ImageOrUrl(image) => {
                 unsafe {
                     bindings::Gecko_NewShapeImage(${ident});
                     let style_image = &mut *${ident}.mShapeImage.mPtr;
                     style_image.set(image);
@@ -5285,18 +5275,20 @@ clip-path
             CursorKind::MozZoomOut => structs::NS_STYLE_CURSOR_ZOOM_OUT,
         } as u8;
 
         unsafe {
             Gecko_SetCursorArrayLength(&mut self.gecko, v.images.len());
         }
         for i in 0..v.images.len() {
             unsafe {
-                Gecko_SetCursorImageValue(&mut self.gecko.mCursorImages[i],
-                                          v.images[i].url.clone().image_value.unwrap().get());
+                Gecko_SetCursorImageValue(
+                    &mut self.gecko.mCursorImages[i],
+                    v.images[i].url.image_value.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 Pointing struct
             // is an inherited struct.
 
             match v.images[i].hotspot {
@@ -5321,17 +5313,17 @@ clip-path
 
     pub fn reset_cursor(&mut self, other: &Self) {
         self.copy_cursor_from(other)
     }
 
     pub fn clone_cursor(&self) -> longhands::cursor::computed_value::T {
         use values::computed::pointing::CursorImage;
         use style_traits::cursor::CursorKind;
-        use values::specified::url::SpecifiedUrl;
+        use values::specified::url::SpecifiedImageUrl;
 
         let keyword = match self.gecko.mCursor as u32 {
             structs::NS_STYLE_CURSOR_AUTO => CursorKind::Auto,
             structs::NS_STYLE_CURSOR_NONE => CursorKind::None,
             structs::NS_STYLE_CURSOR_DEFAULT => CursorKind::Default,
             structs::NS_STYLE_CURSOR_POINTER => CursorKind::Pointer,
             structs::NS_STYLE_CURSOR_CONTEXT_MENU => CursorKind::ContextMenu,
             structs::NS_STYLE_CURSOR_HELP => CursorKind::Help,
@@ -5366,18 +5358,18 @@ clip-path
             structs::NS_STYLE_CURSOR_ZOOM_IN => CursorKind::ZoomIn,
             structs::NS_STYLE_CURSOR_ZOOM_OUT => CursorKind::ZoomOut,
             _ => panic!("Found unexpected value in style struct for cursor property"),
         };
 
         let images = self.gecko.mCursorImages.iter().map(|gecko_cursor_image| {
             let url = unsafe {
                 let gecko_image_request = gecko_cursor_image.mImage.mRawPtr.as_ref().unwrap();
-                SpecifiedUrl::from_image_request(&gecko_image_request)
-                    .expect("mCursorImages.mImage could not convert to SpecifiedUrl")
+                SpecifiedImageUrl::from_image_request(&gecko_image_request)
+                    .expect("mCursorImages.mImage could not convert to SpecifiedImageUrl")
             };
 
             let hotspot =
                 if gecko_cursor_image.mHaveHotspot {
                     Some((gecko_cursor_image.mHotspotX, gecko_cursor_image.mHotspotY))
                 } else {
                     None
                 };
@@ -5545,18 +5537,20 @@ clip-path
                                 &name,
                                 &sep,
                                 style.clone(),
                                 device,
                             );
                         }
                         ContentItem::Url(ref url) => {
                             unsafe {
-                                bindings::Gecko_SetContentDataImageValue(&mut self.gecko.mContents[i],
-                                    url.image_value.clone().unwrap().get())
+                                bindings::Gecko_SetContentDataImageValue(
+                                    &mut self.gecko.mContents[i],
+                                    url.image_value.get(),
+                                )
                             }
                         }
                     }
                 }
             }
         }
     }
 
@@ -5573,17 +5567,17 @@ clip-path
 
     pub fn clone_content(&self) -> longhands::content::computed_value::T {
         use Atom;
         use gecko::conversions::string_from_chars_pointer;
         use gecko_bindings::structs::nsStyleContentType::*;
         use values::computed::counters::{Content, ContentItem};
         use values::{CustomIdent, Either};
         use values::generics::CounterStyleOrNone;
-        use values::specified::url::SpecifiedUrl;
+        use values::specified::url::SpecifiedImageUrl;
         use values::specified::Attr;
 
         if self.gecko.mContents.is_empty() {
             return Content::Normal;
         }
 
         if self.gecko.mContents.len() == 1 &&
            self.gecko.mContents[0].mType == eStyleContentType_AltContent {
@@ -5636,18 +5630,18 @@ clip-path
                             ContentItem::Counters(ident, separator.into_boxed_str(), style)
                         }
                     },
                     eStyleContentType_Image => {
                         unsafe {
                             let gecko_image_request =
                                 &**gecko_content.mContent.mImage.as_ref();
                             ContentItem::Url(
-                                SpecifiedUrl::from_image_request(gecko_image_request)
-                                    .expect("mContent could not convert to SpecifiedUrl")
+                                SpecifiedImageUrl::from_image_request(gecko_image_request)
+                                    .expect("mContent could not convert to SpecifiedImageUrl")
                             )
                         }
                     },
                     _ => panic!("Found unexpected value in style struct for content property"),
                 }
             }).collect::<Vec<_>>().into_boxed_slice()
         )
     }
--- a/servo/components/style/properties/longhand/box.mako.rs
+++ b/servo/components/style/properties/longhand/box.mako.rs
@@ -606,17 +606,16 @@
                          gecko_ffi_name="mAppearance",
                          gecko_constant_prefix="ThemeWidgetType_NS_THEME",
                          products="gecko",
                          spec="Nonstandard (https://developer.mozilla.org/en-US/docs/Web/CSS/-moz-appearance)",
                          animation_value_type="discrete")}
 
 ${helpers.predefined_type("-moz-binding", "UrlOrNone", "Either::Second(None_)",
                           products="gecko",
-                          boxed= product == "gecko",
                           animation_value_type="none",
                           gecko_ffi_name="mBinding",
                           spec="Nonstandard (https://developer.mozilla.org/en-US/docs/Web/CSS/-moz-binding)")}
 
 ${helpers.single_keyword("-moz-orient",
                           "inline block horizontal vertical",
                           products="gecko",
                           gecko_ffi_name="mOrient",
--- a/servo/components/style/properties/longhand/inherited_svg.mako.rs
+++ b/servo/components/style/properties/longhand/inherited_svg.mako.rs
@@ -109,29 +109,26 @@
 ${helpers.single_keyword("clip-rule", "nonzero evenodd",
                          products="gecko",
                          gecko_enum_prefix="StyleFillRule",
                          animation_value_type="discrete",
                          spec="https://www.w3.org/TR/SVG11/masking.html#ClipRuleProperty")}
 
 ${helpers.predefined_type("marker-start", "UrlOrNone", "Either::Second(None_)",
                           products="gecko",
-                          boxed= product == "gecko",
                           animation_value_type="discrete",
                           spec="https://www.w3.org/TR/SVG2/painting.html#VertexMarkerProperties")}
 
 ${helpers.predefined_type("marker-mid", "UrlOrNone", "Either::Second(None_)",
                           products="gecko",
-                          boxed= product == "gecko",
                           animation_value_type="discrete",
                           spec="https://www.w3.org/TR/SVG2/painting.html#VertexMarkerProperties")}
 
 ${helpers.predefined_type("marker-end", "UrlOrNone", "Either::Second(None_)",
                           products="gecko",
-                          boxed= product == "gecko",
                           animation_value_type="discrete",
                           spec="https://www.w3.org/TR/SVG2/painting.html#VertexMarkerProperties")}
 
 ${helpers.predefined_type("paint-order", "SVGPaintOrder", "computed::SVGPaintOrder::normal()",
                           products="gecko",
                           animation_value_type="discrete",
                           spec="https://www.w3.org/TR/SVG2/painting.html#PaintOrder")}
 
--- a/servo/components/style/properties/longhand/list.mako.rs
+++ b/servo/components/style/properties/longhand/list.mako.rs
@@ -42,17 +42,16 @@
                               servo_restyle_damage="rebuild_and_reflow")}
 % endif
 
 ${helpers.predefined_type("list-style-image",
                           "ListStyleImage",
                           initial_value="specified::ListStyleImage::none()",
                           initial_specified_value="specified::ListStyleImage::none()",
                           animation_value_type="discrete",
-                          boxed=product == "gecko",
                           spec="https://drafts.csswg.org/css-lists/#propdef-list-style-image",
                           servo_restyle_damage="rebuild_and_reflow")}
 
 ${helpers.predefined_type("quotes",
                           "Quotes",
                           "computed::Quotes::get_initial_value()",
                           animation_value_type="discrete",
                           spec="https://drafts.csswg.org/css-content/#propdef-quotes",
--- a/servo/components/style/servo/url.rs
+++ b/servo/components/style/servo/url.rs
@@ -1,57 +1,58 @@
 /* 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 parser::ParserContext;
+use cssparser::Parser;
+use parser::{Parse, ParserContext};
 use servo_url::ServoUrl;
 use std::fmt::{self, Write};
 // Note: We use std::sync::Arc rather than servo_arc::Arc here because the
 // nonzero optimization is important in keeping the size of SpecifiedUrl below
 // the threshold.
 use std::sync::Arc;
 use style_traits::{CssWriter, ParseError, ToCss};
-use values::computed::{Context, ToComputedValue, ComputedUrl};
+use values::computed::{Context, ToComputedValue};
 
-/// A specified url() value for servo.
+/// A CSS 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, Deserialize, MallocSizeOf, Serialize)]
-pub struct SpecifiedUrl {
+pub struct CssUrl {
     /// 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.
     #[ignore_malloc_size_of = "Arc"]
     original: Option<Arc<String>>,
 
     /// The resolved value for the url, if valid.
     resolved: Option<ServoUrl>,
 }
 
-impl SpecifiedUrl {
+impl CssUrl {
     /// 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: String,
                                  context: &ParserContext)
                                  -> Result<Self, ParseError<'a>> {
         let serialization = Arc::new(url);
         let resolved = context.url_data.join(&serialization).ok();
-        Ok(SpecifiedUrl {
+        Ok(CssUrl {
             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 {
@@ -82,40 +83,49 @@ impl SpecifiedUrl {
             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 {
+        CssUrl {
             original: None,
             resolved: Some(url),
         }
     }
 
     /// Gets a new url from a string for unit tests.
     pub fn new_for_testing(url: &str) -> Self {
-        SpecifiedUrl {
+        CssUrl {
             original: Some(Arc::new(url.into())),
             resolved: ServoUrl::parse(url).ok(),
         }
     }
 }
 
-impl PartialEq for SpecifiedUrl {
+impl Parse for CssUrl {
+    fn parse<'i, 't>(context: &ParserContext, input: &mut Parser<'i, 't>) -> Result<Self, ParseError<'i>> {
+        let url = input.expect_url()?;
+        Self::parse_from_string(url.as_ref().to_owned(), context)
+    }
+}
+
+impl PartialEq for CssUrl {
     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 {
+impl Eq for CssUrl {}
+
+impl ToCss for CssUrl {
     fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
     where
         W: Write,
     {
         let string = match self.original {
             Some(ref original) => &**original,
             None => match self.resolved {
                 Some(ref url) => url.as_str(),
@@ -127,16 +137,19 @@ impl ToCss for SpecifiedUrl {
         };
 
         dest.write_str("url(")?;
         string.to_css(dest)?;
         dest.write_str(")")
     }
 }
 
+/// A specified url() value for servo.
+pub type SpecifiedUrl = CssUrl;
+
 impl ToComputedValue for SpecifiedUrl {
     type ComputedValue = ComputedUrl;
 
     // If we can't resolve the URL from the specified one, we fall back to the original
     // but still return it as a ComputedUrl::Invalid
     fn to_computed_value(&self, _: &Context) -> Self::ComputedValue {
         match self.resolved {
             Some(ref url) => ComputedUrl::Valid(url.clone()),
@@ -158,8 +171,48 @@ impl ToComputedValue for SpecifiedUrl {
             ComputedUrl::Invalid(ref url) => SpecifiedUrl {
                 original: Some(url.clone()),
                 resolved: None,
             }
         }
     }
 }
 
+/// A specified image url() value for servo.
+pub type SpecifiedImageUrl = CssUrl;
+
+/// The computed value of a CSS `url()`, resolved relative to the stylesheet URL.
+#[derive(Clone, Debug, Deserialize, MallocSizeOf, PartialEq, Serialize)]
+pub enum ComputedUrl {
+    /// The `url()` was invalid or it wasn't specified by the user.
+    Invalid(#[ignore_malloc_size_of = "Arc"] Arc<String>),
+    /// The resolved `url()` relative to the stylesheet URL.
+    Valid(ServoUrl),
+}
+
+impl ComputedUrl {
+    /// Returns the resolved url if it was valid.
+    pub fn url(&self) -> Option<&ServoUrl> {
+        match *self {
+            ComputedUrl::Valid(ref url) => Some(url),
+            _ => None,
+        }
+    }
+}
+
+impl ToCss for ComputedUrl {
+    fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
+    where
+        W: Write,
+    {
+        let string = match *self {
+            ComputedUrl::Valid(ref url) => url.as_str(),
+            ComputedUrl::Invalid(ref invalid_string) => invalid_string,
+        };
+
+        dest.write_str("url(")?;
+        string.to_css(dest)?;
+        dest.write_str(")")
+    }
+}
+
+/// The computed value of a CSS `url()` for image.
+pub type ComputedImageUrl = ComputedUrl;
--- a/servo/components/style/stylesheets/document_rule.rs
+++ b/servo/components/style/stylesheets/document_rule.rs
@@ -12,17 +12,17 @@ use malloc_size_of::{MallocSizeOfOps, Ma
 use media_queries::Device;
 use parser::{Parse, ParserContext};
 use servo_arc::Arc;
 use shared_lock::{DeepCloneParams, DeepCloneWithLock, Locked, SharedRwLock, SharedRwLockReadGuard, ToCssWithGuard};
 use std::fmt::{self, Write};
 use str::CssStringWriter;
 use style_traits::{CssWriter, ParseError, StyleParseErrorKind, ToCss};
 use stylesheets::CssRules;
-use values::specified::url::SpecifiedUrl;
+use values::CssUrl;
 
 #[derive(Debug)]
 /// A @-moz-document rule
 pub struct DocumentRule {
     /// The parsed condition
     pub condition: DocumentCondition,
     /// Child rules
     pub rules: Arc<Locked<CssRules>>,
@@ -70,17 +70,17 @@ impl DeepCloneWithLock for DocumentRule 
     }
 }
 
 /// A URL matching function for a `@document` rule's condition.
 #[derive(Clone, Debug, ToCss)]
 pub enum UrlMatchingFunction {
     /// Exact URL matching function. It evaluates to true whenever the
     /// URL of the document being styled is exactly the URL given.
-    Url(SpecifiedUrl),
+    Url(CssUrl),
     /// URL prefix matching function. It evaluates to true whenever the
     /// URL of the document being styled has the argument to the
     /// function as an initial substring (which is true when the two
     /// strings are equal). When the argument is the empty string,
     /// it evaluates to true for all documents.
     #[css(function)]
     UrlPrefix(String),
     /// Domain matching function. It evaluates to true whenever the URL
@@ -125,17 +125,17 @@ impl UrlMatchingFunction {
         if input.try(|input| input.expect_function_matching("url-prefix")).is_ok() {
             parse_quoted_or_unquoted_string!(input, UrlMatchingFunction::UrlPrefix)
         } else if input.try(|input| input.expect_function_matching("domain")).is_ok() {
             parse_quoted_or_unquoted_string!(input, UrlMatchingFunction::Domain)
         } else if input.try(|input| input.expect_function_matching("regexp")).is_ok() {
             input.parse_nested_block(|input| {
                 Ok(UrlMatchingFunction::Regexp(input.expect_string()?.as_ref().to_owned()))
             })
-        } else if let Ok(url) = input.try(|input| SpecifiedUrl::parse(context, input)) {
+        } else if let Ok(url) = input.try(|input| CssUrl::parse(context, input)) {
             Ok(UrlMatchingFunction::Url(url))
         } else {
             Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError))
         }
     }
 
     #[cfg(feature = "gecko")]
     /// Evaluate a URL matching function.
--- a/servo/components/style/stylesheets/import_rule.rs
+++ b/servo/components/style/stylesheets/import_rule.rs
@@ -8,17 +8,17 @@
 
 use cssparser::SourceLocation;
 use media_queries::MediaList;
 use shared_lock::{DeepCloneWithLock, DeepCloneParams, SharedRwLock, SharedRwLockReadGuard, ToCssWithGuard};
 use std::fmt::{self, Write};
 use str::CssStringWriter;
 use style_traits::{CssWriter, ToCss};
 use stylesheets::{StylesheetContents, StylesheetInDocument};
-use values::specified::url::SpecifiedUrl;
+use values::CssUrl;
 
 /// A sheet that is held from an import rule.
 #[cfg(feature = "gecko")]
 #[derive(Debug)]
 pub struct ImportSheet(pub ::gecko::data::GeckoStyleSheet);
 
 #[cfg(feature = "gecko")]
 impl DeepCloneWithLock for ImportSheet {
@@ -75,17 +75,17 @@ impl DeepCloneWithLock for ImportSheet {
 }
 
 /// The [`@import`][import] at-rule.
 ///
 /// [import]: https://drafts.csswg.org/css-cascade-3/#at-import
 #[derive(Debug)]
 pub struct ImportRule {
     /// The `<url>` this `@import` rule is loading.
-    pub url: SpecifiedUrl,
+    pub url: CssUrl,
 
     /// The stylesheet is always present.
     ///
     /// It contains an empty list of rules and namespace set that is updated
     /// when it loads.
     pub stylesheet: ImportSheet,
 
     /// The line and column of the rule's source code.
--- a/servo/components/style/stylesheets/loader.rs
+++ b/servo/components/style/stylesheets/loader.rs
@@ -6,24 +6,24 @@
 //! for `@import` rules.
 
 use cssparser::SourceLocation;
 use media_queries::MediaList;
 use parser::ParserContext;
 use servo_arc::Arc;
 use shared_lock::{Locked, SharedRwLock};
 use stylesheets::import_rule::ImportRule;
-use values::specified::url::SpecifiedUrl;
+use values::CssUrl;
 
 /// The stylesheet loader is the abstraction used to trigger network requests
 /// for `@import` rules.
 pub trait StylesheetLoader {
     /// Request a stylesheet after parsing a given `@import` rule, and return
     /// the constructed `@import` rule.
     fn request_stylesheet(
         &self,
-        url: SpecifiedUrl,
+        url: CssUrl,
         location: SourceLocation,
         context: &ParserContext,
         lock: &SharedRwLock,
         media: Arc<Locked<MediaList>>,
     ) -> Arc<Locked<ImportRule>>;
 }
--- a/servo/components/style/stylesheets/rule_parser.rs
+++ b/servo/components/style/stylesheets/rule_parser.rs
@@ -23,20 +23,18 @@ use stylesheets::{CssRule, CssRules, Css
 use stylesheets::{DocumentRule, FontFeatureValuesRule, KeyframesRule, MediaRule};
 use stylesheets::{NamespaceRule, PageRule, StyleRule, SupportsRule, ViewportRule};
 use stylesheets::document_rule::DocumentCondition;
 use stylesheets::font_feature_values_rule::parse_family_name_list;
 use stylesheets::keyframes_rule::parse_keyframe_list;
 use stylesheets::stylesheet::Namespaces;
 use stylesheets::supports_rule::SupportsCondition;
 use stylesheets::viewport_rule;
-use values::CustomIdent;
-use values::KeyframesName;
+use values::{CssUrl, CustomIdent, KeyframesName};
 use values::computed::font::FamilyName;
-use values::specified::url::SpecifiedUrl;
 
 /// The parser for the top-level rules in a stylesheet.
 pub struct TopLevelRuleParser<'a, R: 'a> {
     /// The origin of the stylesheet we're parsing.
     pub stylesheet_origin: Origin,
     /// A reference to the lock we need to use to create rules.
     pub shared_lock: &'a SharedRwLock,
     /// A reference to a stylesheet loader if applicable, for `@import` rules.
@@ -129,17 +127,17 @@ pub enum AtRuleBlockPrelude {
     Page(SourceLocation),
     /// A @document rule, with its conditional.
     Document(DocumentCondition, SourceLocation),
 }
 
 /// A rule prelude for at-rule without block.
 pub enum AtRuleNonBlockPrelude {
     /// A @import rule prelude.
-    Import(SpecifiedUrl, Arc<Locked<MediaList>>, SourceLocation),
+    Import(CssUrl, Arc<Locked<MediaList>>, SourceLocation),
     /// A @namespace rule prelude.
     Namespace(Option<Prefix>, Namespace, SourceLocation),
 }
 
 
 #[cfg(feature = "gecko")]
 fn register_namespace(ns: &Namespace) -> i32 {
     use gecko_bindings::bindings;
@@ -169,23 +167,23 @@ impl<'a, 'i, R: ParseErrorReporter> AtRu
             "import" => {
                 if self.state > State::Imports {
                     // "@import must be before any rule but @charset"
                     self.had_hierarchy_error = true;
                     return Err(input.new_custom_error(StyleParseErrorKind::UnexpectedImportRule))
                 }
 
                 let url_string = input.expect_url_or_string()?.as_ref().to_owned();
-                let specified_url = SpecifiedUrl::parse_from_string(url_string, &self.context)?;
+                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 = Arc::new(self.shared_lock.wrap(media));
 
-                let prelude = AtRuleNonBlockPrelude::Import(specified_url, media, location);
+                let prelude = AtRuleNonBlockPrelude::Import(url, media, location);
                 return Ok(AtRuleType::WithoutBlock(prelude));
             },
             "namespace" => {
                 if self.state > State::Namespaces {
                     // "@namespace must be before any rule but @charset and @import"
                     self.had_hierarchy_error = true;
                     return Err(input.new_custom_error(StyleParseErrorKind::UnexpectedNamespaceRule))
                 }
@@ -223,22 +221,22 @@ impl<'a, 'i, R: ParseErrorReporter> AtRu
     ) -> Result<CssRule, ParseError<'i>> {
         AtRuleParser::parse_block(&mut self.nested(), prelude, input)
             .map(|rule| { self.state = State::Body; rule })
     }
 
     #[inline]
     fn rule_without_block(&mut self, prelude: AtRuleNonBlockPrelude) -> CssRule {
         match prelude {
-            AtRuleNonBlockPrelude::Import(specified_url, media, location) => {
+            AtRuleNonBlockPrelude::Import(url, media, location) => {
                 let loader =
                     self.loader.expect("Expected a stylesheet loader for @import");
 
                 let import_rule = loader.request_stylesheet(
-                    specified_url,
+                    url,
                     location,
                     &self.context,
                     &self.shared_lock,
                     media,
                 );
 
                 self.state = State::Imports;
                 CssRule::Import(import_rule)
--- a/servo/components/style/values/computed/counters.rs
+++ b/servo/components/style/values/computed/counters.rs
@@ -13,17 +13,17 @@ use style_traits::{ParseError, StylePars
 use values::CustomIdent;
 #[cfg(feature = "gecko")]
 use values::generics::CounterStyleOrNone;
 use values::generics::counters::CounterIncrement as GenericCounterIncrement;
 use values::generics::counters::CounterReset as GenericCounterReset;
 #[cfg(feature = "gecko")]
 use values::specified::Attr;
 #[cfg(feature = "gecko")]
-use values::specified::url::SpecifiedUrl;
+use values::specified::url::SpecifiedImageUrl;
 pub use values::specified::{Content, ContentItem};
 
 /// A computed value for the `counter-increment` property.
 pub type CounterIncrement = GenericCounterIncrement<i32>;
 
 /// A computed value for the `counter-increment` property.
 pub type CounterReset = GenericCounterReset<i32>;
 
@@ -74,18 +74,17 @@ impl Parse for Content {
             if input.try(|input| input.expect_ident_matching("-moz-alt-content")).is_ok() {
                 return Ok(Content::MozAltContent);
             }
         }
 
         let mut content = vec![];
         loop {
             #[cfg(feature = "gecko")] {
-                if let Ok(mut url) = input.try(|i| SpecifiedUrl::parse(_context, i)) {
-                    url.build_image_value();
+                if let Ok(url) = input.try(|i| SpecifiedImageUrl::parse(_context, i)) {
                     content.push(ContentItem::Url(url));
                     continue;
                 }
             }
             // FIXME: remove clone() when lifetimes are non-lexical
             match input.next().map(|t| t.clone()) {
                 Ok(Token::QuotedString(ref value)) => {
                     content.push(ContentItem::String(value.as_ref().to_owned().into_boxed_str()));
--- a/servo/components/style/values/computed/image.rs
+++ b/servo/components/style/values/computed/image.rs
@@ -7,33 +7,34 @@
 //!
 //! [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, ComputedUrl, Context, Length, LengthOrPercentage, NumberOrPercentage, ToComputedValue};
+use values::computed::{Angle, ComputedImageUrl, Context};
+use values::computed::{Length, LengthOrPercentage, NumberOrPercentage, ToComputedValue};
 #[cfg(feature = "gecko")]
 use values::computed::Percentage;
 use values::computed::position::Position;
 use values::generics::image::{CompatMode, ColorStop as GenericColorStop, EndingShape as GenericEndingShape};
 use values::generics::image::{Gradient as GenericGradient, GradientItem as GenericGradientItem};
 use values::generics::image::{Image as GenericImage, GradientKind as GenericGradientKind};
 use values::generics::image::{LineDirection as GenericLineDirection, MozImageRect as GenericMozImageRect};
 use values::specified::image::LineDirection as SpecifiedLineDirection;
 use values::specified::position::{X, Y};
 
 /// A computed image layer.
 pub type ImageLayer = Either<None_, Image>;
 
 /// Computed values for an image according to CSS-IMAGES.
 /// <https://drafts.csswg.org/css-images/#image-values>
-pub type Image = GenericImage<Gradient, MozImageRect, ComputedUrl>;
+pub type Image = GenericImage<Gradient, MozImageRect, ComputedImageUrl>;
 
 /// Computed values for a CSS gradient.
 /// <https://drafts.csswg.org/css-images/#gradients>
 pub type Gradient = GenericGradient<
     LineDirection,
     Length,
     LengthOrPercentage,
     Position,
@@ -71,17 +72,17 @@ pub type EndingShape = GenericEndingShap
 
 /// A computed gradient item.
 pub type GradientItem = GenericGradientItem<RGBA, LengthOrPercentage>;
 
 /// A computed color stop.
 pub type ColorStop = GenericColorStop<RGBA, LengthOrPercentage>;
 
 /// Computed values for `-moz-image-rect(...)`.
-pub type MozImageRect = GenericMozImageRect<NumberOrPercentage, ComputedUrl>;
+pub type MozImageRect = GenericMozImageRect<NumberOrPercentage, ComputedImageUrl>;
 
 impl GenericLineDirection for LineDirection {
     fn points_downwards(&self, compat_mode: CompatMode) -> bool {
         match *self {
             LineDirection::Angle(angle) => angle.radians() == PI,
             LineDirection::Vertical(Y::Bottom)
                 if compat_mode == CompatMode::Modern => true,
             LineDirection::Vertical(Y::Top)
--- a/servo/components/style/values/computed/mod.rs
+++ b/servo/components/style/values/computed/mod.rs
@@ -10,24 +10,20 @@ use Prefix;
 use context::QuirksMode;
 use euclid::Size2D;
 use font_metrics::{FontMetricsProvider, get_metrics_provider_for_product};
 use media_queries::Device;
 #[cfg(feature = "gecko")]
 use properties;
 use properties::{ComputedValues, LonghandId, StyleBuilder};
 use rule_cache::RuleCacheConditions;
-#[cfg(feature = "servo")]
-use servo_url::ServoUrl;
 use std::cell::RefCell;
 use std::cmp;
 use std::f32;
 use std::fmt::{self, Write};
-#[cfg(feature = "servo")]
-use std::sync::Arc;
 use style_traits::{CssWriter, ToCss};
 use style_traits::cursor::CursorKind;
 use super::{CSSFloat, CSSInteger};
 use super::animated::ToAnimatedValue;
 use super::generics::{GreaterThanOrEqualToOne, NonNegative};
 use super::generics::grid::{GridLine as GenericGridLine, TrackBreadth as GenericTrackBreadth};
 use super::generics::grid::{TrackSize as GenericTrackSize, TrackList as GenericTrackList};
 use super::generics::grid::GridTemplateComponent as GenericGridTemplateComponent;
@@ -79,16 +75,17 @@ pub use self::svg::{SVGPaintOrder, SVGSt
 pub use self::svg::MozContextProperties;
 pub use self::table::XSpan;
 pub use self::text::{InitialLetter, LetterSpacing, LineHeight, MozTabSize};
 pub use self::text::{TextAlign, TextOverflow, WordSpacing};
 pub use self::time::Time;
 pub use self::transform::{Rotate, Scale, TimingFunction, Transform, TransformOperation};
 pub use self::transform::{TransformOrigin, TransformStyle, Translate};
 pub use self::ui::MozForceBrokenImageIcon;
+pub use self::url::{ComputedUrl, ComputedImageUrl};
 
 #[cfg(feature = "gecko")]
 pub mod align;
 pub mod angle;
 pub mod background;
 pub mod basic_shape;
 pub mod border;
 #[path = "box.rs"]
@@ -112,16 +109,24 @@ pub mod position;
 pub mod rect;
 pub mod svg;
 pub mod table;
 pub mod text;
 pub mod time;
 pub mod transform;
 pub mod ui;
 
+/// Common handling for the computed value CSS url() values.
+pub mod url {
+#[cfg(feature = "servo")]
+pub use ::servo::url::{ComputedUrl, ComputedImageUrl};
+#[cfg(feature = "gecko")]
+pub use ::gecko::url::{ComputedUrl, ComputedImageUrl};
+}
+
 /// A `Context` is all the data a specified value could ever need to compute
 /// itself and be transformed to a computed value.
 pub struct Context<'a> {
     /// Whether the current element is the root element.
     pub is_root_element: bool,
 
     /// Values accessed through this need to be in the properties "computed
     /// early": color, text-decoration, font-size, display, position, float,
@@ -631,52 +636,13 @@ impl ClipRectOrAuto {
     pub fn is_auto(&self) -> bool {
         match *self {
             Either::Second(_) => true,
             _ => false
         }
     }
 }
 
-/// The computed value of a CSS `url()`, resolved relative to the stylesheet URL.
-#[cfg(feature = "servo")]
-#[derive(Clone, Debug, Deserialize, MallocSizeOf, PartialEq, Serialize)]
-pub enum ComputedUrl {
-    /// The `url()` was invalid or it wasn't specified by the user.
-    Invalid(#[ignore_malloc_size_of = "Arc"] Arc<String>),
-    /// The resolved `url()` relative to the stylesheet URL.
-    Valid(ServoUrl),
-}
-
-/// TODO: Properly build ComputedUrl for gecko
-#[cfg(feature = "gecko")]
-pub type ComputedUrl = specified::url::SpecifiedUrl;
-
-#[cfg(feature = "servo")]
-impl ComputedUrl {
-    /// Returns the resolved url if it was valid.
-    pub fn url(&self) -> Option<&ServoUrl> {
-        match *self {
-            ComputedUrl::Valid(ref url) => Some(url),
-            _ => None,
-        }
-    }
-}
-
-#[cfg(feature = "servo")]
-impl ToCss for ComputedUrl {
-    fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
-    where
-        W: Write,
-    {
-        let string = match *self {
-            ComputedUrl::Valid(ref url) => url.as_str(),
-            ComputedUrl::Invalid(ref invalid_string) => invalid_string,
-        };
-
-        dest.write_str("url(")?;
-        string.to_css(dest)?;
-        dest.write_str(")")
-    }
-}
-
 /// <url> | <none>
 pub type UrlOrNone = Either<ComputedUrl, None_>;
+
+/// <url> | <none> for image
+pub type ImageUrlOrNone = Either<ComputedImageUrl, None_>;
--- a/servo/components/style/values/computed/pointing.rs
+++ b/servo/components/style/values/computed/pointing.rs
@@ -13,17 +13,17 @@ use selectors::parser::SelectorParseErro
 use std::fmt::{self, Write};
 #[cfg(feature = "gecko")]
 use style_traits::{CssWriter, ToCss};
 use style_traits::ParseError;
 use style_traits::cursor::CursorKind;
 use values::computed::color::Color;
 use values::generics::pointing::CaretColor as GenericCaretColor;
 #[cfg(feature = "gecko")]
-use values::specified::url::SpecifiedUrl;
+use values::specified::url::SpecifiedImageUrl;
 
 /// The computed value for the `cursor` property.
 ///
 /// https://drafts.csswg.org/css-ui/#cursor
 pub use values::specified::pointing::Cursor;
 #[cfg(feature = "gecko")]
 pub use values::specified::pointing::CursorImage;
 
@@ -60,20 +60,17 @@ impl Parse for Cursor {
     #[cfg(feature = "gecko")]
     fn parse<'i, 't>(
         context: &ParserContext,
         input: &mut Parser<'i, 't>
     ) -> Result<Self, ParseError<'i>> {
         let mut images = vec![];
         loop {
             match input.try(|input| CursorImage::parse_image(context, input)) {
-                Ok(mut image) => {
-                    image.url.build_image_value();
-                    images.push(image)
-                }
+                Ok(image) => images.push(image),
                 Err(_) => break,
             }
             input.expect_comma()?;
         }
         Ok(Self {
             images: images.into_boxed_slice(),
             keyword: CursorKind::parse(context, input)?,
         })
@@ -109,17 +106,17 @@ impl Parse for CursorKind {
 
 #[cfg(feature = "gecko")]
 impl CursorImage {
     fn parse_image<'i, 't>(
         context: &ParserContext,
         input: &mut Parser<'i, 't>
     ) -> Result<Self, ParseError<'i>> {
         Ok(Self {
-            url: SpecifiedUrl::parse(context, input)?,
+            url: SpecifiedImageUrl::parse(context, input)?,
             // FIXME(emilio): Should use Number::parse to handle calc() correctly.
             hotspot: match input.try(|input| input.expect_number()) {
                 Ok(number) => Some((number, input.expect_number()?)),
                 Err(_) => None,
             },
         })
     }
 }
--- a/servo/components/style/values/mod.rs
+++ b/servo/components/style/values/mod.rs
@@ -12,16 +12,21 @@ use Atom;
 pub use cssparser::{RGBA, Token, Parser, serialize_identifier, CowRcStr, SourceLocation};
 use parser::{Parse, ParserContext};
 use selectors::parser::SelectorParseErrorKind;
 use std::fmt::{self, Debug, Write};
 use std::hash;
 use style_traits::{CssWriter, ParseError, StyleParseErrorKind, ToCss};
 use values::distance::{ComputeSquaredDistance, SquaredDistance};
 
+#[cfg(feature = "servo")]
+pub use servo::url::CssUrl;
+#[cfg(feature = "gecko")]
+pub use gecko::url::CssUrl;
+
 pub mod animated;
 pub mod computed;
 pub mod distance;
 pub mod generics;
 pub mod specified;
 
 /// A CSS float value.
 pub type CSSFloat = f32;
--- a/servo/components/style/values/specified/counters.rs
+++ b/servo/components/style/values/specified/counters.rs
@@ -13,17 +13,17 @@ use values::CustomIdent;
 #[cfg(feature = "gecko")]
 use values::generics::CounterStyleOrNone;
 use values::generics::counters::CounterIncrement as GenericCounterIncrement;
 use values::generics::counters::CounterReset as GenericCounterReset;
 #[cfg(feature = "gecko")]
 use values::specified::Attr;
 use values::specified::Integer;
 #[cfg(feature = "gecko")]
-use values::specified::url::SpecifiedUrl;
+use values::specified::url::SpecifiedImageUrl;
 
 /// A specified value for the `counter-increment` property.
 pub type CounterIncrement = GenericCounterIncrement<Integer>;
 
 impl Parse for CounterIncrement {
     fn parse<'i, 't>(
         context: &ParserContext,
         input: &mut Parser<'i, 't>
@@ -114,10 +114,10 @@ pub enum ContentItem {
     NoOpenQuote,
     /// `no-close-quote`.
     NoCloseQuote,
     /// `attr([namespace? `|`]? ident)`
     #[cfg(feature = "gecko")]
     Attr(Attr),
     /// `url(url)`
     #[cfg(feature = "gecko")]
-    Url(SpecifiedUrl),
+    Url(SpecifiedImageUrl),
 }
--- a/servo/components/style/values/specified/image.rs
+++ b/servo/components/style/values/specified/image.rs
@@ -26,24 +26,24 @@ use values::generics::image::{EndingShap
 use values::generics::image::{GradientItem as GenericGradientItem, GradientKind as GenericGradientKind};
 use values::generics::image::{Image as GenericImage, LineDirection as GenericsLineDirection};
 use values::generics::image::{MozImageRect as GenericMozImageRect, ShapeExtent};
 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::position::{LegacyPosition, Position, PositionComponent, Side, X, Y};
-use values::specified::url::SpecifiedUrl;
+use values::specified::url::SpecifiedImageUrl;
 
 /// A specified image layer.
 pub type ImageLayer = Either<None_, Image>;
 
 /// Specified values for an image according to CSS-IMAGES.
 /// <https://drafts.csswg.org/css-images/#image-values>
-pub type Image = GenericImage<Gradient, MozImageRect, SpecifiedUrl>;
+pub type Image = GenericImage<Gradient, MozImageRect, SpecifiedImageUrl>;
 
 /// Specified values for a CSS gradient.
 /// <https://drafts.csswg.org/css-images/#gradients>
 #[cfg(not(feature = "gecko"))]
 pub type Gradient = GenericGradient<
     LineDirection,
     Length,
     LengthOrPercentage,
@@ -119,54 +119,46 @@ pub type EndingShape = GenericEndingShap
 /// A specified gradient item.
 pub type GradientItem = GenericGradientItem<RGBAColor, LengthOrPercentage>;
 
 /// A computed color stop.
 pub type ColorStop = GenericColorStop<RGBAColor, LengthOrPercentage>;
 
 /// Specified values for `moz-image-rect`
 /// -moz-image-rect(<uri>, top, right, bottom, left);
-pub type MozImageRect = GenericMozImageRect<NumberOrPercentage, SpecifiedUrl>;
+pub type MozImageRect = GenericMozImageRect<NumberOrPercentage, SpecifiedImageUrl>;
 
 impl Parse for Image {
-    #[cfg_attr(not(feature = "gecko"), allow(unused_mut))]
     fn parse<'i, 't>(context: &ParserContext, input: &mut Parser<'i, 't>) -> Result<Image, ParseError<'i>> {
-        if let Ok(mut url) = input.try(|input| SpecifiedUrl::parse(context, input)) {
-            #[cfg(feature = "gecko")]
-            {
-                url.build_image_value();
-            }
+        if let Ok(url) = input.try(|input| SpecifiedImageUrl::parse(context, input)) {
             return Ok(GenericImage::Url(url));
         }
         if let Ok(gradient) = input.try(|i| Gradient::parse(context, i)) {
             return Ok(GenericImage::Gradient(Box::new(gradient)));
         }
         #[cfg(feature = "servo")]
         {
             if let Ok(paint_worklet) = input.try(|i| PaintWorklet::parse(context, i)) {
                 return Ok(GenericImage::PaintWorklet(paint_worklet));
             }
         }
-        if let Ok(mut image_rect) = input.try(|input| MozImageRect::parse(context, input)) {
-            #[cfg(feature = "gecko")]
-            {
-                image_rect.url.build_image_value();
-            }
+        if let Ok(image_rect) = input.try(|input| MozImageRect::parse(context, input)) {
             return Ok(GenericImage::Rect(Box::new(image_rect)));
         }
         Ok(GenericImage::Element(Image::parse_element(input)?))
     }
 }
 
 impl Image {
     /// Creates an already specified image value from an already resolved URL
     /// for insertion in the cascade.
     #[cfg(feature = "servo")]
     pub fn for_cascade(url: ServoUrl) -> Self {
-        GenericImage::Url(SpecifiedUrl::for_cascade(url))
+        use values::CssUrl;
+        GenericImage::Url(CssUrl::for_cascade(url))
     }
 
     /// Parses a `-moz-element(# <element-id>)`.
     fn parse_element<'i, 't>(input: &mut Parser<'i, 't>) -> Result<Atom, ParseError<'i>> {
         input.try(|i| i.expect_function_matching("-moz-element"))?;
         let location = input.current_source_location();
         input.parse_nested_block(|i| {
             match *i.next()? {
@@ -939,28 +931,21 @@ impl Parse for PaintWorklet {
     }
 }
 
 impl Parse for MozImageRect {
     fn parse<'i, 't>(context: &ParserContext, input: &mut Parser<'i, 't>) -> Result<Self, ParseError<'i>> {
         input.try(|i| i.expect_function_matching("-moz-image-rect"))?;
         input.parse_nested_block(|i| {
             let string = i.expect_url_or_string()?;
-            let url = SpecifiedUrl::parse_from_string(string.as_ref().to_owned(), context)?;
+            let url = SpecifiedImageUrl::parse_from_string(string.as_ref().to_owned(), context)?;
             i.expect_comma()?;
             let top = NumberOrPercentage::parse_non_negative(context, i)?;
             i.expect_comma()?;
             let right = NumberOrPercentage::parse_non_negative(context, i)?;
             i.expect_comma()?;
             let bottom = NumberOrPercentage::parse_non_negative(context, i)?;
             i.expect_comma()?;
             let left = NumberOrPercentage::parse_non_negative(context, i)?;
-
-            Ok(MozImageRect {
-                url: url,
-                top: top,
-                right: right,
-                bottom: bottom,
-                left: left,
-            })
+            Ok(MozImageRect { url, top, right, bottom, left })
         })
     }
 }
--- a/servo/components/style/values/specified/list.rs
+++ b/servo/components/style/values/specified/list.rs
@@ -8,17 +8,17 @@ use cssparser::{Parser, Token};
 use parser::{Parse, ParserContext};
 use std::fmt::{self, Write};
 use style_traits::{CssWriter, ParseError, StyleParseErrorKind, ToCss};
 use values::{Either, None_};
 #[cfg(feature = "gecko")]
 use values::CustomIdent;
 #[cfg(feature = "gecko")]
 use values::generics::CounterStyleOrNone;
-use values::specified::UrlOrNone;
+use values::specified::ImageUrlOrNone;
 
 /// Specified and computed `list-style-type` property.
 #[cfg(feature = "gecko")]
 #[derive(Clone, Debug, Eq, MallocSizeOf, PartialEq, ToComputedValue, ToCss)]
 pub enum ListStyleType {
     /// <counter-style> | none
     CounterStyle(CounterStyleOrNone),
     /// <string>
@@ -70,44 +70,36 @@ impl Parse for ListStyleType {
         }
 
         Ok(ListStyleType::String(input.expect_string()?.as_ref().to_owned()))
     }
 }
 
 /// Specified and computed `list-style-image` property.
 #[derive(Clone, Debug, MallocSizeOf, PartialEq, ToCss)]
-pub struct ListStyleImage(pub UrlOrNone);
+pub struct ListStyleImage(pub ImageUrlOrNone);
 
 // FIXME(nox): This is wrong, there are different types for specified
 // and computed URLs in Servo.
 trivial_to_computed_value!(ListStyleImage);
 
 impl ListStyleImage {
     /// Initial specified value for `list-style-image`.
     #[inline]
     pub fn none() -> ListStyleImage {
         ListStyleImage(Either::Second(None_))
     }
 }
 
 impl Parse for ListStyleImage {
-    fn parse<'i, 't>(context: &ParserContext, input: &mut Parser<'i, 't>)
-                         -> Result<ListStyleImage, ParseError<'i>> {
-        #[allow(unused_mut)]
-        let mut value = input.try(|input| UrlOrNone::parse(context, input))?;
-
-        #[cfg(feature = "gecko")]
-        {
-            if let Either::First(ref mut url) = value {
-                url.build_image_value();
-            }
-        }
-
-        return Ok(ListStyleImage(value));
+    fn parse<'i, 't>(
+        context: &ParserContext,
+        input: &mut Parser<'i, 't>,
+    ) -> Result<ListStyleImage, ParseError<'i>> {
+        ImageUrlOrNone::parse(context, input).map(ListStyleImage)
     }
 }
 
 /// Specified and computed `quote` property.
 ///
 /// FIXME(emilio): It's a shame that this allocates all the time it's computed,
 /// probably should just be refcounted.
 #[derive(Clone, Debug, MallocSizeOf, PartialEq, ToComputedValue)]
--- a/servo/components/style/values/specified/mod.rs
+++ b/servo/components/style/values/specified/mod.rs
@@ -6,17 +6,17 @@
 //!
 //! TODO(emilio): Enhance docs.
 
 use Prefix;
 use context::QuirksMode;
 use cssparser::{Parser, Token, serialize_identifier};
 use num_traits::One;
 use parser::{ParserContext, Parse};
-use self::url::SpecifiedUrl;
+use self::url::{SpecifiedImageUrl, SpecifiedUrl};
 use std::f32;
 use std::fmt::{self, Write};
 use style_traits::{CssWriter, ParseError, StyleParseErrorKind, ToCss};
 use style_traits::values::specified::AllowedNumericType;
 use super::{Auto, CSSFloat, CSSInteger, Either, None_};
 use super::computed::{Context, ToComputedValue};
 use super::generics::{GreaterThanOrEqualToOne, NonNegative};
 use super::generics::grid::{GridLine as GenericGridLine, TrackBreadth as GenericTrackBreadth};
@@ -111,33 +111,20 @@ pub mod svg;
 pub mod table;
 pub mod text;
 pub mod time;
 pub mod transform;
 pub mod ui;
 
 /// Common handling for the specified value CSS url() values.
 pub mod url {
-use cssparser::Parser;
-use parser::{Parse, ParserContext};
-use style_traits::ParseError;
-
 #[cfg(feature = "servo")]
-pub use ::servo::url::*;
+pub use ::servo::url::{SpecifiedUrl, SpecifiedImageUrl};
 #[cfg(feature = "gecko")]
-pub use ::gecko::url::*;
-
-impl Parse for SpecifiedUrl {
-    fn parse<'i, 't>(context: &ParserContext, input: &mut Parser<'i, 't>) -> Result<Self, ParseError<'i>> {
-        let url = input.expect_url()?;
-        Self::parse_from_string(url.as_ref().to_owned(), context)
-    }
-}
-
-impl Eq for SpecifiedUrl {}
+pub use ::gecko::url::{SpecifiedUrl, SpecifiedImageUrl};
 }
 
 /// Parse a `<number>` value, with a given clamping mode.
 fn parse_number_with_clamping_mode<'i, 't>(
     context: &ParserContext,
     input: &mut Parser<'i, 't>,
     clamping_mode: AllowedNumericType,
 ) -> Result<Number, ParseError<'i>> {
@@ -529,16 +516,19 @@ impl Parse for PositiveInteger {
     fn parse<'i, 't>(context: &ParserContext, input: &mut Parser<'i, 't>) -> Result<Self, ParseError<'i>> {
         Integer::parse_positive(context, input).map(GreaterThanOrEqualToOne::<Integer>)
     }
 }
 
 #[allow(missing_docs)]
 pub type UrlOrNone = Either<SpecifiedUrl, None_>;
 
+/// The specified value of a `<url>` for image or `none`.
+pub type ImageUrlOrNone = Either<SpecifiedImageUrl, None_>;
+
 /// The specified value of a grid `<track-breadth>`
 pub type TrackBreadth = GenericTrackBreadth<LengthOrPercentage>;
 
 /// The specified value of a grid `<track-size>`
 pub type TrackSize = GenericTrackSize<LengthOrPercentage>;
 
 /// The specified value of a grid `<track-list>`
 /// (could also be `<auto-track-list>` or `<explicit-track-list>`)
--- a/servo/components/style/values/specified/pointing.rs
+++ b/servo/components/style/values/specified/pointing.rs
@@ -8,17 +8,17 @@
 
 use cssparser::Parser;
 use parser::{Parse, ParserContext};
 use style_traits::ParseError;
 use style_traits::cursor::CursorKind;
 use values::generics::pointing::CaretColor as GenericCaretColor;
 use values::specified::color::Color;
 #[cfg(feature = "gecko")]
-use values::specified::url::SpecifiedUrl;
+use values::specified::url::SpecifiedImageUrl;
 
 /// The specified value for the `cursor` property.
 ///
 /// https://drafts.csswg.org/css-ui/#cursor
 #[cfg(feature = "servo")]
 #[derive(Clone, Copy, Debug, MallocSizeOf, PartialEq, ToComputedValue, ToCss)]
 pub struct Cursor(pub CursorKind);
 
@@ -34,17 +34,17 @@ pub struct Cursor {
     pub keyword: CursorKind,
 }
 
 /// The specified value for the `image cursors`.
 #[cfg(feature = "gecko")]
 #[derive(Clone, Debug, MallocSizeOf, PartialEq, ToComputedValue)]
 pub struct CursorImage {
     /// The url to parse images from.
-    pub url: SpecifiedUrl,
+    pub url: SpecifiedImageUrl,
     /// The <x> and <y> coordinates.
     pub hotspot: Option<(f32, f32)>,
 }
 
 /// A specified value for the `caret-color` property.
 pub type CaretColor = GenericCaretColor<Color>;
 
 impl Parse for CaretColor {
--- a/servo/ports/geckolib/glue.rs
+++ b/servo/ports/geckolib/glue.rs
@@ -3539,29 +3539,28 @@ pub extern "C" fn Servo_DeclarationBlock
     declarations: RawServoDeclarationBlockBorrowed,
     value: *const nsAString,
     raw_extra_data: *mut URLExtraData,
 ) {
     use style::properties::PropertyDeclaration;
     use style::properties::longhands::background_image::SpecifiedValue as BackgroundImage;
     use style::values::Either;
     use style::values::generics::image::Image;
-    use style::values::specified::url::SpecifiedUrl;
+    use style::values::specified::url::SpecifiedImageUrl;
 
     let url_data = unsafe { RefPtr::from_ptr_ref(&raw_extra_data) };
     let string = unsafe { (*value).to_string() };
     let context = ParserContext::new(
         Origin::Author,
         url_data,
         Some(CssRuleType::Style),
         ParsingMode::DEFAULT,
         QuirksMode::NoQuirks,
     );
-    if let Ok(mut url) = SpecifiedUrl::parse_from_string(string.into(), &context) {
-        url.build_image_value();
+    if let Ok(url) = SpecifiedImageUrl::parse_from_string(string.into(), &context) {
         let decl = PropertyDeclaration::BackgroundImage(BackgroundImage(
             vec![Either::Second(Image::Url(url))]
         ));
         write_locked_arc(declarations, |decls: &mut PropertyDeclarationBlock| {
             decls.push(decl, Importance::Normal, DeclarationSource::CssOm);
         })
     }
 }
--- a/servo/ports/geckolib/stylesheet_loader.rs
+++ b/servo/ports/geckolib/stylesheet_loader.rs
@@ -8,33 +8,33 @@ use style::gecko::data::GeckoStyleSheet;
 use style::gecko_bindings::bindings::Gecko_LoadStyleSheet;
 use style::gecko_bindings::structs::{Loader, ServoStyleSheet, SheetLoadData, LoaderReusableStyleSheets};
 use style::gecko_bindings::sugar::ownership::FFIArcHelpers;
 use style::media_queries::MediaList;
 use style::parser::ParserContext;
 use style::shared_lock::{Locked, SharedRwLock};
 use style::stylesheets::{ImportRule, StylesheetLoader as StyleStylesheetLoader};
 use style::stylesheets::import_rule::ImportSheet;
-use style::values::specified::url::SpecifiedUrl;
+use style::values::CssUrl;
 
 pub struct StylesheetLoader(*mut Loader, *mut ServoStyleSheet, *mut SheetLoadData, *mut LoaderReusableStyleSheets);
 
 impl StylesheetLoader {
     pub fn new(loader: *mut Loader,
                parent: *mut ServoStyleSheet,
                parent_load_data: *mut SheetLoadData,
                reusable_sheets: *mut LoaderReusableStyleSheets) -> Self {
         StylesheetLoader(loader, parent, parent_load_data, reusable_sheets)
     }
 }
 
 impl StyleStylesheetLoader for StylesheetLoader {
     fn request_stylesheet(
         &self,
-        url: SpecifiedUrl,
+        url: CssUrl,
         source_location: SourceLocation,
         _context: &ParserContext,
         lock: &SharedRwLock,
         media: Arc<Locked<MediaList>>,
     ) -> Arc<Locked<ImportRule>> {
         // After we get this raw pointer ImportRule will be moved into a lock and Arc
         // and so the Arc<Url> pointer inside will also move,
         // but the Url it points to or the allocating backing the String inside that Url won’t,
--- a/servo/ports/geckolib/tests/size_of.rs
+++ b/servo/ports/geckolib/tests/size_of.rs
@@ -37,15 +37,15 @@ size_of_test!(test_size_of_property_decl
 
 size_of_test!(test_size_of_application_declaration_block, ApplicableDeclarationBlock, 24);
 size_of_test!(test_size_of_rule_node, RuleNode, 80);
 
 // This is huge, but we allocate it on the stack and then never move it,
 // we only pass `&mut SourcePropertyDeclaration` references around.
 size_of_test!(test_size_of_parsed_declaration, style::properties::SourcePropertyDeclaration, 608);
 
-size_of_test!(test_size_of_computed_image, computed::image::Image, 40);
-size_of_test!(test_size_of_specified_image, specified::image::Image, 40);
+size_of_test!(test_size_of_computed_image, computed::image::Image, 32);
+size_of_test!(test_size_of_specified_image, specified::image::Image, 32);
 
 // FIXME(bz): These can shrink if we move the None_ value inside the
 // enum instead of paying an extra word for the Either discriminant.
-size_of_test!(test_size_of_computed_image_layer, computed::image::ImageLayer, 40);
-size_of_test!(test_size_of_specified_image_layer, specified::image::ImageLayer, 40);
+size_of_test!(test_size_of_computed_image_layer, computed::image::ImageLayer, 32);
+size_of_test!(test_size_of_specified_image_layer, specified::image::ImageLayer, 32);