layout/style/test/test_transitions_per_property.html
author Emmanuele Bassi <ebassi@mozilla.com>
Sun, 08 Jul 2012 21:25:10 -0400
changeset 98673 137f4655cf255762958f6a52f55b794a98dc13fd
parent 98583 57126745d4629a4be7049756fb649f59bd4de2d5
child 98927 2e3c67b2d95dbb43ab4ce9c7c41faba5da67b507
child 106581 7aa128dbd1a9e5b9272376aa9a1f4aa23afd8787
permissions -rw-r--r--
Bug 762303 - Unprefix CSS Transition properties and provide temporary aliases for -moz-transition and exposed subproperties. r=dbaron

<!DOCTYPE HTML>
<html>
<!--
https://bugzilla.mozilla.org/show_bug.cgi?id=435441
-->
<head>
  <title>Test for Bug 435441</title>
  <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
  <script type="text/javascript" src="property_database.js"></script>
  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
  <style type="text/css">

  #display > p { margin-top: 0; margin-bottom: 0; }

  </style>
</head>
<body>
<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=435441">Mozilla Bug 435441</a>

<!--
  fixed-height container so percentage heights compute to different
  (i.e., nonzero) values
  fixed-width container so that percentages for margin-top and
  margin-bottom are all relative to the same size container (rather than
  one that depends on whether we're tall enough to need a scrollbar)

  Use a 20px font size and line-height so that percentage line-height
  and vertical-align doesn't accumulate rounding error.
  -->
<div style="height: 50px; width: 300px; font-size: 20px; line-height: 20px">

<div id="display">

</div>
</div>
<pre id="test">
<script type="application/javascript">

/** Test for Bug 435441 **/

function has_num(str)
{
    return !!String(str).match(/^([\d.]+)/);
}

function any_unit_to_num(str)
{
    return Number(String(str).match(/^([\d.]+)/)[1]);
}

var FUNC_NEGATIVE = "cubic-bezier(0.25, -2, 0.75, 1)";

var supported_properties = {
    "border-bottom-left-radius": [ test_radius_transition ],
    "border-bottom-right-radius": [ test_radius_transition ],
    "border-top-left-radius": [ test_radius_transition ],
    "border-top-right-radius": [ test_radius_transition ],
    "-moz-box-flex": [ test_float_zeroToOne_transition,
                       test_float_aboveOne_transition,
                       test_float_zeroToOne_clamped ],
    "box-shadow": [ test_shadow_transition ],
    "-moz-column-count": [ test_pos_integer_or_auto_transition,
                           test_integer_at_least_one_clamping ],
    "-moz-column-gap": [ test_length_transition,
                         test_length_clamped ],
    "-moz-column-rule-color": [ test_color_transition,
                                test_border_color_transition ],
    "-moz-column-rule-width": [ test_length_transition,
                                test_length_clamped ],
    "-moz-column-width": [ test_length_transition,
                           test_length_clamped ],
    "-moz-image-region": [ test_rect_transition ],
    "-moz-outline-radius-bottomleft": [ test_radius_transition ],
    "-moz-outline-radius-bottomright": [ test_radius_transition ],
    "-moz-outline-radius-topleft": [ test_radius_transition ],
    "-moz-outline-radius-topright": [ test_radius_transition ],
    "-moz-text-decoration-color": [ test_color_transition,
                                    test_border_color_transition ],
    "background-color": [ test_color_transition ],
    "background-position": [ test_background_position_transition,
                             // FIXME: We don't currently test clamping,
                             // since background-position uses calc() as
                             // an intermediate form.
                             /* test_length_percent_pair_unclamped */ ],
    "background-size": [ test_background_size_transition,
                         // FIXME: We don't currently test clamping,
                         // since background-size uses calc() as an
                         // intermediate form.
                         /* test_length_percent_pair_clamped */ ],
    "border-bottom-color": [ test_color_transition,
                             test_border_color_transition ],
    "border-bottom-width": [ test_length_transition,
                             test_length_clamped ],
    "border-left-color": [ test_color_transition,
                           test_border_color_transition ],
    "border-left-width": [ test_length_transition,
                           test_length_clamped ],
    "border-right-color": [ test_color_transition,
                            test_border_color_transition ],
    "border-right-width": [ test_length_transition,
                            test_length_clamped ],
    "border-spacing": [ test_length_pair_transition,
                        test_length_pair_transition_clamped ],
    "border-top-color": [ test_color_transition,
                          test_border_color_transition ],
    "border-top-width": [ test_length_transition,
                           test_length_clamped ],
    "bottom": [ test_length_transition, test_percent_transition,
                test_length_percent_calc_transition,
                test_length_unclamped, test_percent_unclamped ],
    "clip": [ test_rect_transition ],
    "color": [ test_color_transition ],
    "fill": [ test_color_transition ],
    "fill-opacity" : [ test_float_zeroToOne_transition,
                       // opacity is clamped in computed style
                       // (not parsing/interpolation)
                       test_float_zeroToOne_clamped ],
/* XXXdholbert In builds with MOZ_FLEXBOX enabled, this should be uncommented.
   (This would be #ifdef MOZ_FLEXBOX, if that worked in JS files.)

    "-moz-flex-basis": [ test_length_transition, test_percent_transition,
                         test_length_clamped, test_percent_clamped ],
    "-moz-flex-grow": [ test_float_zeroToOne_transition,
                        test_float_aboveOne_transition ],
    "-moz-flex-shrink": [ test_float_zeroToOne_transition,
                          test_float_aboveOne_transition ],
    "-moz-order": [ test_integer_transition ],

*/
    "flood-color": [ test_color_transition ],
    "flood-opacity" : [ test_float_zeroToOne_transition,
                        // opacity is clamped in computed style
                        // (not parsing/interpolation)
                        test_float_zeroToOne_clamped ],
    "font-size": [ test_length_transition, test_percent_transition,
                   test_length_percent_calc_transition,
                   test_length_clamped, test_percent_clamped ],
    "font-size-adjust": [ test_float_zeroToOne_transition,
                          test_float_aboveOne_transition,
                          /* FIXME: font-size-adjust treats zero specially */
                          /* test_float_zeroToOne_clamped */ ],
    "font-stretch": [ test_font_stretch ],
    "font-weight": [ test_font_weight ],
    "height": [ test_length_transition, test_percent_transition,
                test_length_percent_calc_transition,
                test_length_clamped, test_percent_clamped ],
    "left": [ test_length_transition, test_percent_transition,
              test_length_percent_calc_transition,
              test_length_unclamped, test_percent_unclamped ],
    "letter-spacing": [ test_length_transition, test_length_unclamped ],
    "lighting-color": [ test_color_transition ],
    // NOTE: when calc() is supported on 'line-height', we should add
    // test_length_percent_calc_transition.
    "line-height": [ test_length_transition, test_percent_transition,
                     test_length_clamped, test_percent_clamped ],
    "margin-bottom": [ test_length_transition, test_percent_transition,
                       test_length_percent_calc_transition,
                       test_length_unclamped, test_percent_unclamped ],
    "margin-left": [ test_length_transition, test_percent_transition,
                     test_length_percent_calc_transition,
                     test_length_unclamped, test_percent_unclamped ],
    "margin-right": [ test_length_transition, test_percent_transition,
                      test_length_percent_calc_transition,
                      test_length_unclamped, test_percent_unclamped ],
    "margin-top": [ test_length_transition, test_percent_transition,
                    test_length_percent_calc_transition,
                    test_length_unclamped, test_percent_unclamped ],
    "marker-offset": [ test_length_transition,
                       test_length_unclamped ],
    "max-height": [ test_length_transition, test_percent_transition,
                    test_length_percent_calc_transition,
                    test_length_clamped, test_percent_clamped ],
    "max-width": [ test_length_transition, test_percent_transition,
                   test_length_percent_calc_transition,
                   test_length_clamped, test_percent_clamped ],
    "min-height": [ test_length_transition, test_percent_transition,
                    test_length_percent_calc_transition,
                    test_length_clamped, test_percent_clamped ],
    "min-width": [ test_length_transition, test_percent_transition,
                   test_length_percent_calc_transition,
                   test_length_clamped, test_percent_clamped ],
    "opacity" : [ test_float_zeroToOne_transition,
                  // opacity is clamped in computed style
                  // (not parsing/interpolation)
                  test_float_zeroToOne_clamped ],
    "outline-color": [ test_color_transition ],
    "outline-offset": [ test_length_transition, test_length_unclamped ],
    "outline-width": [ test_length_transition, test_length_clamped ],
    "padding-bottom": [ test_length_transition, test_percent_transition,
                        test_length_percent_calc_transition,
                        test_length_clamped, test_percent_clamped ],
    "padding-left": [ test_length_transition, test_percent_transition,
                      test_length_percent_calc_transition,
                      test_length_clamped, test_percent_clamped ],
    "padding-right": [ test_length_transition, test_percent_transition,
                       test_length_percent_calc_transition,
                       test_length_clamped, test_percent_clamped ],
    "padding-top": [ test_length_transition, test_percent_transition,
                     test_length_percent_calc_transition,
                     test_length_clamped, test_percent_clamped ],
    "perspective": [ test_length_transition ],
    "perspective-origin": [ test_length_pair_transition,
                            test_length_percent_pair_transition,
                            test_length_percent_pair_unclamped ],
    "right": [ test_length_transition, test_percent_transition,
               test_length_percent_calc_transition,
               test_length_unclamped, test_percent_unclamped ],
    "stop-color": [ test_color_transition ],
    "stop-opacity" : [ test_float_zeroToOne_transition,
                       // opacity is clamped in computed style
                       // (not parsing/interpolation)
                       test_float_zeroToOne_clamped ],
    "stroke": [ test_color_transition ],
    "stroke-dasharray": [ test_dasharray_transition ],
    // NOTE: when calc() is supported on 'stroke-dashoffset', we should
    // add test_length_percent_calc_transition.
    "stroke-dashoffset": [ test_length_transition, test_percent_transition,
                           test_length_unclamped, test_percent_unclamped ],
    "stroke-miterlimit": [ test_float_aboveOne_transition,
                           test_float_aboveOne_clamped ],
    "stroke-opacity" : [ test_float_zeroToOne_transition,
                         // opacity is clamped in computed style
                         // (not parsing/interpolation)
                         test_float_zeroToOne_clamped ],
    // NOTE: when calc() is supported on 'stroke-width', we should add
    // test_length_percent_calc_transition.
    "stroke-width": [ test_length_transition, test_percent_transition,
                      test_length_clamped, test_percent_clamped ],
    "text-indent": [ test_length_transition, test_percent_transition,
                     test_length_percent_calc_transition,
                     test_length_unclamped, test_percent_unclamped ],
    "text-shadow": [ test_shadow_transition ],
    "top": [ test_length_transition, test_percent_transition,
             test_length_percent_calc_transition,
             test_length_unclamped, test_percent_unclamped ],
    "transform": [ test_transform_transition ],
    "transform-origin": [ test_length_pair_transition,
                          test_length_percent_pair_transition,
                          test_length_percent_pair_unclamped ],
    "vertical-align": [ test_length_transition, test_percent_transition,
                        test_length_percent_calc_transition,
                        test_length_unclamped, test_percent_unclamped ],
    "visibility": [ test_visibility_transition ],
    "width": [ test_length_transition, test_percent_transition,
               test_length_percent_calc_transition,
               test_length_clamped, test_percent_clamped ],
    "word-spacing": [ test_length_transition, test_length_unclamped ],
    "z-index": [ test_integer_transition, test_pos_integer_or_auto_transition ],
};

var div = document.getElementById("display");
var cs = getComputedStyle(div, "");

var prop;
for (prop in supported_properties) {
  // Test that prop is in the property database.
  ok(prop in gCSSProperties, "property " + prop + " in gCSSProperties");

  // Test that the entry has at least one test function.
  ok(supported_properties[prop].length > 0,
     "property " + prop + " must have at least one test function");
}

// Test that transitions don't do anything (i.e., aren't supported) on
// the properties not in our test list above (and not transition
// properties themselves).
for (prop in gCSSProperties) {
  var info = gCSSProperties[prop];
  if (!(prop in supported_properties) &&
      info.type != CSS_TYPE_TRUE_SHORTHAND &&
      !prop.match(/^transition-/) &&
      // FIXME (Bug 119078): THIS SHOULD REALLY NOT BE NEEDED!
      prop != "-moz-binding") {

    if ("prerequisites" in info) {
      var prereqs = info.prerequisites;
      for (var prereq in prereqs) {
        div.style.setProperty(prereq, prereqs[prereq], "");
      }
    }

    var all_values = info.initial_values.concat(info.other_values);
    var all_computed = [];
    for (var idx in all_values) {
      var val = all_values[idx];
      div.style.setProperty(prop, val, "");
      all_computed.push(cs.getPropertyValue(prop));
    }
    div.style.removeProperty(prop);

    div.style.setProperty("transition", prop + " 20s linear", "");
    for (var i = 0; i < all_values.length; ++i) {
      for (var j = i + 1; j < all_values.length; ++j) {
        div.style.setProperty(prop, all_values[i], "");
        is(cs.getPropertyValue(prop), all_computed[i],
           "transitions not supported for property " + prop +
           " value " + all_values[i]);
        div.style.setProperty(prop, all_values[j], "");
        is(cs.getPropertyValue(prop), all_computed[j],
           "transitions not supported for property " + prop +
           " value " + all_values[j]);
      }
    }

    div.style.removeProperty("transition");
    div.style.removeProperty(prop);
    if ("prerequisites" in info) {
      var prereqs = info.prerequisites;
      for (var prereq in prereqs) {
        div.style.removeProperty(prereq);
      }
    }
  }
}

// Do 4-second linear transitions with -1 second transition delay and
// linear timing function so that we can expect the transition to be
// one quarter of the way through the value space right after changing
// the property.
div.style.setProperty("transition-duration", "4s", "");
div.style.setProperty("transition-delay", "-1s", "");
div.style.setProperty("transition-timing-function", "linear", "");
for (prop in supported_properties) {
  var tinfo = supported_properties[prop];
  var info = gCSSProperties[prop];

  isnot(info.type, CSS_TYPE_TRUE_SHORTHAND,
        prop + " must not be a shorthand");
  if ("prerequisites" in info) {
    var prereqs = info.prerequisites;
    for (var prereq in prereqs) {
      // We don't want the 19px font-size prereq of line-height, since we
      // want to leave it 20px.
      if (prop != "line-height" || prereq != "font-size") {
        div.style.setProperty(prereq, prereqs[prereq], "");
      }
    }
  }

  for (var idx in tinfo) {
    tinfo[idx](prop);
  }

  // Make sure to unset the property and stop transitions on it.
  div.style.setProperty("transition-property", "none", "");
  div.style.removeProperty(prop);
  cs.getPropertyValue(prop);

  if ("prerequisites" in info) {
    var prereqs = info.prerequisites;
    for (var prereq in prereqs) {
      div.style.removeProperty(prereq);
    }
  }
}
div.style.removeProperty("transition");

function get_distance(prop, v1, v2)
{
  return SpecialPowers.DOMWindowUtils
           .computeAnimationDistance(div, prop, v1, v2);
}

function transform3D_enabled()
{
  return SpecialPowers.getBoolPref("layout.3d-transforms.enabled");
}

function check_distance(prop, start, quarter, end)
{
  var sq = get_distance(prop, start, quarter);
  var se = get_distance(prop, start, end);
  var qe = get_distance(prop, quarter, end);

  ok(Math.abs((sq * 4 - se) / se) < 0.0001, "property '" + prop + "': distance " + sq + " from start '" + start + "' to quarter '" + quarter + "' should be quarter distance " + se + " from start '" + start + "' to end '" + end + "'");
  ok(Math.abs((qe * 4 - se * 3) / se) < 0.0001, "property '" + prop + "': distance " + qe + " from quarter '" + quarter + "' to end '" + end + "' should be three quarters distance " + se + " from start '" + start + "' to end '" + end + "'");
}

function test_length_transition(prop) {
  div.style.setProperty("transition-property", "none", "");
  div.style.setProperty(prop, "4px", "");
  is(cs.getPropertyValue(prop), "4px",
     "length-valued property " + prop + ": computed value before transition");
  div.style.setProperty("transition-property", prop, "");
  div.style.setProperty(prop, "12px", "");
  is(cs.getPropertyValue(prop), "6px",
     "length-valued property " + prop + ": interpolation of lengths");
  check_distance(prop, "4px", "6px", "12px");
}

function test_length_clamped(prop) {
  test_length_clamped_or_unclamped(prop, true);
}

function test_length_unclamped(prop) {
  test_length_clamped_or_unclamped(prop, false);
}

function test_length_clamped_or_unclamped(prop, is_clamped) {
  div.style.setProperty("transition-timing-function", FUNC_NEGATIVE, "");
  div.style.setProperty("transition-property", "none", "");
  div.style.setProperty(prop, "0px", "");
  is(cs.getPropertyValue(prop), "0px",
     "length-valued property " + prop + ": flush before clamping test");
  div.style.setProperty("transition-property", prop, "");
  div.style.setProperty(prop, "100px", "");
  (is_clamped ? is : isnot)(cs.getPropertyValue(prop), "0px",
     "length-valued property " + prop + ": clamping of negatives");
  div.style.setProperty("transition-timing-function", "linear", "");
}

// Test using float values in the range [0, 1] (e.g. opacity)
function test_float_zeroToOne_transition(prop) {
  div.style.setProperty("transition-property", "none", "");
  div.style.setProperty(prop, "0.3", "");
  is(cs.getPropertyValue(prop), "0.3",
     "float-valued property " + prop + ": computed value before transition");
  div.style.setProperty("transition-property", prop, "");
  div.style.setProperty(prop, "0.8", "");
  is(cs.getPropertyValue(prop), "0.425",
     "float-valued property " + prop + ": interpolation of floats");
  check_distance(prop, "0.3", "0.425", "0.8");
}

function test_float_zeroToOne_clamped(prop) {
  test_float_zeroToOne_clamped_or_unclamped(prop, true);
}
function test_float_zeroToOne_unclamped(prop) {
  test_float_zeroToOne_clamped_or_unclamped(prop, false);
}

function test_float_zeroToOne_clamped_or_unclamped(prop, is_clamped) {
  div.style.setProperty("transition-timing-function", FUNC_NEGATIVE, "");
  div.style.setProperty("transition-property", "none", "");
  div.style.setProperty(prop, "0", "");
  is(cs.getPropertyValue(prop), "0",
     "float-valued property " + prop + ": flush before clamping test");
  div.style.setProperty("transition-property", prop, "");
  div.style.setProperty(prop, "1", "");
  (is_clamped ? is : isnot)(cs.getPropertyValue(prop), "0",
     "float-valued property " + prop + ": clamping of negatives");
  div.style.setProperty("transition-timing-function", "linear", "");
}

// Test using float values in the range [1, infinity) (e.g. stroke-miterlimit)
function test_float_aboveOne_transition(prop) {
  div.style.setProperty("transition-property", "none", "");
  div.style.setProperty(prop, "1", "");
  is(cs.getPropertyValue(prop), "1",
     "float-valued property " + prop + ": computed value before transition");
  div.style.setProperty("transition-property", prop, "");
  div.style.setProperty(prop, "2.1", "");
  is(cs.getPropertyValue(prop), "1.275",
     "float-valued property " + prop + ": interpolation of floats");
  check_distance(prop, "1", "1.275", "2.1");
}

function test_float_aboveOne_clamped(prop) {
  div.style.setProperty("transition-timing-function", FUNC_NEGATIVE, "");
  div.style.setProperty("transition-property", "none", "");
  div.style.setProperty(prop, "1", "");
  is(cs.getPropertyValue(prop), "1",
     "float-valued property " + prop + ": flush before clamping test");
  div.style.setProperty("transition-property", prop, "");
  div.style.setProperty(prop, "5", "");
  is(cs.getPropertyValue(prop), "1",
     "float-valued property " + prop + ": clamping of negatives");
  div.style.setProperty("transition-timing-function", "linear", "");
}

function test_percent_transition(prop) {
  div.style.setProperty("transition-property", "none", "");
  div.style.setProperty(prop, "25%", "");
  var av = cs.getPropertyValue(prop);
  var a = any_unit_to_num(av);
  div.style.setProperty(prop, "75%", "");
  var bv = cs.getPropertyValue(prop);
  var b = any_unit_to_num(bv);
  isnot(b, a, "different percentages (" + av + " and " + bv +
              ") should be different for " + prop);
  div.style.setProperty("transition-property", prop, "");
  div.style.setProperty(prop, "25%", "");
  var res = cs.getPropertyValue(prop);
  is(any_unit_to_num(res) * 4, 3 * b + a,
     "percent-valued property " + prop + ": interpolation of percents: " +
     res + " should be a quarter of the way between " + bv + " and " + av);
  ok(has_num(res),
     "percent-valued property " + prop + ": percent computes to number");
  check_distance(prop, "25%", "37.5%", "75%");
}

function test_percent_clamped(prop) {
  test_percent_clamped_or_unclamped(prop, true);
}

function test_percent_unclamped(prop) {
  test_percent_clamped_or_unclamped(prop, false);
}

function test_percent_clamped_or_unclamped(prop, is_clamped) {
  div.style.setProperty("transition-timing-function", FUNC_NEGATIVE, "");
  div.style.setProperty("transition-property", "none", "");
  div.style.setProperty(prop, "0%", "");
  var zero_val = cs.getPropertyValue(prop); // flushes too
  div.style.setProperty("transition-property", prop, "");
  div.style.setProperty(prop, "150%", "");
  (is_clamped ? is : isnot)(cs.getPropertyValue(prop), zero_val,
     "percent-valued property " + prop + ": clamping of negatives");
  div.style.setProperty("transition-timing-function", "linear", "");
}

function test_length_percent_calc_transition(prop) {
  div.style.setProperty("transition-property", "none", "");
  div.style.setProperty(prop, "0%", "");
  var av = cs.getPropertyValue(prop);
  var a = any_unit_to_num(av);
  div.style.setProperty(prop, "100%", "");
  var bv = cs.getPropertyValue(prop);
  var b = any_unit_to_num(bv);
  div.style.setProperty(prop, "100px", "");
  var cv = cs.getPropertyValue(prop);
  var c = any_unit_to_num(cv);
  isnot(b, a, "different percentages (" + av + " and " + bv +
              ") should be different for " + prop);

  div.style.setProperty(prop, "50%", "");
  var v1v = cs.getPropertyValue(prop);
  is(any_unit_to_num(v1v) * 2, a + b,
     "computed value before transition for " + prop + ": '" +
     v1v + "' should be halfway " +
     "between '" + av + "' + and '" + bv + "'.");
  div.style.setProperty("transition-property", prop, "");
  div.style.setProperty(prop, "200px", "");
  var v2v = cs.getPropertyValue(prop);
  is(any_unit_to_num(v2v) * 8, 5*a + 3*b + 4*c,
     "interpolation between length and percent for " + prop + ": '"
     + v2v + "'");

  div.style.setProperty("transition-property", "none", "");
  div.style.setProperty(prop, "-moz-calc(25% + 100px)", "");
  v1v = cs.getPropertyValue(prop);
  is(any_unit_to_num(v1v) * 4, b + 4*c,
     "computed value before transition for " + prop + ": '" + v1v + "'");
  div.style.setProperty("transition-property", prop, "");
  div.style.setProperty(prop, "75%", "");
  v2v = cs.getPropertyValue(prop);
  is(any_unit_to_num(v2v) * 8, 5*a + 3*b + 6*c,
     "interpolation between calc() and percent for " + prop + ": '" +
     v2v + "'");

  div.style.setProperty("transition-property", "none", "");
  div.style.setProperty(prop, "150px", "");
  v1v = cs.getPropertyValue(prop);
  is(any_unit_to_num(v1v) * 2, c * 3,
     "computed value before transition for " + prop + ": '" + v1v + "'");
  div.style.setProperty("transition-property", prop, "");
  div.style.setProperty(prop, "-moz-calc(50% + 50px)", "");
  v2v = cs.getPropertyValue(prop);
  is(any_unit_to_num(v2v) * 8, 7 * a + b + 10*c,
     "interpolation between length and calc() for " + prop + ": '" +
     v2v + "'");

  check_distance(prop, "50%", "-moz-calc(37.5% + 50px)", "200px");
  check_distance(prop, "-moz-calc(25% + 100px)", "-moz-calc(37.5% + 75px)",
                       "75%");
  check_distance(prop, "150px", "-moz-calc(125px + 12.5%)",
                       "-moz-calc(50% + 50px)");
}

function test_color_transition(prop) {
  div.style.setProperty("transition-property", "none", "");
  div.style.setProperty(prop, "rgb(255, 28, 0)", "");
  is(cs.getPropertyValue(prop), "rgb(255, 28, 0)",
     "color-valued property " + prop + ": computed value before transition");
  div.style.setProperty("transition-property", prop, "");
  div.style.setProperty(prop, "rgb(75, 84, 128)", "");
  is(cs.getPropertyValue(prop), "rgb(210, 42, 32)",
     "color-valued property " + prop + ": interpolation of colors");

  div.style.setProperty("transition-property", "none", "");
  div.style.setProperty(prop, "rgb(128, 64, 0)", "");
  (prop == "color" ? div.parentNode : div).style.
    setProperty("color", "rgb(0, 0, 128)", "");
  is(cs.getPropertyValue(prop), "rgb(128, 64, 0)",
     "color-valued property " + prop + ": computed value before transition");
  div.style.setProperty("transition-property", prop, "");
  div.style.setProperty(prop, "currentColor", "");
  is(cs.getPropertyValue(prop), "rgb(96, 48, 32)",
     "color-valued property " + prop + ": interpolation of currentColor");

  check_distance(prop, "rgb(255, 28, 0)", "rgb(210, 42, 32)",
                       "rgb(75, 84, 128)");
  check_distance(prop, "rgb(128, 64, 0)", "rgb(96, 48, 32)", "currentColor");

  (prop == "color" ? div.parentNode : div).style.removeProperty("color");

  div.style.setProperty("transition-timing-function", FUNC_NEGATIVE, "");
  div.style.setProperty("transition-property", "none", "");
  div.style.setProperty(prop, "rgb(0, 255, 0)", "");
  var vals = cs.getPropertyValue(prop).match(/rgb\(([^, ]*), ([^, ]*), ([^, ]*)\)/);
  is(vals.length, 4,
     "color-valued property " + prop + ": flush before clamping test (length)");
  is(vals[1], "0",
     "color-valued property " + prop + ": flush before clamping test (red)");
  is(vals[2], "255",
     "color-valued property " + prop + ": flush before clamping test (green)");
  is(vals[3], "0",
     "color-valued property " + prop + ": flush before clamping test (blue)");
  div.style.setProperty("transition-property", prop, "");
  div.style.setProperty(prop, "rgb(255, 0, 128)", "");
  // FIXME: Once we support non-sRGB colors, these tests will need fixing.
  vals = cs.getPropertyValue(prop).match(/rgb\(([^, ]*), ([^, ]*), ([^, ]*)\)/);
  is(vals.length, 4,
     "color-valued property " + prop + ": clamping of negatives (length)");
  is(vals[1], "0",
     "color-valued property " + prop + ": clamping of negatives (red)");
  is(vals[2], "255",
     "color-valued property " + prop + ": clamping of above-range (green)");
  is(vals[3], "0",
     "color-valued property " + prop + ": clamping of negatives (blue)");
  div.style.setProperty("transition-timing-function", "linear", "");
}

function test_border_color_transition(prop) {
  div.style.setProperty("transition-property", "none", "");
  div.style.setProperty(prop, "rgb(128, 64, 0)", "");
  div.style.setProperty("color", "rgb(0, 0, 128)", "");
  is(cs.getPropertyValue(prop), "rgb(128, 64, 0)",
     "color-valued property " + prop + ": computed value before transition");
  div.style.setProperty("transition-property", prop, "");
  div.style.removeProperty(prop);
  is(cs.getPropertyValue(prop), "rgb(96, 48, 32)",
     "color-valued property " + prop + ": interpolation of initial value");

  check_distance(prop, "rgb(128, 64, 0)", "rgb(96, 48, 32)", "-moz-initial");

  div.style.removeProperty("color");
}

function test_shadow_transition(prop) {
  var spreadStr = (prop == "box-shadow") ? " 0px" : "";

  div.style.setProperty("transition-property", "none", "");
  div.style.setProperty(prop, "none", "");
  is(cs.getPropertyValue(prop), "none",
     "shadow-valued property " + prop + ": computed value before transition");
  div.style.setProperty("transition-property", prop, "");
  div.style.setProperty(prop, "4px 8px 3px red", "");
  is(cs.getPropertyValue(prop), "rgba(255, 0, 0, 0.25) 1px 2px 0.75px" + spreadStr,
     "shadow-valued property " + prop + ": interpolation of shadows");
  check_distance(prop, "none", "rgba(255, 0, 0, 0.25) 1px 2px 0.75px",
                       "4px 8px 3px red");

  div.style.setProperty("transition-property", "none", "");
  div.style.setProperty(prop, "#038000 4px 4px, 2px 2px blue", "");
  is(cs.getPropertyValue(prop), "rgb(3, 128, 0) 4px 4px 0px" + spreadStr + ", rgb(0, 0, 255) 2px 2px 0px" + spreadStr,
     "shadow-valued property " + prop + ": computed value before transition");
  div.style.setProperty("transition-property", prop, "");
  div.style.setProperty(prop, "8px 8px 8px red", "");
  is(cs.getPropertyValue(prop), "rgb(66, 96, 0) 5px 5px 2px" + spreadStr + ", rgba(0, 0, 255, 0.75) 1.5px 1.5px 0px" + spreadStr,
     "shadow-valued property " + prop + ": interpolation of shadows");
  check_distance(prop, "#038000 4px 4px, 2px 2px blue",
                       "rgb(66, 96, 0) 5px 5px 2px, rgba(0, 0, 255, 0.75) 1.5px 1.5px 0px",
                       "8px 8px 8px red");

  if (prop == "box-shadow") {
    div.style.setProperty(prop, "8px 8px 8px red inset", "");
    is(cs.getPropertyValue(prop), "rgb(255, 0, 0) 8px 8px 8px 0px inset",
       "shadow-valued property " + prop + ": non-interpolable cases");
    div.style.setProperty(prop, "8px 8px 8px 8px red inset", "");
    is(cs.getPropertyValue(prop), "rgb(255, 0, 0) 8px 8px 8px 2px inset",
       "shadow-valued property " + prop + ": interpolation of spread");
    // Leave in same state whether in the |if| or not.
    div.style.setProperty(prop, "8px 8px 8px red", "");
    is(cs.getPropertyValue(prop), "rgb(255, 0, 0) 8px 8px 8px 0px",
       "shadow-valued property " + prop + ": non-interpolable cases");
    check_distance(prop, "8px 8px 8px red inset",
                         "rgb(255, 0, 0) 8px 8px 8px 2px inset",
                         "8px 8px 8px 8px red inset");
  }

  var defaultColor = cs.getPropertyValue("color") + " ";
  div.style.setProperty(prop, "2px 2px 2px", "");
  is(cs.getPropertyValue(prop), defaultColor + "2px 2px 2px" + spreadStr,
     "shadow-valued property " + prop + ": non-interpolable cases");
  div.style.setProperty(prop, "6px 14px 10px", "");
  is(cs.getPropertyValue(prop), defaultColor + "3px 5px 4px" + spreadStr,
     "shadow-valued property " + prop + ": interpolation without color");
  check_distance(prop, "2px 2px 2px", "3px 5px 4px", "6px 14px 10px");

  div.style.setProperty("transition-timing-function", FUNC_NEGATIVE, "");
  div.style.setProperty("transition-property", "none", "");
  div.style.setProperty(prop, "0px 0px 0px black", "");
  is(cs.getPropertyValue(prop), "rgb(0, 0, 0) 0px 0px 0px" + spreadStr,
     "shadow-valued property " + prop + ": flush before clamping test");
  div.style.setProperty("transition-property", prop, "");
  div.style.setProperty(prop, "10px 10px 10px black", "");
  var vals = cs.getPropertyValue(prop).split(" ");
  is(vals.length, 6 + (prop == "box-shadow"), "unexpected number of values");
  is(vals.slice(0, 3).join(" "), "rgb(0, 0, 0)",
     "shadow-valued property " + prop + " (color): clamping of negatives");
  isnot(vals[3], "0px",
        "shadow-valued property " + prop + " (x): clamping of negatives");
  isnot(vals[4], "0px",
        "shadow-valued property " + prop + " (y): clamping of negatives");
  is(vals[5], "0px",
     "shadow-valued property " + prop + " (radius): clamping of negatives");
  if (prop == "box-shadow") {
    div.style.setProperty("transition-property", "none", "");
    div.style.setProperty(prop, "0px 0px 0px 0px black", "");
    is(cs.getPropertyValue(prop), "rgb(0, 0, 0) 0px 0px 0px 0px",
       "shadow-valued property " + prop + ": flush before clamping test");
    div.style.setProperty("transition-property", prop, "");
    div.style.setProperty(prop, "10px 10px 10px 10px black", "");
    var vals = cs.getPropertyValue(prop).split(" ");
    is(vals.length, 7, "unexpected number of values");
    is(vals.slice(0, 3).join(" "), "rgb(0, 0, 0)",
       "shadow-valued property " + prop + " (color): clamping of negatives");
    isnot(vals[3], "0px",
          "shadow-valued property " + prop + " (x): clamping of negatives");
    isnot(vals[4], "0px",
          "shadow-valued property " + prop + " (y): clamping of negatives");
    is(vals[5], "0px",
       "shadow-valued property " + prop + " (radius): clamping of negatives");
    isnot(vals[6], "0px",
          "shadow-valued property " + prop + " (spread): clamping of negatives");
  }
  div.style.setProperty("transition-timing-function", "linear", "");
}

function test_dasharray_transition(prop) {
  div.style.setProperty("transition-property", "none", "");
  div.style.setProperty(prop, "3", "");
  is(cs.getPropertyValue(prop), "3",
     "dasharray-valued property " + prop +
     ": computed value before transition");
  div.style.setProperty("transition-property", prop, "");
  div.style.setProperty(prop, "15px", "");
  is(cs.getPropertyValue(prop), "6",
     "dasharray-valued property " + prop + ": interpolation of dasharray");
  check_distance(prop, "3", "6", "15px");
  div.style.setProperty(prop, "none", "");
  is(cs.getPropertyValue(prop), "none",
     "dasharray-valued property " + prop + ": non-interpolability of none");
  div.style.setProperty(prop, "6,8px,4,4", "");
  is(cs.getPropertyValue(prop), "6, 8px, 4, 4",
     "dasharray-valued property " + prop +
     ": computed value before transition");
  div.style.setProperty(prop, "14, 12,16,16px", "");
  is(cs.getPropertyValue(prop), "8, 9, 7, 7",
     "dasharray-valued property " + prop + ": interpolation of dasharray");
  check_distance(prop, "6,8px,4,4", "8,9,7,7", "14, 12,16,16px");
  div.style.setProperty(prop, "none", "");
  is(cs.getPropertyValue(prop), "none",
     "dasharray-valued property " + prop + ": non-interpolability of none");
  div.style.setProperty(prop, "8,16,4", "");
  is(cs.getPropertyValue(prop), "8, 16, 4",
     "dasharray-valued property " + prop +
     ": computed value before transition");
  div.style.setProperty(prop, "4,8,12,16", "");
  is(cs.getPropertyValue(prop), "7, 14, 6, 10, 13, 5, 9, 16, 4, 8, 15, 7",
     "dasharray-valued property " + prop + ": interpolation of dasharray");
  check_distance(prop, "8,16,4", "7, 14, 6, 10, 13, 5, 9, 16, 4, 8, 15, 7",
                       "4,8,12,16");
  div.style.setProperty(prop, "2,50%,6,10", "");
  is(cs.getPropertyValue(prop), "2, 50%, 6, 10",
     "dasharray-valued property " + prop + ": non-interpolability of mixed units");
  div.style.setProperty(prop, "6,30%,2,2", "");
  is(cs.getPropertyValue(prop), "3, 45%, 5, 8",
     "dasharray-valued property " + prop + ": interpolation of dasharray");
  check_distance(prop, "2,50%,6,10", "3, 45%, 5, 8", "6,30%,2,2");

  div.style.setProperty("transition-timing-function", FUNC_NEGATIVE, "");
  div.style.setProperty("transition-property", "none", "");
  div.style.setProperty(prop, "0,0%", "");
  is(cs.getPropertyValue(prop), "0, 0%",
     "dasharray-valued property " + prop + ": flush before clamping test");
  div.style.setProperty("transition-property", prop, "");
  div.style.setProperty(prop, "5, 25%", "");
  is(cs.getPropertyValue(prop), "0, 0%",
     "dasharray-valued property " + prop + ": clamping of negatives");
  div.style.setProperty("transition-timing-function", "linear", "");
}

function test_radius_transition(prop) {
  div.style.setProperty("transition-property", "none", "");

  // FIXME: Test a square for now, since we haven't updated to the spec
  // for vertical components being relative to the height.
  // Note: We use powers of two here so the floating-point math comes out
  // nicely.
  div.style.setProperty("width", "256px", "");
  div.style.setProperty("height", "256px", "");
  div.style.setProperty("border", "none", "");
  div.style.setProperty("padding", "0", "");

  div.style.setProperty(prop, "3px", "");
  is(cs.getPropertyValue(prop), "3px",
     "radius-valued property " + prop +
     ": computed value before transition");
  div.style.setProperty("transition-property", prop, "");
  div.style.setProperty(prop, "15px", "");
  is(cs.getPropertyValue(prop), "6px",
     "radius-valued property " + prop + ": interpolation of radius");
  check_distance(prop, "3px", "6px", "15px");
  div.style.setProperty("transition-property", "none", "");
  div.style.setProperty(prop, "12.5%", "");
  is(cs.getPropertyValue(prop), "32px",
     "radius-valued property " + prop + ": computed value before transition");
  div.style.setProperty("transition-property", prop, "");
  div.style.setProperty(prop, "25%", "");
  is(cs.getPropertyValue(prop), "40px",
     "radius-valued property " + prop + ": interpolation of radius");
  check_distance(prop, "12.5%", "15.625%", "25%");
  div.style.setProperty("transition-property", "none", "");
  div.style.setProperty(prop, "3px 8px", "");
  is(cs.getPropertyValue(prop), "3px 8px",
     "radius-valued property " + prop + ": computed value before transition");
  div.style.setProperty("transition-property", prop, "");
  div.style.setProperty(prop, "15px 12px", "");
  is(cs.getPropertyValue(prop), "6px 9px",
     "radius-valued property " + prop + ": interpolation of radius");
  check_distance(prop, "3px 8px", "6px 9px", "15px 12px");
  div.style.setProperty("transition-property", "none", "");
  div.style.setProperty(prop, "12.5% 6.25%", "");
  is(cs.getPropertyValue(prop), "32px 16px",
     "radius-valued property " + prop + ": computed value before transition");
  div.style.setProperty("transition-property", prop, "");
  div.style.setProperty(prop, "25%", "");
  is(cs.getPropertyValue(prop), "40px 28px",
     "radius-valued property " + prop + ": interpolation of radius");
  check_distance(prop, "12.5% 6.25%", "15.625% 10.9375%", "25%");
  div.style.setProperty("transition-property", "none", "");
  div.style.setProperty(prop, "6.25% 12.5%", "");
  is(cs.getPropertyValue(prop), "16px 32px",
     "radius-valued property " + prop + ": computed value before transition");
  div.style.setProperty("transition-property", prop, "");
  div.style.setProperty(prop, "64px 16px", "");
  is(cs.getPropertyValue(prop), "28px",
     "radius-valued property " + prop + ": interpolation of radius with mixed units");
  check_distance(prop, "6.25% 12.5%",
                 "-moz-calc(4.6875% + 16px) -moz-calc(9.375% + 4px)",
                 "64px 16px");

  div.style.setProperty("transition-timing-function", FUNC_NEGATIVE, "");
  div.style.setProperty("transition-property", "none", "");
  div.style.setProperty(prop, "0px 0px", "");
  is(cs.getPropertyValue(prop), "0px",
     "radius-valued property " + prop + ": flush before clamping test");
  div.style.setProperty("transition-property", prop, "");
  div.style.setProperty(prop, "10px 20px", "");
  is(cs.getPropertyValue(prop), "0px",
     "radius-valued property " + prop + ": clamping of negatives");
  div.style.setProperty("transition-timing-function", "linear", "");

  test_length_percent_calc_transition(prop);

  div.style.removeProperty("width");
  div.style.removeProperty("height");
  div.style.removeProperty("border");
  div.style.removeProperty("padding");
}

function test_integer_transition(prop) {
  div.style.setProperty("transition-property", "none", "");
  div.style.setProperty(prop, "4", "");
  is(cs.getPropertyValue(prop), "4",
     "integer-valued property " + prop + ": computed value before transition");
  div.style.setProperty("transition-property", prop, "");
  div.style.setProperty(prop, "-14", "");
  is(cs.getPropertyValue(prop), "-1",
     "integer-valued property " + prop + ": interpolation of integers");
  check_distance(prop, "6", "1", "-14");

  div.style.setProperty("transition-property", "none", "");
  div.style.setProperty(prop, "-4", "");
  is(cs.getPropertyValue(prop), "-4",
     "integer-valued property " + prop + ": computed value before transition");
  div.style.setProperty("transition-property", prop, "");
  div.style.setProperty(prop, "8", "");
  is(cs.getPropertyValue(prop), "-1",
     "integer-valued property " + prop + ": interpolation of integers");
  check_distance(prop, "-4", "-1", "8");

  div.style.setProperty("transition-timing-function", FUNC_NEGATIVE, "");
  div.style.setProperty("transition-property", "none", "");
  div.style.setProperty(prop, "0", "");
  is(cs.getPropertyValue(prop), "0",
     "integer-valued property " + prop + ": flush before clamping test");
  div.style.setProperty("transition-property", prop, "");
  div.style.setProperty(prop, "100", "");
  isnot(cs.getPropertyValue(prop), "0",
        "integer-valued property " + prop + ": clamping of negatives");
  div.style.setProperty("transition-timing-function", "linear", "");
}

function test_font_stretch(prop) {
  is(prop, "font-stretch", "only designed for one property");

  div.style.setProperty("transition-property", "none", "");
  div.style.setProperty(prop, "normal", "");
  is(cs.getPropertyValue(prop), "normal",
     "font-stretch property " + prop + ": computed value before transition");
  div.style.setProperty("transition-property", prop, "");
  div.style.setProperty(prop, "ultra-expanded", "");
  is(cs.getPropertyValue(prop), "semi-expanded",
     "font-stretch property " + prop + ": interpolation of font-stretches");
  check_distance(prop, "normal", "semi-expanded", "ultra-expanded");
  div.style.setProperty("transition-property", "none", "");
  div.style.setProperty(prop, "expanded", "");
  is(cs.getPropertyValue(prop), "expanded",
     "font-stretch property " + prop + ": computed value before transition");
  div.style.setProperty("transition-property", prop, "");
  div.style.setProperty(prop, "extra-condensed", "");
  is(cs.getPropertyValue(prop), "normal",
     "font-stretch property " + prop + ": interpolation of font-stretches");
  check_distance(prop, "expanded", "semi-expanded", "condensed");

  div.style.setProperty("transition-timing-function", FUNC_NEGATIVE, "");
  div.style.setProperty("transition-property", "none", "");
  div.style.setProperty(prop, "ultra-condensed", "");
  is(cs.getPropertyValue(prop), "ultra-condensed",
     "font-stretch property " + prop + ": flush before clamping test");
  div.style.setProperty("transition-property", prop, "");
  div.style.setProperty(prop, "ultra-expanded", "");
  is(cs.getPropertyValue(prop), "ultra-condensed",
     "font-stretch property " + prop + ": clamping of values");
  div.style.setProperty("transition-property", "none", "");
  div.style.setProperty(prop, "ultra-expanded", "");
  is(cs.getPropertyValue(prop), "ultra-expanded",
     "font-stretch property " + prop + ": flush before clamping test");
  div.style.setProperty("transition-property", prop, "");
  div.style.setProperty(prop, "ultra-condensed", "");
  is(cs.getPropertyValue(prop), "ultra-expanded",
     "font-stretch property " + prop + ": clamping of values");
  div.style.setProperty("transition-timing-function", "linear", "");
}

function test_font_weight(prop) {
  is(prop, "font-weight", "only designed for one property");

  div.style.setProperty("transition-property", "none", "");
  div.style.setProperty(prop, "normal", "");
  is(cs.getPropertyValue(prop), "400",
     "font-weight property " + prop + ": computed value before transition");
  div.style.setProperty("transition-property", prop, "");
  div.style.setProperty(prop, "900", "");
  is(cs.getPropertyValue(prop), "500",
     "font-weight property " + prop + ": interpolation of font-weights");
  check_distance(prop, "400", "500", "800");

  div.style.setProperty("transition-property", "none", "");
  div.style.setProperty(prop, "900", "");
  is(cs.getPropertyValue(prop), "900",
     "font-weight property " + prop + ": computed value before transition");
  div.style.setProperty("transition-property", prop, "");
  div.style.setProperty(prop, "100", "");
  is(cs.getPropertyValue(prop), "700",
     "font-weight property " + prop + ": interpolation of font-weights");
  check_distance(prop, "900", "700", "100");

  div.style.setProperty("transition-timing-function", FUNC_NEGATIVE, "");
  div.style.setProperty("transition-property", "none", "");
  div.style.setProperty(prop, "100", "");
  is(cs.getPropertyValue(prop), "100",
     "font-weight property " + prop + ": flush before clamping test");
  div.style.setProperty("transition-property", prop, "");
  div.style.setProperty(prop, "900", "");
  is(cs.getPropertyValue(prop), "100",
     "font-weight property " + prop + ": clamping of values");
  div.style.setProperty("transition-property", "none", "");
  div.style.setProperty(prop, "900", "");
  is(cs.getPropertyValue(prop), "900",
     "font-weight property " + prop + ": flush before clamping test");
  div.style.setProperty("transition-property", prop, "");
  div.style.setProperty(prop, "100", "");
  is(cs.getPropertyValue(prop), "900",
     "font-weight property " + prop + ": clamping of values");
  div.style.setProperty("transition-timing-function", "linear", "");
}

function test_pos_integer_or_auto_transition(prop) {
  div.style.setProperty("transition-property", "none", "");
  div.style.setProperty(prop, "4", "");
  is(cs.getPropertyValue(prop), "4",
     "integer-valued property " + prop + ": computed value before transition");
  div.style.setProperty("transition-property", prop, "");
  div.style.setProperty(prop, "11", "");
  is(cs.getPropertyValue(prop), "5",
     "integer-valued property " + prop + ": interpolation of integers");
  check_distance(prop, "4", "6", "12");
  div.style.setProperty(prop, "auto", "");
  is(cs.getPropertyValue(prop), "auto",
     "integer-valued property " + prop + ": auto not interpolable");
  div.style.setProperty(prop, "8", "");
  is(cs.getPropertyValue(prop), "8",
     "integer-valued property " + prop + ": computed value before transition");
  div.style.setProperty(prop, "4", "");
  is(cs.getPropertyValue(prop), "7",
     "integer-valued property " + prop + ": interpolation of integers");
  check_distance(prop, "8", "7", "4");
}

function test_integer_at_least_one_clamping(prop) {
  div.style.setProperty("transition-timing-function", FUNC_NEGATIVE, "");
  div.style.setProperty("transition-property", "none", "");
  div.style.setProperty(prop, "1", "");
  is(cs.getPropertyValue(prop), "1",
     "integer-valued property " + prop + ": flush before clamping test");
  div.style.setProperty("transition-property", prop, "");
  div.style.setProperty(prop, "5", "");
  is(cs.getPropertyValue(prop), "1",
     "integer-valued property " + prop + ": clamping of negatives");
  div.style.setProperty("transition-timing-function", "linear", "");
}

function test_length_pair_transition(prop) {
  div.style.setProperty("transition-property", "none", "");
  div.style.setProperty(prop, "4px 6px", "");
  is(cs.getPropertyValue(prop), "4px 6px",
     "length-valued property " + prop + ": computed value before transition");
  div.style.setProperty("transition-property", prop, "");
  div.style.setProperty(prop, "12px 10px", "");
  is(cs.getPropertyValue(prop), "6px 7px",
     "length-valued property " + prop + ": interpolation of lengths");
  check_distance(prop, "4px 6px", "6px 7px", "12px 10px");
}

function test_length_pair_transition_clamped(prop) {
  div.style.setProperty("transition-timing-function", FUNC_NEGATIVE, "");
  div.style.setProperty("transition-property", "none", "");
  div.style.setProperty(prop, "0px 0px", "");
  is(cs.getPropertyValue(prop), "0px 0px",
     "length-valued property " + prop + ": flush before clamping test");
  div.style.setProperty("transition-property", prop, "");
  div.style.setProperty(prop, "30px 50px", "");
  is(cs.getPropertyValue(prop), "0px 0px",
     "length-valued property " + prop + ": clamping of negatives");
  div.style.setProperty("transition-timing-function", "linear", "");
}

function test_length_percent_pair_transition(prop) {
  div.style.setProperty("transition-property", "none", "");
  div.style.setProperty(prop, "4px 50%", "");
  is(cs.getPropertyValue(prop), "4px 5px",
     "length-valued property " + prop + ": computed value before transition");
  div.style.setProperty("transition-property", prop, "");
  div.style.setProperty(prop, "12px 70%", "");
  is(cs.getPropertyValue(prop), "6px 5.5px",
     "length-valued property " + prop + ": interpolation of lengths");
  check_distance(prop, "4px 50%", "6px 55%", "12px 70%");
}

function test_length_percent_pair_clamped(prop) {
  test_length_percent_pair_clamped_or_unclamped(prop, true);
}

function test_length_percent_pair_unclamped(prop) {
  test_length_percent_pair_clamped_or_unclamped(prop, false);
}

function test_length_percent_pair_clamped_or_unclamped(prop, is_clamped) {
  div.style.setProperty("transition-timing-function", FUNC_NEGATIVE, "");
  div.style.setProperty("transition-property", "none", "");
  div.style.setProperty(prop, "0px 0%", "");
  is(cs.getPropertyValue(prop), "0px 0px",
     "length+percent-valued property " + prop + ": flush before clamping test");
  div.style.setProperty("transition-property", prop, "");
  div.style.setProperty(prop, "30px 25%", "");
  (is_clamped ? is : isnot)(cs.getPropertyValue(prop), "0px 0px",
     "length+percent-valued property " + prop + ": clamping of negatives");
  div.style.setProperty("transition-timing-function", "linear", "");
}

function test_rect_transition(prop) {
  div.style.setProperty("transition-property", "none", "");
  div.style.setProperty(prop, "rect(4px, 16px, 12px, 6px)", "");
  is(cs.getPropertyValue(prop), "rect(4px, 16px, 12px, 6px)",
     "rect-valued property " + prop + ": computed value before transition");
  div.style.setProperty("transition-property", prop, "");
  div.style.setProperty(prop, "rect(0px, 4px, 4px, 2px)", "");
  is(cs.getPropertyValue(prop), "rect(3px, 13px, 10px, 5px)",
     "rect-valued property " + prop + ": interpolation of rects");
  check_distance(prop, "rect(4px, 16px, 12px, 6px)",
                       "rect(3px, 13px, 10px, 5px)",
                       "rect(0px, 4px, 4px, 2px)");
  if (prop == "clip") {
    div.style.setProperty(prop, "rect(0px, 6px, 4px, auto)", "");
    is(cs.getPropertyValue(prop), "rect(0px, 6px, 4px, auto)",
       "rect-valued property " + prop + ": can't interpolate auto components");
    div.style.setProperty(prop, "rect(0px, 6px, 4px, 2px)", "");
  }
  div.style.setProperty(prop, "auto", "");
  is(cs.getPropertyValue(prop), "auto",
     "rect-valued property " + prop + ": can't interpolate auto components");

  div.style.setProperty("transition-timing-function", FUNC_NEGATIVE, "");
  div.style.setProperty("transition-property", "none", "");
  div.style.setProperty(prop, "rect(-10px, 30px, 0px, 0px)", "");
  var vals = cs.getPropertyValue(prop).match(/rect\(([^, ]*), ([^, ]*), ([^, ]*), ([^, ]*)\)/);
  is(vals.length, 5,
     "rect-valued property " + prop + ": flush before clamping test (length)");
  is(vals[1], "-10px",
     "rect-valued property " + prop + ": flush before clamping test (top)");
  is(vals[2], "30px",
     "rect-valued property " + prop + ": flush before clamping test (right)");
  is(vals[3], "0px",
     "rect-valued property " + prop + ": flush before clamping test (bottom)");
  is(vals[4], "0px",
     "rect-valued property " + prop + ": flush before clamping test (left)");
  div.style.setProperty("transition-property", prop, "");
  div.style.setProperty(prop, "rect(0px, 40px, 10px, 10px)", "");
  vals = cs.getPropertyValue(prop).match(/rect\(([^, ]*), ([^, ]*), ([^, ]*), ([^, ]*)\)/);
  is(vals.length, 5,
     "rect-valued property " + prop + ": clamping of negatives (length)");
  isnot(vals[1], "-10px",
     "rect-valued property " + prop + ": clamping of negatives (top)");
  isnot(vals[2], "30px",
     "rect-valued property " + prop + ": clamping of negatives (right)");
  isnot(vals[3], "0px",
     "rect-valued property " + prop + ": clamping of negatives (bottom)");
  isnot(vals[4], "0px",
     "rect-valued property " + prop + ": clamping of negatives (left)");
  div.style.setProperty("transition-timing-function", "linear", "");
}

function test_visibility_transition(prop) {
  div.style.setProperty("transition-property", "none", "");
  div.style.setProperty(prop, "visible", "");
  is(cs.getPropertyValue(prop), "visible",
     "visibility property " + prop + ": computed value before transition");
  div.style.setProperty("transition-property", prop, "");
  div.style.setProperty(prop, "hidden", "");
  is(cs.getPropertyValue(prop), "visible",
     "visibility property " + prop + ": interpolation of visibility");
  isnot(get_distance(prop, "visible", "hidden"), 0,
        "distance between visible and hidden should not be zero");
  is(get_distance(prop, "visible", "visible"), 0,
     "distance between visible and visible should not be zero");
  is(get_distance(prop, "hidden", "hidden"), 0,
     "distance between hidden and hidden should not be zero");

  div.style.setProperty("transition-timing-function", FUNC_NEGATIVE, "");
  div.style.setProperty("transition-property", "none", "");
  div.style.setProperty(prop, "visible", "");
  is(cs.getPropertyValue(prop), "visible",
     "visibility property " + prop + ": flush before clamping test");
  div.style.setProperty("transition-property", prop, "");
  div.style.setProperty(prop, "hidden", "");
  is(cs.getPropertyValue(prop), "visible",
     "visibility property " + prop + ": clamping of negatives");
  div.style.setProperty("transition-property", "none", "");
  div.style.setProperty(prop, "hidden", "");
  is(cs.getPropertyValue(prop), "hidden",
     "visibility property " + prop + ": flush before clamping test");
  div.style.setProperty("transition-property", prop, "");
  div.style.setProperty(prop, "visible", "");
  is(cs.getPropertyValue(prop), "hidden",
     "visibility property " + prop + ": clamping of negatives");
  div.style.setProperty("transition-timing-function", "linear", "");
}

function test_background_size_transition(prop) {
  div.style.setProperty("transition-property", "none", "");
  div.style.setProperty(prop, "50% 80%", "");
  is(cs.getPropertyValue(prop), "50% 80%",
     "property " + prop + ": computed value before transition");
  div.style.setProperty("transition-property", prop, "");
  div.style.setProperty(prop, "100% 100%", "");
  is(cs.getPropertyValue(prop), "62.5% 85%",
     "property " + prop + ": interpolation of percents");
  check_distance(prop, "50% 80%", "62.5% 85%", "100% 100%");
  div.style.setProperty(prop, "contain", "");
  is(cs.getPropertyValue(prop), "contain",
     "property " + prop + ": can't interpolate 'contain'");
  test_background_position_size_common(prop);
}

function test_background_position_transition(prop) {
  div.style.setProperty("transition-property", "none", "");
  div.style.setProperty(prop, "center 80%", "");
  is(cs.getPropertyValue(prop), "50% 80%",
     "property " + prop + ": computed value before transition");
  div.style.setProperty("transition-property", prop, "");
  div.style.setProperty(prop, "bottom right", "");
  is(cs.getPropertyValue(prop), "62.5% 85%",
     "property " + prop + ": interpolation of percents");
  check_distance(prop, "center 80%", "62.5% 85%", "bottom right");
  test_background_position_size_common(prop);
}

function test_background_position_size_common(prop) {
  div.style.setProperty("transition-property", "none", "");
  div.style.setProperty(prop, "40% 0%", "");
  is(cs.getPropertyValue(prop), "40% 0%",
     "property " + prop + ": computed value before transition");
  div.style.setProperty("transition-property", prop, "");
  div.style.setProperty(prop, "0% 0%", "");
  is(cs.getPropertyValue(prop), "30% 0%",
     "property " + prop + ": interpolation of percentages");
  check_distance(prop, "40% 0%", "30% 0%", "0% 0%");

  div.style.setProperty("transition-property", "none", "");
  div.style.setProperty(prop, "0% 40%", "");
  is(cs.getPropertyValue(prop), "0% 40%",
     "property " + prop + ": computed value before transition");
  div.style.setProperty("transition-property", prop, "");
  div.style.setProperty(prop, "0% 0%", "");
  is(cs.getPropertyValue(prop), "0% 30%",
     "property " + prop + ": interpolation of percentages");
  check_distance(prop, "0% 40%", "0% 30%", "0% 0%");

  div.style.setProperty("transition-property", "none", "");
  div.style.setProperty(prop, "10px 40px", "");
  is(cs.getPropertyValue(prop), "10px 40px",
     "property " + prop + ": computed value before transition");
  div.style.setProperty("transition-property", prop, "");
  div.style.setProperty(prop, "50px 0", "");
  is(cs.getPropertyValue(prop), "20px 30px",
     "property " + prop + ": interpolation of lengths");
  check_distance(prop, "10px 40px", "20px 30px", "50px 0");
  div.style.setProperty(prop, "10px 40px, 50px 50px, 30px 20px", "");
  is(cs.getPropertyValue(prop), "10px 40px, 50px 50px, 30px 20px",
     "property " + prop + ": computed value before transition");
  div.style.setProperty(prop, "50px 20px, 70px 50px, 30px 40px", "");
  is(cs.getPropertyValue(prop), "20px 35px, 55px 50px, 30px 25px",
     "property " + prop + ": interpolation of lists of lengths");
  check_distance(prop, "10px 40px, 50px 50px, 30px 20px",
                       "20px 35px, 55px 50px, 30px 25px",
                       "50px 20px, 70px 50px, 30px 40px");
  div.style.setProperty(prop, "10px 40%, 50% 50px, 30% 20%, 5px 10px", "");
  is(cs.getPropertyValue(prop), "10px 40%, 50% 50px, 30% 20%, 5px 10px",
     "property " + prop + ": computed value before transition");
  div.style.setProperty(prop, "50px 20%, 70% 50px, 30% 40%, 25px 50px", "");
  is(cs.getPropertyValue(prop), "20px 35%, 55% 50px, 30% 25%, 10px 20px",
     "property " + prop + ": interpolation of lists of lengths and percents");
  check_distance(prop, "10px 40%, 50% 50px, 30% 20%, 5px 10px",
                       "20px 35%, 55% 50px, 30% 25%, 10px 20px",
                       "50px 20%, 70% 50px, 30% 40%, 25px 50px");
  div.style.setProperty(prop, "20% 40%, 8px 12px", "");
  is(cs.getPropertyValue(prop), "20% 40%, 8px 12px",
     "property " + prop + ": computed value before transition");
  div.style.setProperty(prop, "12px 20px, 40% 16%", "");
  is(cs.getPropertyValue(prop), "-moz-calc(3px + 15%) -moz-calc(5px + 30%), -moz-calc(6px + 10%) -moz-calc(9px + 4%)",
     "property " + prop + ": interpolation that computes to calc()");
  check_distance(prop, "20% 40%, 8px 12px",
                       "-moz-calc(3px + 15%) -moz-calc(5px + 30%), -moz-calc(6px + 10%) -moz-calc(9px + 4%)",
                       "12px 20px, 40% 16%");
  div.style.setProperty(prop, "-moz-calc(20% + 40px) -moz-calc(40px + 40%), 8px 12%, -moz-calc(20px + 12%) -moz-calc(24px + 8%)", "");
  is(cs.getPropertyValue(prop), "-moz-calc(40px + 20%) -moz-calc(40px + 40%), 8px 12%, -moz-calc(20px + 12%) -moz-calc(24px + 8%)",
     "property " + prop + ": computed value before transition");
  div.style.setProperty(prop, "12px 20%, -moz-calc(20%) -moz-calc(16px + 60%), -moz-calc(8px + 20%) -moz-calc(40px + 16%)", "");
  is(cs.getPropertyValue(prop), "-moz-calc(33px + 15%) -moz-calc(30px + 35%), -moz-calc(6px + 5%) -moz-calc(4px + 24%), -moz-calc(17px + 14%) -moz-calc(28px + 10%)",
     "property " + prop + ": interpolation that computes to calc()");
  check_distance(prop, "-moz-calc(20% + 40px) -moz-calc(40px + 40%), 8px 12%, -moz-calc(20px + 12%) -moz-calc(24px + 8%)",
                       "-moz-calc(33px + 15%) -moz-calc(30px + 35%), -moz-calc(6px + 5%) -moz-calc(4px + 24%), -moz-calc(17px + 14%) -moz-calc(28px + 10%)",
                       "12px 20%, -moz-calc(20%) -moz-calc(16px + 60%), -moz-calc(8px + 20%) -moz-calc(40px + 16%)");
}

function test_transform_transition(prop) {
  function c(v) {
    div.style.setProperty(prop, v, "");
    var result = cs.getPropertyValue(prop);
    div.style.removeProperty(prop);
    return result;
  }
  var c_rot_15 = c("rotate(15deg)");
  is(c_rot_15.substring(0,6), "matrix", "should compute to matrix value");
  var c_rot_60 = c("rotate(60deg)");
  is(c_rot_60.substring(0,6), "matrix", "should compute to matrix value");

  var tests = [
    // rotate
    { start: 'none', end: 'rotate(60deg)',
      expected_uncomputed: 'rotate(15deg)',
      expected: c_rot_15 },
    { start: 'rotate(0)', end: 'rotate(60deg)',
      expected_uncomputed: 'rotate(15deg)',
      expected: c_rot_15 },
    { start: 'rotate(0deg)', end: 'rotate(60deg)',
      expected_uncomputed: 'rotate(15deg)',
      expected: c_rot_15 },
    { start: 'none', end: c_rot_60,
      expected: c_rot_15 },
    { start: 'none', end: 'rotate(360deg)',
      expected_uncomputed: 'rotate(90deg)',
      expected: c('rotate(90deg)') },
    { start: 'none', end: 'rotatez(360deg)',
      expected_uncomputed: 'rotate(90deg)',
      expected: c('rotate(90deg)'),
      requires_3d: true },
    { start: 'none', end: 'rotate(720deg)',
      expected_uncomputed: 'rotate(180deg)',
      expected: c('rotate(180deg)') },
    { start: 'none', end: 'rotate(720deg)',
      expected_uncomputed: 'rotatez(180deg)',
      expected: c('rotate(180deg)'),
      requires_3d: true },
    { start: 'none', end: 'rotate(1080deg)',
      expected_uncomputed: 'rotate(270deg)',
      expected: c('rotate(270deg)') },
    { start: 'none', end: 'rotate(1080deg)',
      expected_uncomputed: 'rotate(270deg)',
      expected: c('rotatez(270deg)'),
      requires_3d: true },
    { start: 'none', end: 'rotate(1440deg)',
      expected_uncomputed: 'rotate(360deg)',
      expected: c('scale(1)'),
      round_error_ok: true },
    { start: 'none', end: 'rotatey(60deg)',
      expected_uncomputed: 'rotatey(15deg)',
      expected: c('rotatey(15deg)'),
      requires_3d: true },
    { start: 'none', end: 'rotatey(720deg)',
      expected_uncomputed: 'rotatey(180deg)',
      expected: c('rotatey(180deg)'),
      requires_3d: true },
    { start: 'none', end: 'rotatex(60deg)',
      expected_uncomputed: 'rotatex(15deg)',
      expected: c('rotatex(15deg)'),
      requires_3d: true },
    { start: 'none', end: 'rotatex(720deg)',
      expected_uncomputed: 'rotatex(180deg)',
      expected: c('rotatex(180deg)'),
      requires_3d: true },

    // translate
    { start: 'translate(20px)', end: 'none',
      expected_uncomputed: 'translate(15px)',
      expected: 'matrix(1, 0, 0, 1, 15, 0)' },
    { start: 'translate(20px, 12px)', end: 'none',
      expected_uncomputed: 'translate(15px, 9px)',
      expected: 'matrix(1, 0, 0, 1, 15, 9)' },
    { start: 'translateX(-20px)', end: 'none',
      expected_uncomputed: 'translateX(-15px)',
      expected: 'matrix(1, 0, 0, 1, -15, 0)' },
    { start: 'translateY(-40px)', end: 'none',
      expected_uncomputed: 'translateY(-30px)',
      expected: 'matrix(1, 0, 0, 1, 0, -30)' },
    { start: 'translateZ(40px)', end: 'none',
      expected_uncomputed: 'translateZ(30px)',
      expected: 'matrix3d(1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 30, 1)',
      requires_3d: true },
    { start: 'none', end: 'translate3D(40px, 60px, -40px)',
      expected_uncomputed: 'translate3D(10px, 15px, -10px)',
      expected: 'matrix3d(1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 10, 15, -10, 1)',
      requires_3d: true },
    // percentages are relative to 300px (width) and 50px (height)
    // per the prerequisites in property_database.js
    { start: 'translate(20%)', end: 'none',
      expected_uncomputed: 'translate(15%)',
      expected: 'matrix(1, 0, 0, 1, 45, 0)',
      round_error_ok: true },
    { start: 'translate(20%, 12%)', end: 'none',
      expected_uncomputed: 'translate(15%, 9%)',
      expected: 'matrix(1, 0, 0, 1, 45, 4.5)',
      round_error_ok: true },
    { start: 'translateX(-20%)', end: 'none',
      expected_uncomputed: 'translateX(-15%)',
      expected: 'matrix(1, 0, 0, 1, -45, 0)',
      round_error_ok: true },
    { start: 'translateY(-40%)', end: 'none',
      expected_uncomputed: 'translateY(-30%)',
      expected: 'matrix(1, 0, 0, 1, 0, -15)',
      round_error_ok: true },
    { start: 'none', end: 'rotate(90deg) translate(20%, 20%) rotate(-90deg)',
      expected_uncomputed: 'rotate(22.5deg) translate(5%, 5%) rotate(-22.5deg)',
      round_error_ok: true },
    { start: 'none', end: 'rotate(-90deg) translate(20%, 20%) rotate(90deg)',
      expected_uncomputed: 'rotate(-22.5deg) translate(5%, 5%) rotate(22.5deg)',
      round_error_ok: true },
    // test percent translation using matrix decomposition
    { start: 'rotate(45deg) rotate(-45deg)',
      end: 'rotate(90deg) translate(20%, 20%) rotate(-90deg)',
      expected: 'matrix(1, 0, 0, 1, -2.5, 15)',
      round_error_ok: true },
    { start: 'rotate(45deg) rotate(-45deg)',
      end: 'rotate(-90deg) translate(20%, 20%) rotate(90deg)',
      expected: 'matrix(1, 0, 0, 1, 2.5, -15)',
      round_error_ok: true },
    // test calc() in translate
    // Note that font-size: is 20px, and that percentages are relative
    // to 300px (width) and 50px (height) per the prerequisites in
    // property_database.js
    { start: 'translateX(20%)', /* 60px */
      end: 'translateX(-moz-calc(10% + 1em))', /* 30px + 20px = 50px */
      expected_uncomputed: 'translateX(-moz-calc(17.5% + 0.25em))',
      expected: 'matrix(1, 0, 0, 1, 57.5, 0)' },
    { start: 'translate(-moz-calc(0.75 * 3em + 1.5 * 10%), -moz-calc(0.5 * 5em + 0.5 * 8%))', /* 90px, 52px */
      end: 'rotate(90deg) translateY(20%) rotate(90deg) translateY(-moz-calc(10% + 0.5em)) rotate(180deg)', /* -10px, -15px */
      expected: 'matrix(1, 0, 0, 1, 65, 35.25)' },

    // scale
    { start: 'scale(2)', end: 'none',
      expected_uncomputed: 'scale(1.75)',
      expected: 'matrix(1.75, 0, 0, 1.75, 0, 0)' },
    { start: 'none', end: 'scale(0.4)',
      expected_uncomputed: 'scale(0.85)',
      expected: 'matrix(0.85, 0, 0, 0.85, 0, 0)',
      round_error_ok: true },
    { start: 'scale(2)', end: 'scale(-2)',
      expected_uncomputed: 'scale(1)',
      expected: 'matrix(1, 0, 0, 1, 0, 0)' },
    { start: 'scale(2)', end: 'scale(-6)',
      expected_uncomputed: 'scale(0)',
      expected: 'matrix(0, 0, 0, 0, 0, 0)' },
    { start: 'scale(2, 0.4)', end: 'none',
      expected_uncomputed: 'scale(1.75, 0.55)',
      expected: 'matrix(1.75, 0, 0, 0.55, 0, 0)',
      round_error_ok: true },
    { start: 'scaleX(3)', end: 'none',
      expected_uncomputed: 'scaleX(2.5)',
      expected: 'matrix(2.5, 0, 0, 1, 0, 0)' },
    { start: 'scaleY(5)', end: 'none',
      expected_uncomputed: 'scaleY(4)',
      expected: 'matrix(1, 0, 0, 4, 0, 0)' },
    { start: 'scaleZ(5)', end: 'none',
      expected_uncomputed: 'scaleZ(4)',
      expected: 'matrix3d(1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 4, 0, 0, 0, 0, 1)',
      requires_3d: true },
    { start: 'none', end: 'scale3D(5, 5, 5)',
      expected_uncomputed: 'scale3D(2, 2, 2)',
      expected: 'matrix3d(2, 0, 0, 0, 0, 2, 0, 0, 0, 0, 2, 0, 0, 0, 0, 1)',
      requires_3d: true },

    // skew
    { start: 'skewX(45deg)', end: 'none',
      expected_uncomputed: 'skewX(33.75deg)' },
    { start: 'skewY(45deg)', end: 'none',
      expected_uncomputed: 'skewY(33.75deg)' },
    { start: 'skewX(45deg)', end: 'skewX(-45deg)',
      expected_uncomputed: 'skewX(22.5deg)' },
    { start: 'skewX(0)', end: 'skewX(-45deg)',
      expected_uncomputed: 'skewX(-11.25deg)' },
    { start: 'skewY(45deg)', end: 'skewY(-45deg)',
      expected_uncomputed: 'skewY(22.5deg)' },

    // matrix : skewX
    { start: 'matrix(1, 0, 3, 1, 0, 0)', end: 'none',
      expected: 'matrix(1, 0, ' + 3 * 0.75 + ', 1, 0, 0)',
      round_error_ok: true },
    { start: 'skewX(0)', end: 'skewX(-45deg) translate(0)',
      expected: 'matrix(1, 0, -0.25, 1, 0, 0)',
      round_error_ok: true },
    // matrix : rotate
    { start: 'rotate(-30deg)', end: 'matrix(0, 1, -1, 0, 0, 0)',
      expected: 'matrix(1, 0, 0, 1, 0, 0)',
      round_error_ok: true },
    { start: 'rotate(-30deg) translateX(0)',
      end: 'translateX(0) rotate(-90deg)',
      expected: c('rotate(-45deg)'),
      round_error_ok: true },
    // matrix decomposition of skewY
    { start: 'skewY(60deg)', end: 'skewY(-60deg) translateX(0)',
      /* rotate(30deg) skewX(60deg)/2 scale(2, 0.5) */
      expected: c('rotate(30deg) skewX(' + Math.atan(Math.tan(Math.PI * 60/180) / 2) + 'rad) scale(2, 0.5)'),
      round_error_ok: true },

    // matrix decomposition

    // Four pairs of the same matrix expressed different ways.
    { start: 'matrix(-1, 0, 0, -1, 0pt, 0pt)', /* rotate(180deg) */
      end: 'matrix(1, 0, 0, 1, 0, 0)',
      expected: c('rotate(135deg)') },
    { start: 'scale(-1)', end: 'none',
      expected_uncomputed: 'scale(-0.5)',
      expected: 'matrix(-0.5, 0, 0, -0.5, 0, 0)' },
    { start: 'rotate(180deg)', end: 'none',
      expected_uncomputed: 'rotate(135deg)' },
    { start: 'rotate(-180deg)', end: 'none',
      expected_uncomputed: 'rotate(-135deg)',
      expected: c('rotate(225deg)') },

    // matrix followed by scale
    { start: 'matrix(2, 0, 0, 2, 10px, 20px) scale(2)',
      end: 'none',
      expected: 'matrix(3.0625, 0, 0, 3.0625, 7.5, 15)' },

    // ... and a bunch of similar possibilities.  The spec isn't settled
    // here; there are multiple options.  See:
    // http://lists.w3.org/Archives/Public/www-style/2010Jun/0602.html
    { start: 'matrix(-1, 0, 0, 1, 0pt, 0pt)', /* scaleX(-1) */
      end: 'matrix(1, 0, 0, 1, 0, 0)',
      expected: c('scaleX(-0.5)') },

    { start: 'matrix(1, 0, 0, -1, 0pt, 0pt)', /* rotate(-180deg) scaleX(-1) */
      end: 'matrix(1, 0, 0, 1, 0, 0)',
      expected: c('rotate(-135deg) scaleX(-0.5)') },

    { start: 'matrix(0, 1, 1, 0, 0pt, 0pt)', /* rotate(-90deg) scaleX(-1) */
      end: 'matrix(1, 0, 0, 1, 0, 0)',
      expected: c('rotate(-67.5deg) scaleX(-0.5)') },

    { start: 'matrix(0, -1, 1, 0, 0pt, 0pt)', /* rotate(-90deg) */
      end: 'matrix(1, 0, 0, 1, 0, 0)',
      expected: c('rotate(-67.5deg)') },

    { start: 'matrix(0, 1, -1, 0, 0pt, 0pt)', /* rotate(90deg) */
      end: 'matrix(1, 0, 0, 1, 0, 0)',
      expected: c('rotate(67.5deg)') },

    { start: 'matrix(0, -1, -1, 0, 0pt, 0pt)', /* rotate(90deg) scaleX(-1) */
      end: 'matrix(1, 0, 0, 1, 0, 0)',
      expected: c('rotate(67.5deg) scaleX(-0.5)') },

    // Similar decomposition tests, but with skewX.  I checked visually
    // that the sign of the skew was correct by checking visually that
    // the animations in
    // http://dbaron.org/css/test/2010/transition-negative-determinant
    // don't flip when they finish, and then wrote tests corresponding
    // to the current code's behavior.
    // ... start with four with positive determinants
    { start: 'none',
      end: 'matrix(1, 0, 1.5, 1, 0pt, 0pt)',
      /* skewX(atan(1.5)) */
      expected: 'matrix(1, 0, ' + 1.5 * 0.25 + ', 1, 0, 0)',
      round_error_ok: true },       
    { start: 'none',
      end: 'matrix(-1, 0, 2, -1, 0pt, 0pt)',
              /* rotate(180deg) skewX(atan(-2)) */
      expected: c('rotate(45deg) matrix(1, 0, ' + -2 * 0.25 + ', 1, 0, 0)'),
      round_error_ok: true },
    { start: 'none',
      end: 'matrix(0, -1, 1, -3, 0pt, 0pt)',
              /* rotate(-90deg) skewX(atan(3)) */
      expected: c('rotate(-22.5deg) matrix(1, 0, ' + 3 * 0.25 + ', 1, 0, 0)'),
      round_error_ok: true },
    { start: 'none',
      end: 'matrix(0, 1, -1, 4, 0pt, 0pt)',
              /* rotate(90deg) skewX(atan(4)) */
      expected: c('rotate(22.5deg) matrix(1, 0, ' + 4 * 0.25 + ', 1, 0, 0)'),
      round_error_ok: true },
    // and then four with negative determinants
    { start: 'none',
      end: 'matrix(1, 0, 1, -1, 0pt, 0pt)',
              /* rotate(-180deg) skewX(atan(-1)) scaleX(-1) */
      expected: c('rotate(-45deg) matrix(1, 0, ' + -1 * 0.25 + ', 1, 0, 0) scaleX(0.5)'),
      round_error_ok: true },
    { start: 'none',
      end: 'matrix(-1, 0, -1, 1, 0pt, 0pt)',
              /* skewX(atan(-1)) scaleX(-1) */
      expected: c('matrix(1, 0, ' + -1 * 0.25 + ', 1, 0, 0) scaleX(0.5)') },
    { start: 'none',
      end: 'matrix(0, 1, 1, -2, 0pt, 0pt)',
              /* rotate(-90deg) skewX(atan(2)) scaleX(-1) */
      expected: c('rotate(-22.5deg) matrix(1, 0, ' + 2 * 0.25 + ', 1, 0, 0) scaleX(0.5)'),
      round_error_ok: true },
    { start: 'none',
      end: 'matrix(0, -1, -1, 0.5, 0pt, 0pt)',
              /* rotate(90deg) skewX(atan(0.5)) scaleX(-1) */
      expected: c('rotate(22.5deg) matrix(1, 0, ' + 0.5 * 0.25 + ', 1, 0, 0) scaleX(0.5)'),
      round_error_ok: true },

    // lists vs. matrix decomposition
    { start: 'translate(10px) skewY(45deg)',
      end: 'translate(30px) skewY(-45deg)',
      expected_uncomputed: 'translate(15px) skewY(22.5deg)' },
    { start: 'skewY(45deg) rotate(90deg)',
      end: 'skewY(-45deg) rotate(90deg)',
      expected_uncomputed: 'skewY(22.5deg) rotate(90deg)' },
    { start: 'skewY(45deg) rotate(90deg) translate(0)',
      end: 'skewY(-45deg) rotate(90deg)',
      expected: 'matrix(0, 1, -1, -0.5, 0, 0)',
      round_error_ok: true },
    { start: 'skewX(45deg) rotate(90deg)',
      end: 'skewX(-45deg) rotate(90deg)',
      expected_uncomputed: 'skewX(22.5deg) rotate(90deg)' },
    { start: 'skewX(-60deg) rotate(90deg) translate(0)',
      end: 'skewX(60deg) rotate(90deg)',
      expected: c('rotate(120deg) skewX(' + Math.atan(Math.tan(Math.PI * 60/180) / 2) + 'rad) scale(2, 0.5)'),
      round_error_ok: true },
  ];

  var matrix_re = /^matrix\(([^,]*), ([^,]*), ([^,]*), ([^,]*), ([^,]*), ([^,]*)\)$/;
  for (var i in tests) {
    var test = tests[i];
    if (!("expected" in test)) {
      var v = test.expected_uncomputed;
      if (v.match(matrix_re) && !test.force_compute) {
        test.expected = v;
      } else {
        test.expected = c(v);
      }
    }
  }

  for (var i in tests) {
    var test = tests[i];
    if (test.requires_3d && !transform3D_enabled()) {
      continue;
    }
    div.style.setProperty("transition-property", "none", "");
    div.style.setProperty(prop, test.start, "");
    cs.getPropertyValue(prop);
    div.style.setProperty("transition-property", prop, "");
    div.style.setProperty(prop, test.end, "");
    var actual = cs.getPropertyValue(prop);
    if (!test.round_error_ok || actual == test.expected) {
      // In most cases, we'll get an exact match, but in some cases
      // there can be a small amount of rounding error.
      is(actual, test.expected,
         "interpolation of transitions: " + test.start + " to " + test.end);
    } else {
      function s(mat) {
        return mat.match(matrix_re).slice(1,7);
      }
      var pass = true;
      var actual_split = s(actual);
      var expected_split = s(test.expected);
      for (var i = 0; i < 6; ++i) {
        // Allow differences of 1 at the sixth decimal place, and allow
        // a drop extra for floating point error from the subtraction.
        if (Math.abs(Number(actual_split[i]) - Number(expected_split[i])) >
              0.0000011) {
          pass = false;
        }
      }
      ok(pass,
         "interpolation of transitions: " + test.start + " to " + test.end +
         ": " + actual + " should approximately equal " + test.expected);
    }
  }

  // FIXME: should perhaps test that no clamping occurs
}

</script>
</pre>
</body>
</html>