servo: Merge #15697 - Add support for most of the border-image properties, pass to WR (from glennw:border-image); r=jdm,pcwalton
authorGlenn Watson <github@intuitionlibrary.com>
Thu, 23 Feb 2017 13:40:40 -0800
changeset 373569 0de9166647a7d171e504ed6e8a6b85f3943c9977
parent 373568 2c2c93735e70ece5858734fdbc9e49bf3bd3ac8d
child 373570 f95acc1bb74ed29c86d2fe688c4a65e8ba00821d
push id10863
push userjlorenzo@mozilla.com
push dateMon, 06 Mar 2017 23:02:23 +0000
treeherdermozilla-aurora@0931190cd725 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersjdm, pcwalton
milestone54.0a1
servo: Merge #15697 - Add support for most of the border-image properties, pass to WR (from glennw:border-image); r=jdm,pcwalton This adds support for: * border-image (images, not gradients yet) * border-image-source * border-image-slice * border-image-repeat (stretch + repeat only for now) Remaining work: * Connect border-image-outset (WR supports this). * border-image-width Source-Repo: https://github.com/servo/servo Source-Revision: 6c9e94b1c07dd148430d175e79c595db82dea742
servo/components/gfx/display_list/mod.rs
servo/components/layout/display_list_builder.rs
servo/components/layout/webrender_helpers.rs
servo/components/script/dom/webidls/CSSStyleDeclaration.webidl
servo/components/style/properties/longhand/border.mako.rs
servo/components/style/properties/shorthand/border.mako.rs
--- a/servo/components/gfx/display_list/mod.rs
+++ b/servo/components/gfx/display_list/mod.rs
@@ -886,37 +886,75 @@ pub struct GradientDisplayItem {
 
     /// The end point of the gradient (computed during display list construction).
     pub end_point: Point2D<Au>,
 
     /// A list of color stops.
     pub stops: Vec<GradientStop>,
 }
 
-/// Paints a border.
+/// A normal border, supporting CSS border styles.
 #[derive(Clone, HeapSizeOf, Deserialize, Serialize)]
-pub struct BorderDisplayItem {
-    /// Fields common to all display items.
-    pub base: BaseDisplayItem,
-
-    /// Border widths.
-    pub border_widths: SideOffsets2D<Au>,
-
+pub struct NormalBorder {
     /// Border colors.
     pub color: SideOffsets2D<ColorF>,
 
     /// Border styles.
     pub style: SideOffsets2D<border_style::T>,
 
     /// Border radii.
     ///
     /// TODO(pcwalton): Elliptical radii.
     pub radius: BorderRadii<Au>,
 }
 
+/// A border that is made of image segments.
+#[derive(Clone, HeapSizeOf, Deserialize, Serialize)]
+pub struct ImageBorder {
+    /// The image this border uses, border-image-source.
+    pub image: WebRenderImageInfo,
+
+    /// How to slice the image, as per border-image-slice.
+    pub slice: SideOffsets2D<u32>,
+
+    /// Outsets for the border, as per border-image-outset.
+    pub outset: SideOffsets2D<f32>,
+
+    /// If fill is true, draw the center patch of the image.
+    pub fill: bool,
+
+    /// How to repeat or stretch horizontal edges (border-image-repeat).
+    #[ignore_heap_size_of = "WebRender traits type, and tiny"]
+    pub repeat_horizontal: webrender_traits::RepeatMode,
+
+    /// How to repeat or stretch vertical edges (border-image-repeat).
+    #[ignore_heap_size_of = "WebRender traits type, and tiny"]
+    pub repeat_vertical: webrender_traits::RepeatMode,
+}
+
+/// Specifies the type of border
+#[derive(Clone, HeapSizeOf, Deserialize, Serialize)]
+pub enum BorderDetails {
+    Normal(NormalBorder),
+    Image(ImageBorder),
+}
+
+/// Paints a border.
+#[derive(Clone, HeapSizeOf, Deserialize, Serialize)]
+pub struct BorderDisplayItem {
+    /// Fields common to all display items.
+    pub base: BaseDisplayItem,
+
+    /// Border widths.
+    pub border_widths: SideOffsets2D<Au>,
+
+    /// Details for specific border type
+    pub details: BorderDetails,
+}
+
 /// Information about the border radii.
 ///
 /// TODO(pcwalton): Elliptical radii.
 #[derive(Clone, PartialEq, Debug, Copy, HeapSizeOf, Deserialize, Serialize)]
 pub struct BorderRadii<T> {
     pub top_left: Size2D<T>,
     pub top_right: Size2D<T>,
     pub bottom_right: Size2D<T>,
--- a/servo/components/layout/display_list_builder.rs
+++ b/servo/components/layout/display_list_builder.rs
@@ -15,17 +15,18 @@ use block::{BlockFlow, BlockStackingCont
 use canvas_traits::{CanvasData, CanvasMsg, FromLayoutMsg};
 use context::LayoutContext;
 use euclid::{Point2D, Rect, SideOffsets2D, Size2D, TypedSize2D};
 use flex::FlexFlow;
 use flow::{BaseFlow, Flow, IS_ABSOLUTELY_POSITIONED};
 use flow_ref::FlowRef;
 use fragment::{CoordinateSystem, Fragment, ImageFragmentInfo, ScannedTextFragmentInfo};
 use fragment::{SpecificFragmentInfo, TruncatedFragmentInfo};
-use gfx::display_list::{BLUR_INFLATION_FACTOR, BaseDisplayItem, BorderDisplayItem};
+use gfx::display_list::{BLUR_INFLATION_FACTOR, BaseDisplayItem, BorderDetails};
+use gfx::display_list::{BorderDisplayItem, ImageBorder, NormalBorder};
 use gfx::display_list::{BorderRadii, BoxShadowClipMode, BoxShadowDisplayItem, ClippingRegion};
 use gfx::display_list::{DisplayItem, DisplayItemMetadata, DisplayList, DisplayListSection};
 use gfx::display_list::{GradientDisplayItem, IframeDisplayItem, ImageDisplayItem};
 use gfx::display_list::{LineDisplayItem, OpaqueNode};
 use gfx::display_list::{SolidColorDisplayItem, ScrollRoot, StackingContext, StackingContextType};
 use gfx::display_list::{TextDisplayItem, TextOrientation, WebGLDisplayItem, WebRenderImageInfo};
 use gfx_traits::{ScrollRootId, StackingContextId};
 use inline::{FIRST_FRAGMENT_OF_ELEMENT, InlineFlow, LAST_FRAGMENT_OF_ELEMENT};
@@ -40,32 +41,59 @@ use servo_config::opts;
 use servo_url::ServoUrl;
 use std::{cmp, f32};
 use std::collections::HashMap;
 use std::default::Default;
 use std::mem;
 use std::sync::Arc;
 use style::computed_values::{background_attachment, background_clip, background_origin};
 use style::computed_values::{background_repeat, background_size, border_style};
-use style::computed_values::{cursor, image_rendering, overflow_x};
+use style::computed_values::{cursor, image_rendering, overflow_x, border_image_slice};
 use style::computed_values::{pointer_events, position, transform_style, visibility};
 use style::computed_values::_servo_overflow_clip_box as overflow_clip_box;
 use style::computed_values::filter::Filter;
 use style::computed_values::text_shadow::TextShadow;
 use style::logical_geometry::{LogicalPoint, LogicalRect, LogicalSize, WritingMode};
 use style::properties::{self, ServoComputedValues};
+use style::properties::longhands::border_image_repeat::computed_value::RepeatKeyword;
 use style::properties::style_structs;
 use style::servo::restyle_damage::REPAINT;
 use style::values::{RGBA, computed};
 use style::values::computed::{AngleOrCorner, Gradient, GradientKind, LengthOrPercentage, LengthOrPercentageOrAuto};
 use style::values::specified::{HorizontalDirection, VerticalDirection};
 use style_traits::PagePx;
 use style_traits::cursor::Cursor;
 use table_cell::CollapsedBordersForCell;
-use webrender_traits::{ColorF, GradientStop, ScrollPolicy};
+use webrender_traits::{ColorF, GradientStop, RepeatMode, ScrollPolicy};
+
+trait ResolvePercentage {
+    fn resolve(&self, length: u32) -> u32;
+}
+
+impl ResolvePercentage for border_image_slice::PercentageOrNumber {
+    fn resolve(&self, length: u32) -> u32 {
+        match *self {
+            border_image_slice::PercentageOrNumber::Percentage(p) => {
+                (p.0 * length as f32).round() as u32
+            }
+            border_image_slice::PercentageOrNumber::Number(n) => {
+                n.round() as u32
+            }
+        }
+    }
+}
+
+fn convert_repeat_mode(from: RepeatKeyword) -> RepeatMode {
+    match from {
+        RepeatKeyword::Stretch => RepeatMode::Stretch,
+        RepeatKeyword::Repeat => RepeatMode::Repeat,
+        RepeatKeyword::Round => RepeatMode::Round,
+        RepeatKeyword::Space => RepeatMode::Space,
+    }
+}
 
 trait RgbColor {
     fn rgb(r: u8, g: u8, b: u8) -> Self;
 }
 
 impl RgbColor for ColorF {
     fn rgb(r: u8, g: u8, b: u8) -> Self {
         ColorF {
@@ -1051,26 +1079,65 @@ impl FragmentDisplayListBuilding for Fra
         }
 
         // Append the border to the display list.
         let base = state.create_base_display_item(&bounds,
                                                   &clip,
                                                   self.node,
                                                   style.get_cursor(Cursor::Default),
                                                   display_list_section);
-        state.add_display_item(DisplayItem::Border(box BorderDisplayItem {
-            base: base,
-            border_widths: border.to_physical(style.writing_mode),
-            color: SideOffsets2D::new(colors.top.to_gfx_color(),
-                                      colors.right.to_gfx_color(),
-                                      colors.bottom.to_gfx_color(),
-                                      colors.left.to_gfx_color()),
-            style: border_style,
-            radius: build_border_radius(&bounds, border_style_struct),
-        }));
+
+        match border_style_struct.border_image_source.0 {
+            None => {
+                state.add_display_item(DisplayItem::Border(box BorderDisplayItem {
+                    base: base,
+                    border_widths: border.to_physical(style.writing_mode),
+                    details: BorderDetails::Normal(NormalBorder {
+                        color: SideOffsets2D::new(colors.top.to_gfx_color(),
+                                                  colors.right.to_gfx_color(),
+                                                  colors.bottom.to_gfx_color(),
+                                                  colors.left.to_gfx_color()),
+                        style: border_style,
+                        radius: build_border_radius(&bounds, border_style_struct),
+                    }),
+                }));
+            }
+            Some(computed::Image::Gradient(..)) => {
+                // TODO(gw): Handle border-image with gradient.
+            }
+            Some(computed::Image::Url(ref image_url)) => {
+                if let Some(url) = image_url.url() {
+                    let webrender_image = state.layout_context
+                                               .get_webrender_image_for_url(self.node,
+                                                                            url.clone(),
+                                                                            UsePlaceholder::No);
+                    if let Some(webrender_image) = webrender_image {
+                        // The corners array is guaranteed to be len=4 by the css parser.
+                        let corners = &border_style_struct.border_image_slice.corners;
+
+                        state.add_display_item(DisplayItem::Border(box BorderDisplayItem {
+                            base: base,
+                            border_widths: border.to_physical(style.writing_mode),
+                            details: BorderDetails::Image(ImageBorder {
+                                image: webrender_image,
+                                fill: border_style_struct.border_image_slice.fill,
+                                slice: SideOffsets2D::new(corners[0].resolve(webrender_image.height),
+                                                          corners[1].resolve(webrender_image.width),
+                                                          corners[2].resolve(webrender_image.height),
+                                                          corners[3].resolve(webrender_image.width)),
+                                // TODO(gw): Support border-image-outset
+                                outset: SideOffsets2D::zero(),
+                                repeat_horizontal: convert_repeat_mode(border_style_struct.border_image_repeat.0),
+                                repeat_vertical: convert_repeat_mode(border_style_struct.border_image_repeat.1),
+                            }),
+                        }));
+                    }
+                }
+            }
+        }
     }
 
     fn build_display_list_for_outline_if_applicable(&self,
                                                     state: &mut DisplayListBuildState,
                                                     style: &ServoComputedValues,
                                                     bounds: &Rect<Au>,
                                                     clip: &ClippingRegion) {
         use style::values::Either;
@@ -1100,19 +1167,21 @@ impl FragmentDisplayListBuilding for Fra
         let base = state.create_base_display_item(&bounds,
                                                   &clip,
                                                   self.node,
                                                   style.get_cursor(Cursor::Default),
                                                   DisplayListSection::Outlines);
         state.add_display_item(DisplayItem::Border(box BorderDisplayItem {
             base: base,
             border_widths: SideOffsets2D::new_all_same(width),
-            color: SideOffsets2D::new_all_same(color),
-            style: SideOffsets2D::new_all_same(outline_style),
-            radius: Default::default(),
+            details: BorderDetails::Normal(NormalBorder {
+                color: SideOffsets2D::new_all_same(color),
+                style: SideOffsets2D::new_all_same(outline_style),
+                radius: Default::default(),
+            }),
         }));
     }
 
     fn build_debug_borders_around_text_fragments(&self,
                                                  state: &mut DisplayListBuildState,
                                                  style: &ServoComputedValues,
                                                  stacking_relative_border_box: &Rect<Au>,
                                                  stacking_relative_content_box: &Rect<Au>,
@@ -1125,19 +1194,21 @@ impl FragmentDisplayListBuilding for Fra
         let base = state.create_base_display_item(stacking_relative_border_box,
                                                   clip,
                                                   self.node,
                                                   style.get_cursor(Cursor::Default),
                                                   DisplayListSection::Content);
         state.add_display_item(DisplayItem::Border(box BorderDisplayItem {
             base: base,
             border_widths: SideOffsets2D::new_all_same(Au::from_px(1)),
-            color: SideOffsets2D::new_all_same(ColorF::rgb(0, 0, 200)),
-            style: SideOffsets2D::new_all_same(border_style::T::solid),
-            radius: Default::default(),
+            details: BorderDetails::Normal(NormalBorder {
+                color: SideOffsets2D::new_all_same(ColorF::rgb(0, 0, 200)),
+                style: SideOffsets2D::new_all_same(border_style::T::solid),
+                radius: Default::default(),
+            }),
         }));
 
         // Draw a rectangle representing the baselines.
         let mut baseline = LogicalRect::from_physical(self.style.writing_mode,
                                                       *stacking_relative_content_box,
                                                       container_size);
         baseline.start.b = baseline.start.b + text_fragment.run.ascent();
         baseline.size.block = Au(0);
@@ -1163,19 +1234,21 @@ impl FragmentDisplayListBuilding for Fra
         let base = state.create_base_display_item(stacking_relative_border_box,
                                                   clip,
                                                   self.node,
                                                   self.style.get_cursor(Cursor::Default),
                                                   DisplayListSection::Content);
         state.add_display_item(DisplayItem::Border(box BorderDisplayItem {
             base: base,
             border_widths: SideOffsets2D::new_all_same(Au::from_px(1)),
-            color: SideOffsets2D::new_all_same(ColorF::rgb(0, 0, 200)),
-            style: SideOffsets2D::new_all_same(border_style::T::solid),
-            radius: Default::default(),
+            details: BorderDetails::Normal(NormalBorder {
+                color: SideOffsets2D::new_all_same(ColorF::rgb(0, 0, 200)),
+                style: SideOffsets2D::new_all_same(border_style::T::solid),
+                radius: Default::default(),
+            }),
         }));
     }
 
     fn adjust_clip_for_style(&self,
                              parent_clip: &mut ClippingRegion,
                              stacking_relative_border_box: &Rect<Au>) {
         use style::values::Either;
         // Account for `clip` per CSS 2.1 ยง 11.1.2.
@@ -2124,19 +2197,21 @@ impl BaseFlowDisplayListBuilding for Bas
             &stacking_context_relative_bounds.inflate(Au::from_px(2), Au::from_px(2)),
             &self.clip,
             node,
             None,
             DisplayListSection::Content);
         state.add_display_item(DisplayItem::Border(box BorderDisplayItem {
             base: base,
             border_widths: SideOffsets2D::new_all_same(Au::from_px(2)),
-            color: SideOffsets2D::new_all_same(color),
-            style: SideOffsets2D::new_all_same(border_style::T::solid),
-            radius: BorderRadii::all_same(Au(0)),
+            details: BorderDetails::Normal(NormalBorder {
+                color: SideOffsets2D::new_all_same(color),
+                style: SideOffsets2D::new_all_same(border_style::T::solid),
+                radius: BorderRadii::all_same(Au(0)),
+            }),
         }));
     }
 }
 
 trait ServoComputedValuesCursorUtility {
     fn get_cursor(&self, default_cursor: Cursor) -> Option<Cursor>;
 }
 
--- a/servo/components/layout/webrender_helpers.rs
+++ b/servo/components/layout/webrender_helpers.rs
@@ -3,18 +3,18 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 // TODO(gw): This contains helper traits and implementations for converting Servo display lists
 //           into WebRender display lists. In the future, this step should be completely removed.
 //           This might be achieved by sharing types between WR and Servo display lists, or
 //           completely converting layout to directly generate WebRender display lists, for example.
 
 use app_units::Au;
-use euclid::{Point2D, Rect, Size2D};
-use gfx::display_list::{BorderRadii, BoxShadowClipMode, ClippingRegion};
+use euclid::{Point2D, Rect, SideOffsets2D, Size2D};
+use gfx::display_list::{BorderDetails, BorderRadii, BoxShadowClipMode, ClippingRegion};
 use gfx::display_list::{DisplayItem, DisplayList, DisplayListTraversal, StackingContextType};
 use gfx_traits::{FragmentType, ScrollRootId};
 use msg::constellation_msg::PipelineId;
 use style::computed_values::{image_rendering, mix_blend_mode};
 use style::computed_values::filter::{self, Filter};
 use style::values::computed::BorderStyle;
 use webrender_traits::{self, DisplayListBuilder, ExtendMode, LayoutTransform};
 
@@ -41,16 +41,32 @@ impl ToBorderStyle for BorderStyle {
             BorderStyle::hidden => webrender_traits::BorderStyle::Hidden,
             BorderStyle::groove => webrender_traits::BorderStyle::Groove,
             BorderStyle::ridge => webrender_traits::BorderStyle::Ridge,
             BorderStyle::inset => webrender_traits::BorderStyle::Inset,
             BorderStyle::outset => webrender_traits::BorderStyle::Outset,
         }
     }
 }
+
+trait ToBorderWidths {
+    fn to_border_widths(&self) -> webrender_traits::BorderWidths;
+}
+
+impl ToBorderWidths for SideOffsets2D<Au> {
+    fn to_border_widths(&self) -> webrender_traits::BorderWidths {
+        webrender_traits::BorderWidths {
+            left: self.left.to_f32_px(),
+            top: self.top.to_f32_px(),
+            right: self.right.to_f32_px(),
+            bottom: self.bottom.to_f32_px(),
+        }
+    }
+}
+
 trait ToBoxShadowClipMode {
     fn to_clip_mode(&self) -> webrender_traits::BoxShadowClipMode;
 }
 
 impl ToBoxShadowClipMode for BoxShadowClipMode {
     fn to_clip_mode(&self) -> webrender_traits::BoxShadowClipMode {
         match *self {
             BoxShadowClipMode::None => webrender_traits::BoxShadowClipMode::None,
@@ -265,51 +281,67 @@ impl WebRenderDisplayItemConverter for D
                 }
             }
             DisplayItem::WebGL(ref item) => {
                 let clip = item.base.clip.to_clip_region(builder);
                 builder.push_webgl_canvas(item.base.bounds.to_rectf(), clip, item.context_id);
             }
             DisplayItem::Border(ref item) => {
                 let rect = item.base.bounds.to_rectf();
-                let widths = webrender_traits::BorderWidths {
-                    left: item.border_widths.left.to_f32_px(),
-                    top: item.border_widths.top.to_f32_px(),
-                    right: item.border_widths.right.to_f32_px(),
-                    bottom: item.border_widths.bottom.to_f32_px(),
-                };
-                let left = webrender_traits::BorderSide {
-                    color: item.color.left,
-                    style: item.style.left.to_border_style(),
-                };
-                let top = webrender_traits::BorderSide {
-                    color: item.color.top,
-                    style: item.style.top.to_border_style(),
-                };
-                let right = webrender_traits::BorderSide {
-                    color: item.color.right,
-                    style: item.style.right.to_border_style(),
+                let widths = item.border_widths.to_border_widths();
+                let clip = item.base.clip.to_clip_region(builder);
+
+                let details = match item.details {
+                    BorderDetails::Normal(ref border) => {
+                        let left = webrender_traits::BorderSide {
+                            color: border.color.left,
+                            style: border.style.left.to_border_style(),
+                        };
+                        let top = webrender_traits::BorderSide {
+                            color: border.color.top,
+                            style: border.style.top.to_border_style(),
+                        };
+                        let right = webrender_traits::BorderSide {
+                            color: border.color.right,
+                            style: border.style.right.to_border_style(),
+                        };
+                        let bottom = webrender_traits::BorderSide {
+                            color: border.color.bottom,
+                            style: border.style.bottom.to_border_style(),
+                        };
+                        let radius = border.radius.to_border_radius();
+                        webrender_traits::BorderDetails::Normal(webrender_traits::NormalBorder {
+                            left: left,
+                            top: top,
+                            right: right,
+                            bottom: bottom,
+                            radius: radius,
+                        })
+                    }
+                    BorderDetails::Image(ref image) => {
+                        match image.image.key {
+                            None => return,
+                            Some(key) => {
+                                webrender_traits::BorderDetails::Image(webrender_traits::ImageBorder {
+                                    image_key: key,
+                                    patch: webrender_traits::NinePatchDescriptor {
+                                        width: image.image.width,
+                                        height: image.image.height,
+                                        slice: image.slice,
+                                    },
+                                    outset: image.outset,
+                                    repeat_horizontal: image.repeat_horizontal,
+                                    repeat_vertical: image.repeat_vertical,
+                                })
+                            }
+                        }
+                    }
                 };
-                let bottom = webrender_traits::BorderSide {
-                    color: item.color.bottom,
-                    style: item.style.bottom.to_border_style(),
-                };
-                let radius = item.radius.to_border_radius();
-                let clip = item.base.clip.to_clip_region(builder);
-                let details = webrender_traits::NormalBorder {
-                    left: left,
-                    top: top,
-                    right: right,
-                    bottom: bottom,
-                    radius: radius,
-                };
-                builder.push_border(rect,
-                                    clip,
-                                    widths,
-                                    webrender_traits::BorderDetails::Normal(details));
+
+                builder.push_border(rect, clip, widths, details);
             }
             DisplayItem::Gradient(ref item) => {
                 let rect = item.base.bounds.to_rectf();
                 let start_point = item.start_point.to_pointf();
                 let end_point = item.end_point.to_pointf();
                 let clip = item.base.clip.to_clip_region(builder);
                 builder.push_gradient(rect,
                                       clip,
--- a/servo/components/script/dom/webidls/CSSStyleDeclaration.webidl
+++ b/servo/components/script/dom/webidls/CSSStyleDeclaration.webidl
@@ -101,16 +101,28 @@ partial interface CSSStyleDeclaration {
   [SetterThrows, TreatNullAs=EmptyString] attribute DOMString borderTopLeftRadius;
   [SetterThrows, TreatNullAs=EmptyString] attribute DOMString border-top-left-radius;
   [SetterThrows, TreatNullAs=EmptyString] attribute DOMString borderTopRightRadius;
   [SetterThrows, TreatNullAs=EmptyString] attribute DOMString border-top-right-radius;
   [SetterThrows, TreatNullAs=EmptyString] attribute DOMString borderTopStyle;
   [SetterThrows, TreatNullAs=EmptyString] attribute DOMString border-top-style;
   [SetterThrows, TreatNullAs=EmptyString] attribute DOMString borderTopWidth;
   [SetterThrows, TreatNullAs=EmptyString] attribute DOMString border-top-width;
+  [SetterThrows, TreatNullAs=EmptyString] attribute DOMString border-image-source;
+  [SetterThrows, TreatNullAs=EmptyString] attribute DOMString borderImageSource;
+  [SetterThrows, TreatNullAs=EmptyString] attribute DOMString border-image-slice;
+  [SetterThrows, TreatNullAs=EmptyString] attribute DOMString borderImageSlice;
+  [SetterThrows, TreatNullAs=EmptyString] attribute DOMString border-image-repeat;
+  [SetterThrows, TreatNullAs=EmptyString] attribute DOMString borderImageRepeat;
+  [SetterThrows, TreatNullAs=EmptyString] attribute DOMString border-image-outset;
+  [SetterThrows, TreatNullAs=EmptyString] attribute DOMString borderImageOutset;
+  [SetterThrows, TreatNullAs=EmptyString] attribute DOMString border-image-width;
+  [SetterThrows, TreatNullAs=EmptyString] attribute DOMString borderImageWidth;
+  [SetterThrows, TreatNullAs=EmptyString] attribute DOMString border-image;
+  [SetterThrows, TreatNullAs=EmptyString] attribute DOMString borderImage;
 
   [SetterThrows, TreatNullAs=EmptyString] attribute DOMString border-block-start-color;
   [SetterThrows, TreatNullAs=EmptyString] attribute DOMString borderBlockStartColor;
   [SetterThrows, TreatNullAs=EmptyString] attribute DOMString border-block-start-width;
   [SetterThrows, TreatNullAs=EmptyString] attribute DOMString borderBlockStartWidth;
   [SetterThrows, TreatNullAs=EmptyString] attribute DOMString border-block-start-style;
   [SetterThrows, TreatNullAs=EmptyString] attribute DOMString borderBlockStartStyle;
   [SetterThrows, TreatNullAs=EmptyString] attribute DOMString border-block-end-color;
--- a/servo/components/style/properties/longhand/border.mako.rs
+++ b/servo/components/style/properties/longhand/border.mako.rs
@@ -81,17 +81,17 @@
 ${helpers.single_keyword("-moz-float-edge", "content-box margin-box",
                          gecko_ffi_name="mFloatEdge",
                          gecko_enum_prefix="StyleFloatEdge",
                          gecko_inexhaustive=True,
                          products="gecko",
                          spec="Nonstandard (https://developer.mozilla.org/en-US/docs/Web/CSS/-moz-float-edge)",
                          animatable=False)}
 
-<%helpers:longhand name="border-image-source" products="gecko" animatable="False" boxed="True"
+<%helpers:longhand name="border-image-source" animatable="False" boxed="True"
                    spec="https://drafts.csswg.org/css-backgrounds/#border-image-source">
     use std::fmt;
     use style_traits::ToCss;
     use values::HasViewportPercentage;
     use values::specified::Image;
 
     no_viewport_percentage!(SpecifiedValue);
 
@@ -157,17 +157,17 @@
         if input.try(|input| input.expect_ident_matching("none")).is_ok() {
             return Ok(SpecifiedValue(None));
         }
 
         Ok(SpecifiedValue(Some(try!(Image::parse(context, input)))))
     }
 </%helpers:longhand>
 
-<%helpers:longhand name="border-image-outset" products="gecko" animatable="False"
+<%helpers:longhand name="border-image-outset" animatable="False"
                    spec="https://drafts.csswg.org/css-backgrounds/#border-image-outset">
     use std::fmt;
     use style_traits::ToCss;
     use values::HasViewportPercentage;
     use values::specified::{LengthOrNumber, Number};
 
     impl HasViewportPercentage for SpecifiedValue {
         fn has_viewport_percentage(&self) -> bool {
@@ -273,17 +273,17 @@
         if values.len() > 0 {
             Ok(SpecifiedValue(values))
         } else {
             Err(())
         }
     }
 </%helpers:longhand>
 
-<%helpers:longhand name="border-image-repeat" products="gecko" animatable="False"
+<%helpers:longhand name="border-image-repeat" animatable="False"
                    spec="https://drafts.csswg.org/css-backgrounds/#border-image-repeat">
     use std::fmt;
     use style_traits::ToCss;
     use values::HasViewportPercentage;
 
     no_viewport_percentage!(SpecifiedValue);
 
     pub mod computed_value {
@@ -351,17 +351,17 @@
     pub fn parse(_context: &ParserContext, input: &mut Parser) -> Result<SpecifiedValue, ()> {
         let first = try!(RepeatKeyword::parse(input));
         let second = input.try(RepeatKeyword::parse).ok();
 
         Ok(SpecifiedValue(first, second))
     }
 </%helpers:longhand>
 
-<%helpers:longhand name="border-image-width" products="gecko" animatable="False"
+<%helpers:longhand name="border-image-width" animatable="False"
                    spec="https://drafts.csswg.org/css-backgrounds/#border-image-width">
     use std::fmt;
     use style_traits::ToCss;
     use values::HasViewportPercentage;
     use values::specified::{LengthOrPercentage, Number};
 
     impl HasViewportPercentage for SpecifiedValue {
         fn has_viewport_percentage(&self) -> bool {
@@ -551,17 +551,17 @@
         if values.len() > 0 {
             Ok(SpecifiedValue(values))
         } else {
             Err(())
         }
     }
 </%helpers:longhand>
 
-<%helpers:longhand name="border-image-slice" products="gecko" animatable="False"
+<%helpers:longhand name="border-image-slice" animatable="False"
                    spec="https://drafts.csswg.org/css-backgrounds/#border-image-slice">
     use std::fmt;
     use style_traits::ToCss;
     use values::HasViewportPercentage;
     use values::specified::{Number, Percentage};
 
     no_viewport_percentage!(SpecifiedValue);
 
--- a/servo/components/style/properties/shorthand/border.mako.rs
+++ b/servo/components/style/properties/shorthand/border.mako.rs
@@ -179,17 +179,17 @@ pub fn parse_border(context: &ParserCont
             try!(self.border_bottom_right_radius.to_css(dest));
             try!(write!(dest, " "));
 
             self.border_bottom_left_radius.to_css(dest)
         }
     }
 </%helpers:shorthand>
 
-<%helpers:shorthand name="border-image" products="gecko" sub_properties="border-image-outset
+<%helpers:shorthand name="border-image" sub_properties="border-image-outset
     border-image-repeat border-image-slice border-image-source border-image-width"
     extra_prefixes="moz webkit" spec="https://drafts.csswg.org/css-backgrounds-3/#border-image">
     use properties::longhands::{border_image_outset, border_image_repeat, border_image_slice};
     use properties::longhands::{border_image_source, border_image_width};
 
     pub fn parse_value(context: &ParserContext, input: &mut Parser) -> Result<Longhands, ()> {
         % for name in "outset repeat slice source width".split():
             let mut border_image_${name} = border_image_${name}::get_initial_specified_value();