Add SpecifiedImageUrl for <url> used as images. r?emilio draft
authorXidorn Quan <me@upsuper.org>
Tue, 06 Mar 2018 09:03:05 +1100
changeset 764808 84f9d53fe19be70f24e9d754442828d1dbe7b0ec
parent 764726 c19e88c387b91ddd482f09f38f82155af8e245aa
child 764809 86dad42f3e780501318a27ca0467f3ac49c12ca5
push id101854
push userxquan@mozilla.com
push dateThu, 08 Mar 2018 10:32:37 +0000
reviewersemilio
milestone60.0a1
Add SpecifiedImageUrl for <url> used as images. r?emilio MozReview-Commit-ID: EgRPEcCCx15
servo/components/style/gecko/conversions.rs
servo/components/style/gecko/url.rs
servo/components/style/properties/gecko.mako.rs
servo/components/style/servo/url.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/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
--- 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.as_ref().unwrap().get());
+                    Gecko_SetLayerImageImageValue(self, url.image_value.get());
                 }
             },
             GenericImage::Rect(ref image_rect) => {
                 unsafe {
-                    Gecko_SetLayerImageImageValue(self, image_rect.url.image_value.as_ref().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/url.rs
+++ b/servo/components/style/gecko/url.rs
@@ -1,21 +1,23 @@
 /* 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::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.
 #[css(function = "url")]
 #[derive(Clone, Debug, PartialEq, ToCss)]
 pub struct SpecifiedUrl {
@@ -23,36 +25,30 @@ pub struct SpecifiedUrl {
     ///
     /// 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 {
     /// 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 {
             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
@@ -66,33 +62,19 @@ impl SpecifiedUrl {
                 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 +95,95 @@ 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 MallocSizeOf for SpecifiedUrl {
     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.get()) };
+        0
+    }
+}
+
+/// 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: SpecifiedUrl,
+    /// 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_specified_url(url: SpecifiedUrl) -> 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>> {
+        SpecifiedUrl::parse_from_string(url, context).map(Self::from_specified_url)
+    }
+
+    /// Convert from URLValueData to SpecifiedUrl.
+    pub unsafe fn from_url_value_data(url: &URLValueData) -> Result<Self, ()> {
+        SpecifiedUrl::from_url_value_data(url).map(Self::from_specified_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>> {
+        SpecifiedUrl::parse(context, input).map(Self::from_specified_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
     }
 }
--- a/servo/components/style/properties/gecko.mako.rs
+++ b/servo/components/style/properties/gecko.mako.rs
@@ -4067,18 +4067,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.as_ref().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 +4086,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;
@@ -5285,18 +5284,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.image_value.as_ref().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 +5322,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 +5367,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 +5546,20 @@ clip-path
                                 &name,
                                 &sep,
                                 style.clone(),
                                 device,
                             );
                         }
                         ContentItem::Url(ref url) => {
                             unsafe {
-                                bindings::Gecko_SetContentDataImageValue(&mut self.gecko.mContents[i],
-                                    url.image_value.as_ref().unwrap().get())
+                                bindings::Gecko_SetContentDataImageValue(
+                                    &mut self.gecko.mContents[i],
+                                    url.image_value.get(),
+                                )
                             }
                         }
                     }
                 }
             }
         }
     }
 
@@ -5573,17 +5576,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 +5639,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/servo/url.rs
+++ b/servo/components/style/servo/url.rs
@@ -158,8 +158,10 @@ 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 = SpecifiedUrl;
--- 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
@@ -641,19 +641,27 @@ impl ClipRectOrAuto {
 #[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
+/// The computed value of a CSS `url()` for image.
+#[cfg(feature = "servo")]
+pub type ComputedImageUrl = ComputedUrl;
+
+// TODO: Properly build ComputedUrl for gecko
+/// The computed value of a CSS `url()`.
 #[cfg(feature = "gecko")]
 pub type ComputedUrl = specified::url::SpecifiedUrl;
+/// The computed value of a CSS `url()` for image.
+#[cfg(feature = "gecko")]
+pub type ComputedImageUrl = specified::url::SpecifiedImageUrl;
 
 #[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,
@@ -675,8 +683,11 @@ impl ToCss for ComputedUrl {
         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/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,42 +119,33 @@ 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
@@ -939,28 +930,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};
@@ -529,16 +529,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);
         })
     }
 }