Backed out 6 changesets (bug 1476054) for failing on servo/ports/geckolib/glue.rs on a CLOSED TREE
authorGurzau Raul <rgurzau@mozilla.com>
Mon, 16 Jul 2018 23:56:23 +0300
changeset 484591 6339739cba9a0099d6bbefa294ce22989935ab69
parent 484590 d55a38a6be534e3f7c62da7ae36a65d8526ea0a4
child 484592 2b4612def0fe62def3b633c91c942c354d183c42
push id1815
push userffxbld-merge
push dateMon, 15 Oct 2018 10:40:45 +0000
treeherdermozilla-release@18d4c09e9378 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
bugs1476054
milestone63.0a1
backs outf4941fe345ade851ebd49fecea9f89aa3bfbc79f
cc571c618e4c256cedbdd8859ef0d9f7e97d0afd
0a712d7bcb66d19a94ba05d020c28b845071aee6
62293a989ed23d16194315a37bed2b19dd6aec46
ef2a43e3fc6c6acd0f7dea8424837b18edd15a95
a2bb0089cf1d69cf713913250b89c1b828ab1e77
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Backed out 6 changesets (bug 1476054) for failing on servo/ports/geckolib/glue.rs on a CLOSED TREE Backed out changeset f4941fe345ad (bug 1476054) Backed out changeset cc571c618e4c (bug 1476054) Backed out changeset 0a712d7bcb66 (bug 1476054) Backed out changeset 62293a989ed2 (bug 1476054) Backed out changeset ef2a43e3fc6c (bug 1476054) Backed out changeset a2bb0089cf1d (bug 1476054)
layout/generic/BRFrame.cpp
layout/generic/BlockReflowInput.cpp
layout/generic/WritingModes.h
layout/generic/nsBlockFrame.cpp
layout/generic/nsFloatManager.cpp
layout/generic/nsFrame.cpp
layout/generic/nsIFrame.h
layout/generic/nsLineBox.cpp
layout/style/ServoCSSPropList.mako.py
layout/style/nsStyleConsts.h
layout/style/nsStyleStruct.h
servo/components/style/properties/gecko.mako.rs
servo/components/style/properties/longhands/box.mako.rs
servo/components/style/properties/properties.mako.rs
servo/components/style/values/computed/box.rs
servo/components/style/values/computed/mod.rs
servo/components/style/values/specified/box.rs
servo/components/style/values/specified/mod.rs
servo/ports/geckolib/glue.rs
servo/tests/unit/style/keyframes.rs
servo/tests/unit/style/lib.rs
servo/tests/unit/style/stylesheets.rs
--- a/layout/generic/BRFrame.cpp
+++ b/layout/generic/BRFrame.cpp
@@ -153,17 +153,17 @@ BRFrame::Reflow(nsPresContext* aPresCont
       // XXX This also fixes bug 10036!
       // Warning: nsTextControlFrame::CalculateSizeStandard depends on
       // the following line, see bug 228752.
       // The code below in AddInlinePrefISize also adds 1 appunit to width
       finalSize.ISize(wm) = 1;
     }
 
     // Return our reflow status
-    StyleClear breakType = aReflowInput.mStyleDisplay->mBreakType;
+    StyleClear breakType = aReflowInput.mStyleDisplay->PhysicalBreakType(wm);
     if (StyleClear::None == breakType) {
       breakType = StyleClear::Line;
     }
 
     aStatus.SetInlineLineBreakAfter(breakType);
     ll->SetLineEndsInBR(true);
   }
 
--- a/layout/generic/BlockReflowInput.cpp
+++ b/layout/generic/BlockReflowInput.cpp
@@ -737,17 +737,17 @@ BlockReflowInput::FlowAndPlaceFloat(nsIF
   // ``above'' another float that preceded it in the flow.
   mBCoord = std::max(FloatManager()->GetLowestFloatTop(), mBCoord);
 
   // See if the float should clear any preceding floats...
   // XXX We need to mark this float somehow so that it gets reflowed
   // when floats are inserted before it.
   if (StyleClear::None != floatDisplay->mBreakType) {
     // XXXldb Does this handle vertical margins correctly?
-    mBCoord = ClearFloats(mBCoord, floatDisplay->mBreakType);
+    mBCoord = ClearFloats(mBCoord, floatDisplay->PhysicalBreakType(wm));
   }
   // Get the band of available space with respect to margin box.
   nsFlowAreaRect floatAvailableSpace =
     GetFloatAvailableSpaceForPlacingFloat(mBCoord);
   LogicalRect adjustedAvailableSpace =
     mBlock->AdjustFloatAvailableSpace(*this, floatAvailableSpace.mRect, aFloat);
 
   NS_ASSERTION(aFloat->GetParent() == mBlock,
@@ -780,17 +780,17 @@ BlockReflowInput::FlowAndPlaceFloat(nsIF
                  "letter frames and orthogonal floats with auto block-size "
                  "shouldn't break, and if they do now, then they're breaking "
                  "at the wrong point");
   }
 
   // Find a place to place the float. The CSS2 spec doesn't want
   // floats overlapping each other or sticking out of the containing
   // block if possible (CSS2 spec section 9.5.1, see the rule list).
-  StyleFloat floatStyle = floatDisplay->mFloat;
+  StyleFloat floatStyle = floatDisplay->PhysicalFloats(wm);
   MOZ_ASSERT(StyleFloat::Left == floatStyle || StyleFloat::Right == floatStyle,
              "Invalid float type!");
 
   // Can the float fit here?
   bool keepFloatOnSameLine = false;
 
   // Are we required to place at least part of the float because we're
   // at the top of the page (to avoid an infinite loop of pushing and
@@ -1040,17 +1040,18 @@ BlockReflowInput::FlowAndPlaceFloat(nsIF
 void
 BlockReflowInput::PushFloatPastBreak(nsIFrame *aFloat)
 {
   // This ensures that we:
   //  * don't try to place later but smaller floats (which CSS says
   //    must have their tops below the top of this float)
   //  * don't waste much time trying to reflow this float again until
   //    after the break
-  StyleFloat floatStyle = aFloat->StyleDisplay()->mFloat;
+  StyleFloat floatStyle =
+    aFloat->StyleDisplay()->PhysicalFloats(mReflowInput.GetWritingMode());
   if (floatStyle == StyleFloat::Left) {
     FloatManager()->SetPushedLeftFloatPastBreak();
   } else {
     MOZ_ASSERT(floatStyle == StyleFloat::Right, "Unexpected float value!");
     FloatManager()->SetPushedRightFloatPastBreak();
   }
 
   // Put the float on the pushed floats list, even though it
--- a/layout/generic/WritingModes.h
+++ b/layout/generic/WritingModes.h
@@ -2182,16 +2182,42 @@ nsStylePosition::MinBSizeDependsOnContai
 }
 inline bool
 nsStylePosition::MaxBSizeDependsOnContainer(mozilla::WritingMode aWM) const
 {
   return aWM.IsVertical() ? MaxWidthDependsOnContainer()
                           : MaxHeightDependsOnContainer();
 }
 
+inline mozilla::StyleFloat
+nsStyleDisplay::PhysicalFloats(mozilla::WritingMode aWM) const
+{
+  using StyleFloat = mozilla::StyleFloat;
+  if (mFloat == StyleFloat::InlineStart) {
+    return aWM.IsBidiLTR() ? StyleFloat::Left : StyleFloat::Right;
+  }
+  if (mFloat == StyleFloat::InlineEnd) {
+    return aWM.IsBidiLTR() ? StyleFloat::Right : StyleFloat::Left;
+  }
+  return mFloat;
+}
+
+inline mozilla::StyleClear
+nsStyleDisplay::PhysicalBreakType(mozilla::WritingMode aWM) const
+{
+  using StyleClear = mozilla::StyleClear;
+  if (mBreakType == StyleClear::InlineStart) {
+    return aWM.IsBidiLTR() ? StyleClear::Left : StyleClear::Right;
+  }
+  if (mBreakType == StyleClear::InlineEnd) {
+    return aWM.IsBidiLTR() ? StyleClear::Right : StyleClear::Left;
+  }
+  return mBreakType;
+}
+
 inline bool
 nsStyleMargin::HasBlockAxisAuto(mozilla::WritingMode aWM) const
 {
   return mMargin.HasBlockAxisAuto(aWM);
 }
 inline bool
 nsStyleMargin::HasInlineAxisAuto(mozilla::WritingMode aWM) const
 {
--- a/layout/generic/nsBlockFrame.cpp
+++ b/layout/generic/nsBlockFrame.cpp
@@ -837,17 +837,18 @@ nsBlockFrame::GetPrefISize(gfxContext *a
       }
       AutoNoisyIndenter lineindent(gNoisyIntrinsic);
 #endif
       if (line->IsBlock()) {
         StyleClear breakType;
         if (!data.mLineIsEmpty || BlockCanIntersectFloats(line->mFirstChild)) {
           breakType = StyleClear::Both;
         } else {
-          breakType = line->mFirstChild->StyleDisplay()->mBreakType;
+          breakType = line->mFirstChild->
+            StyleDisplay()->PhysicalBreakType(data.mLineContainerWM);
         }
         data.ForceBreak(breakType);
         data.mCurrentLine = nsLayoutUtils::IntrinsicForContainer(aRenderingContext,
                         line->mFirstChild, nsLayoutUtils::PREF_ISIZE);
         data.ForceBreak();
       } else {
         if (!curFrame->GetPrevContinuation() &&
             line == curFrame->LinesBegin()) {
@@ -3220,17 +3221,18 @@ nsBlockFrame::ReflowBlockFrame(BlockRefl
   if (!frame) {
     NS_ASSERTION(false, "program error - unexpected empty line");
     return;
   }
 
   // Prepare the block reflow engine
   nsBlockReflowContext brc(aState.mPresContext, aState.mReflowInput);
 
-  StyleClear breakType = frame->StyleDisplay()->mBreakType;
+  StyleClear breakType = frame->StyleDisplay()->
+    PhysicalBreakType(aState.mReflowInput.GetWritingMode());
   if (StyleClear::None != aState.mFloatBreakType) {
     breakType = nsLayoutUtils::CombineBreakType(breakType,
                                                 aState.mFloatBreakType);
     aState.mFloatBreakType = StyleClear::None;
   }
 
   // Clear past floats before the block if the clear style is not none
   aLine->SetBreakTypeBefore(breakType);
@@ -4339,17 +4341,18 @@ nsBlockFrame::SplitFloat(BlockReflowInpu
   } else {
     nextInFlow = aState.mPresContext->PresShell()->FrameConstructor()->
       CreateContinuingFrame(aState.mPresContext, aFloat, this);
   }
   if (aFloatStatus.IsOverflowIncomplete()) {
     nextInFlow->AddStateBits(NS_FRAME_IS_OVERFLOW_CONTAINER);
   }
 
-  StyleFloat floatStyle = aFloat->StyleDisplay()->mFloat;
+  StyleFloat floatStyle =
+    aFloat->StyleDisplay()->PhysicalFloats(aState.mReflowInput.GetWritingMode());
   if (floatStyle == StyleFloat::Left) {
     aState.FloatManager()->SetSplitLeftFloatAcrossBreak();
   } else {
     MOZ_ASSERT(floatStyle == StyleFloat::Right, "Unexpected float side!");
     aState.FloatManager()->SetSplitRightFloatAcrossBreak();
   }
 
   aState.AppendPushedFloatChain(nextInFlow);
--- a/layout/generic/nsFloatManager.cpp
+++ b/layout/generic/nsFloatManager.cpp
@@ -201,17 +201,17 @@ nsFloatManager::GetFlowArea(WritingMode 
     // WidthWithinHeight call is at least as narrow on both sides as a
     // BandFromPoint call beginning at its blockStart.
     else if (blockStart < floatBEnd &&
              (floatBStart < blockEnd ||
               (floatBStart == blockEnd && blockStart == blockEnd))) {
       // This float is in our band.
 
       // Shrink our band's width if needed.
-      StyleFloat floatStyle = fi.mFrame->StyleDisplay()->mFloat;
+      StyleFloat floatStyle = fi.mFrame->StyleDisplay()->PhysicalFloats(aWM);
 
       // When aBandInfoType is BandFromPoint, we're only intended to
       // consider a point along the y axis rather than a band.
       const nscoord bandBlockEnd =
         aBandInfoType == BandInfoType::BandFromPoint ? blockStart : blockEnd;
       if (floatStyle == StyleFloat::Left) {
         // A left float
         nscoord lineRightEdge =
@@ -278,17 +278,17 @@ nsFloatManager::AddFloat(nsIFrame* aFloa
   if (HasAnyFloats()) {
     FloatInfo &tail = mFloats[mFloats.Length() - 1];
     info.mLeftBEnd = tail.mLeftBEnd;
     info.mRightBEnd = tail.mRightBEnd;
   } else {
     info.mLeftBEnd = nscoord_MIN;
     info.mRightBEnd = nscoord_MIN;
   }
-  StyleFloat floatStyle = aFloatFrame->StyleDisplay()->mFloat;
+  StyleFloat floatStyle = aFloatFrame->StyleDisplay()->PhysicalFloats(aWM);
   MOZ_ASSERT(floatStyle == StyleFloat::Left || floatStyle == StyleFloat::Right,
              "Unexpected float style!");
   nscoord& sideBEnd =
     floatStyle == StyleFloat::Left ? info.mLeftBEnd : info.mRightBEnd;
   nscoord thisBEnd = info.BEnd();
   if (thisBEnd > sideBEnd)
     sideBEnd = thisBEnd;
 
@@ -311,17 +311,17 @@ nsFloatManager::CalculateRegionFor(Writi
   region.Inflate(aWM, aMargin);
 
   // Don't store rectangles with negative margin-box width or height in
   // the float manager; it can't deal with them.
   if (region.ISize(aWM) < 0) {
     // Preserve the right margin-edge for left floats and the left
     // margin-edge for right floats
     const nsStyleDisplay* display = aFloat->StyleDisplay();
-    StyleFloat floatStyle = display->mFloat;
+    StyleFloat floatStyle = display->PhysicalFloats(aWM);
     if ((StyleFloat::Left == floatStyle) == aWM.IsBidiLTR()) {
       region.IStart(aWM) = region.IEnd(aWM);
     }
     region.ISize(aWM) = 0;
   }
   if (region.BSize(aWM) < 0) {
     region.BSize(aWM) = 0;
   }
--- a/layout/generic/nsFrame.cpp
+++ b/layout/generic/nsFrame.cpp
@@ -5352,38 +5352,39 @@ nsIFrame::InlinePrefISizeData::ForceBrea
   if (mFloats.Length() != 0 && aBreakType != StyleClear::None) {
             // preferred widths accumulated for floats that have already
             // been cleared past
     nscoord floats_done = 0,
             // preferred widths accumulated for floats that have not yet
             // been cleared past
             floats_cur_left = 0,
             floats_cur_right = 0;
+    const WritingMode wm = mLineContainerWM;
 
     for (uint32_t i = 0, i_end = mFloats.Length(); i != i_end; ++i) {
       const FloatInfo& floatInfo = mFloats[i];
       const nsStyleDisplay* floatDisp = floatInfo.Frame()->StyleDisplay();
-      StyleClear breakType = floatDisp->mBreakType;
+      StyleClear breakType = floatDisp->PhysicalBreakType(wm);
       if (breakType == StyleClear::Left ||
           breakType == StyleClear::Right ||
           breakType == StyleClear::Both) {
         nscoord floats_cur = NSCoordSaturatingAdd(floats_cur_left,
                                                   floats_cur_right);
         if (floats_cur > floats_done) {
           floats_done = floats_cur;
         }
         if (breakType != StyleClear::Right) {
           floats_cur_left = 0;
         }
         if (breakType != StyleClear::Left) {
           floats_cur_right = 0;
         }
       }
 
-      StyleFloat floatStyle = floatDisp->mFloat;
+      StyleFloat floatStyle = floatDisp->PhysicalFloats(wm);
       nscoord& floats_cur =
         floatStyle == StyleFloat::Left ? floats_cur_left : floats_cur_right;
       nscoord floatWidth = floatInfo.Width();
       // Negative-width floats don't change the available space so they
       // shouldn't change our intrinsic line width either.
       floats_cur =
         NSCoordSaturatingAdd(floats_cur, std::max(0, floatWidth));
     }
@@ -5408,27 +5409,27 @@ nsIFrame::InlinePrefISizeData::ForceBrea
                  aBreakType == StyleClear::Right,
                  "Other values should have been handled in other branches");
       StyleFloat clearFloatType =
         aBreakType == StyleClear::Left ? StyleFloat::Left : StyleFloat::Right;
       // Iterate the array in reverse so that we can stop when there are
       // no longer any floats we need to keep. See below.
       for (FloatInfo& floatInfo : Reversed(mFloats)) {
         const nsStyleDisplay* floatDisp = floatInfo.Frame()->StyleDisplay();
-        if (floatDisp->mFloat != clearFloatType) {
+        if (floatDisp->PhysicalFloats(wm) != clearFloatType) {
           newFloats.AppendElement(floatInfo);
         } else {
           // This is a float on the side that this break directly clears
           // which means we're not keeping it in mFloats. However, if
           // this float clears floats on the opposite side (via a value
           // of either 'both' or one of 'left'/'right'), any remaining
           // (earlier) floats on that side would be indirectly cleared
           // as well. Thus, we should break out of this loop and stop
           // considering earlier floats to be kept in mFloats.
-          StyleClear floatBreakType = floatDisp->mBreakType;
+          StyleClear floatBreakType = floatDisp->PhysicalBreakType(wm);
           if (floatBreakType != aBreakType &&
               floatBreakType != StyleClear::None) {
             break;
           }
         }
       }
       newFloats.Reverse();
       mFloats = std::move(newFloats);
--- a/layout/generic/nsIFrame.h
+++ b/layout/generic/nsIFrame.h
@@ -2185,29 +2185,32 @@ public:
       , mSkipWhitespace(true)
     {}
 
     // The line. This may be null if the inlines are not associated with
     // a block or if we just don't know the line.
     const nsLineList_iterator* mLine;
 
     // The line container. Private, to ensure we always use SetLineContainer
-    // to update it.
+    // to update it (so that we have a chance to store the mLineContainerWM).
     //
     // Note that nsContainerFrame::DoInlineIntrinsicISize will clear the
     // |mLine| and |mLineContainer| fields when following a next-in-flow link,
     // so we must not assume these can always be dereferenced.
   private:
     nsIFrame* mLineContainer;
 
     // Setter and getter for the lineContainer field:
   public:
     void SetLineContainer(nsIFrame* aLineContainer)
     {
       mLineContainer = aLineContainer;
+      if (mLineContainer) {
+        mLineContainerWM = mLineContainer->GetWritingMode();
+      }
     }
     nsIFrame* LineContainer() const { return mLineContainer; }
 
     // The maximum intrinsic width for all previous lines.
     nscoord mPrevLines;
 
     // The maximum intrinsic width for the current line.  At a line
     // break (mandatory for preferred width; allowed for minimum width),
@@ -2218,16 +2221,20 @@ public:
     // |mCurrentLine|; it is zero if there is no such whitespace.
     nscoord mTrailingWhitespace;
 
     // True if initial collapsable whitespace should be skipped.  This
     // should be true at the beginning of a block, after hard breaks
     // and when the last text ended with whitespace.
     bool mSkipWhitespace;
 
+    // Writing mode of the line container (stored here so that we don't
+    // lose track of it if the mLineContainer field is reset).
+    mozilla::WritingMode mLineContainerWM;
+
     // Floats encountered in the lines.
     class FloatInfo {
     public:
       FloatInfo(const nsIFrame* aFrame, nscoord aWidth)
         : mFrame(aFrame), mWidth(aWidth)
       { }
       const nsIFrame* Frame() const { return mFrame; }
       nscoord         Width() const { return mWidth; }
--- a/layout/generic/nsLineBox.cpp
+++ b/layout/generic/nsLineBox.cpp
@@ -205,16 +205,18 @@ ListFloats(FILE* out, const char* aPrefi
 
 /* static */ const char*
 nsLineBox::BreakTypeToString(StyleClear aBreakType)
 {
   switch (aBreakType) {
     case StyleClear::None: return "nobr";
     case StyleClear::Left: return "leftbr";
     case StyleClear::Right: return "rightbr";
+    case StyleClear::InlineStart: return "inlinestartbr";
+    case StyleClear::InlineEnd: return "inlineendbr";
     case StyleClear::Both: return "leftbr+rightbr";
     case StyleClear::Line: return "linebr";
     case StyleClear::Max: return "leftbr+rightbr+linebr";
   }
   return "unknown";
 }
 
 char*
--- a/layout/style/ServoCSSPropList.mako.py
+++ b/layout/style/ServoCSSPropList.mako.py
@@ -69,22 +69,20 @@ def method(prop):
     return prop.camel_case
 
 # Colors, integers and lengths are easy as well.
 #
 # TODO(emilio): This will go away once the rest of the longhands have been
 # moved or perhaps using a blacklist for the ones with non-layout-dependence
 # but other non-trivial dependence like scrollbar colors.
 SERIALIZED_PREDEFINED_TYPES = [
-    "Clear",
     "Color",
     "Content",
     "CounterIncrement",
     "CounterReset",
-    "Float",
     "FontFamily",
     "FontFeatureSettings",
     "FontLanguageOverride",
     "FontSize",
     "FontSizeAdjust",
     "FontStretch",
     "FontStyle",
     "FontSynthesis",
--- a/layout/style/nsStyleConsts.h
+++ b/layout/style/nsStyleConsts.h
@@ -72,22 +72,21 @@ enum class StyleBoxShadowType : uint8_t 
   Inset,
 };
 
 // clear
 enum class StyleClear : uint8_t {
   None = 0,
   Left,
   Right,
+  InlineStart,
+  InlineEnd,
   Both,
   // StyleClear::Line can be added to one of the other values in layout
   // so it needs to use a bit value that none of the other values can have.
-  //
-  // FIXME(emilio): Doesn't look like we do that anymore, so probably can be
-  // made a single value instead, and Max removed.
   Line = 8,
   Max = 13  // Max = (Both | Line)
 };
 
 // Counters and generated content.
 enum class StyleContentType : uint8_t {
   String = 1,
   Image = 10,
@@ -140,16 +139,18 @@ enum class StyleFillRule : uint8_t {
 };
 
 // float
 // https://developer.mozilla.org/en-US/docs/Web/CSS/float
 enum class StyleFloat : uint8_t {
   None,
   Left,
   Right,
+  InlineStart,
+  InlineEnd
 };
 
 // float-edge
 enum class StyleFloatEdge : uint8_t {
   ContentBox,
   MarginBox,
 };
 
--- a/layout/style/nsStyleStruct.h
+++ b/layout/style/nsStyleStruct.h
@@ -2450,16 +2450,22 @@ struct MOZ_NEEDS_MEMMOVABLE_MEMBERS nsSt
 
 private:
   // Helpers for above functions, which do some but not all of the tests
   // for them (since transform must be tested separately for each).
   inline bool HasAbsPosContainingBlockStyleInternal() const;
   inline bool HasFixedPosContainingBlockStyleInternal(
     mozilla::ComputedStyle&) const;
   void GenerateCombinedTransform();
+public:
+  // Return the 'float' and 'clear' properties, with inline-{start,end} values
+  // resolved to {left,right} according to the given writing mode. These are
+  // defined in WritingModes.h.
+  inline mozilla::StyleFloat PhysicalFloats(mozilla::WritingMode aWM) const;
+  inline mozilla::StyleClear PhysicalBreakType(mozilla::WritingMode aWM) const;
 };
 
 struct MOZ_NEEDS_MEMMOVABLE_MEMBERS nsStyleTable
 {
   explicit nsStyleTable(const nsPresContext* aContext);
   nsStyleTable(const nsStyleTable& aOther);
   ~nsStyleTable();
   void FinishStyle(nsPresContext*, const nsStyleTable*) {}
--- a/servo/components/style/properties/gecko.mako.rs
+++ b/servo/components/style/properties/gecko.mako.rs
@@ -3078,27 +3078,16 @@ fn static_assert() {
         v: longhands::display::computed_value::T,
         _is_item_or_root: bool
     ) {
         self.gecko.mDisplay = Self::match_display_keyword(v);
     }
 
     <%call expr="impl_keyword_clone('display', 'mDisplay', display_keyword)"></%call>
 
-    <% float_keyword = Keyword("float", "Left Right None", gecko_enum_prefix="StyleFloat") %>
-    ${impl_keyword('float', 'mFloat', float_keyword)}
-
-    <% clear_keyword = Keyword(
-        "clear",
-        "Left Right None Both",
-        gecko_enum_prefix="StyleClear",
-        gecko_inexhaustive=True,
-    ) %>
-    ${impl_keyword('clear', 'mBreakType', clear_keyword)}
-
     <% overflow_x = data.longhands_by_name["overflow-x"] %>
     pub fn set_overflow_y(&mut self, v: longhands::overflow_y::computed_value::T) {
         use properties::longhands::overflow_x::computed_value::T as BaseType;
         // FIXME(bholley): Align binary representations and ditch |match| for cast + static_asserts
         self.gecko.mOverflowY = match v {
             % for value in overflow_x.keyword.values_for('gecko'):
                 BaseType::${to_camel_case(value)} => structs::${overflow_x.keyword.gecko_constant(value)} as u8,
             % endfor
--- a/servo/components/style/properties/longhands/box.mako.rs
+++ b/servo/components/style/properties/longhands/box.mako.rs
@@ -49,39 +49,126 @@
                          spec="Internal (not web-exposed)")}
 
 ${helpers.single_keyword("position", "static absolute relative fixed sticky",
                          animation_value_type="discrete",
                          flags="CREATES_STACKING_CONTEXT ABSPOS_CB",
                          spec="https://drafts.csswg.org/css-position/#position-property",
                          servo_restyle_damage="rebuild_and_reflow")}
 
-${helpers.predefined_type(
-    "float",
-    "Float",
-    "computed::Float::None",
-    initial_specified_value="specified::Float::None",
-    spec="https://drafts.csswg.org/css-box/#propdef-float",
-    animation_value_type="discrete",
-    needs_context=False,
-    flags="APPLIES_TO_FIRST_LETTER",
-    servo_restyle_damage="rebuild_and_reflow",
+<%helpers:single_keyword
+    name="float"
+    values="none left right"
+    // https://drafts.csswg.org/css-logical-props/#float-clear
+    extra_specified="inline-start inline-end"
+    needs_conversion="True"
+    animation_value_type="discrete"
+    gecko_enum_prefix="StyleFloat"
+    gecko_inexhaustive="True"
     gecko_ffi_name="mFloat"
-)}
+    flags="APPLIES_TO_FIRST_LETTER"
+    spec="https://drafts.csswg.org/css-box/#propdef-float"
+    servo_restyle_damage="rebuild_and_reflow"
+>
+    impl ToComputedValue for SpecifiedValue {
+        type ComputedValue = computed_value::T;
+
+        #[inline]
+        fn to_computed_value(&self, context: &Context) -> computed_value::T {
+            let ltr = context.style().writing_mode.is_bidi_ltr();
+            // https://drafts.csswg.org/css-logical-props/#float-clear
+            match *self {
+                SpecifiedValue::InlineStart => {
+                    context.rule_cache_conditions.borrow_mut()
+                        .set_writing_mode_dependency(context.builder.writing_mode);
+                    if ltr {
+                        computed_value::T::Left
+                    } else {
+                        computed_value::T::Right
+                    }
+                }
+                SpecifiedValue::InlineEnd => {
+                    context.rule_cache_conditions.borrow_mut()
+                        .set_writing_mode_dependency(context.builder.writing_mode);
+                    if ltr {
+                        computed_value::T::Right
+                    } else {
+                        computed_value::T::Left
+                    }
+                }
+                % for value in "None Left Right".split():
+                    SpecifiedValue::${value} => computed_value::T::${value},
+                % endfor
+            }
+        }
+        #[inline]
+        fn from_computed_value(computed: &computed_value::T) -> SpecifiedValue {
+            match *computed {
+                % for value in "None Left Right".split():
+                    computed_value::T::${value} => SpecifiedValue::${value},
+                % endfor
+            }
+        }
+    }
+</%helpers:single_keyword>
 
-${helpers.predefined_type(
-    "clear",
-    "Clear",
-    "computed::Clear::None",
-    animation_value_type="discrete",
-    needs_context=False,
-    gecko_ffi_name="mBreakType",
-    spec="https://drafts.csswg.org/css-box/#propdef-clear",
+<%helpers:single_keyword
+    name="clear"
+    values="none left right both"
+    // https://drafts.csswg.org/css-logical-props/#float-clear
+    extra_specified="inline-start inline-end"
+    needs_conversion="True"
+    gecko_inexhaustive="True"
+    animation_value_type="discrete"
+    gecko_enum_prefix="StyleClear"
+    gecko_ffi_name="mBreakType"
+    spec="https://drafts.csswg.org/css-box/#propdef-clear"
     servo_restyle_damage="rebuild_and_reflow"
-)}
+>
+    impl ToComputedValue for SpecifiedValue {
+        type ComputedValue = computed_value::T;
+
+        #[inline]
+        fn to_computed_value(&self, context: &Context) -> computed_value::T {
+            let ltr = context.style().writing_mode.is_bidi_ltr();
+            // https://drafts.csswg.org/css-logical-props/#float-clear
+            match *self {
+                SpecifiedValue::InlineStart => {
+                    context.rule_cache_conditions.borrow_mut()
+                        .set_writing_mode_dependency(context.builder.writing_mode);
+                    if ltr {
+                        computed_value::T::Left
+                    } else {
+                        computed_value::T::Right
+                    }
+                }
+                SpecifiedValue::InlineEnd => {
+                    context.rule_cache_conditions.borrow_mut()
+                        .set_writing_mode_dependency(context.builder.writing_mode);
+                    if ltr {
+                        computed_value::T::Right
+                    } else {
+                        computed_value::T::Left
+                    }
+                }
+                % for value in "None Left Right Both".split():
+                    SpecifiedValue::${value} => computed_value::T::${value},
+                % endfor
+            }
+        }
+        #[inline]
+        fn from_computed_value(computed: &computed_value::T) -> SpecifiedValue {
+            match *computed {
+                % for value in "None Left Right Both".split():
+                    computed_value::T::${value} => SpecifiedValue::${value},
+                % endfor
+            }
+        }
+    }
+</%helpers:single_keyword>
 
 ${helpers.predefined_type(
     "vertical-align",
     "VerticalAlign",
     "computed::VerticalAlign::baseline()",
     animation_value_type="ComputedValue",
     flags="APPLIES_TO_FIRST_LETTER APPLIES_TO_FIRST_LINE APPLIES_TO_PLACEHOLDER",
     spec="https://www.w3.org/TR/CSS2/visudet.html#propdef-vertical-align",
--- a/servo/components/style/properties/properties.mako.rs
+++ b/servo/components/style/properties/properties.mako.rs
@@ -30,17 +30,17 @@ use font_metrics::FontMetricsProvider;
 #[cfg(feature = "gecko")] use gecko_bindings::bindings;
 #[cfg(feature = "gecko")] use gecko_bindings::structs::{self, nsCSSPropertyID};
 #[cfg(feature = "servo")] use logical_geometry::LogicalMargin;
 #[cfg(feature = "servo")] use computed_values;
 use logical_geometry::WritingMode;
 #[cfg(feature = "gecko")] use malloc_size_of::{MallocSizeOf, MallocSizeOfOps};
 use media_queries::Device;
 use parser::ParserContext;
-use properties::longhands::system_font::SystemFont;
+#[cfg(feature = "gecko")] use properties::longhands::system_font::SystemFont;
 use rule_cache::{RuleCache, RuleCacheConditions};
 use selector_parser::PseudoElement;
 use selectors::parser::SelectorParseErrorKind;
 #[cfg(feature = "servo")] use servo_config::prefs::PREFS;
 use shared_lock::StylesheetGuards;
 use style_traits::{CssWriter, KeywordsCollectFn, ParseError, ParsingMode};
 use style_traits::{SpecifiedValueInfo, StyleParseErrorKind, ToCss};
 use stylesheets::{CssRuleType, Origin, UrlExtraData};
--- a/servo/components/style/values/computed/box.rs
+++ b/servo/components/style/values/computed/box.rs
@@ -1,22 +1,21 @@
 /* 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/. */
 
 //! Computed types for box properties.
 
-use values::computed::{Context, Number, ToComputedValue};
+use values::computed::Number;
 use values::computed::length::{LengthOrPercentage, NonNegativeLength};
 use values::generics::box_::AnimationIterationCount as GenericAnimationIterationCount;
 use values::generics::box_::Perspective as GenericPerspective;
 use values::generics::box_::VerticalAlign as GenericVerticalAlign;
 
 pub use values::specified::box_::{AnimationName, Contain, Display, OverflowClipBox};
-pub use values::specified::box_::{Clear as SpecifiedClear, Float as SpecifiedFloat};
 pub use values::specified::box_::{OverscrollBehavior, ScrollSnapType, TouchAction, TransitionProperty, WillChange};
 
 /// A computed value for the `vertical-align` property.
 pub type VerticalAlign = GenericVerticalAlign<LengthOrPercentage>;
 
 /// A computed value for the `animation-iteration-count` property.
 pub type AnimationIterationCount = GenericAnimationIterationCount<Number>;
 
@@ -25,117 +24,8 @@ impl AnimationIterationCount {
     #[inline]
     pub fn one() -> Self {
         GenericAnimationIterationCount::Number(1.0)
     }
 }
 
 /// A computed value for the `perspective` property.
 pub type Perspective = GenericPerspective<NonNegativeLength>;
-
-#[allow(missing_docs)]
-#[cfg_attr(feature = "servo", derive(Deserialize, Serialize))]
-#[derive(Clone, Copy, Debug, Eq, Hash, MallocSizeOf, Parse, PartialEq,
-         SpecifiedValueInfo, ToCss)]
-/// A computed value for the `float` property.
-pub enum Float {
-    Left,
-    Right,
-    None
-}
-
-impl ToComputedValue for SpecifiedFloat {
-    type ComputedValue = Float;
-
-    #[inline]
-    fn to_computed_value(&self, context: &Context) -> Self::ComputedValue {
-        let ltr = context.style().writing_mode.is_bidi_ltr();
-        // https://drafts.csswg.org/css-logical-props/#float-clear
-        match *self {
-            SpecifiedFloat::InlineStart => {
-                context.rule_cache_conditions.borrow_mut()
-                    .set_writing_mode_dependency(context.builder.writing_mode);
-                if ltr {
-                    Float::Left
-                } else {
-                    Float::Right
-                }
-            },
-            SpecifiedFloat::InlineEnd => {
-                context.rule_cache_conditions.borrow_mut()
-                    .set_writing_mode_dependency(context.builder.writing_mode);
-                if ltr {
-                    Float::Right
-                } else {
-                    Float::Left
-                }
-            },
-            SpecifiedFloat::Left => Float::Left,
-            SpecifiedFloat::Right => Float::Right,
-            SpecifiedFloat::None => Float::None
-        }
-    }
-
-    #[inline]
-    fn from_computed_value(computed: &Self::ComputedValue) -> SpecifiedFloat {
-        match *computed {
-            Float::Left => SpecifiedFloat::Left,
-            Float::Right => SpecifiedFloat::Right,
-            Float::None => SpecifiedFloat::None
-        }
-    }
-}
-
-#[allow(missing_docs)]
-#[cfg_attr(feature = "servo", derive(Deserialize, Serialize))]
-#[derive(Clone, Copy, Debug, Eq, Hash, MallocSizeOf, Parse, PartialEq,
-SpecifiedValueInfo, ToCss)]
-/// A computed value for the `clear` property.
-pub enum Clear {
-    None,
-    Left,
-    Right,
-    Both
-}
-
-impl ToComputedValue for SpecifiedClear {
-    type ComputedValue = Clear;
-
-    #[inline]
-    fn to_computed_value(&self, context: &Context) -> Self::ComputedValue {
-        let ltr = context.style().writing_mode.is_bidi_ltr();
-        // https://drafts.csswg.org/css-logical-props/#float-clear
-        match *self {
-            SpecifiedClear::InlineStart => {
-                context.rule_cache_conditions.borrow_mut()
-                    .set_writing_mode_dependency(context.builder.writing_mode);
-                if ltr {
-                    Clear::Left
-                } else {
-                    Clear::Right
-                }
-            },
-            SpecifiedClear::InlineEnd => {
-                context.rule_cache_conditions.borrow_mut()
-                    .set_writing_mode_dependency(context.builder.writing_mode);
-                if ltr {
-                    Clear::Right
-                } else {
-                    Clear::Left
-                }
-            },
-            SpecifiedClear::None => Clear::None,
-            SpecifiedClear::Left => Clear::Left,
-            SpecifiedClear::Right => Clear::Right,
-            SpecifiedClear::Both => Clear::Both
-        }
-    }
-
-    #[inline]
-    fn from_computed_value(computed: &Self::ComputedValue) -> SpecifiedClear {
-        match *computed {
-            Clear::None => SpecifiedClear::None,
-            Clear::Left => SpecifiedClear::Left,
-            Clear::Right => SpecifiedClear::Right,
-            Clear::Both => SpecifiedClear::Both,
-        }
-    }
-}
--- a/servo/components/style/values/computed/mod.rs
+++ b/servo/components/style/values/computed/mod.rs
@@ -38,17 +38,16 @@ pub use self::angle::Angle;
 pub use self::background::{BackgroundRepeat, BackgroundSize};
 pub use self::border::{BorderImageRepeat, BorderImageSideWidth, BorderImageSlice, BorderImageWidth};
 pub use self::border::{BorderCornerRadius, BorderRadius, BorderSpacing};
 pub use self::font::{FontSize, FontSizeAdjust, FontStretch, FontSynthesis, FontVariantAlternates, FontWeight};
 pub use self::font::{FontFamily, FontLanguageOverride, FontStyle, FontVariantEastAsian, FontVariationSettings};
 pub use self::font::{FontFeatureSettings, FontVariantLigatures, FontVariantNumeric};
 pub use self::font::{MozScriptLevel, MozScriptMinSize, MozScriptSizeMultiplier, XLang, XTextZoom};
 pub use self::box_::{AnimationIterationCount, AnimationName, Contain, Display, TransitionProperty};
-pub use self::box_::{Clear, Float};
 pub use self::box_::{OverflowClipBox, OverscrollBehavior, Perspective};
 pub use self::box_::{ScrollSnapType, TouchAction, VerticalAlign, WillChange};
 pub use self::color::{Color, ColorPropertyValue, RGBAColor};
 pub use self::column::ColumnCount;
 pub use self::counters::{Content, ContentItem, CounterIncrement, CounterReset};
 pub use self::effects::{BoxShadow, Filter, SimpleShadow};
 pub use self::flex::FlexBasis;
 pub use self::image::{Gradient, GradientItem, Image, ImageLayer, LineDirection, MozImageRect};
--- a/servo/components/style/values/specified/box.rs
+++ b/servo/components/style/values/specified/box.rs
@@ -823,35 +823,8 @@ impl TransitionProperty {
             }
             TransitionProperty::Shorthand(ref id) => id.to_nscsspropertyid(),
             TransitionProperty::Longhand(ref id) => id.to_nscsspropertyid(),
             TransitionProperty::Custom(..) |
             TransitionProperty::Unsupported(..) => return Err(()),
         })
     }
 }
-
-#[allow(missing_docs)]
-#[cfg_attr(feature = "servo", derive(Deserialize, Serialize))]
-#[derive(Clone, Copy, Debug, Eq, Hash, MallocSizeOf, Parse, PartialEq,
-         SpecifiedValueInfo, ToCss)]
-pub enum Float {
-    Left,
-    Right,
-    None,
-    // https://drafts.csswg.org/css-logical-props/#float-clear
-    InlineStart,
-    InlineEnd
-}
-
-#[allow(missing_docs)]
-#[cfg_attr(feature = "servo", derive(Deserialize, Serialize))]
-#[derive(Clone, Copy, Debug, Eq, Hash, MallocSizeOf, Parse, PartialEq,
-         SpecifiedValueInfo, ToCss)]
-pub enum Clear {
-    None,
-    Left,
-    Right,
-    Both,
-    // https://drafts.csswg.org/css-logical-props/#float-clear
-    InlineStart,
-    InlineEnd
-}
--- a/servo/components/style/values/specified/mod.rs
+++ b/servo/components/style/values/specified/mod.rs
@@ -33,17 +33,16 @@ pub use self::border::{BorderCornerRadiu
 pub use self::border::{BorderImageRepeat, BorderImageSideWidth};
 pub use self::border::{BorderRadius, BorderSideWidth, BorderSpacing};
 pub use self::column::ColumnCount;
 pub use self::font::{FontSize, FontSizeAdjust, FontStretch, FontSynthesis, FontVariantAlternates, FontWeight};
 pub use self::font::{FontFamily, FontLanguageOverride, FontStyle, FontVariantEastAsian, FontVariationSettings};
 pub use self::font::{FontFeatureSettings, FontVariantLigatures, FontVariantNumeric};
 pub use self::font::{MozScriptLevel, MozScriptMinSize, MozScriptSizeMultiplier, XLang, XTextZoom};
 pub use self::box_::{AnimationIterationCount, AnimationName, Contain, Display};
-pub use self::box_::{Clear, Float};
 pub use self::box_::{OverflowClipBox, OverscrollBehavior, Perspective};
 pub use self::box_::{ScrollSnapType, TouchAction, TransitionProperty, VerticalAlign, WillChange};
 pub use self::color::{Color, ColorPropertyValue, RGBAColor};
 pub use self::counters::{Content, ContentItem, CounterIncrement, CounterReset};
 pub use self::effects::{BoxShadow, Filter, SimpleShadow};
 pub use self::flex::FlexBasis;
 #[cfg(feature = "gecko")]
 pub use self::gecko::ScrollSnapPoint;
--- a/servo/ports/geckolib/glue.rs
+++ b/servo/ports/geckolib/glue.rs
@@ -3946,40 +3946,31 @@ pub unsafe extern "C" fn Servo_Declarati
 pub extern "C" fn Servo_DeclarationBlock_SetKeywordValue(
     declarations: RawServoDeclarationBlockBorrowed,
     property: nsCSSPropertyID,
     value: i32
 ) {
     use style::properties::{PropertyDeclaration, LonghandId};
     use style::properties::longhands;
     use style::values::specified::BorderStyle;
-    use style::values::specified::Float;
     use style::values::generics::font::FontStyle;
 
     let long = get_longhand_from_id!(property);
     let value = value as u32;
 
     let prop = match_wrap_declared! { long,
         MozUserModify => longhands::_moz_user_modify::SpecifiedValue::from_gecko_keyword(value),
+        // TextEmphasisPosition => FIXME implement text-emphasis-position
         Direction => longhands::direction::SpecifiedValue::from_gecko_keyword(value),
         Display => longhands::display::SpecifiedValue::from_gecko_keyword(value),
-        Float => {
-            const LEFT: u32 = structs::StyleFloat::Left as u32;
-            const RIGHT: u32 = structs::StyleFloat::Right as u32;
-            const NONE: u32 = structs::StyleFloat::None as u32;
-            match value {
-                LEFT => Float::Left,
-                RIGHT => Float::Right,
-                NONE => Float::None,
-                _ => unreachable!(),
-            }
-        },
+        Float => longhands::float::SpecifiedValue::from_gecko_keyword(value),
         VerticalAlign => longhands::vertical_align::SpecifiedValue::from_gecko_keyword(value),
         TextAlign => longhands::text_align::SpecifiedValue::from_gecko_keyword(value),
         TextEmphasisPosition => longhands::text_emphasis_position::SpecifiedValue::from_gecko_keyword(value),
+        Clear => longhands::clear::SpecifiedValue::from_gecko_keyword(value),
         FontSize => {
             // We rely on Gecko passing in font-size values (0...7) here.
             longhands::font_size::SpecifiedValue::from_html_size(value as u8)
         },
         FontStyle => {
             let val = if value == structs::NS_FONT_STYLE_ITALIC {
                 FontStyle::Italic
             } else {
new file mode 100644
--- /dev/null
+++ b/servo/tests/unit/style/keyframes.rs
@@ -0,0 +1,259 @@
+/* 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 cssparser::SourceLocation;
+use servo_arc::Arc;
+use style::properties::{LonghandId, LonghandIdSet, PropertyDeclaration, PropertyDeclarationBlock, Importance};
+use style::properties::DeclarationSource;
+use style::shared_lock::SharedRwLock;
+use style::stylesheets::keyframes_rule::{Keyframe, KeyframesAnimation, KeyframePercentage,  KeyframeSelector};
+use style::stylesheets::keyframes_rule::{KeyframesStep, KeyframesStepValue};
+use style::values::specified::{LengthOrPercentageOrAuto, NoCalcLength};
+
+macro_rules! longhand_set {
+    ($($word:ident),+) => {{
+        let mut set = LonghandIdSet::new();
+        $(
+            set.insert(LonghandId::$word);
+        )+
+        set
+    }}
+}
+
+
+#[test]
+fn test_empty_keyframe() {
+    let shared_lock = SharedRwLock::new();
+    let keyframes = vec![];
+    let animation = KeyframesAnimation::from_keyframes(&keyframes,
+                                                       /* vendor_prefix = */ None,
+                                                       &shared_lock.read());
+    let expected = KeyframesAnimation {
+        steps: vec![],
+        properties_changed: LonghandIdSet::new(),
+        vendor_prefix: None,
+    };
+
+    assert_eq!(format!("{:#?}", animation), format!("{:#?}", expected));
+}
+
+#[test]
+fn test_no_property_in_keyframe() {
+    let shared_lock = SharedRwLock::new();
+    let dummy_location = SourceLocation { line: 0, column: 0 };
+    let keyframes = vec![
+        Arc::new(shared_lock.wrap(Keyframe {
+            selector: KeyframeSelector::new_for_unit_testing(vec![KeyframePercentage::new(1.)]),
+            block: Arc::new(shared_lock.wrap(PropertyDeclarationBlock::new())),
+            source_location: dummy_location,
+        })),
+    ];
+    let animation = KeyframesAnimation::from_keyframes(&keyframes,
+                                                       /* vendor_prefix = */ None,
+                                                       &shared_lock.read());
+    let expected = KeyframesAnimation {
+        steps: vec![],
+        properties_changed: LonghandIdSet::new(),
+        vendor_prefix: None,
+    };
+
+    assert_eq!(format!("{:#?}", animation), format!("{:#?}", expected));
+}
+
+#[test]
+fn test_missing_property_in_initial_keyframe() {
+    let shared_lock = SharedRwLock::new();
+    let declarations_on_initial_keyframe =
+        Arc::new(shared_lock.wrap(PropertyDeclarationBlock::with_one(
+            PropertyDeclaration::Width(
+                LengthOrPercentageOrAuto::Length(NoCalcLength::from_px(20f32))),
+            Importance::Normal
+        )));
+
+    let declarations_on_final_keyframe =
+        Arc::new(shared_lock.wrap({
+            let mut block = PropertyDeclarationBlock::new();
+            block.push(
+                PropertyDeclaration::Width(
+                    LengthOrPercentageOrAuto::Length(NoCalcLength::from_px(20f32))),
+                Importance::Normal,
+                DeclarationSource::Parsing,
+            );
+            block.push(
+                PropertyDeclaration::Height(
+                    LengthOrPercentageOrAuto::Length(NoCalcLength::from_px(20f32))),
+                Importance::Normal,
+                DeclarationSource::Parsing,
+            );
+            block
+        }));
+
+    let dummy_location = SourceLocation { line: 0, column: 0 };
+    let keyframes = vec![
+        Arc::new(shared_lock.wrap(Keyframe {
+            selector: KeyframeSelector::new_for_unit_testing(vec![KeyframePercentage::new(0.)]),
+            block: declarations_on_initial_keyframe.clone(),
+            source_location: dummy_location,
+        })),
+
+        Arc::new(shared_lock.wrap(Keyframe {
+            selector: KeyframeSelector::new_for_unit_testing(vec![KeyframePercentage::new(1.)]),
+            block: declarations_on_final_keyframe.clone(),
+            source_location: dummy_location,
+        })),
+    ];
+    let animation = KeyframesAnimation::from_keyframes(&keyframes,
+                                                       /* vendor_prefix = */ None,
+                                                       &shared_lock.read());
+    let expected = KeyframesAnimation {
+        steps: vec![
+            KeyframesStep {
+                start_percentage: KeyframePercentage(0.),
+                value: KeyframesStepValue::Declarations { block: declarations_on_initial_keyframe },
+                declared_timing_function: false,
+            },
+            KeyframesStep {
+                start_percentage: KeyframePercentage(1.),
+                value: KeyframesStepValue::Declarations { block: declarations_on_final_keyframe },
+                declared_timing_function: false,
+            },
+        ],
+        properties_changed: longhand_set!(Width, Height),
+        vendor_prefix: None,
+    };
+
+    assert_eq!(format!("{:#?}", animation), format!("{:#?}", expected));
+}
+
+#[test]
+fn test_missing_property_in_final_keyframe() {
+    let shared_lock = SharedRwLock::new();
+    let declarations_on_initial_keyframe =
+        Arc::new(shared_lock.wrap({
+            let mut block = PropertyDeclarationBlock::new();
+            block.push(
+                PropertyDeclaration::Width(
+                    LengthOrPercentageOrAuto::Length(NoCalcLength::from_px(20f32))),
+                Importance::Normal,
+                DeclarationSource::Parsing,
+            );
+            block.push(
+                PropertyDeclaration::Height(
+                    LengthOrPercentageOrAuto::Length(NoCalcLength::from_px(20f32))),
+                Importance::Normal,
+                DeclarationSource::Parsing,
+            );
+            block
+        }));
+
+    let declarations_on_final_keyframe =
+        Arc::new(shared_lock.wrap(PropertyDeclarationBlock::with_one(
+            PropertyDeclaration::Height(
+                LengthOrPercentageOrAuto::Length(NoCalcLength::from_px(20f32))),
+            Importance::Normal,
+        )));
+
+    let dummy_location = SourceLocation { line: 0, column: 0 };
+    let keyframes = vec![
+        Arc::new(shared_lock.wrap(Keyframe {
+            selector: KeyframeSelector::new_for_unit_testing(vec![KeyframePercentage::new(0.)]),
+            block: declarations_on_initial_keyframe.clone(),
+            source_location: dummy_location,
+        })),
+
+        Arc::new(shared_lock.wrap(Keyframe {
+            selector: KeyframeSelector::new_for_unit_testing(vec![KeyframePercentage::new(1.)]),
+            block: declarations_on_final_keyframe.clone(),
+            source_location: dummy_location,
+        })),
+    ];
+    let animation = KeyframesAnimation::from_keyframes(&keyframes,
+                                                       /* vendor_prefix = */ None,
+                                                       &shared_lock.read());
+    let expected = KeyframesAnimation {
+        steps: vec![
+            KeyframesStep {
+                start_percentage: KeyframePercentage(0.),
+                value: KeyframesStepValue::Declarations { block: declarations_on_initial_keyframe },
+                declared_timing_function: false,
+            },
+            KeyframesStep {
+                start_percentage: KeyframePercentage(1.),
+                value: KeyframesStepValue::Declarations { block: declarations_on_final_keyframe },
+                declared_timing_function: false,
+            },
+        ],
+        properties_changed: longhand_set!(Width, Height),
+        vendor_prefix: None,
+    };
+
+    assert_eq!(format!("{:#?}", animation), format!("{:#?}", expected));
+}
+
+#[test]
+fn test_missing_keyframe_in_both_of_initial_and_final_keyframe() {
+    let shared_lock = SharedRwLock::new();
+    let declarations =
+        Arc::new(shared_lock.wrap({
+            let mut block = PropertyDeclarationBlock::new();
+            block.push(
+                PropertyDeclaration::Width(
+                    LengthOrPercentageOrAuto::Length(NoCalcLength::from_px(20f32))),
+                Importance::Normal,
+                DeclarationSource::Parsing,
+            );
+            block.push(
+                PropertyDeclaration::Height(
+                    LengthOrPercentageOrAuto::Length(NoCalcLength::from_px(20f32))),
+                Importance::Normal,
+                DeclarationSource::Parsing,
+            );
+            block
+        }));
+
+    let dummy_location = SourceLocation { line: 0, column: 0 };
+    let keyframes = vec![
+        Arc::new(shared_lock.wrap(Keyframe {
+            selector: KeyframeSelector::new_for_unit_testing(vec![KeyframePercentage::new(0.)]),
+            block: Arc::new(shared_lock.wrap(PropertyDeclarationBlock::new())),
+            source_location: dummy_location,
+        })),
+        Arc::new(shared_lock.wrap(Keyframe {
+            selector: KeyframeSelector::new_for_unit_testing(vec![KeyframePercentage::new(0.5)]),
+            block: declarations.clone(),
+            source_location: dummy_location,
+        })),
+    ];
+    let animation = KeyframesAnimation::from_keyframes(&keyframes,
+                                                       /* vendor_prefix = */ None,
+                                                       &shared_lock.read());
+    let expected = KeyframesAnimation {
+        steps: vec![
+            KeyframesStep {
+                start_percentage: KeyframePercentage(0.),
+                value: KeyframesStepValue::Declarations {
+                    block: Arc::new(shared_lock.wrap(
+                        // XXX: Should we use ComputedValues in this case?
+                        PropertyDeclarationBlock::new()
+                    ))
+                },
+                declared_timing_function: false,
+            },
+            KeyframesStep {
+                start_percentage: KeyframePercentage(0.5),
+                value: KeyframesStepValue::Declarations { block: declarations },
+                declared_timing_function: false,
+            },
+            KeyframesStep {
+                start_percentage: KeyframePercentage(1.),
+                value: KeyframesStepValue::ComputedValues,
+                declared_timing_function: false,
+            }
+        ],
+        properties_changed: longhand_set!(Width, Height),
+        vendor_prefix: None,
+    };
+
+    assert_eq!(format!("{:#?}", animation), format!("{:#?}", expected));
+}
--- a/servo/tests/unit/style/lib.rs
+++ b/servo/tests/unit/style/lib.rs
@@ -20,16 +20,17 @@ extern crate servo_url;
 #[macro_use] extern crate size_of_test;
 #[macro_use] extern crate style;
 extern crate style_traits;
 extern crate test;
 
 mod animated_properties;
 mod attr;
 mod custom_properties;
+mod keyframes;
 mod logical_geometry;
 mod parsing;
 mod properties;
 mod rule_tree;
 mod size_of;
 mod specified_values;
 mod str;
 mod stylesheets;
--- a/servo/tests/unit/style/stylesheets.rs
+++ b/servo/tests/unit/style/stylesheets.rs
@@ -12,34 +12,34 @@ use servo_atoms::Atom;
 use servo_config::prefs::{PREFS, PrefValue};
 use servo_url::ServoUrl;
 use std::borrow::ToOwned;
 use std::cell::RefCell;
 use std::sync::atomic::AtomicBool;
 use style::context::QuirksMode;
 use style::error_reporting::{ParseErrorReporter, ContextualParseError};
 use style::media_queries::MediaList;
-use style::properties::{CSSWideKeyword, CustomDeclaration, DeclarationPushMode};
+use style::properties::{CSSWideKeyword, CustomDeclaration, DeclarationSource};
 use style::properties::{DeclaredValueOwned, Importance};
 use style::properties::{PropertyDeclaration, PropertyDeclarationBlock};
 use style::properties::longhands::{self, animation_timing_function};
 use style::shared_lock::SharedRwLock;
 use style::stylesheets::{Origin, Namespaces};
 use style::stylesheets::{Stylesheet, StylesheetContents, NamespaceRule, CssRule, CssRules, StyleRule, KeyframesRule};
 use style::stylesheets::keyframes_rule::{Keyframe, KeyframeSelector, KeyframePercentage};
 use style::values::{KeyframesName, CustomIdent};
 use style::values::computed::Percentage;
 use style::values::specified::{LengthOrPercentageOrAuto, PositionComponent};
 use style::values::specified::transform::TimingFunction;
 
 pub fn block_from<I>(iterable: I) -> PropertyDeclarationBlock
 where I: IntoIterator<Item=(PropertyDeclaration, Importance)> {
     let mut block = PropertyDeclarationBlock::new();
     for (d, i) in iterable {
-        block.push(d, i, DeclarationPushMode::Append);
+        block.push(d, i, DeclarationSource::CssOm);
     }
     block
 }
 
 #[test]
 fn test_parse_stylesheet() {
     let css = r"
         @namespace url(http://www.w3.org/1999/xhtml);