servo: Merge #11890 - Bug 10104 - Only restyle nodes that uses viewport percentage units on viewport size change (from shinglyu:viewport-percent-recalc); r=mbrubeck
authorShing Lyu <shing.lyu@gmail.com>
Sun, 17 Jul 2016 20:46:24 -0700
changeset 339379 2de25a8b8e9996cd18083ad2e59f5bbcdfef90d5
parent 339378 dfe1e28673f01967eb9156244a4acce2707fa2cc
child 339380 c15b2ba0f888b3c2286138c856f47205c5146a88
push id86548
push userkwierso@gmail.com
push dateSat, 04 Feb 2017 01:35:21 +0000
treeherdermozilla-inbound@e7b96d015d03 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmbrubeck
bugs10104
servo: Merge #11890 - Bug 10104 - Only restyle nodes that uses viewport percentage units on viewport size change (from shinglyu:viewport-percent-recalc); r=mbrubeck <!-- Please describe your changes on the following line: --> Bug 10104 - Only restyle nodes that uses viewport percentage units on viewport size change --- <!-- Thank you for contributing to Servo! Please replace each `[ ]` by `[X]` when the step is complete, and replace `__` with appropriate data: --> - [X] `./mach build -d` does not report any errors - [X] `./mach test-tidy` does not report any errors - [X] These changes fix #10104 (github issue number if applicable). <!-- Either: --> - [X] There are tests for these changes OR - [ ] These changes do not require tests because _____ <!-- Pull requests that do not address these steps are welcome, but they will require additional verification as part of the review process. --> Source-Repo: https://github.com/servo/servo Source-Revision: d87ea67bf2efec45368d3e48634c6c16de7486ff
servo/components/layout_thread/lib.rs
servo/components/script/dom/node.rs
servo/components/script/layout_wrapper.rs
servo/components/style/dom.rs
servo/components/style/properties/data.py
servo/components/style/properties/helpers.mako.rs
servo/components/style/properties/longhand/background.mako.rs
servo/components/style/properties/longhand/border.mako.rs
servo/components/style/properties/longhand/box.mako.rs
servo/components/style/properties/longhand/column.mako.rs
servo/components/style/properties/longhand/counters.mako.rs
servo/components/style/properties/longhand/effects.mako.rs
servo/components/style/properties/longhand/font.mako.rs
servo/components/style/properties/longhand/inherited_box.mako.rs
servo/components/style/properties/longhand/inherited_table.mako.rs
servo/components/style/properties/longhand/inherited_text.mako.rs
servo/components/style/properties/longhand/list.mako.rs
servo/components/style/properties/longhand/outline.mako.rs
servo/components/style/properties/longhand/pointing.mako.rs
servo/components/style/properties/longhand/position.mako.rs
servo/components/style/properties/longhand/text.mako.rs
servo/components/style/properties/properties.mako.rs
servo/components/style/traversal.rs
servo/components/style/values.rs
servo/ports/geckolib/wrapper.rs
servo/tests/unit/style/lib.rs
servo/tests/unit/style/properties.rs
servo/tests/unit/style/value.rs
--- a/servo/components/layout_thread/lib.rs
+++ b/servo/components/layout_thread/lib.rs
@@ -1067,19 +1067,25 @@ impl LayoutThread {
         let viewport_size_changed = self.viewport_size != old_viewport_size;
         if viewport_size_changed {
             if let Some(constraints) = constraints {
                 // let the constellation know about the viewport constraints
                 rw_data.constellation_chan
                        .send(ConstellationMsg::ViewportConstrained(self.id, constraints))
                        .unwrap();
             }
-            // FIXME (#10104): Only dirty nodes affected by vh/vw/vmin/vmax styles.
             if data.document_stylesheets.iter().any(|sheet| sheet.dirty_on_viewport_size_change) {
-                needs_dirtying = true;
+                for node in node.traverse_preorder() {
+                    if node.needs_dirty_on_viewport_size_changed() {
+                        node.dirty_self();
+                        node.dirty_descendants();
+                        // TODO(shinglyu): We can skip the traversal if the descendants were already
+                        // dirtied
+                    }
+                }
             }
         }
 
         // If the entire flow tree is invalid, then it will be reflowed anyhow.
         needs_dirtying |= Arc::get_mut(&mut rw_data.stylist).unwrap().update(&data.document_stylesheets,
                                                                              data.stylesheets_changed);
         let needs_reflow = viewport_size_changed && !needs_dirtying;
         unsafe {
--- a/servo/components/script/dom/node.rs
+++ b/servo/components/script/dom/node.rs
@@ -154,17 +154,19 @@ bitflags! {
         #[doc = "Specifies whether or not there is an authentic click in progress on \
                  this element."]
         const CLICK_IN_PROGRESS = 0x10,
         #[doc = "Specifies whether this node is focusable and whether it is supposed \
                  to be reachable with using sequential focus navigation."]
         const SEQUENTIALLY_FOCUSABLE = 0x20,
 
         /// Whether any ancestor is a fragmentation container
-        const CAN_BE_FRAGMENTED = 0x40
+        const CAN_BE_FRAGMENTED = 0x40,
+        #[doc = "Specifies whether this node needs to be dirted when viewport size changed."]
+        const DIRTY_ON_VIEWPORT_SIZE_CHANGE = 0x80
     }
 }
 
 impl NodeFlags {
     pub fn new() -> NodeFlags {
         HAS_CHANGED | IS_DIRTY | HAS_DIRTY_DESCENDANTS
     }
 }
--- a/servo/components/script/layout_wrapper.rs
+++ b/servo/components/script/layout_wrapper.rs
@@ -31,17 +31,17 @@
 #![allow(unsafe_code)]
 
 use dom::bindings::inheritance::{CharacterDataTypeId, ElementTypeId};
 use dom::bindings::inheritance::{HTMLElementTypeId, NodeTypeId};
 use dom::bindings::js::LayoutJS;
 use dom::characterdata::LayoutCharacterDataHelpers;
 use dom::document::{Document, LayoutDocumentHelpers};
 use dom::element::{Element, LayoutElementHelpers, RawLayoutElementHelpers};
-use dom::node::{CAN_BE_FRAGMENTED, HAS_CHANGED, HAS_DIRTY_DESCENDANTS, IS_DIRTY};
+use dom::node::{CAN_BE_FRAGMENTED, HAS_CHANGED, HAS_DIRTY_DESCENDANTS, IS_DIRTY, DIRTY_ON_VIEWPORT_SIZE_CHANGE};
 use dom::node::{Node, LayoutNodeHelpers};
 use dom::text::Text;
 use gfx_traits::ByteIndex;
 use msg::constellation_msg::PipelineId;
 use range::Range;
 use script_layout_interface::restyle_damage::RestyleDamage;
 use script_layout_interface::wrapper_traits::{DangerousThreadSafeLayoutNode, LayoutNode, PseudoElementType};
 use script_layout_interface::wrapper_traits::{ThreadSafeLayoutNode, ThreadSafeLayoutElement};
@@ -188,16 +188,33 @@ impl<'ln> TNode for ServoLayoutNode<'ln>
     fn has_dirty_descendants(&self) -> bool {
         unsafe { self.node.get_flag(HAS_DIRTY_DESCENDANTS) }
     }
 
     unsafe fn set_dirty_descendants(&self, value: bool) {
         self.node.set_flag(HAS_DIRTY_DESCENDANTS, value)
     }
 
+    fn needs_dirty_on_viewport_size_changed(&self) -> bool {
+        unsafe { self.node.get_flag(DIRTY_ON_VIEWPORT_SIZE_CHANGE) }
+    }
+
+    unsafe fn set_dirty_on_viewport_size_changed(&self) {
+        self.node.set_flag(DIRTY_ON_VIEWPORT_SIZE_CHANGE, true);
+    }
+
+    fn set_descendants_dirty_on_viewport_size_changed(&self) {
+        for ref child in self.children() {
+            unsafe {
+                child.set_dirty_on_viewport_size_changed();
+            }
+            child.set_descendants_dirty_on_viewport_size_changed();
+        }
+    }
+
     fn can_be_fragmented(&self) -> bool {
         unsafe { self.node.get_flag(CAN_BE_FRAGMENTED) }
     }
 
     unsafe fn set_can_be_fragmented(&self, value: bool) {
         self.node.set_flag(CAN_BE_FRAGMENTED, value)
     }
 
--- a/servo/components/style/dom.rs
+++ b/servo/components/style/dom.rs
@@ -122,16 +122,29 @@ pub trait TNode : Sized + Copy + Clone {
 
     fn dirty_descendants(&self) {
         for ref child in self.children() {
             child.dirty_self();
             child.dirty_descendants();
         }
     }
 
+    fn needs_dirty_on_viewport_size_changed(&self) -> bool;
+
+    unsafe fn set_dirty_on_viewport_size_changed(&self);
+
+    fn set_descendants_dirty_on_viewport_size_changed(&self) {
+        for ref child in self.children() {
+            unsafe {
+                child.set_dirty_on_viewport_size_changed();
+            }
+            child.set_descendants_dirty_on_viewport_size_changed();
+        }
+    }
+
     fn can_be_fragmented(&self) -> bool;
 
     unsafe fn set_can_be_fragmented(&self, value: bool);
 
     /// Borrows the PrivateStyleData without checks.
     #[inline(always)]
     unsafe fn borrow_data_unchecked(&self)
         -> Option<*const PrivateStyleData<<Self::ConcreteElement as Element>::Impl,
--- a/servo/components/style/properties/data.py
+++ b/servo/components/style/properties/data.py
@@ -42,28 +42,29 @@ class Keyword(object):
 
     def gecko_constant(self, value):
         return self.gecko_constant_prefix + "_" + value.replace("-moz-", "").replace("-", "_").upper()
 
 
 class Longhand(object):
     def __init__(self, style_struct, name, animatable=None, derived_from=None, keyword=None,
                  predefined_type=None, custom_cascade=False, experimental=False, internal=False,
-                 need_clone=False, need_index=False, gecko_ffi_name=None):
+                 need_clone=False, need_index=False, gecko_ffi_name=None, depend_on_viewport_size=False):
         self.name = name
         self.keyword = keyword
         self.predefined_type = predefined_type
         self.ident = to_rust_ident(name)
         self.camel_case = to_camel_case(self.ident)
         self.style_struct = style_struct
         self.experimental = ("layout.%s.enabled" % name) if experimental else None
         self.custom_cascade = custom_cascade
         self.internal = internal
         self.need_index = need_index
         self.gecko_ffi_name = gecko_ffi_name or "m" + self.camel_case
+        self.depend_on_viewport_size = depend_on_viewport_size
         self.derived_from = (derived_from or "").split()
 
         # This is done like this since just a plain bool argument seemed like
         # really random.
         if animatable is None:
             raise TypeError("animatable should be specified for " + name + ")")
         if isinstance(animatable, bool):
             self.animatable = animatable
--- a/servo/components/style/properties/helpers.mako.rs
+++ b/servo/components/style/properties/helpers.mako.rs
@@ -46,16 +46,24 @@
     We assume that the default/initial value is an empty vector for these.
     `initial_value` need not be defined for these.
 </%doc>
 <%def name="vector_longhand(name, gecko_only=False, allow_empty=False, **kwargs)">
     <%call expr="longhand(name, **kwargs)">
         % if product == "gecko" or not gecko_only:
             use cssparser::ToCss;
             use std::fmt;
+            use values::HasViewportPercentage;
+
+            impl HasViewportPercentage for SpecifiedValue {
+                fn has_viewport_percentage(&self) -> bool {
+                    let &SpecifiedValue(ref vec) = self;
+                    vec.iter().any(|ref x| x.has_viewport_percentage())
+                }
+            }
 
             pub mod single_value {
                 use cssparser::Parser;
                 use parser::{ParserContext, ParserContextExtraData};
                 use properties::{CSSWideKeyword, DeclaredValue, Shorthand};
                 use values::computed::{TContext, ToComputedValue};
                 use values::{computed, specified};
                 ${caller.body()}
@@ -269,17 +277,19 @@
             }
         % endif
     }
 </%def>
 
 <%def name="single_keyword(name, values, **kwargs)">
     <%call expr="single_keyword_computed(name, values, **kwargs)">
         use values::computed::ComputedValueAsSpecified;
+        use values::NoViewportPercentage;
         impl ComputedValueAsSpecified for SpecifiedValue {}
+        impl NoViewportPercentage for SpecifiedValue {}
     </%call>
 </%def>
 
 <%def name="single_keyword_computed(name, values, **kwargs)">
     <%
         keyword_kwargs = {a: kwargs.pop(a, None) for a in [
             'gecko_constant_prefix', 'extra_gecko_values', 'extra_servo_values'
         ]}
@@ -310,16 +320,18 @@
     <%
         keyword_kwargs = {a: kwargs.pop(a, None) for a in [
             'gecko_constant_prefix', 'extra_gecko_values', 'extra_servo_values'
         ]}
     %>
     <%call expr="longhand(name, keyword=Keyword(name, values, **keyword_kwargs), **kwargs)">
         use values::computed::ComputedValueAsSpecified;
         pub use self::computed_value::T as SpecifiedValue;
+        use values::NoViewportPercentage;
+        impl NoViewportPercentage for SpecifiedValue {}
         pub mod computed_value {
             use cssparser::ToCss;
             use std::fmt;
 
             #[derive(Debug, Clone, PartialEq)]
             #[cfg_attr(feature = "servo", derive(HeapSizeOf))]
             pub struct T(pub Vec<${to_camel_case(name)}>);
 
--- a/servo/components/style/properties/longhand/background.mako.rs
+++ b/servo/components/style/properties/longhand/background.mako.rs
@@ -10,16 +10,17 @@
     "::cssparser::Color::RGBA(::cssparser::RGBA { red: 0., green: 0., blue: 0., alpha: 0. }) /* transparent */",
     animatable=True)}
 
 <%helpers:vector_longhand gecko_only="True" name="background-image" animatable="False">
     use cssparser::ToCss;
     use std::fmt;
     use values::specified::Image;
     use values::LocalToCss;
+    use values::NoViewportPercentage;
 
     pub mod computed_value {
         use values::computed;
         #[derive(Debug, Clone, PartialEq)]
         #[cfg_attr(feature = "servo", derive(HeapSizeOf))]
         pub struct T(pub Option<computed::Image>);
     }
 
@@ -29,16 +30,18 @@
                 None => dest.write_str("none"),
                 Some(computed::Image::Url(ref url)) => url.to_css(dest),
                 Some(computed::Image::LinearGradient(ref gradient)) =>
                     gradient.to_css(dest)
             }
         }
     }
 
+    impl NoViewportPercentage for SpecifiedValue {}
+
     #[derive(Debug, Clone, PartialEq)]
     #[cfg_attr(feature = "servo", derive(HeapSizeOf))]
     pub struct SpecifiedValue(pub Option<Image>);
 
     impl ToCss for SpecifiedValue {
         fn to_css<W>(&self, dest: &mut W) -> fmt::Result where W: fmt::Write {
             match *self {
                 SpecifiedValue(Some(ref image)) => image.to_css(dest),
@@ -71,28 +74,35 @@
         }
     }
 </%helpers:vector_longhand>
 
 <%helpers:longhand name="background-position" animatable="True">
         use cssparser::ToCss;
         use std::fmt;
         use values::LocalToCss;
+        use values::HasViewportPercentage;
 
         pub mod computed_value {
             use values::computed::LengthOrPercentage;
 
             #[derive(PartialEq, Copy, Clone, Debug)]
             #[cfg_attr(feature = "servo", derive(HeapSizeOf))]
             pub struct T {
                 pub horizontal: LengthOrPercentage,
                 pub vertical: LengthOrPercentage,
             }
         }
 
+        impl HasViewportPercentage for SpecifiedValue {
+            fn has_viewport_percentage(&self) -> bool {
+                return self.horizontal.has_viewport_percentage() || self.vertical.has_viewport_percentage();
+            }
+        }
+
         #[derive(Debug, Clone, PartialEq, Copy)]
         #[cfg_attr(feature = "servo", derive(HeapSizeOf))]
         pub struct SpecifiedValue {
             pub horizontal: specified::LengthOrPercentage,
             pub vertical: specified::LengthOrPercentage,
         }
 
         impl ToCss for SpecifiedValue {
@@ -202,16 +212,17 @@
 ${helpers.single_keyword("background-origin",
                          "padding-box border-box content-box",
                          animatable=False)}
 
 <%helpers:longhand name="background-size" animatable="True">
     use cssparser::{ToCss, Token};
     use std::ascii::AsciiExt;
     use std::fmt;
+    use values::HasViewportPercentage;
 
     pub mod computed_value {
         use values::computed::LengthOrPercentageOrAuto;
 
         #[derive(PartialEq, Clone, Debug)]
         #[cfg_attr(feature = "servo", derive(HeapSizeOf))]
         pub struct ExplicitSize {
             pub width: LengthOrPercentageOrAuto,
@@ -232,16 +243,22 @@
             match *self {
                 computed_value::T::Explicit(ref size) => size.to_css(dest),
                 computed_value::T::Cover => dest.write_str("cover"),
                 computed_value::T::Contain => dest.write_str("contain"),
             }
         }
     }
 
+    impl HasViewportPercentage for SpecifiedExplicitSize {
+        fn has_viewport_percentage(&self) -> bool {
+            return self.width.has_viewport_percentage() || self.height.has_viewport_percentage();
+        }
+    }
+
     #[derive(Clone, PartialEq, Debug)]
     #[cfg_attr(feature = "servo", derive(HeapSizeOf))]
     pub struct SpecifiedExplicitSize {
         pub width: specified::LengthOrPercentageOrAuto,
         pub height: specified::LengthOrPercentageOrAuto,
     }
 
     impl ToCss for SpecifiedExplicitSize {
@@ -255,16 +272,24 @@
     impl ToCss for computed_value::ExplicitSize {
         fn to_css<W>(&self, dest: &mut W) -> fmt::Result where W: fmt::Write {
             try!(self.width.to_css(dest));
             try!(dest.write_str(" "));
             self.height.to_css(dest)
         }
     }
 
+    impl HasViewportPercentage for SpecifiedValue {
+        fn has_viewport_percentage(&self) -> bool {
+            match *self {
+                SpecifiedValue::Explicit(ref explicit_size) => explicit_size.has_viewport_percentage(),
+                _ => false
+            }
+        }
+    }
 
     #[derive(Clone, PartialEq, Debug)]
     #[cfg_attr(feature = "servo", derive(HeapSizeOf))]
     pub enum SpecifiedValue {
         Explicit(SpecifiedExplicitSize),
         Cover,
         Contain,
     }
--- a/servo/components/style/properties/longhand/border.mako.rs
+++ b/servo/components/style/properties/longhand/border.mako.rs
@@ -21,31 +21,40 @@
                               need_clone=True, animatable=False)}
 % endfor
 
 % for side in ["top", "right", "bottom", "left"]:
     <%helpers:longhand name="border-${side}-width" animatable="True">
         use app_units::Au;
         use cssparser::ToCss;
         use std::fmt;
+        use values::HasViewportPercentage;
 
         impl ToCss for SpecifiedValue {
             fn to_css<W>(&self, dest: &mut W) -> fmt::Result where W: fmt::Write {
                 self.0.to_css(dest)
             }
         }
 
         #[inline]
         pub fn parse(_context: &ParserContext, input: &mut Parser)
                                -> Result<SpecifiedValue, ()> {
             specified::parse_border_width(input).map(SpecifiedValue)
         }
         #[derive(Debug, Clone, PartialEq)]
         #[cfg_attr(feature = "servo", derive(HeapSizeOf))]
         pub struct SpecifiedValue(pub specified::Length);
+
+        impl HasViewportPercentage for SpecifiedValue {
+            fn has_viewport_percentage(&self) -> bool {
+                let &SpecifiedValue(length) = self;
+                length.has_viewport_percentage()
+            }
+        }
+
         pub mod computed_value {
             use app_units::Au;
             pub type T = Au;
         }
         #[inline] pub fn get_initial_value() -> computed_value::T {
             Au::from_px(3)  // medium
         }
 
--- a/servo/components/style/properties/longhand/box.mako.rs
+++ b/servo/components/style/properties/longhand/box.mako.rs
@@ -23,16 +23,19 @@
         """.split()
         if product == "gecko":
             values += "-moz-box -moz-inline-box".split()
         experimental_values = set("flex".split())
     %>
     pub use self::computed_value::T as SpecifiedValue;
     use values::computed::{Context, ComputedValueAsSpecified};
 
+    use values::NoViewportPercentage;
+    impl NoViewportPercentage for SpecifiedValue {}
+
     pub mod computed_value {
         #[allow(non_camel_case_types)]
         #[derive(Clone, Eq, PartialEq, Copy, Hash, RustcEncodable, Debug)]
         #[cfg_attr(feature = "servo", derive(HeapSizeOf, Deserialize, Serialize))]
         pub enum T {
             % for value in values:
                 ${to_rust_ident(value)},
             % endfor
@@ -89,17 +92,20 @@
 </%helpers:longhand>
 
 ${helpers.single_keyword("position", "static absolute relative fixed",
                          need_clone=True, extra_gecko_values="sticky", animatable=False)}
 
 <%helpers:single_keyword_computed name="float"
                                   values="none left right"
                                   animatable="False"
-                                  need_clone="True">
+                                  need_clone="True"
+                                  gecko_ffi_name="mFloat">
+    use values::NoViewportPercentage;
+    impl NoViewportPercentage for SpecifiedValue {}
     impl ToComputedValue for SpecifiedValue {
         type ComputedValue = computed_value::T;
 
         #[inline]
         fn to_computed_value<Cx: TContext>(&self, context: &Cx) -> computed_value::T {
             let positioned = matches!(context.style().get_box().clone_position(),
                 longhands::position::SpecifiedValue::absolute |
                 longhands::position::SpecifiedValue::fixed);
@@ -141,16 +147,26 @@
   use std::fmt;
 
   <% vertical_align = data.longhands_by_name["vertical-align"] %>
   <% vertical_align.keyword = Keyword("vertical-align",
                                       "baseline sub super top text-top middle bottom text-bottom",
                                       extra_gecko_values="middle-with-baseline") %>
   <% vertical_align_keywords = vertical_align.keyword.values_for(product) %>
 
+  use values::HasViewportPercentage;
+  impl HasViewportPercentage for SpecifiedValue {
+      fn has_viewport_percentage(&self) -> bool {
+          match *self {
+              SpecifiedValue::LengthOrPercentage(length) => length.has_viewport_percentage(),
+              _ => false
+          }
+      }
+  }
+
   #[allow(non_camel_case_types)]
   #[derive(Debug, Clone, PartialEq, Copy)]
   #[cfg_attr(feature = "servo", derive(HeapSizeOf))]
   pub enum SpecifiedValue {
       % for keyword in vertical_align_keywords:
           ${to_rust_ident(keyword)},
       % endfor
       LengthOrPercentage(specified::LengthOrPercentage),
@@ -247,16 +263,19 @@
                    animatable="False">
   use super::overflow_x;
 
   use cssparser::ToCss;
   use std::fmt;
 
   pub use self::computed_value::T as SpecifiedValue;
 
+  use values::NoViewportPercentage;
+  impl NoViewportPercentage for SpecifiedValue {}
+
   impl ToCss for SpecifiedValue {
       fn to_css<W>(&self, dest: &mut W) -> fmt::Result where W: fmt::Write {
           self.0.to_css(dest)
       }
   }
 
   pub mod computed_value {
       #[derive(Debug, Clone, Copy, PartialEq)]
@@ -286,16 +305,18 @@
 <%helpers:longhand name="transition-duration"
                    need_index="True"
                    animatable="False">
     use values::computed::ComputedValueAsSpecified;
     use values::specified::Time;
 
     pub use self::computed_value::T as SpecifiedValue;
     pub use values::specified::Time as SingleSpecifiedValue;
+    use values::NoViewportPercentage;
+    impl NoViewportPercentage for SpecifiedValue {}
 
     pub mod computed_value {
         use cssparser::ToCss;
         use std::fmt;
         use values::computed::{TContext, ToComputedValue};
 
         pub use values::computed::Time as SingleComputedValue;
 
@@ -464,16 +485,19 @@
                     }
                     try!(value.to_css(dest))
                 }
                 Ok(())
             }
         }
     }
 
+    use values::NoViewportPercentage;
+    impl NoViewportPercentage for SpecifiedValue {}
+
     impl ToComputedValue for SpecifiedValue {
         type ComputedValue = computed_value::T;
 
         #[inline]
         fn to_computed_value<Cx: TContext>(&self, _: &Cx) -> computed_value::T {
             (*self).clone()
         }
     }
@@ -587,16 +611,19 @@
     pub fn parse_one(input: &mut Parser) -> Result<SingleSpecifiedValue, ()> {
         SingleSpecifiedValue::parse(input)
     }
 
     pub fn parse(_: &ParserContext, input: &mut Parser) -> Result<SpecifiedValue,()> {
         Ok(SpecifiedValue(try!(input.parse_comma_separated(SingleSpecifiedValue::parse))))
     }
 
+    use values::NoViewportPercentage;
+    impl NoViewportPercentage for SpecifiedValue {}
+
     impl ToComputedValue for SpecifiedValue {
         type ComputedValue = computed_value::T;
 
         #[inline]
         fn to_computed_value<Cx: TContext>(&self, _: &Cx) -> computed_value::T {
             (*self).clone()
         }
     }
@@ -610,16 +637,17 @@
     pub use properties::longhands::transition_duration::{get_initial_single_value};
     pub use properties::longhands::transition_duration::{get_initial_value, parse, parse_one};
 </%helpers:longhand>
 
 <%helpers:longhand name="animation-name"
                    need_index="True"
                    animatable="False">
     use values::computed::ComputedValueAsSpecified;
+    use values::NoViewportPercentage;
 
     pub mod computed_value {
         use cssparser::ToCss;
         use std::fmt;
         use string_cache::Atom;
 
         pub use string_cache::Atom as SingleComputedValue;
 
@@ -641,16 +669,17 @@
                     try!(dest.write_str(&*name.to_string()));
                 }
                 Ok(())
             }
         }
     }
 
     pub use self::computed_value::T as SpecifiedValue;
+    impl NoViewportPercentage for SpecifiedValue {}
     pub use string_cache::Atom as SingleSpecifiedValue;
 
     #[inline]
     pub fn parse_one(input: &mut Parser) -> Result<SingleSpecifiedValue, ()> {
         use cssparser::Token;
 
         Ok(match input.next() {
             Ok(Token::Ident(ref value)) if value != "none" => Atom::from(&**value),
@@ -691,16 +720,17 @@
     pub use super::transition_timing_function::SpecifiedValue;
     pub use super::transition_timing_function::SingleSpecifiedValue;
 </%helpers:longhand>
 
 <%helpers:longhand name="animation-iteration-count"
                    need_index="True"
                    animatable="False">
     use values::computed::ComputedValueAsSpecified;
+    use values::NoViewportPercentage;
 
     pub mod computed_value {
         use cssparser::ToCss;
         use std::fmt;
 
         pub use self::AnimationIterationCount as SingleComputedValue;
 
         #[derive(Debug, Clone, PartialEq)]
@@ -737,16 +767,17 @@
                 Ok(())
             }
         }
     }
 
     pub use self::computed_value::AnimationIterationCount;
     pub use self::computed_value::AnimationIterationCount as SingleSpecifiedValue;
     pub use self::computed_value::T as SpecifiedValue;
+    impl NoViewportPercentage for SpecifiedValue {}
 
     #[inline]
     pub fn get_initial_single_value() -> AnimationIterationCount {
         AnimationIterationCount::Number(1)
     }
 
     pub fn parse_one(input: &mut Parser) -> Result<AnimationIterationCount, ()> {
         if input.try(|input| input.expect_ident_matching("infinite")).is_ok() {
@@ -880,16 +911,17 @@
 
 // Non-standard: https://developer.mozilla.org/en-US/docs/Web/CSS/-moz-binding
 <%helpers:longhand name="-moz-binding" products="gecko" animatable="False">
     use cssparser::{CssStringWriter, ToCss};
     use gecko_bindings::ptr::{GeckoArcPrincipal, GeckoArcURI};
     use std::fmt::{self, Write};
     use url::Url;
     use values::computed::ComputedValueAsSpecified;
+    use values::NoViewportPercentage;
 
     #[derive(PartialEq, Clone, Debug)]
     #[cfg_attr(feature = "servo", derive(HeapSizeOf))]
     pub struct UrlExtraData {
         pub base: GeckoArcURI,
         pub referrer: GeckoArcURI,
         pub principal: GeckoArcPrincipal,
     }
@@ -897,16 +929,17 @@
     #[derive(PartialEq, Clone, Debug)]
     #[cfg_attr(feature = "servo", derive(HeapSizeOf))]
     pub enum SpecifiedValue {
         Url(Url, UrlExtraData),
         None,
     }
 
     impl ComputedValueAsSpecified for SpecifiedValue {}
+    impl NoViewportPercentage for SpecifiedValue {}
 
     impl ToCss for SpecifiedValue {
         fn to_css<W>(&self, dest: &mut W) -> fmt::Result where W: fmt::Write {
             use values::LocalToCss;
             match *self {
                 SpecifiedValue::Url(ref url, _) => {
                     url.to_css(dest)
                 }
--- a/servo/components/style/properties/longhand/column.mako.rs
+++ b/servo/components/style/properties/longhand/column.mako.rs
@@ -6,16 +6,26 @@
 
 <% data.new_style_struct("Column", inherited=False) %>
 
 // FIXME: This prop should be animatable.
 <%helpers:longhand name="column-width" experimental="True" animatable="False">
     use cssparser::ToCss;
     use std::fmt;
     use values::LocalToCss;
+    use values::HasViewportPercentage;
+
+    impl HasViewportPercentage for SpecifiedValue {
+        fn has_viewport_percentage(&self) -> bool {
+            match *self {
+                SpecifiedValue::Specified(length) => length.has_viewport_percentage(),
+                _ => false
+            }
+        }
+    }
 
     #[derive(Debug, Clone, Copy, PartialEq)]
     #[cfg_attr(feature = "servo", derive(HeapSizeOf))]
     pub enum SpecifiedValue {
         Auto,
         Specified(specified::Length),
     }
 
@@ -70,16 +80,19 @@
         }
     }
 </%helpers:longhand>
 
 // FIXME: This prop should be animatable.
 <%helpers:longhand name="column-count" experimental="True" animatable="False">
     use cssparser::ToCss;
     use std::fmt;
+    use values::NoViewportPercentage;
+
+    impl NoViewportPercentage for SpecifiedValue {}
 
     #[derive(Debug, Clone, Copy, PartialEq)]
     #[cfg_attr(feature = "servo", derive(HeapSizeOf))]
     pub enum SpecifiedValue {
         Auto,
         Specified(u32),
     }
 
@@ -139,16 +152,26 @@
     }
 </%helpers:longhand>
 
 // FIXME: This prop should be animatable.
 <%helpers:longhand name="column-gap" experimental="True" animatable="False">
     use cssparser::ToCss;
     use std::fmt;
     use values::LocalToCss;
+    use values::HasViewportPercentage;
+
+    impl HasViewportPercentage for SpecifiedValue {
+        fn has_viewport_percentage(&self) -> bool {
+            match *self {
+                SpecifiedValue::Specified(length) => length.has_viewport_percentage(),
+                _ => false
+            }
+        }
+    }
 
     #[derive(Debug, Clone, Copy, PartialEq)]
     #[cfg_attr(feature = "servo", derive(HeapSizeOf))]
     pub enum SpecifiedValue {
         Normal,
         Specified(specified::Length),
     }
 
--- a/servo/components/style/properties/longhand/counters.mako.rs
+++ b/servo/components/style/properties/longhand/counters.mako.rs
@@ -5,23 +5,25 @@
 <%namespace name="helpers" file="/helpers.mako.rs" />
 
 <% data.new_style_struct("Counters", inherited=False, gecko_name="Content") %>
 
 <%helpers:longhand name="content" animatable="False">
     use cssparser::Token;
     use std::ascii::AsciiExt;
     use values::computed::ComputedValueAsSpecified;
+    use values::NoViewportPercentage;
 
     use super::list_style_type;
 
     pub use self::computed_value::T as SpecifiedValue;
     pub use self::computed_value::ContentItem;
 
     impl ComputedValueAsSpecified for SpecifiedValue {}
+    impl NoViewportPercentage for SpecifiedValue {}
 
     pub mod computed_value {
         use super::super::list_style_type;
 
         use cssparser::{self, ToCss};
         use std::fmt;
 
         #[derive(Debug, PartialEq, Eq, Clone)]
@@ -169,16 +171,17 @@
             Err(())
         }
     }
 </%helpers:longhand>
 
 <%helpers:longhand name="counter-increment" animatable="False">
     use std::fmt;
     use super::content;
+    use values::NoViewportPercentage;
     use values::computed::ComputedValueAsSpecified;
 
     use cssparser::{ToCss, Token, serialize_identifier};
     use std::borrow::{Cow, ToOwned};
 
     pub use self::computed_value::T as SpecifiedValue;
 
     pub mod computed_value {
@@ -188,16 +191,17 @@
     }
 
     #[inline]
     pub fn get_initial_value() -> computed_value::T {
         computed_value::T(Vec::new())
     }
 
     impl ComputedValueAsSpecified for SpecifiedValue {}
+    impl NoViewportPercentage for SpecifiedValue {}
 
     impl ToCss for SpecifiedValue {
         fn to_css<W>(&self, dest: &mut W) -> fmt::Result where W: fmt::Write {
             let mut first = true;
             for pair in &self.0 {
                 if !first {
                     try!(dest.write_str(" "));
                 }
--- a/servo/components/style/properties/longhand/effects.mako.rs
+++ b/servo/components/style/properties/longhand/effects.mako.rs
@@ -11,28 +11,38 @@
                           "Opacity",
                           "1.0",
                           animatable=True)}
 
 <%helpers:vector_longhand name="box-shadow" allow_empty="True" animatable="True">
     use cssparser::{self, ToCss};
     use std::fmt;
     use values::LocalToCss;
+    use values::HasViewportPercentage;
 
     #[derive(Debug, Clone, PartialEq)]
     #[cfg_attr(feature = "servo", derive(HeapSizeOf))]
     pub struct SpecifiedValue {
         pub offset_x: specified::Length,
         pub offset_y: specified::Length,
         pub blur_radius: specified::Length,
         pub spread_radius: specified::Length,
         pub color: Option<specified::CSSColor>,
         pub inset: bool,
     }
 
+    impl HasViewportPercentage for SpecifiedValue {
+        fn has_viewport_percentage(&self) -> bool {
+            self.offset_x.has_viewport_percentage() ||
+            self.offset_y.has_viewport_percentage() ||
+            self.blur_radius.has_viewport_percentage() ||
+            self.spread_radius.has_viewport_percentage()
+        }
+    }
+
     impl ToCss for SpecifiedValue {
         fn to_css<W>(&self, dest: &mut W) -> fmt::Result where W: fmt::Write {
             if self.inset {
                 try!(dest.write_str("inset "));
             }
             try!(self.blur_radius.to_css(dest));
             try!(dest.write_str(" "));
             try!(self.spread_radius.to_css(dest));
@@ -164,16 +174,17 @@
     }
 </%helpers:vector_longhand>
 
 // FIXME: This prop should be animatable
 <%helpers:longhand name="clip" animatable="False">
     use cssparser::ToCss;
     use std::fmt;
     use values::LocalToCss;
+    use values::HasViewportPercentage;
 
     // NB: `top` and `left` are 0 if `auto` per CSS 2.1 11.1.2.
 
     pub mod computed_value {
         use app_units::Au;
 
         #[derive(Clone, PartialEq, Eq, Copy, Debug)]
         #[cfg_attr(feature = "servo", derive(HeapSizeOf))]
@@ -214,25 +225,41 @@
                     try!(rect.left.to_css(dest));
                     try!(dest.write_str(")"));
                     Ok(())
                 }
             }
         }
     }
 
+    impl HasViewportPercentage for SpecifiedClipRect {
+        fn has_viewport_percentage(&self) -> bool {
+            self.top.has_viewport_percentage() ||
+            self.right.map_or(false, |x| x.has_viewport_percentage()) ||
+            self.bottom.map_or(false, |x| x.has_viewport_percentage()) ||
+            self.left.has_viewport_percentage()
+        }
+    }
+
     #[derive(Clone, Debug, PartialEq, Copy)]
     #[cfg_attr(feature = "servo", derive(HeapSizeOf))]
     pub struct SpecifiedClipRect {
         pub top: specified::Length,
         pub right: Option<specified::Length>,
         pub bottom: Option<specified::Length>,
         pub left: specified::Length,
     }
 
+    impl HasViewportPercentage for SpecifiedValue {
+        fn has_viewport_percentage(&self) -> bool {
+            let &SpecifiedValue(clip) = self;
+            clip.map_or(false, |x| x.has_viewport_percentage())
+        }
+    }
+
     #[derive(Clone, Debug, PartialEq, Copy)]
     #[cfg_attr(feature = "servo", derive(HeapSizeOf))]
     pub struct SpecifiedValue(Option<SpecifiedClipRect>);
 
     impl ToCss for SpecifiedClipRect {
         fn to_css<W>(&self, dest: &mut W) -> fmt::Result where W: fmt::Write {
             try!(dest.write_str("rect("));
 
@@ -338,22 +365,39 @@
 
 // FIXME: This prop should be animatable
 <%helpers:longhand name="filter" animatable="False">
     //pub use self::computed_value::T as SpecifiedValue;
     use cssparser::ToCss;
     use std::fmt;
     use values::LocalToCss;
     use values::CSSFloat;
+    use values::HasViewportPercentage;
     use values::specified::{Angle, Length};
 
+    impl HasViewportPercentage for SpecifiedValue {
+        fn has_viewport_percentage(&self) -> bool {
+            let &SpecifiedValue(ref vec) = self;
+            vec.iter().any(|ref x| x.has_viewport_percentage())
+        }
+    }
+
     #[derive(Debug, Clone, PartialEq)]
     #[cfg_attr(feature = "servo", derive(HeapSizeOf))]
     pub struct SpecifiedValue(Vec<SpecifiedFilter>);
 
+    impl HasViewportPercentage for SpecifiedFilter {
+        fn has_viewport_percentage(&self) -> bool {
+            match *self {
+                SpecifiedFilter::Blur(length) => length.has_viewport_percentage(),
+                _ => false
+            }
+        }
+    }
+
     // TODO(pcwalton): `drop-shadow`
     #[derive(Clone, PartialEq, Debug)]
     #[cfg_attr(feature = "servo", derive(HeapSizeOf))]
     pub enum SpecifiedFilter {
         Blur(Length),
         Brightness(CSSFloat),
         Contrast(CSSFloat),
         Grayscale(CSSFloat),
@@ -571,16 +615,17 @@
             }).collect() }
         }
     }
 </%helpers:longhand>
 
 <%helpers:longhand name="transform" animatable="True">
     use app_units::Au;
     use values::CSSFloat;
+    use values::HasViewportPercentage;
 
     use cssparser::ToCss;
     use std::fmt;
 
     pub mod computed_value {
         use values::CSSFloat;
         use values::computed;
 
@@ -679,16 +724,30 @@
 
     impl ToCss for computed_value::T {
         fn to_css<W>(&self, _: &mut W) -> fmt::Result where W: fmt::Write {
             // TODO(pcwalton)
             Ok(())
         }
     }
 
+    impl HasViewportPercentage for SpecifiedOperation {
+        fn has_viewport_percentage(&self) -> bool {
+            match *self {
+                SpecifiedOperation::Translate(_, l1, l2, l3) => {
+                    l1.has_viewport_percentage() ||
+                    l2.has_viewport_percentage() ||
+                    l3.has_viewport_percentage()
+                },
+                SpecifiedOperation::Perspective(length) => length.has_viewport_percentage(),
+                _ => false
+            }
+        }
+    }
+
     impl ToCss for SpecifiedOperation {
         fn to_css<W>(&self, dest: &mut W) -> fmt::Result where W: fmt::Write {
             match *self {
                 // todo(gw): implement serialization for transform
                 // types other than translate.
                 SpecifiedOperation::Matrix(_m) => {
                     Ok(())
                 }
@@ -738,16 +797,23 @@
                 }
                 SpecifiedOperation::Perspective(_p) => {
                     Ok(())
                 }
             }
         }
     }
 
+    impl HasViewportPercentage for SpecifiedValue {
+        fn has_viewport_percentage(&self) -> bool {
+            let &SpecifiedValue(ref specified_ops) = self;
+            specified_ops.iter().any(|ref x| x.has_viewport_percentage())
+        }
+    }
+
     #[derive(Clone, Debug, PartialEq)]
     #[cfg_attr(feature = "servo", derive(HeapSizeOf))]
     pub struct SpecifiedValue(Vec<SpecifiedOperation>);
 
     impl ToCss for SpecifiedValue {
         fn to_css<W>(&self, dest: &mut W) -> fmt::Result where W: fmt::Write {
             let mut first = true;
             for operation in &self.0 {
@@ -1128,16 +1194,17 @@ pub fn parse_origin(_: &ParserContext, i
 
 ${helpers.single_keyword("transform-style",
                          "auto flat preserve-3d",
                          animatable=False)}
 
 <%helpers:longhand name="transform-origin" animatable="True">
     use app_units::Au;
     use values::LocalToCss;
+    use values::HasViewportPercentage;
     use values::specified::{Length, LengthOrPercentage, Percentage};
 
     use cssparser::ToCss;
     use std::fmt;
 
     pub mod computed_value {
         use values::computed::{Length, LengthOrPercentage};
 
@@ -1145,16 +1212,24 @@ pub fn parse_origin(_: &ParserContext, i
         #[cfg_attr(feature = "servo", derive(HeapSizeOf))]
         pub struct T {
             pub horizontal: LengthOrPercentage,
             pub vertical: LengthOrPercentage,
             pub depth: Length,
         }
     }
 
+    impl HasViewportPercentage for SpecifiedValue {
+        fn has_viewport_percentage(&self) -> bool {
+            self.horizontal.has_viewport_percentage() ||
+            self.vertical.has_viewport_percentage() ||
+            self.depth.has_viewport_percentage()
+        }
+    }
+
     #[derive(Clone, Copy, Debug, PartialEq)]
     #[cfg_attr(feature = "servo", derive(HeapSizeOf))]
     pub struct SpecifiedValue {
         horizontal: LengthOrPercentage,
         vertical: LengthOrPercentage,
         depth: Length,
     }
 
@@ -1212,16 +1287,17 @@ pub fn parse_origin(_: &ParserContext, i
 
 ${helpers.predefined_type("perspective",
                           "LengthOrNone",
                           "computed::LengthOrNone::None",
                           animatable=True)}
 
 // FIXME: This prop should be animatable
 <%helpers:longhand name="perspective-origin" animatable="False">
+    use values::HasViewportPercentage;
     use values::specified::{LengthOrPercentage, Percentage};
 
     use cssparser::ToCss;
     use std::fmt;
 
     pub mod computed_value {
         use values::computed::LengthOrPercentage;
 
@@ -1236,16 +1312,22 @@ pub fn parse_origin(_: &ParserContext, i
     impl ToCss for computed_value::T {
         fn to_css<W>(&self, dest: &mut W) -> fmt::Result where W: fmt::Write {
             try!(self.horizontal.to_css(dest));
             try!(dest.write_str(" "));
             self.vertical.to_css(dest)
         }
     }
 
+    impl HasViewportPercentage for SpecifiedValue {
+        fn has_viewport_percentage(&self) -> bool {
+            self.horizontal.has_viewport_percentage() || self.vertical.has_viewport_percentage()
+        }
+    }
+
     #[derive(Clone, Copy, Debug, PartialEq)]
     #[cfg_attr(feature = "servo", derive(HeapSizeOf))]
     pub struct SpecifiedValue {
         horizontal: LengthOrPercentage,
         vertical: LengthOrPercentage,
     }
 
     impl ToCss for SpecifiedValue {
--- a/servo/components/style/properties/longhand/font.mako.rs
+++ b/servo/components/style/properties/longhand/font.mako.rs
@@ -5,20 +5,23 @@
 <%namespace name="helpers" file="/helpers.mako.rs" />
 <% from data import Method %>
 
 <% data.new_style_struct("Font",
                          inherited=True,
                          additional_methods=[Method("compute_font_hash", is_mut=True)]) %>
 <%helpers:longhand name="font-family" animatable="False">
     use self::computed_value::FontFamily;
+    use values::NoViewportPercentage;
     use values::computed::ComputedValueAsSpecified;
     pub use self::computed_value::T as SpecifiedValue;
 
     impl ComputedValueAsSpecified for SpecifiedValue {}
+    impl NoViewportPercentage for SpecifiedValue {}
+
     pub mod computed_value {
         use cssparser::ToCss;
         use std::fmt;
         use string_cache::Atom;
 
         #[derive(Debug, PartialEq, Eq, Clone, Hash)]
         #[cfg_attr(feature = "servo", derive(HeapSizeOf, Deserialize, Serialize))]
         pub enum FontFamily {
@@ -123,16 +126,19 @@
                          animatable=False)}
 ${helpers.single_keyword("font-variant",
                          "normal small-caps",
                          animatable=False)}
 
 <%helpers:longhand name="font-weight" need_clone="True" animatable="True">
     use cssparser::ToCss;
     use std::fmt;
+    use values::NoViewportPercentage;
+
+    impl NoViewportPercentage for SpecifiedValue {}
 
     #[derive(Debug, Clone, PartialEq, Eq, Copy)]
     #[cfg_attr(feature = "servo", derive(HeapSizeOf))]
     pub enum SpecifiedValue {
         Bolder,
         Lighter,
         % for weight in range(100, 901, 100):
             Weight${weight},
@@ -246,24 +252,32 @@
     }
 </%helpers:longhand>
 
 <%helpers:longhand name="font-size" need_clone="True" animatable="True">
     use app_units::Au;
     use cssparser::ToCss;
     use std::fmt;
     use values::FONT_MEDIUM_PX;
+    use values::HasViewportPercentage;
     use values::specified::{LengthOrPercentage, Length, Percentage};
 
     impl ToCss for SpecifiedValue {
         fn to_css<W>(&self, dest: &mut W) -> fmt::Result where W: fmt::Write {
             self.0.to_css(dest)
         }
     }
 
+    impl HasViewportPercentage for SpecifiedValue {
+        fn has_viewport_percentage(&self) -> bool {
+            let &SpecifiedValue(length) = self;
+            return length.has_viewport_percentage()
+        }
+    }
+
     #[derive(Debug, Clone, PartialEq)]
     #[cfg_attr(feature = "servo", derive(HeapSizeOf))]
     pub struct SpecifiedValue(pub specified::LengthOrPercentage);
     pub mod computed_value {
         use app_units::Au;
         pub type T = Au;
     }
     #[inline] pub fn get_initial_value() -> computed_value::T {
--- a/servo/components/style/properties/longhand/inherited_box.mako.rs
+++ b/servo/components/style/properties/longhand/inherited_box.mako.rs
@@ -59,16 +59,19 @@
                     T::Auto => dest.write_str("auto"),
                     T::CrispEdges => dest.write_str("crisp-edges"),
                     T::Pixelated => dest.write_str("pixelated"),
                 }
             }
         }
     }
 
+    use values::NoViewportPercentage;
+    impl NoViewportPercentage for SpecifiedValue {}
+
     pub type SpecifiedValue = computed_value::T;
 
     #[inline]
     pub fn get_initial_value() -> computed_value::T {
         computed_value::T::Auto
     }
 
     pub fn parse(_: &ParserContext, input: &mut Parser) -> Result<SpecifiedValue,()> {
@@ -99,16 +102,19 @@
 // descendants of nodes with `display: none`.
 <%helpers:longhand name="-servo-under-display-none"
                    derived_from="display"
                    products="servo"
                    animatable="False">
     use cssparser::ToCss;
     use std::fmt;
     use values::computed::ComputedValueAsSpecified;
+    use values::NoViewportPercentage;
+
+    impl NoViewportPercentage for SpecifiedValue {}
 
     #[derive(Copy, Clone, Debug, Eq, PartialEq)]
     #[cfg_attr(feature = "servo", derive(HeapSizeOf, Deserialize, Serialize))]
     pub struct SpecifiedValue(pub bool);
 
     pub mod computed_value {
         pub type T = super::SpecifiedValue;
     }
--- a/servo/components/style/properties/longhand/inherited_table.mako.rs
+++ b/servo/components/style/properties/longhand/inherited_table.mako.rs
@@ -14,31 +14,38 @@
                          animatable=False)}
 ${helpers.single_keyword("caption-side", "top bottom",
                          extra_gecko_values="right left top-outside bottom-outside",
                          animatable=False)}
 
 <%helpers:longhand name="border-spacing" animatable="False">
     use app_units::Au;
     use values::LocalToCss;
+    use values::HasViewportPercentage;
 
     use cssparser::ToCss;
     use std::fmt;
 
     pub mod computed_value {
         use app_units::Au;
 
         #[derive(Clone, Copy, Debug, PartialEq, RustcEncodable)]
         #[cfg_attr(feature = "servo", derive(HeapSizeOf))]
         pub struct T {
             pub horizontal: Au,
             pub vertical: Au,
         }
     }
 
+    impl HasViewportPercentage for SpecifiedValue {
+        fn has_viewport_percentage(&self) -> bool {
+            return self.horizontal.has_viewport_percentage() || self.vertical.has_viewport_percentage()
+        }
+    }
+
     #[derive(Clone, Debug, PartialEq)]
     #[cfg_attr(feature = "servo", derive(HeapSizeOf))]
     pub struct SpecifiedValue {
         pub horizontal: specified::Length,
         pub vertical: specified::Length,
     }
 
     #[inline]
--- a/servo/components/style/properties/longhand/inherited_text.mako.rs
+++ b/servo/components/style/properties/longhand/inherited_text.mako.rs
@@ -6,16 +6,26 @@
 
 <% data.new_style_struct("InheritedText", inherited=True, gecko_name="Text") %>
 
 <%helpers:longhand name="line-height" animatable="True">
     use cssparser::ToCss;
     use std::fmt;
     use values::LocalToCss;
     use values::CSSFloat;
+    use values::HasViewportPercentage;
+
+    impl HasViewportPercentage for SpecifiedValue {
+        fn has_viewport_percentage(&self) -> bool {
+            match *self {
+                SpecifiedValue::LengthOrPercentage(length) => length.has_viewport_percentage(),
+                _ => false
+            }
+        }
+    }
 
     #[derive(Debug, Clone, PartialEq, Copy)]
     #[cfg_attr(feature = "servo", derive(HeapSizeOf))]
     pub enum SpecifiedValue {
         Normal,
         % if product == "gecko":
             MozBlockHeight,
         % endif
@@ -118,17 +128,19 @@
             }
         }
     }
 </%helpers:longhand>
 
 <%helpers:longhand name="text-align" animatable="False">
     pub use self::computed_value::T as SpecifiedValue;
     use values::computed::ComputedValueAsSpecified;
+    use values::NoViewportPercentage;
     impl ComputedValueAsSpecified for SpecifiedValue {}
+    impl NoViewportPercentage for SpecifiedValue {}
     pub mod computed_value {
         macro_rules! define_text_align {
             ( $( $name: ident ( $string: expr ) => $discriminant: expr, )+ ) => {
                 define_css_keyword_enum! { T:
                     $(
                         $string => $name,
                     )+
                 }
@@ -179,16 +191,26 @@
     }
 </%helpers:longhand>
 
 // FIXME: This prop should be animatable.
 <%helpers:longhand name="letter-spacing" animatable="False">
     use cssparser::ToCss;
     use std::fmt;
     use values::LocalToCss;
+    use values::HasViewportPercentage;
+
+    impl HasViewportPercentage for SpecifiedValue {
+        fn has_viewport_percentage(&self) -> bool {
+            match *self {
+                SpecifiedValue::Specified(length) => length.has_viewport_percentage(),
+                _ => false
+            }
+        }
+    }
 
     #[derive(Debug, Clone, Copy, PartialEq)]
     #[cfg_attr(feature = "servo", derive(HeapSizeOf))]
     pub enum SpecifiedValue {
         Normal,
         Specified(specified::Length),
     }
 
@@ -243,16 +265,26 @@
         }
     }
 </%helpers:longhand>
 
 <%helpers:longhand name="word-spacing" animatable="False">
     use cssparser::ToCss;
     use std::fmt;
     use values::LocalToCss;
+    use values::HasViewportPercentage;
+
+    impl HasViewportPercentage for SpecifiedValue {
+        fn has_viewport_percentage(&self) -> bool {
+            match *self {
+                SpecifiedValue::Specified(length) => length.has_viewport_percentage(),
+                _ => false
+            }
+        }
+    }
 
     #[derive(Debug, Clone, Copy, PartialEq)]
     #[cfg_attr(feature = "servo", derive(HeapSizeOf))]
     pub enum SpecifiedValue {
         Normal,
         Specified(specified::Length),  // FIXME(SimonSapin) support percentages
     }
 
@@ -335,20 +367,22 @@
 
 <%helpers:longhand name="-servo-text-decorations-in-effect"
                    derived_from="display text-decoration"
                    need_clone="True" products="servo"
                    animatable="False">
     use cssparser::{RGBA, ToCss};
     use std::fmt;
 
+    use values:: NoViewportPercentage;
     use values::computed::ComputedValueAsSpecified;
     use properties::style_struct_traits::{Box, Color, Text};
 
     impl ComputedValueAsSpecified for SpecifiedValue {}
+    impl NoViewportPercentage for SpecifiedValue {}
 
     #[derive(Clone, PartialEq, Copy, Debug)]
     #[cfg_attr(feature = "servo", derive(HeapSizeOf))]
     pub struct SpecifiedValue {
         pub underline: Option<RGBA>,
         pub overline: Option<RGBA>,
         pub line_through: Option<RGBA>,
     }
@@ -417,17 +451,19 @@
     }
 </%helpers:longhand>
 
 <%helpers:single_keyword_computed name="white-space"
                                   values="normal pre nowrap pre-wrap pre-line"
                                   gecko_constant_prefix="NS_STYLE_WHITESPACE"
                                   animatable="False">
     use values::computed::ComputedValueAsSpecified;
+    use values::NoViewportPercentage;
     impl ComputedValueAsSpecified for SpecifiedValue {}
+    impl NoViewportPercentage for SpecifiedValue {}
 
     impl SpecifiedValue {
         pub fn allow_wrap(&self) -> bool {
             match *self {
                 SpecifiedValue::nowrap |
                 SpecifiedValue::pre => false,
                 SpecifiedValue::normal |
                 SpecifiedValue::pre_wrap |
@@ -456,21 +492,37 @@
         }
     }
 </%helpers:single_keyword_computed>
 
 <%helpers:longhand name="text-shadow" animatable="True">
     use cssparser::{self, ToCss};
     use std::fmt;
     use values::LocalToCss;
+    use values::HasViewportPercentage;
+
+    impl HasViewportPercentage for SpecifiedValue {
+        fn has_viewport_percentage(&self) -> bool {
+            let &SpecifiedValue(ref vec) = self;
+            vec.iter().any(|ref x| x .has_viewport_percentage())
+        }
+    }
 
     #[derive(Clone, PartialEq, Debug)]
     #[cfg_attr(feature = "servo", derive(HeapSizeOf))]
     pub struct SpecifiedValue(Vec<SpecifiedTextShadow>);
 
+    impl HasViewportPercentage for SpecifiedTextShadow {
+        fn has_viewport_percentage(&self) -> bool {
+            self.offset_x.has_viewport_percentage() ||
+            self.offset_y.has_viewport_percentage() ||
+            self.blur_radius.has_viewport_percentage()
+        }
+    }
+
     #[derive(Clone, PartialEq, Debug)]
     #[cfg_attr(feature = "servo", derive(HeapSizeOf))]
     pub struct SpecifiedTextShadow {
         pub offset_x: specified::Length,
         pub offset_y: specified::Length,
         pub blur_radius: specified::Length,
         pub color: Option<specified::CSSColor>,
     }
--- a/servo/components/style/properties/longhand/list.mako.rs
+++ b/servo/components/style/properties/longhand/list.mako.rs
@@ -26,16 +26,19 @@
     gecko_constant_prefix="NS_STYLE_LIST_STYLE",
     animatable=False)}
 
 <%helpers:longhand name="list-style-image" animatable="False">
     use cssparser::{ToCss, Token};
     use std::fmt;
     use url::Url;
     use values::LocalToCss;
+    use values::NoViewportPercentage;
+
+    impl NoViewportPercentage for SpecifiedValue {}
 
     #[derive(Debug, Clone, PartialEq, Eq)]
     #[cfg_attr(feature = "servo", derive(HeapSizeOf))]
     pub enum SpecifiedValue {
         None,
         Url(Url),
     }
 
@@ -91,29 +94,31 @@
     pub fn get_initial_value() -> computed_value::T {
         computed_value::T(None)
     }
 </%helpers:longhand>
 
 <%helpers:longhand name="quotes" animatable="False">
     use std::borrow::Cow;
     use std::fmt;
+    use values::NoViewportPercentage;
     use values::computed::ComputedValueAsSpecified;
 
     use cssparser::{ToCss, Token};
 
     pub use self::computed_value::T as SpecifiedValue;
 
     pub mod computed_value {
         #[derive(Debug, Clone, PartialEq)]
         #[cfg_attr(feature = "servo", derive(HeapSizeOf))]
         pub struct T(pub Vec<(String,String)>);
     }
 
     impl ComputedValueAsSpecified for SpecifiedValue {}
+    impl NoViewportPercentage for SpecifiedValue {}
 
     impl ToCss for SpecifiedValue {
         fn to_css<W>(&self, dest: &mut W) -> fmt::Result where W: fmt::Write {
             let mut first = true;
             for pair in &self.0 {
                 if !first {
                     try!(dest.write_str(" "));
                 }
--- a/servo/components/style/properties/longhand/outline.mako.rs
+++ b/servo/components/style/properties/longhand/outline.mako.rs
@@ -27,26 +27,35 @@
     }
 </%helpers:longhand>
 
 <%helpers:longhand name="outline-width" animatable="True">
     use app_units::Au;
     use cssparser::ToCss;
     use std::fmt;
     use values::LocalToCss;
+    use values::HasViewportPercentage;
 
     impl ToCss for SpecifiedValue {
         fn to_css<W>(&self, dest: &mut W) -> fmt::Result where W: fmt::Write {
             self.0.to_css(dest)
         }
     }
 
     pub fn parse(_context: &ParserContext, input: &mut Parser) -> Result<SpecifiedValue, ()> {
         specified::parse_border_width(input).map(SpecifiedValue)
     }
+
+    impl HasViewportPercentage for SpecifiedValue {
+        fn has_viewport_percentage(&self) -> bool {
+            let &SpecifiedValue(length) = self;
+            length.has_viewport_percentage()
+        }
+    }
+
     #[derive(Debug, Clone, PartialEq)]
     #[cfg_attr(feature = "servo", derive(HeapSizeOf))]
     pub struct SpecifiedValue(pub specified::Length);
     pub mod computed_value {
         use app_units::Au;
         pub type T = Au;
     }
     pub use super::border_top_width::get_initial_value;
--- a/servo/components/style/properties/longhand/pointing.mako.rs
+++ b/servo/components/style/properties/longhand/pointing.mako.rs
@@ -3,19 +3,21 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 <%namespace name="helpers" file="/helpers.mako.rs" />
 
 <% data.new_style_struct("Pointing", inherited=True, gecko_name="UserInterface") %>
 
 <%helpers:longhand name="cursor" animatable="False">
     pub use self::computed_value::T as SpecifiedValue;
+    use values::NoViewportPercentage;
     use values::computed::ComputedValueAsSpecified;
 
     impl ComputedValueAsSpecified for SpecifiedValue {}
+    impl NoViewportPercentage for SpecifiedValue {}
 
     pub mod computed_value {
         use cssparser::ToCss;
         use std::fmt;
         use style_traits::cursor::Cursor;
 
         #[derive(Clone, PartialEq, Eq, Copy, Debug)]
         #[cfg_attr(feature = "servo", derive(HeapSizeOf))]
--- a/servo/components/style/properties/longhand/position.mako.rs
+++ b/servo/components/style/properties/longhand/position.mako.rs
@@ -8,19 +8,21 @@
 
 % for side in ["top", "right", "bottom", "left"]:
     ${helpers.predefined_type(side, "LengthOrPercentageOrAuto",
                               "computed::LengthOrPercentageOrAuto::Auto",
                               animatable=True)}
 % endfor
 
 <%helpers:longhand name="z-index" animatable="True">
+    use values::NoViewportPercentage;
     use values::computed::ComputedValueAsSpecified;
 
     impl ComputedValueAsSpecified for SpecifiedValue {}
+    impl NoViewportPercentage for SpecifiedValue {}
     pub type SpecifiedValue = computed_value::T;
     pub mod computed_value {
         use cssparser::ToCss;
         use std::fmt;
 
         #[derive(PartialEq, Clone, Eq, Copy, Debug)]
         #[cfg_attr(feature = "servo", derive(HeapSizeOf))]
         pub enum T {
--- a/servo/components/style/properties/longhand/text.mako.rs
+++ b/servo/components/style/properties/longhand/text.mako.rs
@@ -20,18 +20,20 @@
 
 // FIXME: This prop should be animatable.
 <%helpers:longhand name="${'text-decoration' if product == 'servo' else 'text-decoration-line'}"
                    custom_cascade="${product == 'servo'}"
                    animatable="False">
     use cssparser::ToCss;
     use std::fmt;
     use values::computed::ComputedValueAsSpecified;
+    use values::NoViewportPercentage;
 
     impl ComputedValueAsSpecified for SpecifiedValue {}
+    impl NoViewportPercentage for SpecifiedValue {}
 
     #[derive(PartialEq, Eq, Copy, Clone, Debug)]
     #[cfg_attr(feature = "servo", derive(HeapSizeOf))]
     pub struct SpecifiedValue {
         pub underline: bool,
         pub overline: bool,
         pub line_through: bool,
         // 'blink' is accepted in the parser but ignored.
--- a/servo/components/style/properties/properties.mako.rs
+++ b/servo/components/style/properties/properties.mako.rs
@@ -27,16 +27,17 @@ use euclid::side_offsets::SideOffsets2D;
 use euclid::size::Size2D;
 use string_cache::Atom;
 use computed_values;
 use logical_geometry::{LogicalMargin, PhysicalSide, WritingMode};
 use parser::{ParserContext, ParserContextExtraData, log_css_error};
 use selectors::matching::DeclarationBlock;
 use stylesheets::Origin;
 use values::LocalToCss;
+use values::HasViewportPercentage;
 use values::computed::{self, TContext, ToComputedValue};
 use values::specified::BorderStyle;
 
 use self::property_bit_field::PropertyBitField;
 
 <%!
     from data import Method, Keyword, to_rust_ident
 %>
@@ -820,16 +821,29 @@ impl ToCss for PropertyDeclaration {
             PropertyDeclaration::Custom(_, ref value) => value.to_css(dest),
             % if any(property.derived_from for property in data.longhands):
                 _ => Err(fmt::Error),
             % endif
         }
     }
 }
 
+impl HasViewportPercentage for PropertyDeclaration {
+    fn has_viewport_percentage(&self) -> bool {
+        match *self {
+            % for property in data.longhands:
+                PropertyDeclaration::${property.camel_case}(DeclaredValue::Value(ref val)) => {
+                    val.has_viewport_percentage()
+                },
+            % endfor
+            _ => false
+        }
+    }
+}
+
 impl PropertyDeclaration {
     pub fn name(&self) -> PropertyDeclarationName {
         match *self {
             % for property in data.longhands:
                 PropertyDeclaration::${property.camel_case}(..) =>
                 % if not property.derived_from:
                     PropertyDeclarationName::Longhand("${property.name}"),
                 % else:
--- a/servo/components/style/traversal.rs
+++ b/servo/components/style/traversal.rs
@@ -1,23 +1,24 @@
 /* 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/. */
 
 //! Traversing the DOM tree; the bloom filter.
 
 use context::{SharedStyleContext, StyleContext};
-use dom::{OpaqueNode, TNode, TRestyleDamage, UnsafeNode};
+use dom::{OpaqueNode, TElement, TNode, TRestyleDamage, UnsafeNode};
 use matching::{ApplicableDeclarations, ElementMatchMethods, MatchMethods, StyleSharingResult};
 use selector_impl::SelectorImplExt;
 use selectors::Element;
 use selectors::bloom::BloomFilter;
 use std::cell::RefCell;
 use tid::tid;
 use util::opts;
+use values::HasViewportPercentage;
 
 /// Every time we do another layout, the old bloom filters are invalid. This is
 /// detected by ticking a generation number every layout.
 pub type Generation = u32;
 
 /// A pair of the bloom filter used for css selector matching, and the node to
 /// which it applies. This is used to efficiently do `Descendant` selector
 /// matches. Thanks to the bloom filter, we can avoid walking up the tree
@@ -239,10 +240,28 @@ pub fn recalc_style_at<'a, N, C>(context
 
     // Before running the children, we need to insert our nodes into the bloom
     // filter.
     debug!("[{}] + {:X}", tid(), unsafe_layout_node.0);
     node.insert_into_bloom_filter(&mut *bf);
 
     // NB: flow construction updates the bloom filter on the way up.
     put_thread_local_bloom_filter(bf, &unsafe_layout_node, context.shared_context());
+
+    // Mark the node as DIRTY_ON_VIEWPORT_SIZE_CHANGE is it uses viewport percentage units.
+    match node.as_element() {
+        Some(element) => {
+            match *element.style_attribute() {
+                Some(ref property_declaration_block) => {
+                    if property_declaration_block.declarations().any(|d| d.0.has_viewport_percentage()) {
+                        unsafe {
+                            node.set_dirty_on_viewport_size_changed();
+                        }
+                        node.set_descendants_dirty_on_viewport_size_changed();
+                    }
+                },
+                None => {}
+            }
+        },
+        None => {}
+    }
 }
 
--- a/servo/components/style/values.rs
+++ b/servo/components/style/values.rs
@@ -77,33 +77,45 @@ macro_rules! define_numbered_css_keyword
         }
     }
 }
 
 pub type CSSFloat = f32;
 
 pub const FONT_MEDIUM_PX: i32 = 16;
 
+pub trait HasViewportPercentage {
+    fn has_viewport_percentage(&self) -> bool;
+}
+
+pub trait NoViewportPercentage {}
+
+impl<T> HasViewportPercentage for T where T: NoViewportPercentage {
+    fn has_viewport_percentage(&self) -> bool {
+        false
+    }
+}
 
 pub mod specified {
     use app_units::Au;
     use cssparser::{self, Parser, ToCss, Token};
     use euclid::size::Size2D;
     use parser::ParserContext;
     use std::ascii::AsciiExt;
     use std::cmp;
     use std::f32::consts::PI;
     use std::fmt;
     use std::ops::Mul;
     use style_traits::values::specified::AllowedNumericType;
-    use super::LocalToCss;
     use super::computed::{TContext, ToComputedValue};
-    use super::{CSSFloat, FONT_MEDIUM_PX};
+    use super::{CSSFloat, FONT_MEDIUM_PX, HasViewportPercentage, LocalToCss, NoViewportPercentage};
     use url::Url;
 
+    impl NoViewportPercentage for i32 {}  // For PropertyDeclaration::Order
+
     #[derive(Clone, PartialEq, Debug)]
     #[cfg_attr(feature = "servo", derive(HeapSizeOf))]
     pub struct CSSColor {
         pub parsed: cssparser::Color,
         pub authored: Option<String>,
     }
     impl CSSColor {
         pub fn parse(input: &mut Parser) -> Result<CSSColor, ()> {
@@ -115,32 +127,36 @@ pub mod specified {
             input.reset(start_position);
             Ok(CSSColor {
                 parsed: try!(cssparser::Color::parse(input)),
                 authored: authored,
             })
         }
     }
 
+    impl NoViewportPercentage for CSSColor {}
+
     impl ToCss for CSSColor {
         fn to_css<W>(&self, dest: &mut W) -> fmt::Result where W: fmt::Write {
             match self.authored {
                 Some(ref s) => dest.write_str(s),
                 None => self.parsed.to_css(dest),
             }
         }
     }
 
     #[derive(Clone, PartialEq, Debug)]
     #[cfg_attr(feature = "servo", derive(HeapSizeOf))]
     pub struct CSSRGBA {
         pub parsed: cssparser::RGBA,
         pub authored: Option<String>,
     }
 
+    impl NoViewportPercentage for CSSRGBA {}
+
     impl ToCss for CSSRGBA {
         fn to_css<W>(&self, dest: &mut W) -> fmt::Result where W: fmt::Write {
             match self.authored {
                 Some(ref s) => dest.write_str(s),
                 None => self.parsed.to_css(dest),
             }
         }
     }
@@ -187,16 +203,22 @@ pub mod specified {
     #[cfg_attr(feature = "servo", derive(HeapSizeOf))]
     pub enum ViewportPercentageLength {
         Vw(CSSFloat),
         Vh(CSSFloat),
         Vmin(CSSFloat),
         Vmax(CSSFloat)
     }
 
+    impl HasViewportPercentage for ViewportPercentageLength {
+        fn has_viewport_percentage(&self) -> bool {
+            true
+        }
+    }
+
     impl ToCss for ViewportPercentageLength {
         fn to_css<W>(&self, dest: &mut W) -> fmt::Result where W: fmt::Write {
             match *self {
                 ViewportPercentageLength::Vw(length) => write!(dest, "{}vw", length),
                 ViewportPercentageLength::Vh(length) => write!(dest, "{}vh", length),
                 ViewportPercentageLength::Vmin(length) => write!(dest, "{}vmin", length),
                 ViewportPercentageLength::Vmax(length) => write!(dest, "{}vmax", length)
             }
@@ -252,16 +274,25 @@ pub mod specified {
         ///
         /// This cannot be specified by the user directly and is only generated by
         /// `Stylist::synthesize_rules_for_legacy_attributes()`.
         ServoCharacterWidth(CharacterWidth),
 
         Calc(CalcLengthOrPercentage),
     }
 
+    impl HasViewportPercentage for Length {
+        fn has_viewport_percentage(&self) -> bool {
+            match *self {
+                Length::ViewportPercentage(_) => true,
+                _ => false
+            }
+        }
+    }
+
     impl ToCss for Length {
         fn to_css<W>(&self, dest: &mut W) -> fmt::Result where W: fmt::Write {
             match *self {
                 Length::Absolute(length) => write!(dest, "{}px", length.to_f32_px()),
                 Length::FontRelative(length) => length.to_css(dest),
                 Length::ViewportPercentage(length) => length.to_css(dest),
                 Length::Calc(calc) => calc.to_css(dest),
                 Length::ServoCharacterWidth(_)
@@ -870,16 +901,25 @@ pub mod specified {
     #[derive(Clone, PartialEq, Copy, Debug)]
     #[cfg_attr(feature = "servo", derive(HeapSizeOf))]
     pub enum LengthOrPercentage {
         Length(Length),
         Percentage(Percentage),
         Calc(CalcLengthOrPercentage),
     }
 
+    impl HasViewportPercentage for LengthOrPercentage {
+        fn has_viewport_percentage(&self) -> bool {
+            match *self {
+                LengthOrPercentage::Length(length) => length.has_viewport_percentage(),
+                _ => false
+            }
+        }
+    }
+
     impl ToCss for LengthOrPercentage {
         fn to_css<W>(&self, dest: &mut W) -> fmt::Result where W: fmt::Write {
             match *self {
                 LengthOrPercentage::Length(length) => length.to_css(dest),
                 LengthOrPercentage::Percentage(percentage) => percentage.to_css(dest),
                 LengthOrPercentage::Calc(calc) => calc.to_css(dest),
             }
         }
@@ -920,16 +960,25 @@ pub mod specified {
     #[cfg_attr(feature = "servo", derive(HeapSizeOf))]
     pub enum LengthOrPercentageOrAuto {
         Length(Length),
         Percentage(Percentage),
         Auto,
         Calc(CalcLengthOrPercentage),
     }
 
+    impl HasViewportPercentage for LengthOrPercentageOrAuto {
+        fn has_viewport_percentage(&self) -> bool {
+            match *self {
+                LengthOrPercentageOrAuto::Length(length) => length.has_viewport_percentage(),
+                _ => false
+            }
+        }
+    }
+
     impl ToCss for LengthOrPercentageOrAuto {
         fn to_css<W>(&self, dest: &mut W) -> fmt::Result where W: fmt::Write {
             match *self {
                 LengthOrPercentageOrAuto::Length(length) => length.to_css(dest),
                 LengthOrPercentageOrAuto::Percentage(percentage) => percentage.to_css(dest),
                 LengthOrPercentageOrAuto::Auto => dest.write_str("auto"),
                 LengthOrPercentageOrAuto::Calc(calc) => calc.to_css(dest),
             }
@@ -970,16 +1019,25 @@ pub mod specified {
     #[cfg_attr(feature = "servo", derive(HeapSizeOf))]
     pub enum LengthOrPercentageOrNone {
         Length(Length),
         Percentage(Percentage),
         Calc(CalcLengthOrPercentage),
         None,
     }
 
+    impl HasViewportPercentage for LengthOrPercentageOrNone {
+        fn has_viewport_percentage(&self) -> bool {
+            match *self {
+                LengthOrPercentageOrNone::Length(length) => length.has_viewport_percentage(),
+                _ => false
+            }
+        }
+    }
+
     impl ToCss for LengthOrPercentageOrNone {
         fn to_css<W>(&self, dest: &mut W) -> fmt::Result where W: fmt::Write {
             match *self {
                 LengthOrPercentageOrNone::Length(length) => length.to_css(dest),
                 LengthOrPercentageOrNone::Percentage(percentage) => percentage.to_css(dest),
                 LengthOrPercentageOrNone::Calc(calc) => calc.to_css(dest),
                 LengthOrPercentageOrNone::None => dest.write_str("none"),
             }
@@ -1017,16 +1075,25 @@ pub mod specified {
 
     #[derive(Clone, PartialEq, Copy, Debug)]
     #[cfg_attr(feature = "servo", derive(HeapSizeOf))]
     pub enum LengthOrNone {
         Length(Length),
         None,
     }
 
+    impl HasViewportPercentage for LengthOrNone {
+        fn has_viewport_percentage(&self) -> bool {
+            match *self {
+                LengthOrNone::Length(length) => length.has_viewport_percentage(),
+                _ => false
+            }
+        }
+    }
+
     impl ToCss for LengthOrNone {
         fn to_css<W>(&self, dest: &mut W) -> fmt::Result where W: fmt::Write {
             match *self {
                 LengthOrNone::Length(length) => length.to_css(dest),
                 LengthOrNone::None => dest.write_str("none"),
             }
         }
     }
@@ -1061,16 +1128,25 @@ pub mod specified {
     pub enum LengthOrPercentageOrAutoOrContent {
         Length(Length),
         Percentage(Percentage),
         Calc(CalcLengthOrPercentage),
         Auto,
         Content
     }
 
+    impl HasViewportPercentage for LengthOrPercentageOrAutoOrContent {
+        fn has_viewport_percentage(&self) -> bool {
+            match *self {
+                LengthOrPercentageOrAutoOrContent::Length(length) => length.has_viewport_percentage(),
+                _ => false
+            }
+        }
+    }
+
     impl ToCss for LengthOrPercentageOrAutoOrContent {
         fn to_css<W>(&self, dest: &mut W) -> fmt::Result where W: fmt::Write {
             match *self {
                 LengthOrPercentageOrAutoOrContent::Length(len) => len.to_css(dest),
                 LengthOrPercentageOrAutoOrContent::Percentage(perc) => perc.to_css(dest),
                 LengthOrPercentageOrAutoOrContent::Auto => dest.write_str("auto"),
                 LengthOrPercentageOrAutoOrContent::Content => dest.write_str("content"),
                 LengthOrPercentageOrAutoOrContent::Calc(calc) => calc.to_css(dest),
@@ -1100,16 +1176,18 @@ pub mod specified {
             }
         }
     }
 
     #[derive(Clone, PartialEq, Copy, Debug)]
     #[cfg_attr(feature = "servo", derive(HeapSizeOf))]
     pub struct BorderRadiusSize(pub Size2D<LengthOrPercentage>);
 
+    impl NoViewportPercentage for BorderRadiusSize {}
+
     impl BorderRadiusSize {
         pub fn zero() -> BorderRadiusSize {
             let zero = LengthOrPercentage::Length(Length::Absolute(Au(0)));
                 BorderRadiusSize(Size2D::new(zero, zero))
         }
 
         pub fn new(width: LengthOrPercentage, height: LengthOrPercentage) -> BorderRadiusSize {
             BorderRadiusSize(Size2D::new(width, height))
@@ -1140,16 +1218,26 @@ pub mod specified {
     pub enum PositionComponent {
         LengthOrPercentage(LengthOrPercentage),
         Center,
         Left,
         Right,
         Top,
         Bottom,
     }
+
+    impl HasViewportPercentage for PositionComponent {
+        fn has_viewport_percentage(&self) -> bool {
+            match *self {
+                PositionComponent::LengthOrPercentage(length) => length.has_viewport_percentage(),
+                _ => false
+            }
+        }
+    }
+
     impl PositionComponent {
         pub fn parse(input: &mut Parser) -> Result<PositionComponent, ()> {
             input.try(LengthOrPercentage::parse)
             .map(PositionComponent::LengthOrPercentage)
             .or_else(|()| {
                 match try!(input.next()) {
                     Token::Ident(value) => {
                         match_ignore_ascii_case! { value,
@@ -1428,16 +1516,18 @@ pub mod specified {
         "dashed" => dashed = 5,
         "hidden" => hidden = -2,
         "groove" => groove = 1,
         "ridge" => ridge = 3,
         "inset" => inset = 0,
         "outset" => outset = 2,
     }
 
+    impl NoViewportPercentage for BorderStyle {}
+
     impl BorderStyle {
         pub fn none_or_hidden(&self) -> bool {
             matches!(*self, BorderStyle::none | BorderStyle::hidden)
         }
     }
 
     /// A time in seconds according to CSS-VALUES ยง 6.2.
     #[derive(Clone, Copy, Debug, PartialEq, PartialOrd)]
@@ -1489,16 +1579,18 @@ pub mod specified {
             write!(dest, "{}s", self.0)
         }
     }
 
     #[derive(Clone, Copy, Debug, PartialEq, PartialOrd)]
     #[cfg_attr(feature = "servo", derive(HeapSizeOf))]
     pub struct Number(pub CSSFloat);
 
+    impl NoViewportPercentage for Number {}
+
     impl Number {
         pub fn parse(input: &mut Parser) -> Result<Number, ()> {
             parse_number(input).map(Number)
         }
 
         fn parse_with_minimum(input: &mut Parser, min: CSSFloat) -> Result<Number, ()> {
             match parse_number(input) {
                 Ok(value) if value < min => Err(()),
@@ -1527,16 +1619,18 @@ pub mod specified {
             self.0.to_css(dest)
         }
     }
 
     #[derive(Clone, Copy, Debug, PartialEq, PartialOrd)]
     #[cfg_attr(feature = "servo", derive(HeapSizeOf))]
     pub struct Opacity(pub CSSFloat);
 
+    impl NoViewportPercentage for Opacity {}
+
     impl Opacity {
         pub fn parse(input: &mut Parser) -> Result<Opacity, ()> {
             parse_number(input).map(Opacity)
         }
     }
 
     impl ToComputedValue for Opacity {
         type ComputedValue = CSSFloat;
@@ -1921,16 +2015,17 @@ pub mod computed {
     #[cfg_attr(feature = "servo", derive(HeapSizeOf))]
     pub enum LengthOrPercentageOrAutoOrContent {
         Length(Au),
         Percentage(CSSFloat),
         Calc(CalcLengthOrPercentage),
         Auto,
         Content
     }
+
     impl fmt::Debug for LengthOrPercentageOrAutoOrContent {
         fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
             match *self {
                 LengthOrPercentageOrAutoOrContent::Length(length) => write!(f, "{:?}", length),
                 LengthOrPercentageOrAutoOrContent::Percentage(percentage) => write!(f, "{}%", percentage * 100.),
                 LengthOrPercentageOrAutoOrContent::Calc(calc) => write!(f, "{:?}", calc),
                 LengthOrPercentageOrAutoOrContent::Auto => write!(f, "auto"),
                 LengthOrPercentageOrAutoOrContent::Content => write!(f, "content")
@@ -1979,16 +2074,17 @@ pub mod computed {
     #[derive(PartialEq, Clone, Copy)]
     #[cfg_attr(feature = "servo", derive(HeapSizeOf))]
     pub enum LengthOrPercentageOrNone {
         Length(Au),
         Percentage(CSSFloat),
         Calc(CalcLengthOrPercentage),
         None,
     }
+
     impl fmt::Debug for LengthOrPercentageOrNone {
         fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
             match *self {
                 LengthOrPercentageOrNone::Length(length) => write!(f, "{:?}", length),
                 LengthOrPercentageOrNone::Percentage(percentage) => write!(f, "{}%", percentage * 100.),
                 LengthOrPercentageOrNone::Calc(calc) => write!(f, "{:?}", calc),
                 LengthOrPercentageOrNone::None => write!(f, "none"),
             }
@@ -2030,16 +2126,17 @@ pub mod computed {
     }
 
     #[derive(PartialEq, Clone, Copy)]
     #[cfg_attr(feature = "servo", derive(HeapSizeOf))]
     pub enum LengthOrNone {
         Length(Au),
         None,
     }
+
     impl fmt::Debug for LengthOrNone {
         fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
             match *self {
                 LengthOrNone::Length(length) => write!(f, "{:?}", length),
                 LengthOrNone::None => write!(f, "none"),
             }
         }
     }
--- a/servo/ports/geckolib/wrapper.rs
+++ b/servo/ports/geckolib/wrapper.rs
@@ -279,16 +279,27 @@ impl<'ln> TNode for GeckoNode<'ln> {
         }
     }
 
     fn next_sibling(&self) -> Option<GeckoNode<'ln>> {
         unsafe {
             Gecko_GetNextSibling(self.node).as_ref().map(|n| GeckoNode::from_ref(n))
         }
     }
+
+    fn needs_dirty_on_viewport_size_changed(&self) -> bool {
+        // Gecko's node doesn't have the DIRTY_ON_VIEWPORT_SIZE_CHANGE flag,
+        // so we force them to be dirtied on viewport size change, regardless if
+        // they use viewport percentage size or not.
+        // TODO(shinglyu): implement this in Gecko: https://github.com/servo/servo/pull/11890
+        true
+    }
+
+    // TODO(shinglyu): implement this in Gecko: https://github.com/servo/servo/pull/11890
+    unsafe fn set_dirty_on_viewport_size_changed(&self) {}
 }
 
 #[derive(Clone, Copy)]
 pub struct GeckoDocument<'ld> {
     document: *mut RawGeckoDocument,
     chain: PhantomData<&'ld ()>,
 }
 
--- a/servo/tests/unit/style/lib.rs
+++ b/servo/tests/unit/style/lib.rs
@@ -19,16 +19,17 @@ extern crate util;
 
 mod attr;
 mod cache;
 mod logical_geometry;
 mod media_queries;
 mod properties;
 mod str;
 mod stylesheets;
+mod value;
 mod viewport;
 
 mod writing_modes {
     use style::logical_geometry::WritingMode;
     use style::properties::{INITIAL_SERVO_VALUES, ComputedValues, get_writing_mode};
 
     #[test]
     fn initial_writing_mode_is_empty() {
--- a/servo/tests/unit/style/properties.rs
+++ b/servo/tests/unit/style/properties.rs
@@ -1,22 +1,25 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
+use app_units::Au;
 use cssparser::ToCss;
 use rustc_serialize::json::Json;
 use std::env;
 use std::fs::{File, remove_file};
 use std::path::Path;
 use std::process::Command;
 use std::sync::Arc;
 use style::computed_values::display::T::inline_block;
-use style::properties::{PropertyDeclaration, PropertyDeclarationBlock, DeclaredValue};
-use style::values::specified::{Length, LengthOrPercentageOrAuto, LengthOrPercentage};
+use style::properties::longhands::border_top_width;
+use style::properties::{DeclaredValue, PropertyDeclaration, PropertyDeclarationBlock};
+use style::values::HasViewportPercentage;
+use style::values::specified::{Length, LengthOrPercentageOrAuto, LengthOrPercentage, ViewportPercentageLength};
 
 #[test]
 fn properties_list_json() {
     let top = Path::new(file!()).parent().unwrap().join("..").join("..").join("..");
     let json = top.join("target").join("doc").join("servo").join("css-properties.json");
     if json.exists() {
         remove_file(&json).unwrap()
     }
@@ -86,8 +89,26 @@ fn property_declaration_block_should_ser
 
     let css_string = block.to_css_string();
 
     assert_eq!(
         css_string,
         "width: 70px; min-height: 20px; display: inline-block; height: 20px !important;"
     );
 }
+
+#[test]
+fn has_viewport_percentage_for_specified_value() {
+    //TODO: test all specified value with a HasViewportPercentage impl
+    let pvw = PropertyDeclaration::BorderTopWidth(
+                  DeclaredValue::Value(border_top_width::SpecifiedValue(
+                      Length::ViewportPercentage(ViewportPercentageLength::Vw(100.))
+                  ))
+              );
+    assert!(pvw.has_viewport_percentage());
+
+    let pabs = PropertyDeclaration::BorderTopWidth(
+                   DeclaredValue::Value(border_top_width::SpecifiedValue(
+                       Length::Absolute(Au(100))
+                   ))
+               );
+    assert!(!pabs.has_viewport_percentage());
+}
new file mode 100644
--- /dev/null
+++ b/servo/tests/unit/style/value.rs
@@ -0,0 +1,15 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+use app_units::Au;
+use style::values::HasViewportPercentage;
+use style::values::specified::{ViewportPercentageLength, Length};
+
+#[test]
+fn length_has_viewport_percentage() {
+    let l = Length::ViewportPercentage(ViewportPercentageLength::Vw(100.));
+    assert!(l.has_viewport_percentage());
+    let l = Length::Absolute(Au(100));
+    assert!(!l.has_viewport_percentage());
+}