Anforderungen  |   Konzepte  |   Entwurf  |   Entwicklung  |   Qualitätssicherung  |   Lebenszyklus  |   Steuerung
 
 
 
 


Quellverzeichnis  test_transitions_per_property.html

  Interaktion und
PortierbarkeitHTML
 

 products/Sources/formale Sprachen/C/Firefox/layout/style/test/test_transitions_per_property.html


<!DOCTYPE HTML>
<html>
<!--
https://bugzilla.mozilla.org/show_bug.cgi?id=435441
-->

<head>
  <title>Test for Bug 435441</title>
  <meta charset=utf-8>
  <script src="/tests/SimpleTest/SimpleTest.js"></script>
  <script type="text/javascript" src="property_database.js"></script>
  <script type="text/javascript" src="animation_utils.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">

/* eslint no-shadow: ["error", {"allow": ["prop""div"]}] */
/* eslint-disable dot-notation */


/** Test for Bug 435441 **/

SimpleTest.requestLongerTimeout(2);
SimpleTest.waitForExplicitFinish();

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 FUNC_OVERONE = "cubic-bezier(0.25, 0, 0.75, 3)";

var supported_properties = {
    "aspect-ratio" : [ test_aspect_ratio_transition ],
    "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 ],
    "border-start-start-radius": [ test_radius_transition ],
    "border-start-end-radius": [ test_radius_transition ],
    "border-end-start-radius": [ test_radius_transition ],
    "border-end-end-radius": [ test_radius_transition ],
    "-moz-box-flex": [ test_float_zeroToOne_transition,
                       test_float_aboveOne_transition,
                       test_float_zeroToOne_clamped ],
    "box-shadow": [ test_shadow_transition ],
    "column-count": [ test_pos_integer_or_auto_transition,
                      test_integer_at_least_one_clamping ],
    "column-rule-color": [ test_color_transition,
                           test_currentcolor_transition ],
    "column-rule-width": [ test_length_transition,
                           test_length_clamped ],
    "column-width": [ test_length_transition,
                      test_length_clamped ],
    "cx": [ test_length_transition, test_percent_transition,
            test_length_unclamped, test_percent_unclamped ],
    "cy": [ test_length_transition, test_percent_transition,
            test_length_unclamped, test_percent_unclamped ],
    "background-color": [ test_color_transition,
                          test_currentcolor_transition ],
    "background-position": [ test_background_position_transition,
                             test_length_percent_pair_unclamped ],
    "background-position-x": [ test_background_position_coord_transition,
                               test_length_transition,
                               test_percent_transition,
                             // FIXME: We don't currently test clamping,
                             // since background-position-x uses calc() as
                             // an intermediate form.
                             /* test_length_percent_pair_unclamped */ ],
    "background-position-y": [ test_background_position_coord_transition,
                               test_length_transition,
                               test_percent_transition,
                             // FIXME: We don't currently test clamping,
                             // since background-position-y uses calc() as
                             // an intermediate form.
                             /* test_length_percent_pair_unclamped */ ],
    "background-size": [ test_background_size_transition,
                         test_length_percent_pair_clamped ],
    "border-bottom-color": [ test_color_transition,
                             test_currentcolor_transition ],
    "border-bottom-width": [ test_length_transition,
                             test_length_clamped ],
    "border-left-color": [ test_color_transition,
                           test_currentcolor_transition ],
    "border-left-width": [ test_length_transition,
                           test_length_clamped ],
    "border-right-color": [ test_color_transition,
                            test_currentcolor_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_currentcolor_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 ],
    "accent-color": [ test_color_transition,
                      test_currentcolor_transition,
                      test_auto_color_transition ],
    "caret-color": [ test_color_transition,
                     test_currentcolor_transition,
                     test_auto_color_transition ],
    "clip": [ test_rect_transition ],
    "clip-path": [ test_basic_shape_or_url_transition,
                   test_path_function ],
    "color": [ test_color_transition,
               test_currentcolor_transition ],
    "d": [ test_path_function ],
    "fill": [ test_color_transition,
              test_currentcolor_transition ],
    "fill-opacity" : [ test_float_zeroToOne_transition,
                       // opacity is clamped in computed style
                       // (not parsing/interpolation)
                       test_float_zeroToOne_clamped ],
    "filter" : [ test_filter_transition ],
    "flex-basis": [ test_length_transition, test_percent_transition,
                    test_length_clamped, test_percent_clamped,
                    test_flex_basis_content_transition ],
    "flex-grow": [ test_float_zeroToOne_transition,
                   test_float_aboveOne_transition ],
    "flex-shrink": [ test_float_zeroToOne_transition,
                     test_float_aboveOne_transition ],
    "flood-color": [ test_color_transition,
                     test_currentcolor_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_percent_transition, test_percent_clamped ],
    "font-weight": [ test_font_weight ],
    "column-gap": [ test_grid_gap ],
    "row-gap": [ test_grid_gap ],
    "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_percent_transition,
                        test_length_unclamped, test_percent_unclamped ],
    "lighting-color": [ test_color_transition,
                        test_currentcolor_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 ],
    "mask-position": [ test_background_position_transition,
                       test_length_percent_pair_unclamped ],
    "mask-position-x": [ test_background_position_coord_transition,
                         test_length_transition,
                         test_percent_transition,
                         // FIXME: We don't currently test clamping,
                         // since background-position-x uses calc() as
                         // an intermediate form.
                         /* test_length_percent_pair_unclamped */ ],
    "mask-position-y": [ test_background_position_coord_transition,
                         test_length_transition,
                         test_percent_transition,
                         // FIXME: We don't currently test clamping,
                         // since background-position-y uses calc() as
                         // an intermediate form.
                         /* test_length_percent_pair_unclamped */ ],
    "mask-size": [ test_background_size_transition,
                   test_length_percent_pair_clamped ],
    "max-height": [ test_length_transition, test_percent_transition,
                    test_length_clamped, test_percent_clamped ],
    "max-width": [ test_length_transition, test_percent_transition,
                   test_length_clamped, test_percent_clamped ],
    "min-height": [ test_length_transition, test_percent_transition,
                    test_length_clamped, test_percent_clamped ],
    "min-width": [ test_length_transition, test_percent_transition,
                   test_length_clamped, test_percent_clamped ],
    "object-position": [ test_background_position_transition ],
    "overflow-clip-margin": [ test_length_transition ],
    "opacity" : [ test_float_zeroToOne_transition,
                  // opacity is clamped in computed style
                  // (not parsing/interpolation)
                  test_float_zeroToOne_clamped ],
    "order": [ test_integer_transition ],
    "outline-color": [ test_color_transition,
                       test_currentcolor_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 ],
    "r": [ test_length_transition, test_percent_transition,
           test_length_clamped, test_percent_clamped ],
    "rx": [ test_length_transition, test_percent_transition,
            test_length_clamped, test_percent_clamped ],
    "ry": [ test_length_transition, test_percent_transition,
            test_length_clamped, test_percent_clamped ],
    "shape-image-threshold": [ test_float_zeroToOne_transition,
                               // shape-image-threshold (like opacity) is
                               // clamped in computed style
                               // (not parsing/interpolation)
                               test_float_zeroToOne_clamped ],
    "shape-margin": [ test_length_transition, test_percent_transition,
                      test_length_clamped, test_percent_clamped ],
    "shape-outside": [ test_basic_shape_or_url_transition ],
    "stop-color": [ test_color_transition,
                    test_currentcolor_transition ],
    "stop-opacity" : [ test_float_zeroToOne_transition,
                       // opacity is clamped in computed style
                       // (not parsing/interpolation)
                       test_float_zeroToOne_clamped ],
    "stroke": [ test_color_transition,
                test_currentcolor_transition ],
    "stroke-dasharray": [ test_dasharray_transition ],
    "stroke-dashoffset": [ test_length_transition, test_percent_transition,
                           test_length_unclamped, test_percent_unclamped, ],
    "stroke-miterlimit": [ test_float_zeroToOne_transition,
                           test_float_aboveOne_transition,
                           test_float_aboveZero_clamped ],
    "stroke-opacity" : [ test_float_zeroToOne_transition,
                         // opacity is clamped in computed style
                         // (not parsing/interpolation)
                         test_float_zeroToOne_clamped ],
    "stroke-width": [ test_length_transition, test_percent_transition,
                      test_length_clamped, test_percent_clamped, ],
    "tab-size": [ test_float_zeroToOne_transition,
                  test_float_aboveOne_transition, test_length_clamped ],
    "text-decoration": [ test_color_shorthand_transition,
                         test_currentcolor_shorthand_transition ],
    "text-decoration-color": [ test_color_transition,
                               test_currentcolor_transition ],
    "text-emphasis-color": [ test_color_transition,
                             test_currentcolor_transition ],
    "text-indent": [ test_length_transition, test_percent_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 ],
    "rotate": [ test_rotate_transition ],
    "scale": [ test_scale_transition ],
    "translate": [ test_translate_transition ],
    "vertical-align": [ test_length_transition, test_percent_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_percent_transition,
                      test_length_unclamped, test_percent_unclamped ],
    "x": [ test_length_transition, test_percent_transition,
           test_length_unclamped, test_percent_unclamped ],
    "y": [ test_length_transition, test_percent_transition,
           test_length_unclamped, test_percent_unclamped ],
    "z-index": [ test_integer_transition, test_pos_integer_or_auto_transition ],
    "-webkit-line-clamp": [ test_pos_integer_or_none_transition ],
    "-webkit-text-fill-color": [ test_color_transition,
                                 test_currentcolor_transition ],
    "-webkit-text-stroke-color": [ test_color_transition,
                                   test_currentcolor_transition ],
    "text-underline-offset": [ test_length_transition ],
    "text-decoration-thickness": [ test_length_transition ],
    "scroll-margin-top": [
      test_length_transition,
    ],
    "scroll-margin-right": [
      test_length_transition,
    ],
    "scroll-margin-bottom": [
      test_length_transition,
    ],
    "scroll-margin-left": [
      test_length_transition,
    ],
    "scroll-padding-top": [
      test_length_transition, test_percent_transition,
      test_length_clamped, test_percent_clamped,
    ],
    "scroll-padding-right": [
      test_length_transition, test_percent_transition,
      test_length_clamped, test_percent_clamped,
    ],
    "scroll-padding-bottom": [
      test_length_transition, test_percent_transition,
      test_length_clamped, test_percent_clamped,
    ],
    "scroll-padding-left": [
      test_length_transition, test_percent_transition,
      test_length_clamped, test_percent_clamped,
    ],
    "scrollbar-color": [ test_scrollbar_color_transition ],
};

if (IsCSSPropertyPrefEnabled("layout.css.backdrop-filter.enabled")) {
  supported_properties["backdrop-filter"] = [ test_filter_transition ];
}

if (IsCSSPropertyPrefEnabled("layout.css.font-variations.enabled")) {
  supported_properties["font-variation-settings"] = [ test_font_variations_transition ];
}

if (IsCSSPropertyPrefEnabled("layout.css.contain-intrinsic-size.enabled")) {
  supported_properties["contain-intrinsic-width"] = [ test_length_transition, test_auto_with_length_transition ];
  supported_properties["contain-intrinsic-height"] = supported_properties["contain-intrinsic-width"];
}

supported_properties["content-visibility"] = [test_content_visibility_transition];

if (IsCSSPropertyPrefEnabled("layout.css.zoom.enabled")) {
  Object.assign(supported_properties, {
    "zoom": [ test_number_transition, test_percent_transition ],
  });
}

// For properties which are well-tested by web-platform-tests, we don't need to
// test animations/transitions again on them.
var skipped_transitionable_properties = [
  "border-image-outset",
  "border-image-slice",
  "border-image-width",
  "font-style",  // Tests being added in https://github.com/web-platform-tests/wpt/pull/37570
  "grid-template-columns",
  "grid-template-rows",
  "hyphenate-limit-chars",  // WPT tests being added in bug 1521723.
  "offset-path",
  "offset-distance",
  "offset-rotate",
  "offset-anchor",
  "offset-position",
]

// Logical properties.
for (const logical_side of ["inline-start""inline-end""block-start""block-end"]) {
  supported_properties["border-" + logical_side + "-color"] = supported_properties["border-top-color"];
  supported_properties["border-" + logical_side + "-width"] = supported_properties["border-top-width"];
  supported_properties["margin-" + logical_side] = supported_properties["margin-top"];
  supported_properties["padding-" + logical_side] = supported_properties["padding-top"];
  supported_properties["inset-" + logical_side] = supported_properties["top"];
  supported_properties["scroll-margin-" + logical_side] = supported_properties["scroll-margin-top"];
  supported_properties["scroll-padding-" + logical_side] = supported_properties["scroll-padding-top"];
}

for (const logical_size of ["inline""block"]) {
  supported_properties[logical_size + "-size"] = supported_properties["width"];
  supported_properties["min-" + logical_size + "-size"] = supported_properties["min-width"];
  supported_properties["max-" + logical_size + "-size"] = supported_properties["max-width"];
  if (IsCSSPropertyPrefEnabled("layout.css.contain-intrinsic-size.enabled")) {
    supported_properties["contain-intrinsic-" + logical_size + "-size"] = supported_properties["contain-intrinsic-width"];
  }
}

var div = document.getElementById("display");
var cs = getComputedStyle(div"");
var winUtils = SpecialPowers.getDOMWindowUtils(window);

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

var transformTests = [
  // 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: computeMatrix('rotate(90deg)') },
  { start: 'none', end: 'rotatez(360deg)',
    expected_uncomputed: 'rotate(90deg)',
    expected: computeMatrix('rotate(90deg)') },
  { start: 'none', end: 'rotate(720deg)',
    expected_uncomputed: 'rotate(180deg)',
    expected: computeMatrix('rotate(180deg)') },
  { start: 'none', end: 'rotate(720deg)',
    expected_uncomputed: 'rotatez(180deg)',
    expected: computeMatrix('rotate(180deg)') },
  { start: 'none', end: 'rotate(1080deg)',
    expected_uncomputed: 'rotate(270deg)',
    expected: computeMatrix('rotate(270deg)') },
  { start: 'none', end: 'rotate(1080deg)',
    expected_uncomputed: 'rotate(270deg)',
    expected: computeMatrix('rotatez(270deg)') },
  { start: 'none', end: 'rotate(1440deg)',
    expected_uncomputed: 'rotate(360deg)',
    expected: computeMatrix('scale(1)'),
    round_error_ok: true },
  { start: 'none', end: 'rotatey(60deg)',
    expected_uncomputed: 'rotatey(15deg)',
    expected: computeMatrix('rotatey(15deg)') },
  { start: 'none', end: 'rotatey(720deg)',
    expected_uncomputed: 'rotatey(180deg)',
    expected: computeMatrix('rotatey(180deg)') },
  { start: 'none', end: 'rotatex(60deg)',
    expected_uncomputed: 'rotatex(15deg)',
    expected: computeMatrix('rotatex(15deg)') },
  { start: 'none', end: 'rotatex(720deg)',
    expected_uncomputed: 'rotatex(180deg)',
    expected: computeMatrix('rotatex(180deg)') },

  // 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)' },
  { 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)' },
  // 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: 'matrix(1, 0, 0, 1, 0, 0)',
    end: 'rotate(90deg) translate(20%, 20%) rotate(-90deg)',
    expected: 'matrix(1, 0, 0, 1, -2.5, 15)',
    round_error_ok: true },
  { start: 'matrix(1, 0, 0, 1, 0, 0)',
    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(calc(10% + 1em))', /* 30px + 20px = 50px */
    expected_uncomputed: 'translateX(calc(17.5% + 0.25em))',
    expected: 'matrix(1, 0, 0, 1, 57.5, 0)' },
  { start: 'translate(calc(0.75 * 3em + 1.5 * 10%), calc(0.5 * 5em + 0.5 * 8%))', /* 90px, 52px */
    end: 'rotate(90deg) translateY(20%) rotate(90deg) translateY(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)' },
  { 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)' },

  // skew
  { start: 'skewX(45deg)', end: 'none',
    expected_uncomputed: 'skewX(33.75deg)' },
  { start: 'skewY(45deg)', end: 'none',
    expected_uncomputed: 'skewY(33.75deg)' },
  { start: 'skew(45deg)', end: 'none',
    expected_uncomputed: 'skew(33.75deg)' },
  { start: 'skew(45deg, 45deg)', end: 'none',
    expected_uncomputed: 'skew(33.75deg, 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_uncomputed: 'skewX(-11.25deg) translate(0)' },
  // 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: computeMatrix('rotate(-45deg)'),
    round_error_ok: true },
  // extended shorter transform list
  { start: 'skewY(60deg)', end: 'skewY(-60deg) translateX(0)',
    expected_uncomputed: 'skewY(30deg) translateX(0)' },


  // matrix decomposition

  // Four pairs of the same matrix expressed different ways.
  { start: 'matrix(-1, 0, 0, -1, 0, 0)', /* rotate(180deg) */
    end: 'matrix(1, 0, 0, 1, 0, 0)',
    expected: computeMatrix('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: computeMatrix('rotate(225deg)') },

  // matrix followed by scale
  { start: 'matrix(2, 0, 0, 2, 10, 20) 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, 0, 0)', /* scaleX(-1) */
    end: 'matrix(1, 0, 0, 1, 0, 0)',
    expected: computeMatrix('scaleX(-0.5)') },

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

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

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

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

  { start: 'matrix(0, -1, -1, 0, 0, 0)', /* rotate(90deg) scaleX(-1) */
    end: 'matrix(1, 0, 0, 1, 0, 0)',
    expected: computeMatrix('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
  // https://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, 0, 0)',
    /* 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, 0, 0)',
            /* rotate(180deg) skewX(atan(-2)) */
    expected: computeMatrix('rotate(45deg) matrix(1, 0, ' + -2 * 0.25 + ', 1, 0, 0)'),
    round_error_ok: true },
  { start: 'none',
    end: 'matrix(0, -1, 1, -3, 0, 0)',
            /* rotate(-90deg) skewX(atan(3)) */
    expected: computeMatrix('rotate(-22.5deg) matrix(1, 0, ' + 3 * 0.25 + ', 1, 0, 0)'),
    round_error_ok: true },
  { start: 'none',
    end: 'matrix(0, 1, -1, 4, 0, 0)',
            /* rotate(90deg) skewX(atan(4)) */
    expected: computeMatrix('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, 0, 0)',
            /* rotate(-180deg) skewX(atan(-1)) scaleX(-1) */
    expected: computeMatrix('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, 0, 0)',
            /* skewX(atan(-1)) scaleX(-1) */
    expected: computeMatrix('matrix(1, 0, ' + -1 * 0.25 + ', 1, 0, 0) scaleX(0.5)') },
  { start: 'none',
    end: 'matrix(0, 1, 1, -2, 0, 0)',
            /* rotate(-90deg) skewX(atan(2)) scaleX(-1) */
    expected: computeMatrix('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, 0, 0)',
            /* rotate(90deg) skewX(atan(0.5)) scaleX(-1) */
    expected: computeMatrix('rotate(22.5deg) matrix(1, 0, ' + 0.5 * 0.25 + ', 1, 0, 0) scaleX(0.5)'),
    round_error_ok: true },

  // lists
  { 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: 'skewX(45deg) rotate(90deg)',
    end: 'skewX(-45deg) rotate(90deg)',
    expected_uncomputed: 'skewX(22.5deg) rotate(90deg)' },

  // extended lists
  { start: 'skewY(45deg) rotate(90deg) translate(0)',
    end: 'skewY(-45deg) rotate(90deg)',
    expected_uncomputed: 'skewY(22.5deg) rotate(90deg) translate(0)' },
  { start: 'skewX(-60deg) rotate(90deg) translate(0)',
    end: 'skewX(60deg) rotate(90deg)',
    expected_uncomputed: 'skewX(-30deg) rotate(90deg) translate(0)' },
];

// We intentionally use a non-default reference-box so we always serialize it.
// Therefore, we can reuse these tests for clip-path and shape-outside.
// Bug 1313619: Add some tests for two basic shapes with an explicit
// reference-box and a default one, for each property (because they use
// different default reference-box).
const basicShapesTests = [
  { start: "none", end: "none",
    expected: ["none"] },
  // none to shape
  { start: "none",
    end: "circle(500px at 500px 500px) content-box",
    expected: ["circle", ["500px at 500px 500px"], "content-box"]
  },
  { start: "none",
    end: "ellipse(500px 500px at 500px 500px) content-box",
    expected: ["ellipse", ["500px 500px at 500px 500px"], "content-box"]
  },
  { start: "none",
    end: "polygon(evenodd, 500px 500px, 500px 500px) content-box",
    expected: ["polygon", ["evenodd, 500px 500px, 500px 500px"], "content-box"]
  },
  { start: "none",
    end: "inset(500px 500px 500px 500px round 500px 500px) content-box",
    expected: ["inset", ["500px round 500px"], "content-box"]
  },
  // matching functions
  { start: "circle(100px)", end: "circle(500px)",
    expected: ["circle", ["200px"]] },
  { start: "ellipse(100px 100px)", end: "ellipse(500px 500px)",
    expected: ["ellipse", ["200px 200px"]] },
  { start: "circle(100px at 100px 100px) content-box",
    end: "circle(500px at 500px 500px) content-box",
    expected: ["circle", ["200px at 200px 200px"], "content-box"]
  },
  { start: "ellipse(100px 100px at 100px 100px) content-box",
    end: "ellipse(500px 500px at 500px 500px) content-box",
    expected: ["ellipse", ["200px 200px at 200px 200px"], "content-box"]
  },
  { start: "polygon(evenodd, 100px 100px, 100px 100px) content-box",
    end: "polygon(evenodd, 500px 500px, 500px 500px) content-box",
    expected: ["polygon", ["evenodd, 200px 200px, 200px 200px"], "content-box"]
  },
  { start: "inset(100px 100px 100px 100px round 100px 100px) content-box",
    end: "inset(500px 500px 500px 500px round 500px 500px) content-box",
    expected: ["inset", ["200px round 200px"], "content-box"]
  },
  // matching functions percentage
  { start: "circle(100%)", end: "circle(500%)",
    expected: ["circle", ["200%"]] },
  { start: "ellipse(100% 100%)", end: "ellipse(500% 500%)",
    expected: ["ellipse", ["200% 200%"]] },
  { start: "circle(100% at 100% 100%) content-box",
    end: "circle(500% at 500% 500%) content-box",
    expected: ["circle", ["200% at 200% 200%"], "content-box"]
  },
  { start: "ellipse(100% 100% at 100% 100%) content-box",
    end: "ellipse(500% 500% at 500% 500%) content-box",
    expected: ["ellipse", ["200% 200% at 200% 200%"], "content-box"]
  },
  { start: "polygon(evenodd, 100% 100%, 100% 100%) content-box",
    end: "polygon(evenodd, 500% 500%, 500% 500%) content-box",
    expected: ["polygon", ["evenodd, 200% 200%, 200% 200%"], "content-box"]
  },
  { start: "inset(100% 100% 100% 100% round 100% 100%) content-box",
    end: "inset(500% 500% 500% 500% round 500% 500%) content-box",
    expected: ["inset", ["200% round 200%"], "content-box"] },
  // matching functions with calc() values
  { start: "circle(calc(80px + 20px))", end: "circle(calc(200px + 300px))",
    expected: ["circle", ["200px"]] },
  { start: "circle(calc(80% + 20%))", end: "circle(calc(200% + 300%))",
    expected: ["circle", ["200%"]] },
  { start: "circle(calc(10px + 20%))", end: "circle(calc(50px + 40%))",
    expected: ["circle", ["calc(25% + 20px)"]] },
  // matching functions with interpolation between percentage/pixel values
  { start: "circle(20px)", end: "circle(100%)",
    expected: ["circle", ["calc(25% + 15px)"]] },
  { start: "ellipse(100% 100px at 8px 20%) content-box",
    end:   "ellipse(40px 4%    at 80% 60px) content-box",
    expected: ["ellipse", ["calc(75% + 10px) calc(1% + 75px) at " +
                           "calc(20% + 6px) calc(15% + 15px)"],
               "content-box"] },
  // no interpolation for keywords
  { start: "circle()", end: "circle(50px)",
    expected: ["circle", ["50px"]] },
  { start: "circle(closest-side)", end: "circle(500px)",
    expected: ["circle", ["500px"]] },
  { start: "circle(farthest-side)", end: "circle(500px)",
    expected: ["circle", ["500px"]] },
  { start: "circle(500px)", end: "circle(farthest-side)",
    expected: ["circle", ["farthest-side"]]},
  { start: "circle(500px)", end: "circle(closest-side)",
    expected: ["circle", [""]]},
  { start: "ellipse()", end: "ellipse(50px 50px)",
    expected: ["ellipse", ["50px 50px"]] },
  { start: "ellipse(closest-side closest-side)", end: "ellipse(500px 500px)",
    expected: ["ellipse", ["500px 500px"]] },
  { start: "ellipse(farthest-side closest-side)", end: "ellipse(500px 500px)",
    expected: ["ellipse", ["500px 500px"]] },
  { start: "ellipse(farthest-side farthest-side)", end: "ellipse(500px 500px)",
    expected: ["ellipse", ["500px 500px"]] },
  { start: "ellipse(500px 500px)", end: "ellipse(farthest-side farthest-side)",
    expected: ["ellipse", ["farthest-side farthest-side"]] },
  { start: "ellipse(500px 500px)", end: "ellipse(closest-side closest-side)",
    expected: ["ellipse", [""]] },
  // mismatching boxes
  { start: "circle(100px at 100px 100px) border-box",
    end: "circle(500px at 500px 500px) content-box",
    expected: ["circle", ["500px at 500px 500px"], "content-box"]
  },
  { start: "ellipse(100px 100px at 100px 100px) border-box",
    end: "ellipse(500px 500px at 500px 500px) content-box",
    expected: ["ellipse", ["500px 500px at 500px 500px"], "content-box"]
  },
  { start: "polygon(evenodd, 100px 100px, 100px 100px) border-box",
    end: "polygon(evenodd, 500px 500px, 500px 500px) content-box",
    expected: ["polygon", ["evenodd, 500px 500px, 500px 500px"], "content-box"]
  },
  { start: "inset(100px 100px 100px 100px round 100px 100px) border-box",
    end: "inset(500px 500px 500px 500px round 500px 500px) content-box",
    expected: ["inset", ["500px round 500px"], "content-box"]
  },
  // mismatching functions
  { start: "circle(100px at 100px 100px) content-box",
    end: "ellipse(500px 500px at 500px 500px) content-box",
    expected: ["ellipse", ["500px 500px at 500px 500px"], "content-box"]
  },
  { start: "inset(0px round 20px)", end: "ellipse(500px 500px)",
    expected: ["ellipse", ["500px 500px"]]
  },
  // shape to reference box
  { start: "circle(20px)", end: "content-box", expected: ["content-box"] },
  { start: "content-box", end: "circle(20px)", expected: ["circle", ["20px"]] },
  // url to shape
  { start: "circle(20px)", end: "url(http://localhost/a.png)", expected: ["url", ["\"http://localhost/a.png\""]] },
  { start: "url(http://localhost/a.png)", end: "circle(20px)", expected: ["circle", ["20px"]] },
  // url to none
  { start: "none", end: "url(http://localhost/a.png)", expected: ["url", ["\"http://localhost/a.png\""]] },
  { start: "http://localhost/a.png", end: "none", expected: ["none"] },
];

const basicShapesWithFragmentUrlTests = [
  // Fragment url to shape
  { start: "circle(20px)", end: "url('#a')", expected: ["url", ["\"#a\""]] },
  { start: "url('#a')", end: "circle(20px)", expected: ["circle", ["20px"]] },
  // Fragment url to none
  { start: "none", end: "url('#a')", expected: ["url", ["\"#a\""]] },
  { start: "url('#a')", end: "none", expected: ["none"] },
];

// We have a lot of tests in web-platform-tests already, so here we only test
// basic interpolation cases.
const pathFunctionTests = [
  { start: "none", end: "none",
    expected: ["none"] },
  // none to path
  { start: "none",
    end: "path('M 100 100')",
    expected: ["path"'"M 100 100"']
  },
  // path to none
  { start: "path('M 100 100')",
    end: "none",
    expected: ["none"]
  },
  // mismatch
  {
    start: "path('M 0 0 H 100 H 200')",
    end: "path('M 0 0 H 500')",
    expected: ["path"'"M 0 0 H 500"']
  },
  {
    start: "path('M 0 0 V 100')",
    end: "path('M 0 0 H 500')",
    expected: ["path"'"M 0 0 H 500"']
  },
  // match
  {
    start: "path('M 100 100')",
    end: "path('M 100 500')",
    expected: ["path"'"M 100 200"']
  },
  {
    start: "path('M 10 10 L 100 100')",
    end: "path('M 10 10 L 100 500')",
    expected: ["path"'"M 10 10 L 100 200"']
  },
  {
    start: "path('M 10 10 H 100')",
    end: "path('M 10 10 H 500')",
    expected: ["path"'"M 10 10 H 200"']
  },
  {
    start: "path('M 10 10 V 100')",
    end: "path('M 10 10 V 500')",
    expected: ["path"'"M 10 10 V 200"']
  },
  {
    start: "path('M 10 10 C 32 42 52 62 120 2200')",
    end: "path('M 10 10 C 40 50 60 70 200 3000')",
    expected: ["path"'"M 10 10 C 34 44 54 64 140 2400"']
  },
  {
    start: "path('M 10 10 S 45 67 89 123')",
    end: "path('M 10 10 S 61 51 113 99')",
    expected: ["path"'"M 10 10 S 49 63 95 117"']
  },
  {
    start: "path('M 10 10 Q 32 42 120 2200')",
    end: "path('M 10 10 Q 40 50 200 3000')",
    expected: ["path"'"M 10 10 Q 34 44 140 2400"']
  },
  {
    start: "path('M 10 10 T 100 200')",
    end: "path('M 10 10 T 500 280')",
    expected: ["path"'"M 10 10 T 200 220"']
  },
  {
    start: "path('M 10 10 A 10 20 30 0 1 140 450')",
    end: "path('M 10 10 A 50 60 70 0 1 380 290')",
    expected: ["path"'"M 10 10 A 20 30 40 0 1 200 410"']
  },
  {
    start: "path('M 10 10 A 10 20 30 1 0 140 450')",
    end: "path('M 10 10 A 50 60 70 0 1 380 290')",
    expected: ["path"'"M 10 10 A 20 30 40 1 0 200 410"']
  },
  // mix relative and absolute coordinates
  {
    start: "path('m 10 20 h 30 v 60 h 10 v -10 l 110 60')",
    // =="path('M  10  20 H  40 V  80 H  50 V  70 L 160 130')"
    end: "path('M 130 140 H 120 V 160 H 130 V 150 L 200 170')",
    expected: ["path"'"M 40 50 H 60 V 100 H 70 V 90 L 170 140"']
  },
];

const clipPathPathFunctionTests = [
  // match fill-rule
  {
    start: "path(nonzero, 'M 100 100')",
    end: "path(nonzero, 'M 100 500')",
    expected: ["path"'"M 100 200"']
  },
  {
    start: "path(evenodd, 'M 100 100')",
    end: "path(evenodd, 'M 100 500')",
    expected: ["path"'evenodd, "M 100 200"']
  },
  // mismatch fill-rule
  {
    start: "path(nonzero, 'M 100 100')",
    end: "path(evenodd, 'M 100 500')",
    expected: ["path"'evenodd, "M 100 500"']
  },
];

var filterTests = [
  { start: "none", end: "none",
    expected: ["none"] },
  // function from none (number/length)
  { start: "none", end: "brightness(0.5)",
    expected: ["brightness", 0.875] },
  { start: "none", end: "contrast(0.5)",
    expected: ["contrast", 0.875] },
  { start: "none", end: "grayscale(0.5)",
    expected: ["grayscale", 0.125] },
  { start: "none", end: "invert(0.5)",
    expected: ["invert", 0.125] },
  { start: "none", end: "opacity(0.5)",
    expected: ["opacity", 0.875] },
  { start: "none", end: "saturate(0.5)",
    expected: ["saturate", 0.875] },
  { start: "none", end: "sepia(0.5)",
    expected: ["sepia", 0.125] },
  { start: "none", end: "blur(50px)",
    expected: ["blur", 12.5] },
  // function to none (number/length)
  { start: "brightness(0.5)", end: "none",
    expected: ["brightness", 0.625] },
  { start: "contrast(0.5)", end: "none",
    expected: ["contrast", 0.625] },
  { start: "grayscale(0.5)", end: "none",
    expected: ["grayscale", 0.375] },
  { start: "invert(0.5)", end: "none",
    expected: ["invert", 0.375] },
  { start: "opacity(0.5)", end: "none",
    expected: ["opacity", 0.625] },
  { start: "saturate(0.5)", end: "none",
    expected: ["saturate", 0.625] },
  { start: "sepia(0.5)", end: "none",
    expected: ["sepia", 0.375] },
  { start: "blur(50px)", end: "none",
    expected: ["blur", 37.5] },
  // function to same function (number/length)
  { start: "brightness(0.25)", end: "brightness(0.75)",
    expected: ["brightness", 0.375] },
  { start: "contrast(0.25)", end: "contrast(0.75)",
    expected: ["contrast", 0.375] },
  { start: "grayscale(0.25)", end: "grayscale(0.75)",
    expected: ["grayscale", 0.375] },
  { start: "invert(0.25)", end: "invert(0.75)",
    expected: ["invert", 0.375] },
  { start: "opacity(0.25)", end: "opacity(0.75)",
    expected: ["opacity", 0.375] },
  { start: "saturate(0.25)", end: "saturate(0.75)",
    expected: ["saturate", 0.375] },
  { start: "sepia(0.25)", end: "sepia(0.75)",
    expected: ["sepia", 0.375] },
  { start: "blur(25px)", end: "blur(75px)",
    expected: ["blur", 37.5] },
  // function to same function (percent)
  { start: "brightness(25%)", end: "brightness(75%)",
    expected: ["brightness", 0.375] },
  { start: "contrast(25%)", end: "contrast(75%)",
    expected: ["contrast", 0.375] },
  { start: "grayscale(25%)", end: "grayscale(75%)",
    expected: ["grayscale", 0.375] },
  { start: "invert(25%)", end: "invert(75%)",
    expected: ["invert", 0.375] },
  { start: "opacity(25%)", end: "opacity(75%)",
    expected: ["opacity", 0.375] },
  { start: "saturate(25%)", end: "saturate(75%)",
    expected: ["saturate", 0.375] },
  { start: "sepia(25%)", end: "sepia(75%)",
    expected: ["sepia", 0.375] },
  // function to same function (percent, number/length)
  { start: "brightness(0.25)", end: "brightness(75%)",
    expected: ["brightness", 0.375] },
  { start: "contrast(25%)", end: "contrast(0.75)",
    expected: ["contrast", 0.375] },
  // hue-rotate with different angle values
  { start: "hue-rotate(0deg)", end: "hue-rotate(720deg)",
    expected: ["hue-rotate""180deg"] },
  { start: "hue-rotate(0rad)", end: "hue-rotate("+4*Math.PI+"rad)",
    expected: ["hue-rotate""180deg"] },
  { start: "hue-rotate(0grad)", end: "hue-rotate(800grad)",
    expected: ["hue-rotate""180deg"] },
  { start: "hue-rotate(0turn)", end: "hue-rotate(2turn)",
    expected: ["hue-rotate""180deg"] },
  { start: "hue-rotate(0deg)", end: "hue-rotate("+4*Math.PI+"rad)",
    expected: ["hue-rotate""180deg"] },
  { start: "hue-rotate(0turn)", end: "hue-rotate(800grad)",
    expected: ["hue-rotate""180deg"] },
  { start: "hue-rotate(0grad)", end: "hue-rotate("+4*Math.PI+"rad)",
    expected: ["hue-rotate""180deg"] },
  { start: "hue-rotate(0grad)", end: "hue-rotate(0turn)",
    expected: ["hue-rotate""0deg"] },
  // multiple matching functions, same length
  { start: "contrast(25%) brightness(0.25) blur(25px) sepia(75%)",
    end: "contrast(75%) brightness(0.75) blur(75px) sepia(25%)",
    expected: ["contrast", 0.375, "brightness", 0.375, "blur", 37.5, "sepia", 0.625] },
  { start: "invert(25%) brightness(0.25) blur(25px) invert(50%) brightness(0.5) blur(50px)",
    end: "invert(75%) brightness(0.75) blur(75px)",
    expected: ["invert", 0.375, "brightness", 0.375, "blur", 37.5, "invert", 0.375, "brightness", 0.625, "blur", 37.5] },
  // multiple matching functions, different length
  { start: "contrast(25%) brightness(0.5) blur(50px)",
    end: "contrast(75%)",
    expected: ["contrast", 0.375, "brightness", 0.625, "blur", 37.5] },
  // mismatching filter functions
  { start: "contrast(0%)", end: "blur(10px)",
    expected: ["blur", 10] },
  // not supported interpolations
  { start: "none", end: "url('#b')",
    expected: ["url""\"#b\""] },
  { start: "url('#a')", end: "none",
    expected: ["none"] },
  { start: "url('#a')", end: "url('#b')",
    expected: ["url""\"#b\""] },
  { start: "url('#a')", end: "blur(10px)",
    expected: ["blur", 10] },
  { start: "blur(10px)", end: "url('#a')",
    expected: ["url""\"#a\""] },
  { start: "blur(0px) url('#a')", end: "blur(20px)",
    expected: ["blur", 20] },
  { start: "blur(0px)", end: "blur(20px) url('#a')",
    expected: ["blur", 20, "url""\"#a\""] },
  { start: "contrast(0.25) brightness(0.25) blur(25px)",
    end: "contrast(0.75) url('#a')",
    expected: ["contrast", 0.75, "url""\"#a\""] },
  { start: "contrast(0.25) brightness(0.25) blur(75px)",
    end: "brightness(0.75) contrast(0.75) blur(25px)",
    expected: ["brightness", 0.75, "contrast", 0.75, "blur", 25] },
  { start: "contrast(0.25) brightness(0.25) blur(25px)",
    end: "contrast(0.75) brightness(0.75) contrast(0.75)",
    expected: ["contrast", 0.75, "brightness", 0.75, "contrast", 0.75] },
  // drop-shadow animation
  { start: "none",
    end: "drop-shadow(rgb(0, 0, 0) 4px 4px 0px)",
    expected: ["drop-shadow""rgba(0, 0, 0, 0.25) 1px 1px 0px"] },
  { start: "drop-shadow(rgb(0, 0, 0) 0px 0px 0px)",
    end: "drop-shadow(rgb(0, 0, 0) 4px 4px 0px)",
    expected: ["drop-shadow""rgb(0, 0, 0) 1px 1px 0px"] },
  { start: "drop-shadow(#038000 4px 4px)",
    end: "drop-shadow(8px 8px 8px red)",
    expected: ["drop-shadow""rgb(66, 96, 0) 5px 5px 2px"] },
  { start: "blur(25px) drop-shadow(8px 8px)",
    end: "blur(75px)",
    expected: ["blur", 37.5, "drop-shadow""rgba(0, 0, 0, 0.75) 6px 6px 0px"] },
  { start: "blur(75px)",
    end: "blur(25px) drop-shadow(8px 8px)",
    expected: ["blur", 62.5, "drop-shadow""rgba(0, 0, 0, 0.25) 2px 2px 0px"] },
  { start: "drop-shadow(2px 2px blue)",
    end: "none",
    expected: ["drop-shadow""rgba(0, 0, 255, 0.75) 1.5px 1.5px 0px"] },
];

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");
}

// Return a consistent sampling of |count| values out of |array|.
function sample_array(array, count) {
  if (count <= 0) {
    ok(false, "unexpected count");
    return [];
  }
  var ratio = array.length / count;
  if (ratio <= 1) {
    return array;
  }
  var result = new Array(count);
  for (let i = 0; i < count; ++i) {
    result[i] = array[Math.floor(i * ratio)];
  }
  return result;
}

// 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 prop_info = gCSSProperties[prop];
  if (!(prop in supported_properties) &&
      !skipped_transitionable_properties.includes(prop) &&
      prop_info.type != CSS_TYPE_TRUE_SHORTHAND &&
      prop_info.type != CSS_TYPE_LEGACY_SHORTHAND &&
      !("alias_for" in prop_info) &&
      !prop.match(/^transition-/) &&
      prop != "mask") {

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

    var all_values = prop_info.initial_values.concat(prop_info.other_values);

    if (all_values.length > 50) {
      // Since we're using an O(N^2) algorithm here, reduce the list of
      // values that we want to test.  (This test is really only testing
      // that somebody didn't make a property animatable without
      // modifying this test.  The odds of somebody doing that without
      // making at least one of the many pairs of values we have left
      // animatable seems pretty low, at least relative to the chance
      // that any pair of the values listed in property_database.js is
      // animatable.)
      //
      // That said, we still try to use all of the start of the list on
      // the assumption that the more basic values are likely to be at
      // the beginning of the list.
      all_values = [].concat(prop_info.initial_values.slice(0,2),
                             sample_array(prop_info.initial_values.slice(2), 6),
                             prop_info.other_values.slice(0, 10),
                             sample_array(prop_info.other_values.slice(10), 40));
    }

    var all_computed = [];
    for (var idx in all_values) {
      let 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 (let i = 0; i < all_values.length; ++i) {
      for (let 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 prop_info) {
      var prereqs = prop_info.prerequisites;
      for (var prereq in prereqs) {
        div.style.removeProperty(prereq);
      }
    }
  }
}

for (let zoomed of [true, false]) {
  info(`testing all supported properties (zoomed: ${zoomed})`);
  // 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""");
  div.style.setProperty("zoom", zoomed ? "2" : "1");
  for (prop in supported_properties) {
    info(`testing ${prop}`);
    var tinfo = supported_properties[prop];
    var prop_info = gCSSProperties[prop];

    isnot(prop_info.type, CSS_TYPE_TRUE_SHORTHAND,
          prop + " must not be a shorthand");
    if ("prerequisites" in prop_info) {
      var prereqs = prop_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 prop_info) {
      var prereqs = prop_info.prerequisites;
      for (var prereq in prereqs) {
        div.style.removeProperty(prereq);
      }
    }
  }
  div.style.removeProperty("transition");
  div.style.removeProperty("zoom");
}

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

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""");
  let zero_val = cs.getPropertyValue(prop); // Flushes
  div.style.setProperty("transition-property", prop, "");
  div.style.setProperty(prop, "100px""");
  (is_clamped ? is : isnot)(cs.getPropertyValue(prop), zero_val,
     "length-valued property " + prop + ": clamping of negatives");
  div.style.setProperty("transition-timing-function""linear""");
}

// Test transition to/from the special 'flex-basis: content' keyword.
function test_flex_basis_content_transition(prop) {
  is(prop, "flex-basis""this test function should only be called for 'flex-basis'");

  // Test transition from length to 'content':
  div.style.setProperty("transition-property""none""");
  div.style.setProperty(prop, "8px""");
  is(cs.getPropertyValue(prop), "8px",
     "property " + prop + ": computed value before transition to 'content'");
  div.style.setProperty("transition-property", prop, "");
  div.style.setProperty(prop, "content""");
  is(cs.getPropertyValue(prop), "content",
     "property " + prop + ": transition to 'content' (should be discrete)");

  // Test transition from 'content' to length:
  div.style.setProperty("transition-property""none""");
  div.style.setProperty(prop, "content""");
  is(cs.getPropertyValue(prop), "content",
     "property " + prop + ": computed value before transition from 'content'");
  div.style.setProperty("transition-property", prop, "");
  div.style.setProperty(prop, "6px""");
  is(cs.getPropertyValue(prop), "6px",
     "property " + prop + ": transition from 'content' (should be discrete)");
}

// 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)
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_aboveZero_clamped(prop) {
  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, "5""");
  is(cs.getPropertyValue(prop), "0",
     "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""");
}

// FIXME: This doesn't deal well with properties for which the resolved value
// is not the used value, like stroke-dashoffset or stroke-width.
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, "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, "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%""calc(37.5% + 50px)""200px");
  check_distance(prop, "calc(25% + 100px)""calc(37.5% + 75px)",
                       "75%");
  check_distance(prop, "150px""calc(125px + 12.5%)",
                       "calc(50% + 50px)");
}

// This can deal well with properties for which the computed value
// is not the used value, e.g. translate.
function test_calc_wrapped_calc_transition(prop) {
  // Test interpolation that computes to calc() (transition from % to px)
  div.style.setProperty("transition-property""none""");
  div.style.setProperty(prop, "20%""");
  is(cs.getPropertyValue(prop), "20%",
     "property " + prop + ": computed value before transition");
  div.style.setProperty("transition-property", prop, "");
  div.style.setProperty(prop, "12px""");
  is(cs.getPropertyValue(prop), "calc(15% + 3px)",
     "property " + prop + ": interpolation that computes to calc()");

  check_distance(prop, "20%""calc(15% + 3px)""12px");

  // Test interpolation that computes to calc() (transition from px to %)
  div.style.setProperty("transition-property""none""");
  div.style.setProperty(prop, "12px""");
  is(cs.getPropertyValue(prop), "12px",
     "property " + prop + ": computed value before transition");
  div.style.setProperty("transition-property", prop, "");
  div.style.setProperty(prop, "20%""");
  is(cs.getPropertyValue(prop), "calc(5% + 9px)",
     "property " + prop + ": interpolation that computes to calc()");

  check_distance(prop, "12px""calc(5% + 9px)""20%");

  // Test interpolation between calc() and non-calc()
  div.style.setProperty("transition-property""none""");
  div.style.setProperty(prop, "calc(40px + 10%)""");
  is(cs.getPropertyValue(prop), "calc(10% + 40px)",
     "property " + prop + ": computed value before transition");
  div.style.setProperty("transition-property", prop, "");
  div.style.setProperty(prop, "30%""");
  is(cs.getPropertyValue(prop), "calc(15% + 30px)",
     "property " + prop + ": interpolation between calc() and non-calc()");

  check_distance(prop, "calc(40px + 10%)""calc(30px + 15%)""30%");

  div.style.setProperty("transition-property""none""");
  div.style.setProperty(prop, "16px""");
  is(cs.getPropertyValue(prop), "16px",
     "property " + prop + ": computed value before transition");
  div.style.setProperty("transition-property", prop, "");
  div.style.setProperty(prop, "calc(8px + 60%)""");
  is(cs.getPropertyValue(prop), "calc(15% + 14px)",
     "property " + prop + ": interpolation between calc() and non-calc()");

  check_distance(prop, "16px""calc(14px + 15%)""calc(8px + 60%)");
}

function test_number_transition(prop) {
  div.style.transitionProperty = 'none';
  div.style[prop] = '10';
  is(cs[prop], '10',
     `number property ${prop}: computed value before transition`);
  div.style.transitionProperty = prop;
  div.style[prop] = '50';
  is(cs[prop], '20', `number property ${prop}: interpolation of numbers`);
  check_distance(prop, '10''20''50');
}

function test_angle_transition(prop) {
  div.style.transitionProperty = 'none';
  div.style[prop] = '45deg';
  is(cs[prop], '45deg',
     `angle property ${prop}: computed value before transition`);
  div.style.transitionProperty = prop;
  div.style[prop] = '145deg';
  is(cs[prop], '70deg',
     `angle property ${prop}: interpolation of angles`);
  check_distance(prop, '45deg''70deg''145deg');
}

function get_color_options(options) {
  let {
    get_color = x => x,
    set_color = x => x,
    is_shorthand = false,
  } = options;
  return { get_color, set_color, is_shorthand };
}

function test_color_transition(prop, options={}) {
  let { get_color, set_color, is_shorthand } = get_color_options(options);

  div.style.setProperty("transition-property""none""");
  div.style.setProperty(prop, set_color("rgb(255, 28, 0)"), "");
  is(get_color(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, set_color("rgb(75, 84, 128)"), "");
  is(get_color(cs.getPropertyValue(prop)), "rgb(210, 42, 32)",
     "color-valued property " + prop + ": interpolation of colors");

  if (!is_shorthand) {
    check_distance(prop, set_color("rgb(255, 28, 0)"),
                   set_color("rgb(210, 42, 32)"),
                   set_color("rgb(75, 84, 128)"));
  }

  div.style.setProperty("transition-timing-function", FUNC_NEGATIVE, "");
  div.style.setProperty("transition-property""none""");
  div.style.setProperty(prop, set_color("rgb(0, 255, 0)"), "");
  var color = get_color(cs.getPropertyValue(prop));
  var vals = color.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, set_color("rgb(255, 0, 128)"), "");
  // FIXME: Once we support non-sRGB colors, these tests will need fixing.
  color = get_color(cs.getPropertyValue(prop));
  vals = color.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_currentcolor_transition(prop, options={}) {
  let { get_color, set_color } = get_color_options(options);

  const msg_prefix = `color-valued property ${prop}: `;
  div.style.setProperty("transition-property""none""");
  (prop == "color" ? div.parentNode : div).style.
    setProperty("color""rgb(128, 0, 0)""");
  div.style.setProperty(prop, set_color("rgb(0, 0, 128)"), "");
  is(get_color(cs.getPropertyValue(prop)), "rgb(0, 0, 128)",
     msg_prefix + "computed value before transition");
  div.style.setProperty("transition-property", prop, "");
  div.style.setProperty(prop, set_color("currentcolor"), "");
  is(get_color(cs.getPropertyValue(prop)), "rgb(32, 0, 96)",
     msg_prefix + "interpolation of rgb color and currentcolor");

  if (prop != "color") {
    div.style.setProperty("transition-property""none""");
    div.style.setProperty("color""rgb(128, 0, 0)""");
    div.style.setProperty(prop, set_color("rgb(0, 0, 128)"), "");
    is(get_color(cs.getPropertyValue(prop)), "rgb(0, 0, 128)",
       msg_prefix + "computed value before transition");
    div.style.setProperty("transition-property", `color, ${prop}`, "");
    div.style.setProperty("color""rgb(0, 128, 0)""");
    div.style.setProperty(prop, set_color("currentcolor"), "");
    is(cs.getPropertyValue("color"), "rgb(96, 32, 0)",
       "interpolation of rgb color property");
    is(get_color(cs.getPropertyValue(prop)), "rgb(24, 8, 96)",
       msg_prefix + "interpolation of rgb color and interpolated currentcolor");
  }

  div.style.setProperty("transition-property""none""");
  (prop == "color" ? div.parentNode : div).style.
    setProperty("color""rgba(128, 0, 0, 0.6)""");
  div.style.setProperty(prop, set_color("rgba(0, 0, 128, 0.8)"), "");
  is(get_color(cs.getPropertyValue(prop)), "rgba(0, 0, 128, 0.8)",
     msg_prefix + "computed value before transition");
  div.style.setProperty("transition-property", prop, "");
  div.style.setProperty(prop, set_color("currentcolor"), "");
  is(get_color(cs.getPropertyValue(prop)), "rgba(26, 0, 102, 0.75)",
     msg_prefix + "interpolation of rgba color and currentcolor");

  // It is not possible to check distance, because there is a hidden
  // dimension for ratio of currentcolor.

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

function test_auto_color_transition(prop, options={}) {
  let { get_color, set_color } = get_color_options(options);

  const msg_prefix = `color-valued property ${prop}: `;
  const test_color = "rgb(51, 102, 153)";
  div.style.setProperty("transition-property""none""");
  div.style.setProperty(prop, "auto""");
  if (prop == "scrollbar-color") {
    is(cs.getPropertyValue(prop), "auto",
       msg_prefix + "auto should not be resolved to rgb color");
  } else {
    let used_value_of_auto = get_color(cs.getPropertyValue(prop));
    isnot(used_value_of_auto, test_color,
          msg_prefix + "ensure used auto value is different than our test color");
  }

  div.style.setProperty("transition-property", prop, "");
  div.style.setProperty(prop, set_color(test_color), "");
  is(get_color(cs.getPropertyValue(prop)), test_color,
     msg_prefix + "not interpolatable between auto and rgb color");
}

function get_color_from_shorthand_value(value) {
  var m = value.match(/rgba?\([^, ]*, [^, ]*, [^, ]*(?:, [^, ]*)?\)/);
  isnot(m, null, "shorthand property value should contain color");
  return m[0];
}

function test_color_shorthand_transition(prop) {
  test_color_transition(prop, {
    get_color: get_color_from_shorthand_value,
    is_shorthand: true,
  });
}

function test_currentcolor_shorthand_transition(prop) {
  test_currentcolor_transition(prop, {
    get_color: get_color_from_shorthand_value,
    is_shorthand: true,
  });
}

function test_scrollbar_color_transition(prop) {
  function split_colors(value) {
    const colors = value.match(/^(rgba?\(.+?\)) (rgba?\(.+?\))$/);
    isnot(colors, null, "scrollbar-color should consist of two colors");
    return { thumb: colors[1], track: colors[2] };
  }
  const TEST_FUNCS = [
    test_color_transition,
    test_currentcolor_transition,
    test_auto_color_transition,
  ];
  for (let test_func of TEST_FUNCS) {
    test_func(prop, {
      get_color: value => split_colors(value).thumb,
      set_color: value => value + " blue",
    });
    test_func(prop, {
      get_color: value => split_colors(value).track,
      set_color: value => "blue " + value,
    });
  }
}

function test_shape_or_url_equals(computedValStr, expected)
{
  // Check simple case "none"
  if (computedValStr == "none" && computedValStr == expected[0]) {
    return true;
  }
  // We will update the expected list in this function for checking the result,
  // so we clone it first to avoid affecting the input parameter.
  var expectedList = expected.slice();

  var start = String(computedValStr);

  var regBox = /\s*(content\-box|padding\-box|border\-box|margin\-box|view\-box|stroke\-box|fill\-box)/
  var matches = computedValStr.split(regBox);
  var expectRefBox = typeof expectedList[expectedList.length - 1] == "string" &&
                     expectedList[expectedList.length - 1].match(regBox) !== null;

  // Found a reference box? Format: "shape()" or "shape() reference-box"
  if (matches.length > 1) {
    // Our split() did actually split the string, which means computedValStr
    // contains a reference box. That reference box should be at the end,
    // which means split() will have produced an empty string as the final
    // entry in |matches|. Let's first ditch that empty string.
    var trailingJunk = matches.pop();
    is(trailingJunk, """reference box shouldn't have anything after it");

    // Do we expect a reference box?
    if (!expectRefBox) {
      ok(false, "unexpected reference box found");
      matches.pop(); // Get rid of it, so we can test the rest...
    } else {
      is(matches.pop(), expectedList.pop(), "Reference boxes should match");
    }
  } else {
    // No reference box found. Did we expect one?
    if (expectRefBox) {
      ok(false, "expected reference box");
      return false;
    }
  }
  computedValStr = matches[0];
  if (expectedList.length == 0) {
    if (computedValStr == "") {
      return true;
    }
    ok(false, "expected basic shape");
    return false;
  }

  // The regular expression does not filter out the last parenthesis.
  // Remove last character for now.
  is(computedValStr.substring(computedValStr.length - 1, computedValStr.length),
                              ')'"Function should have close-paren");
  computedValStr = computedValStr.substring(0, computedValStr.length - 1);

  var regShape = /\)*\s*(circle|ellipse|polygon|inset|url)\(/
  matches = computedValStr.split(regShape);
  // First item must be empty. All other items are of functionName, functionValue.
  if (!matches || matches.shift() != "") {
    ok(false, "invalid value or unknown shape function");
    return false;
  }

  // Check argument values.
  if (matches[1] != expectedList[1]) {
    ok(false, "function parameters mismatch");
    return false;
  }

  return true;
}

function test_path_function_equals(computedValStr, expectedList)
{
  // Check simple case "none"
  if (expectedList.length === 1 && computedValStr === expectedList[0]) {
    return true;
  }

  var regex = /([a-z]+)\((.*)\)/;
  matches = computedValStr.match(regex)
  if (!matches || matches[0] != computedValStr) {
    ok(false, "Invalid function value");
    return false;
  }

  // Bug 1480665: Support ray() for motion path. For now, only path(...) is
  // acceptable.
  if (matches[1] != "path") {
    ok(false, "Only support path function");
    return false;
  }

  // Check argument values.
  if (matches[2] != expectedList[1]) {
    ok(false, "Function parameters mismatch");
    return false;
  }

  return true;
}

function filter_function_list_equals(computedValStr, expectedList)
{
  // Check simple case "none"
  if (computedValStr == "none" && computedValStr == expectedList[0]) {
    return true;
  }

  // The regular expression does not filter out the last parenthesis.
  // Remove last character for now.
  is(computedValStr.substring(computedValStr.length - 1, computedValStr.length),
                              ')'"Last character should be close-paren");
  computedValStr = computedValStr.substring(0, computedValStr.length - 1);

  var reg = /\)*\s*(blur|brightness|contrast|grayscale|hue\-rotate|invert|opacity|saturate|sepia|drop\-shadow|url)\(/
  var matches = computedValStr.split(reg);
  // First item must be empty. All other items are of functionName, functionValue.
  if (!matches || matches.shift() != "") {
    ok(false, "computed style of 'filter' isn't in the format we expect");
    return false;
  }

  // Odd items are the function name, even items the function value.
  if (!matches.length || matches.length % 2 ||
      expectedList.length != matches.length) {
    ok(false, "computed style of 'filter' isn't in the format we expect");
    return false;
  }
  for (let i = 0; i < matches.length; i += 2) {
    var functionName = matches[i];
    var functionValue = matches[i+1];
    var expected = expectedList[i+1]
    var tolerance = 0;
    // Check if we have the expected function.
    if (functionName != expectedList[i]) {
      return false;
    }
    if (functionName == "blur") {
      // Last two characters must be "px".
      if (functionValue.search("px") != functionValue.length - 2) {
        return false;
      }
      functionValue = functionValue.substring(0, functionValue.length - 2);
    } else if (functionName == "hue-rotate") {
      // Just check for string equality.
      return functionValue == expected;
    } else if (functionName == "drop-shadow" || functionName == "url") {
      if (functionValue != expected) {
        return false;
      }
      continue;
    }
    // Check if string is not a number or difference is not in tolerance level.
    if (isNaN(functionValue) ||
      Math.abs(parseFloat(functionValue) - expected) > tolerance) {
      return false;
    }
  }
  return true;
}

function test_basic_shape_or_url_transition(prop) {
  let tests = basicShapesTests;
  if (prop === "clip-path") {
    // Clip-path won't resolve fragment URLs.
    tests = tests.concat(basicShapesWithFragmentUrlTests);
  }

  for (let test of tests) {
    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);
    ok(test_shape_or_url_equals(actual, test.expected),
       prop + " property is " + actual + " expected values of " +
       test.expected);
  }
}

function test_path_function(prop) {
  let tests = pathFunctionTests;
  if (prop === "clip-path") {
    // The syntax of path() in clip-path has fill-rule, so we have to test more.
    tests = tests.concat(clipPathPathFunctionTests);
  }

  for (const test of tests) {
    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, "");
    const actual = cs.getPropertyValue(prop);
    ok(test_path_function_equals(actual, test.expected),
       prop + " property is " + actual + " expected values of " +
       test.expected[0] + "(" + test.expected[1] + ")");
  }
}

function test_filter_transition(prop) {
  for (let i in filterTests) {
    var test = filterTests[i];
    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);
    ok(filter_function_list_equals(actual, test.expected),
                                   "Filter property is " + actual + " expected values of " +
                                   test.expected);
  }
}

function test_shadow_transition(prop) {
  var origTimingFunc = div.style.getPropertyValue("transition-timing-function");
  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");
  }

  // Transition beween values with color and without color.
  div.style.setProperty("transition-property""none""");
  div.style.setProperty("color""rgb(3, 0, 0)""");
  div.style.setProperty(prop, "2px 2px 2px""");
  is(cs.getPropertyValue(prop), "rgb(3, 0, 0) 2px 2px 2px" + 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, 0, 0) 3.5px 3.5px 3.5px" + spreadStr,
     "shadow-valued property " + prop +
     ": interpolation values with/without color");

  // Transition beween values without color.
  var defaultColor = cs.getPropertyValue("color") + " ";
  div.style.setProperty("transition-property""none""");
  div.style.setProperty(prop, "2px 2px 2px""");
  is(cs.getPropertyValue(prop), defaultColor + "2px 2px 2px" + spreadStr,
     "shadow-valued property " + prop + ": computed value before transition");
  div.style.setProperty("transition-property", prop, "");
  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");

  // Transition between values with currentcolor transitioning.
  div.style.setProperty("transition-property""none""");
  div.style.setProperty("color""rgb(0, 255, 0)""");
  div.style.setProperty(prop, "2px 2px 2px""");
  is(cs.getPropertyValue(prop), "rgb(0, 255, 0) 2px 2px 2px" + spreadStr,
     "shadow-valued property " + prop + ": computed value before transition");
  div.style.setProperty("transition-property""color, " + prop, "");
  div.style.setProperty("color""rgb(0, 0, 255)""");
  div.style.setProperty(prop, "6px 10px 14px red""");
  is(cs.getPropertyValue(prop), "rgb(64, 143, 48) 3px 4px 5px" + spreadStr,
     "shadow-valued property " + prop + ": interpolation with interpolating" +
     "currentcolor");

  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");
  }

  // A test case that timing function produces values greater than 1.0.
  div.style.setProperty("transition-timing-function",
                        // This function produces 1.2989961788069297 at 25%.
                        "cubic-bezier(0, 1.5, 0, 1.5)""");
  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, "0px 0px 0px rgba(100, 100, 100, 0.5)""");
  // The alpha value, 0.5 * 1.2989961788069297 * 255, is 165.622012798, and then
  // converted to 0.649.
  is(cs.getPropertyValue(prop), "rgba(100, 100, 100, 0.649) 0px 0px 0px" + spreadStr,
     "shadow-valued property " + prop + ": interpolation of shadows with " +
     "timing function which produces values greater than 1.0");

  div.style.setProperty("transition-timing-function", origTimingFunc, "");
}

function test_dasharray_transition(prop) {
  div.style.setProperty("transition-property""none""");
  div.style.setProperty(prop, "3""");
  is(cs.getPropertyValue(prop), "3px",
     "dasharray-valued property " + prop +
     ": computed value before transition");
  div.style.setProperty("transition-property", prop, "");
  div.style.setProperty(prop, "15px""");
  is(cs.getPropertyValue(prop), "6px",
     "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), "6px, 8px, 4px, 4px",
     "dasharray-valued property " + prop +
     ": computed value before transition");
  div.style.setProperty(prop, "14, 12,16,16px""");
  is(cs.getPropertyValue(prop), "8px, 9px, 7px, 7px",
     "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), "8px, 16px, 4px",
     "dasharray-valued property " + prop +
     ": computed value before transition");
  div.style.setProperty(prop, "4,8,12,16""");
  is(cs.getPropertyValue(prop), "7px, 14px, 6px, 10px, 13px, 5px, 9px, 16px, 4px, 8px, 15px, 7px",
     "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),
     "5.75px, calc(12.5% + 10.5px), 6px, 10px, 10.25px, calc(12.5% + 3.75px), 8.25px, 14.5px, 3.5px, calc(12.5% + 6px), 12.75px, 7.75px",
     "dasharray-valued property " + prop + ": interpolability of mixed units");
  div.style.setProperty(prop, "none""");
  is(cs.getPropertyValue(prop), "none",
     "dasharray-valued property " + prop + ": non-interpolability of none");
  div.style.setProperty(prop, "2,50%,6,10""");
  is(cs.getPropertyValue(prop), "2px, 50%, 6px, 10px",
     "dasharray-valued property " + prop + ": non-interpolability of none");
  div.style.setProperty(prop, "6,30%,2,2""");
  is(cs.getPropertyValue(prop), "3px, 45%, 5px, 8px",
     "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), "0px, 0%",
     "dasharray-valued property " + prop + ": flush before clamping test");
  div.style.setProperty("transition-property", prop, "");
  div.style.setProperty(prop, "5px, 25%""");
  is(cs.getPropertyValue(prop), "0px, 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), "12.5%",
     "radius-valued property " + prop + ": computed value before transition");
  div.style.setProperty("transition-property", prop, "");
  div.style.setProperty(prop, "25%""");
  is(cs.getPropertyValue(prop), "15.625%",
     "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), "12.5% 6.25%",
     "radius-valued property " + prop + ": computed value before transition");
  div.style.setProperty("transition-property", prop, "");
  div.style.setProperty(prop, "25%""");
  is(cs.getPropertyValue(prop), "15.625% 10.9375%",
     "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), "6.25% 12.5%",
     "radius-valued property " + prop + ": computed value before transition");
  div.style.setProperty("transition-property", prop, "");
  div.style.setProperty(prop, "64px 16px""");
  is(cs.getPropertyValue(prop), "calc(4.6875% + 16px) calc(9.375% + 4px)",
     "radius-valued property " + prop + ": interpolation of radius with mixed units");
  check_distance(prop, "6.25% 12.5%",
                 "calc(4.6875% + 16px) calc(9.375% + 4px)",
                 "64px 16px");

  div.style.setProperty("transition-property""none""");
  div.style.setProperty(prop, "calc(5px) 10px""");
  is(cs.getPropertyValue(prop), "5px 10px",
     "radius-valued property " + prop + ": computed value before transition");
  div.style.setProperty("transition-property", prop, "");
  div.style.setProperty(prop, "calc(25px) calc(50px)""");
  is(cs.getPropertyValue(prop), "10px 20px",
     "radius-valued property " + prop + ": interpolation of radius with calc() units");

  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""");

  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), "0",
     "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_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), "525",
     "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, "1""");
  is(cs.getPropertyValue(prop), "1",
     "font-weight property " + prop + ": flush before clamping test");
  div.style.setProperty("transition-property", prop, "");
  div.style.setProperty(prop, "1000""");
  is(cs.getPropertyValue(prop), "1",
     "font-weight property " + prop + ": clamping of values");
  div.style.setProperty("transition-property""none""");
  div.style.setProperty(prop, "1000""");
  is(cs.getPropertyValue(prop), "1000",
     "font-weight property " + prop + ": flush before clamping test");
  div.style.setProperty("transition-property", prop, "");
  div.style.setProperty(prop, "1""");
  is(cs.getPropertyValue(prop), "1000",
     "font-weight property " + prop + ": clamping of values");
  div.style.setProperty("transition-timing-function""linear""");
}

function test_grid_gap(prop) {
  test_length_transition(prop);
  test_length_clamped(prop);
  test_percent_transition(prop);
  test_percent_clamped(prop);
}

function test_pos_integer_or_keyword_transition(prop, keyword) {
  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), "6",
     "integer-valued property " + prop + ": interpolation of integers");
  check_distance(prop, "4""6""12");
  div.style.setProperty(prop, keyword, "");
  is(cs.getPropertyValue(prop), keyword,
     "integer-valued property " + prop + ": " + keyword + " 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_pos_integer_or_auto_transition(prop) {
  return test_pos_integer_or_keyword_transition(prop, "auto");
}

function test_pos_integer_or_none_transition(prop) {
  return test_pos_integer_or_keyword_transition(prop, "none");
}

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",
     "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",
     "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%""");
  var is_zero = function(val) {
    if (prop == "transform-origin" || prop == "perspective-origin") {
      // These two properties resolve percentages to pixels.
      return val == "0px 0px";
    }
    return val == "0px 0%";
  }
  ok(is_zero(cs.getPropertyValue(prop)),
     "length+percent-valued property " + prop + ": flush before clamping test");
  div.style.setProperty("transition-property", prop, "");
  div.style.setProperty(prop, "30px 25%""");
  is(is_zero(cs.getPropertyValue(prop)), is_clamped,
     "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)");
  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 do_test(prop, from_value, to_value, interp_value) {
  div.style.setProperty("transition-property""none""");
  div.style.setProperty(prop, from_value, "");
  is(cs.getPropertyValue(prop), from_value,
      "property " + prop + ": computed value before transition");
  div.style.setProperty("transition-property", prop, "");
  div.style.setProperty(prop, to_value, "");
  is(cs.getPropertyValue(prop), interp_value,
      "property " + prop + ": interpolation of " + prop);
}

function do_negative_test(prop, from_value, to_value, interpolable) {
  div.style.setProperty("transition-property""none""");
  div.style.setProperty(prop, from_value, "");
  is(cs.getPropertyValue(prop), from_value,
      "property " + prop + ": flush before clamping test");
  div.style.setProperty("transition-property", prop, "");
  div.style.setProperty(prop, to_value, "");
  is(cs.getPropertyValue(prop), interpolable ? from_value : to_value,
      "property " + prop + ": clamping of negatives");
}

function do_overone_test(prop, from_value, to_value) {
  div.style.setProperty("transition-property""none""");
  div.style.setProperty(prop, from_value, "");
  is(cs.getPropertyValue(prop), from_value,
      "property " + prop + ": flush before clamping test");
  div.style.setProperty("transition-property", prop, "");
  div.style.setProperty(prop, to_value, "");
  is(cs.getPropertyValue(prop), to_value,
      "property " + prop + ": clamping of over-ones");
}

function test_visibility_transition(prop) {
  do_test(prop, "visible""hidden""visible");
  do_test(prop, "hidden""visible""visible");
  do_test(prop, "hidden""collapse""collapse"); /* not interpolable */
  do_test(prop, "collapse""hidden""hidden"); /* not interpolable */
  do_test(prop, "visible""collapse""visible");
  do_test(prop, "collapse""visible""visible");

  isnot(get_distance(prop, "visible""hidden"), 0,
        "distance between visible and hidden should not be zero");
  isnot(get_distance(prop, "visible""collapse"), 0,
        "distance between visible and collapse should not be zero");
  is(get_distance(prop, "visible""visible"), 0,
     "distance between visible and visible should be zero");
  is(get_distance(prop, "hidden""hidden"), 0,
     "distance between hidden and hidden should be zero");
  is(get_distance(prop, "collapse""collapse"), 0,
     "distance between collapse and collapse should be zero");

  div.style.setProperty("transition-timing-function", FUNC_NEGATIVE, "");
  do_negative_test(prop, "visible""hidden", true);
  do_negative_test(prop, "hidden""visible", true);
  do_negative_test(prop, "hidden""collapse", false);
  do_negative_test(prop, "collapse""hidden", false);
  do_negative_test(prop, "visible""collapse", true);
  do_negative_test(prop, "collapse""visible", true);

  div.style.setProperty("transition-delay""-3s""");
  div.style.setProperty("transition-timing-function", FUNC_OVERONE, "");
  do_overone_test(prop, "visible""hidden");
  do_overone_test(prop, "hidden""visible");
  do_overone_test(prop, "hidden""collapse");
  do_overone_test(prop, "collapse""hidden");
  do_overone_test(prop, "visible""collapse");
  do_overone_test(prop, "collapse""visible");

  div.style.setProperty("transition-delay""-1s""");
  div.style.setProperty("transition-timing-function""linear""");
}

function test_content_visibility_transition(prop) {
  do_test(prop, "visible""hidden""visible");
  do_test(prop, "hidden""visible""visible");
  do_test(prop, "hidden""auto""auto");
  do_test(prop, "auto""hidden""auto");
  do_test(prop, "visible""auto""auto"); /* not interpolable */
  do_test(prop, "auto""visible""visible"); /* not interpolable */

  isnot(get_distance(prop, "visible""hidden"), 0,
        "distance between visible and hidden should not be zero");
  isnot(get_distance(prop, "auto""hidden"), 0,
        "distance between auto and hidden should not be zero");
  is(get_distance(prop, "visible""visible"), 0,
     "distance between visible and visible should be zero");
  is(get_distance(prop, "hidden""hidden"), 0,
     "distance between hidden and hidden should be zero");
  is(get_distance(prop, "auto""auto"), 0,
     "distance between auto and auto should be zero");

  div.style.setProperty("transition-timing-function", FUNC_NEGATIVE, "");
  do_negative_test(prop, "visible""hidden", true);
  do_negative_test(prop, "hidden""visible", true);
  do_negative_test(prop, "hidden""auto", true);
  do_negative_test(prop, "auto""hidden", true);
  do_negative_test(prop, "visible""auto", false);
  do_negative_test(prop, "auto""visible", false);

  div.style.setProperty("transition-delay""-3s""");
  div.style.setProperty("transition-timing-function", FUNC_OVERONE, "");
  do_overone_test(prop, "visible""hidden");
  do_overone_test(prop, "hidden""visible");
  do_overone_test(prop, "hidden""auto");
  do_overone_test(prop, "auto""hidden");
  do_overone_test(prop, "visible""auto");
  do_overone_test(prop, "auto""visible");

  div.style.setProperty("transition-delay""-1s""");
  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, true, true);
}

function test_background_position_transition(prop) {
  var doesPropTakeListValues = (prop == "background-position") ||
                               (prop == "mask-position");
  var doesPropHaveDistanceComputation = (prop != "background-position") &&
                                        (prop != "mask-position");

  // Test interpolation between edge keywords, and between edge keyword and a
  // percent value. (Note: edge keywords are really aliases for percent vals.)
  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 edge keywords & percents");
  if (doesPropHaveDistanceComputation) {
    check_distance(prop, "center 80%""62.5% 85%""bottom right");
  }

  // Test interpolation between edge keyword *with an offset* and non-keyword
  // values.
  div.style.setProperty("transition-property""none""");
  div.style.setProperty(prop, "right 20px bottom 30%""");
  is(cs.getPropertyValue(prop), "calc(100% - 20px) 70%",
     "property " + prop + ": computed value before transition");
  div.style.setProperty("transition-property", prop, "");
  div.style.setProperty(prop, "calc(40px + 20%) calc(12px + 30%)""");
  is(cs.getPropertyValue(prop), "calc(80% - 5px) calc(60% + 3px)",
     "property " + prop + ": interpolation of edge keywords w/ offsets & calc");
  if (doesPropHaveDistanceComputation) {
    check_distance(prop, "right 20px bottom 30%",
                         "calc(-5px + 80%) calc(3px + 60%)",
                         "calc(40px + 20%) calc(12px + 30%)");
  }

  test_background_position_size_common(prop, doesPropTakeListValues,
                                       doesPropHaveDistanceComputation);
}

function test_background_position_coord_transition(prop) {
  var endEdge = prop.endsWith("-x") ? "right" : "bottom";

  // Test interpolation between edge keywords, and between edge keyword and a
  // percent value. (Note: edge keywords are really aliases for percent vals.)
  div.style.setProperty("transition-property""none""");
  div.style.setProperty(prop, "center""");
  is(cs.getPropertyValue(prop), "50%",
     "property " + prop + ": computed value before transition");
  div.style.setProperty("transition-property", prop, "");
  div.style.setProperty(prop, endEdge, "");
  is(cs.getPropertyValue(prop), "62.5%",
     "property " + prop + ": interpolation of edge keywords & percents");
  check_distance(prop, "center""62.5%", endEdge);

  // Test interpolation between edge keyword *with an offset* and non-keyword
  // values.
  div.style.setProperty("transition-property""none""");
  div.style.setProperty(prop, `${endEdge} 20px`, "");
  is(cs.getPropertyValue(prop), "calc(100% - 20px)",
     "property " + prop + ": computed value before transition");
  div.style.setProperty("transition-property", prop, "");
  div.style.setProperty(prop, "calc(40px + 20%)""");
  is(cs.getPropertyValue(prop), "calc(80% - 5px)",
     "property " + prop + ": interpolation of edge keywords w/ offsets & calc");
  check_distance(prop, `${endEdge} 20px`,
                       "calc(-5px + 80%)",
                       "calc(40px + 20%)");

  div.style.setProperty("transition-property""none""");
  div.style.setProperty(prop, "10px, 50px, 30px""");
  is(cs.getPropertyValue(prop), "10px, 50px, 30px",
     "property " + prop + ": computed value before transition");
  div.style.setProperty("transition-property", prop, "");
  div.style.setProperty(prop, "50px, 70px, 30px""");
  is(cs.getPropertyValue(prop), "20px, 55px, 30px",
     "property " + prop + ": interpolation of lists of lengths");
  check_distance(prop, "10px, 50px, 30px",
                       "20px, 55px, 30px",
                       "50px, 70px, 30px");

  div.style.setProperty("transition-property""none""");
  div.style.setProperty(prop, "10px, 50%, 30%, 5px""");
  is(cs.getPropertyValue(prop), "10px, 50%, 30%, 5px",
     "property " + prop + ": computed value before transition");
  div.style.setProperty("transition-property", prop, "");
  div.style.setProperty(prop, "50px, 70%, 30%, 25px""");
  is(cs.getPropertyValue(prop), "20px, 55%, 30%, 10px",
     "property " + prop + ": interpolation of lists of lengths and percents");
  check_distance(prop, "10px, 50%, 30%, 5px",
                       "20px, 55%, 30%, 10px",
                       "50px, 70%, 30%, 25px");

  div.style.setProperty("transition-property""none""");
  div.style.setProperty(prop, "20%, 8px""");
  is(cs.getPropertyValue(prop), "20%, 8px",
     "property " + prop + ": computed value before transition");
  div.style.setProperty("transition-property", prop, "");
  div.style.setProperty(prop, "12px, 40%""");
  is(cs.getPropertyValue(prop), "calc(15% + 3px), calc(10% + 6px)",
     "property " + prop + ": interpolation that computes to calc()");
  check_distance(prop, "20%, 8px",
                       "calc(3px + 15%), calc(6px + 10%)",
                       "12px, 40%");

  div.style.setProperty("transition-property""none""");
  div.style.setProperty(prop, "calc(20% + 40px), 8px, calc(20px + 12%)""");
  is(cs.getPropertyValue(prop), "calc(20% + 40px), 8px, calc(12% + 20px)",
     "property " + prop + ": computed value before transition");
  div.style.setProperty("transition-property", prop, "");
  div.style.setProperty(prop, "12px, calc(20%), calc(8px + 20%)""");
  is(cs.getPropertyValue(prop), "calc(15% + 33px), calc(5% + 6px), calc(14% + 17px)",
     "property " + prop + ": interpolation that computes to calc()");
  check_distance(prop, "calc(20% + 40px), 8px, calc(20px + 12%)",
                       "calc(33px + 15%), calc(6px + 5%), calc(17px + 14%)",
                       "12px, calc(20%), calc(8px + 20%)");
}

/**
 * Common tests for 'background-position''background-size', and other
 * properties that take CSS value-type 'position' or 'bg-size'.
 *
 *  @arg prop The name of the property
 *  @arg doesPropTakeListValues
 *         If false, the property is assumed to just take a single 'position' or
 *         'bg-size' value. If true, the property is assumed to also accept
 *         comma-separated list of such values.
 */
function test_background_position_size_common(prop, doesPropTakeListValues,
                                              doesPropHaveDistanceComputation) {
  // Test non-list values
  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");
  if (doesPropHaveDistanceComputation) {
    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");
  if (doesPropHaveDistanceComputation) {
    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");
  if (doesPropHaveDistanceComputation) {
    check_distance(prop, "10px 40px""20px 30px""50px 0");
  }

  // Test interpolation that computes to to calc() (transition from % to px)
  div.style.setProperty("transition-property""none""");
  div.style.setProperty(prop, "20% 40%""");
  is(cs.getPropertyValue(prop), "20% 40%",
     "property " + prop + ": computed value before transition");
  div.style.setProperty("transition-property", prop, "");
  div.style.setProperty(prop, "12px 20px""");
  is(cs.getPropertyValue(prop),
     "calc(15% + 3px) calc(30% + 5px)",
     "property " + prop + ": interpolation that computes to calc()");
  if (doesPropHaveDistanceComputation) {
    check_distance(prop, "20% 40%",
                         "calc(3px + 15%) calc(5px + 30%)",
                         "12px 20px");
  }

  // Test interpolation that computes to to calc() (transition from px to %)
  div.style.setProperty("transition-property""none""");
  div.style.setProperty(prop, "12px 20px""");
  is(cs.getPropertyValue(prop), "12px 20px",
     "property " + prop + ": computed value before transition");
  div.style.setProperty("transition-property", prop, "");
  div.style.setProperty(prop, "20% 40%""");
  is(cs.getPropertyValue(prop),
     "calc(5% + 9px) calc(10% + 15px)",
     "property " + prop + ": interpolation that computes to calc()");
  if (doesPropHaveDistanceComputation) {
    check_distance(prop, "12px 20px",
                         "calc(9px + 5%) calc(15px + 10%)",
                         "20% 40%");
  }

  // Test interpolation between calc() and non-calc()
  div.style.setProperty("transition-property""none""");
  div.style.setProperty(prop, "calc(40px + 10%) 16px""");
  is(cs.getPropertyValue(prop), "calc(10% + 40px) 16px",
     "property " + prop + ": computed value before transition");
  div.style.setProperty("transition-property", prop, "");
  div.style.setProperty(prop, "30% calc(8px + 60%)""");
  is(cs.getPropertyValue(prop), "calc(15% + 30px) calc(15% + 14px)",
     "property " + prop + ": interpolation between calc() and non-calc()");
  if (doesPropHaveDistanceComputation) {
    check_distance(prop, "calc(40px + 10%) 16px",
                         "calc(30px + 15%) calc(14px + 15%)",
                         "30% calc(8px + 60%)");
  }

  // Test list values, if appropriate
  if (doesPropTakeListValues) {
    div.style.setProperty("transition-property""none""");
    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("transition-property", prop, "");
    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");
    if (doesPropHaveDistanceComputation) {
      check_distance(prop, "10px 40px, 50px 50px, 30px 20px",
                           "20px 35px, 55px 50px, 30px 25px",
                           "50px 20px, 70px 50px, 30px 40px");
    }
    div.style.setProperty("transition-property""none""");
    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("transition-property", prop, "");
    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");
    if (doesPropHaveDistanceComputation) {
      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("transition-property""none""");
    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("transition-property", prop, "");
    div.style.setProperty(prop, "12px 20px, 40% 16%""");
    is(cs.getPropertyValue(prop),
       "calc(15% + 3px) calc(30% + 5px), calc(10% + 6px) calc(4% + 9px)",
       "property " + prop + ": interpolation that computes to calc()");
    if (doesPropHaveDistanceComputation) {
      check_distance(prop, "20% 40%, 8px 12px",
                           "calc(3px + 15%) calc(5px + 30%), calc(6px + 10%) calc(9px + 4%)",
                           "12px 20px, 40% 16%");
    }
    div.style.setProperty("transition-property""none""");
    div.style.setProperty(prop, "calc(20% + 40px) calc(40px + 40%), 8px 12%, calc(20px + 12%) calc(24px + 8%)""");
    is(cs.getPropertyValue(prop),
       "calc(20% + 40px) calc(40% + 40px), 8px 12%, calc(12% + 20px) calc(8% + 24px)",
       "property " + prop + ": computed value before transition");
    div.style.setProperty("transition-property", prop, "");
    div.style.setProperty(prop, "12px 20%, calc(20%) calc(16px + 60%), calc(8px + 20%) calc(40px + 16%)""");
    is(cs.getPropertyValue(prop),
        "calc(15% + 33px) calc(35% + 30px), calc(5% + 6px) calc(24% + 4px), calc(14% + 17px) calc(10% + 28px)",
       "property " + prop + ": interpolation that computes to calc()");
    if (doesPropHaveDistanceComputation) {
      check_distance(prop, "calc(20% + 40px) calc(40px + 40%), 8px 12%, calc(20px + 12%) calc(24px + 8%)",
                           "calc(33px + 15%) calc(30px + 35%), calc(6px + 5%) calc(4px + 24%), calc(17px + 14%) calc(28px + 10%)",
                           "12px 20%, calc(20%) calc(16px + 60%), calc(8px + 20%) calc(40px + 16%)");
    }
  }
}

function test_transform_transition(prop) {
  if (div.style.zoom == "2") {
    todo(false, "Resolved transforms don't get correctly un-zoomed in getComputedStyle, see bug 1909280");
    return;
  }
  is(prop, "transform""Unexpected transform property!  Test needs to be fixed");
  var matrix_re = /^matrix\(([^,]*), ([^,]*), ([^,]*), ([^,]*), ([^,]*), ([^,]*)\)$/;
  for (let i in transformTests) {
    var test = transformTests[i];
    if (!("expected" in test)) {
      var v = test.expected_uncomputed;
      if (v.match(matrix_re) && !test.force_compute) {
        test.expected = v;
      } else {
        test.expected = computeMatrix(v);
      }
    }
  }

  for (let i in transformTests) {
    var test = transformTests[i];
    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 (let j = 0; j < 6; ++j) {
        // 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[j]) - Number(expected_split[j])) >
              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

  runOMTATest(runAsyncTests, SimpleTest.finish);
}

function test_rotate_transition(prop) {
  // One value: <angle>
  test_angle_transition(prop);

  // With axis: <number> <number> <number> <angle>
  //
  // We don't test for interpolation of the numbers here since it's quite
  // complicated and this is tested by the web-platform tests for this property.
  // Now that we have web-platform tests for animation properties the main
  // purpose of the tests in this file is to check that transitions run on the
  // properties we expect them to.
  div.style.transitionProperty = 'none';
  div.style[prop] = '0 1 0 45deg';
  is(cs[prop], 'y 45deg',
     `rotate property ${prop}: computed value before transition`);
  div.style.transitionProperty = prop;
  div.style[prop] = '0 1 0 145deg';
  is(cs[prop], 'y 70deg',
     `rotate property ${prop}: interpolation of angles`);
  check_distance(prop, '0 1 0 45deg''0 1 0 70deg''0 1 0 145deg');
}

function test_scale_transition(prop) {
  // One value: <number>
  test_number_transition(prop);

  // Two values: <number> <number>
  div.style.transitionProperty = 'none';
  div.style[prop] = '10 20';
  is(cs[prop], '10 20',
     `number property ${prop}: computed value before transition`);
  div.style.transitionProperty = prop;
  div.style[prop] = '50 60';
  is(cs[prop], '20 30', `number property ${prop}: interpolation of numbers`);
  check_distance(prop, '10 20''20 30''50 60');

  // Three values: <number> <number> <number>
  div.style.transitionProperty = 'none';
  div.style[prop] = '10 20 30';
  is(cs[prop], '10 20 30',
     `number property ${prop}: computed value before transition`);
  div.style.transitionProperty = prop;
  div.style[prop] = '50 60 70';
  is(cs[prop], '20 30 40', `number property ${prop}: interpolation of numbers`);
  check_distance(prop, '10 20 30''20 30 40''50 60 70');
}

function test_translate_transition(prop) {
  // One value: <length-percentage>
  test_length_transition(prop);
  test_length_unclamped(prop);
  test_percent_transition(prop);
  test_percent_unclamped(prop);
  test_calc_wrapped_calc_transition(prop);

  // Two values: <length-percentage> <length-percentage>
  // Note: Cannot use test_length_percent_pair_transition(prop) because we
  // don't resolve the percentage.
  test_length_pair_transition(prop);

  div.style.setProperty("transition-property""none""");
  div.style.setProperty(prop, "4px 50%""");
  is(cs.getPropertyValue(prop), "4px 50%",
    `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 55%",
    `length-valued property ${prop}: interpolation of lengths`);
  check_distance(prop, "4px 50%""6px 55%""12px 70%");

  div.style.setProperty("transition-property""none""");
  div.style.setProperty(prop, "4px 50%""");
  is(cs.getPropertyValue(prop), "4px 50%",
    `length-valued property ${prop}: computed value before transition`);
  div.style.setProperty("transition-property", prop, "");
  div.style.setProperty(prop, "20% 20px""");
  is(cs.getPropertyValue(prop), "calc(5% + 3px) calc(37.5% + 5px)",
    `length-valued property ${prop}: interpolation of lengths`);
  check_distance(prop, "4px 50%""calc(5% + 3px) calc(37.5% + 5px)",
                 "20% 20px");
  // We can't use test_length_percent_pair_unclamped here since
  // it assumes that "0px 0px" is serialized as "0px 0px" but
  // translate should serialize it as "0px".

  // Three values: <length-percentage> <length-percentage> <length>
  div.style.transitionProperty = 'none';
  div.style[prop] = '10px 200% 30px';
  is(cs[prop], '10px 200% 30px',
     `translate property ${prop}: computed value before transition`);
  div.style.transitionProperty = prop;
  div.style[prop] = '50px 600% 70px';
  is(cs[prop], '20px 300% 40px',
     `translate property ${prop}: interpolation of three values`);
  check_distance(prop, '10px 20px 30px''20px 30px 40px''50px 60px 70px');
}

function test_font_variations_transition(prop) {
  is(prop, "font-variation-settings""only designed for one property");

  div.style.setProperty("transition-property""none""");
  div.style.setProperty(prop, "\"wght\" 0, \"wdth\" 1.5""");
  // Note that computed-style returns the tags in sorted order.
  is(cs.getPropertyValue(prop), "\"wdth\" 1.5, \"wght\" 0",
     "font-variation-settings property " + prop + ": computed value before transition");
  div.style.setProperty("transition-property", prop, "");
  div.style.setProperty(prop, "\"wght\" 2, \"wdth\" 0.5""");
  is(cs.getPropertyValue(prop), "\"wdth\" 1.25, \"wght\" 0.5",
     "font-variation-settings property " + prop + ": interpolation of font-variation-settings");
  check_distance(prop, "\"wght\" 0, \"wdth\" 1.5""\"wght\" 0.5, \"wdth\" 1.25""\"wght\2, \"wdth\" 0.5");

  div.style.setProperty("transition-property""none""");
  div.style.setProperty(prop, "\"wght\" 2, \"wdth\" 0.5""");
  is(cs.getPropertyValue(prop), "\"wdth\" 0.5, \"wght\" 2",
     "font-variation-settings property " + prop + ": computed value before transition");
  div.style.setProperty("transition-property", prop, "");
  div.style.setProperty(prop, "\"wght\" 0, \"wdth\" 1.5""");
  is(cs.getPropertyValue(prop), "\"wdth\" 0.75, \"wght\" 1.5",
     "font-variation-settings property " + prop + ": interpolation of font-variation-settings");
  check_distance(prop, "\"wght\" 2, \"wdth\" 0.5""\"wght\" 1.5, \"wdth\" 0.75""\"wght\0, \"wdth\" 1.5");
}

function test_aspect_ratio_transition(prop) {
  [
    // No transition between auto and <ratio>.
    { start: "auto", end: "1 / 1",
      expected: "1 / 1" },
    // No transition between auto && <ratio> and <ratio>.
    { start: "auto 1 / 1", end: "1 / 1",
      expected: "1 / 1" },
    // No transition between auto && <ratio> and auto.
    { start: "auto 1 / 1", end: "auto",
      expected: "auto" },
    { start: "1 / 2", end: "8 / 1",
      expected: "1 / 1" },
    { start: "auto 1 / 2", end: "auto 8 / 1",
      expected: "auto 1 / 1" },
  ].forEach(test => {
    div.style.transitionProperty = 'none';
    div.style[prop] = test.start;
    is(cs[prop], test.start,
       `aspect-ratio: computed value before transition`);
    div.style.transitionProperty = prop;
    div.style[prop] = test.end;
    is(cs[prop], test.expected,
       `aspect-ratio: interpolation of aspect-ratio`);
    // We check distance only if there is a transition.
    if (test.end != test.expected) {
      check_distance(prop, test.start, test.expected, test.end);
    }
  });
}

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

var OMTAdiv;
var OMTACs;

function prepareForOMTATest() {
  if (OMTAdiv) {
    OMTAdiv.remove();
  }
  OMTAdiv = document.createElement("div");
  OMTAdiv.style = "height:100px; width:100px; background-color:blue;";
  OMTAdiv.style.setProperty("transition-duration""300s""");
  OMTAdiv.style.setProperty("transition-timing-function""linear""");
  document.body.appendChild(OMTAdiv);

  OMTACs = getComputedStyle(OMTAdiv, "");
}

function runAsyncTests() {
  // These tests check the value on the compositor 2/3rds of the way through
  // the transition.
  // For the transform tests we simply compare the value on the compositor
  // with the computed value, but for the opacity test we check the absolute
  // value as well.
  addAsyncTransformTests();
  addAsyncOpacityTest();
  addAsyncDelayTest();

  runAllAsyncAnimTests().then(function() {
    OMTAdiv.style.removeProperty("transition");
    SimpleTest.finish();
  });
}

function addAsyncTransformTests() {
  transformTests.forEach(function(test) {
    addAsyncAnimTest(function () { return runTransformTest(test); } );
  });
}

async function runTransformTest(test) {
  prepareForOMTATest();

  OMTAdiv.style.setProperty("transition-property""none""");
  OMTAdiv.style.setProperty("transform", test.start, "");
  OMTACs.getPropertyValue("transform");
  OMTAdiv.style.setProperty("transition-property""transform""");
  OMTAdiv.style.setProperty("transform", test.end, "");
  OMTACs.getPropertyValue("transform");
  await waitForPaints();

  // If the start value produced a non-invertible matrix the layer won't be
  // created yet so we need to force an extra sample.
  if (!isTransformInvertible(test.start)) {
    winUtils.advanceTimeAndRefresh(100000);
    await waitForPaints();
    winUtils.advanceTimeAndRefresh(100000);
    await waitForPaints();
  } else {
    winUtils.advanceTimeAndRefresh(200000);
    await waitForPaints();
  }

  omta_is_approx(OMTAdiv, "transform", OMTACs.getPropertyValue("transform"),
                 0.0001, RunningOn.Compositor,
                 "compositor transform transition " +
                 "from '" + test.start + "' " +
                 "to '" + test.end + "' " +
                 "at 2/3rds duration matches computed style");
}

function addAsyncOpacityTest() {
  addAsyncAnimTest(async function() {
    prepareForOMTATest();

    OMTAdiv.style.setProperty("transition-property""none""");
    OMTAdiv.style.setProperty("opacity", 0, "");
    OMTACs.getPropertyValue("opacity");
    OMTAdiv.style.setProperty("transition-property""opacity""");
    OMTAdiv.style.setProperty("opacity", 1, "");
    OMTACs.getPropertyValue("opacity");

    await waitForPaints();

    winUtils.advanceTimeAndRefresh(200000);

    omta_is_approx(OMTAdiv, "opacity", 2/3, 0.00001, RunningOn.Compositor,
                   "compositor opacity transition at 2/3rds duration");
  });
}

function addAsyncDelayTest() {
  addAsyncAnimTest(async function() {
    prepareForOMTATest();

    OMTAdiv.style.setProperty("transition-property""none""");
    OMTAdiv.style.setProperty("transition-delay""100s""");
    OMTAdiv.style.setProperty("transition-duration""200s""");
    OMTAdiv.style.setProperty("transform""""");
    OMTACs.getPropertyValue("transform");
    OMTAdiv.style.setProperty("transition-property""transform""");
    OMTAdiv.style.setProperty("transform""translate(100px)""");
    OMTACs.getPropertyValue("transform");

    winUtils.advanceTimeAndRefresh(200000);
    await waitForPaints();

    omta_is_approx(OMTAdiv, "transform", { tx: 50 }, 0.0001,
                   RunningOn.Compositor,
                   "compositor transform transition with delay at 1/2"
                   + " duration");
  });
}

function isTransformInvertible(transformStr) {
  var computedStr = transformStrToComputedStr(transformStr);
  if (!transformStr)
    return false;
  var matrix = convertTo3dMatrix(computedStr);
  if (matrix === null)
    return false;
  return isInvertible(matrix);
}

function transformStrToComputedStr(transformStr) {
  var div = document.createElement("div");
  div.style.transform = transformStr;
  return window.getComputedStyle(div).transform;
}
</script>
</pre>
</body>
</html>

Messung V0.5 in Prozent
C=97 H=95 G=95

¤ Diese beiden folgenden Angebotsgruppen bietet das Unternehmen0.103Angebot  (Wie Sie bei der Firma Beratungs- und Dienstleistungen beauftragen können 2026-04-28) ¤

*Eine klare Vorstellung vom Zielzustand






Wurzel

Suchen

Beweissystem der NASA

Beweissystem Isabelle

NIST Cobol Testsuite

Cephes Mathematical Library

Wiener Entwicklungsmethode

Haftungshinweis

Die Informationen auf dieser Webseite wurden nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit, noch Qualität der bereit gestellten Informationen zugesichert.

Bemerkung:

Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.






                                                                                                                                                                                                                                                                                                                                                                                                     


Neuigkeiten

     Aktuelles
     Motto des Tages

Software

     Produkte
     Quellcodebibliothek

Aktivitäten

     Artikel über Sicherheit
     Anleitung zur Aktivierung von SSL

Muße

     Gedichte
     Musik
     Bilder

Jenseits des Üblichen ....

Besucherstatistik

Besucherstatistik

Monitoring

Montastic status badge