[Web Animations] Expose Web Animations CSS integration as an experimental feature
[WebKit-https.git] / LayoutTests / transitions / resources / transition-test-helpers.js
index d67a41e..ed39e25 100644 (file)
@@ -21,11 +21,11 @@ Function parameters:
 
 */
 
-const usePauseAPI = true;
-const dontUsePauseAPI = false;
+var usePauseAPI = true;
+var dontUsePauseAPI = false;
 
-const shouldBeTransitioning = true;
-const shouldNotBeTransitioning = false;
+var shouldBeTransitioning = true;
+var shouldNotBeTransitioning = false;
 
 function roundNumber(num, decimalPlaces)
 {
@@ -59,6 +59,131 @@ function compareRGB(rgb, expected, tolerance)
             isCloseEnough(parseInt(rgb[2]), expected[2], tolerance));
 }
 
+function parseCrossFade(s)
+{
+    var matches = s.match("(?:-webkit-)?cross-fade\\((.*)\\s*,\\s*(.*)\\s*,\\s*(.*)\\)");
+
+    if (!matches)
+        return null;
+
+    return {"from": matches[1], "to": matches[2], "percent": parseFloat(matches[3])}
+}
+
+function extractPathValues(path)
+{
+    var components = path.split(' ');
+    var result = [];
+    for (component of components) {
+        var compMatch;
+        if (compMatch = component.match(/[0-9.-]+/)) {
+            result.push(parseFloat(component))
+        }
+    }
+    return result;
+}
+
+function parseClipPath(s)
+{
+    var pathMatch;
+    if (pathMatch = s.match(/path\(((evenodd|nonzero), ?)?\'(.+)\'\)/))
+        return extractPathValues(pathMatch[pathMatch.length - 1]);
+
+    if (pathMatch = s.match(/path\(((evenodd|nonzero), ?)?\"(.+)\"\)/))
+        return extractPathValues(pathMatch[pathMatch.length - 1]);
+
+    // FIXME: This only matches a subset of the shape syntax, and the polygon expects 4 points.
+    var patterns = [
+        /inset\(([\d.]+)\w+ ([\d.]+)\w+\)/,
+        /circle\(([\d.]+)\w+ at ([\d.]+)\w+ ([\d.]+)\w+\)/,
+        /ellipse\(([\d.]+)\w+ ([\d.]+)\w+ at ([\d.]+)\w+ ([\d.]+)\w+\)/,
+        /polygon\(([\d.]+)\w* ([\d.]+)\w*\, ([\d.]+)\w* ([\d.]+)\w*\, ([\d.]+)\w* ([\d.]+)\w*\, ([\d.]+)\w* ([\d.]+)\w*\)/,
+    ];
+    
+    for (pattern of patterns) {
+        var matchResult;
+        if (matchResult = s.match(pattern)) {
+            var result = [];
+            for (var i = 1; i < matchResult.length; ++i)
+                result.push(parseFloat(matchResult[i]));
+            return result;
+        }
+    }
+
+    window.console.log('failed to match ' + s);
+    return null;
+}
+
+function hasFloatValue(value)
+{
+    switch (value.primitiveType) {
+    case CSSPrimitiveValue.CSS_FR:
+    case CSSPrimitiveValue.CSS_NUMBER:
+    case CSSPrimitiveValue.CSS_PARSER_INTEGER:
+    case CSSPrimitiveValue.CSS_PERCENTAGE:
+    case CSSPrimitiveValue.CSS_EMS:
+    case CSSPrimitiveValue.CSS_EXS:
+    case CSSPrimitiveValue.CSS_CHS:
+    case CSSPrimitiveValue.CSS_REMS:
+    case CSSPrimitiveValue.CSS_PX:
+    case CSSPrimitiveValue.CSS_CM:
+    case CSSPrimitiveValue.CSS_MM:
+    case CSSPrimitiveValue.CSS_IN:
+    case CSSPrimitiveValue.CSS_PT:
+    case CSSPrimitiveValue.CSS_PC:
+    case CSSPrimitiveValue.CSS_DEG:
+    case CSSPrimitiveValue.CSS_RAD:
+    case CSSPrimitiveValue.CSS_GRAD:
+    case CSSPrimitiveValue.CSS_TURN:
+    case CSSPrimitiveValue.CSS_MS:
+    case CSSPrimitiveValue.CSS_S:
+    case CSSPrimitiveValue.CSS_HZ:
+    case CSSPrimitiveValue.CSS_KHZ:
+    case CSSPrimitiveValue.CSS_DIMENSION:
+    case CSSPrimitiveValue.CSS_VW:
+    case CSSPrimitiveValue.CSS_VH:
+    case CSSPrimitiveValue.CSS_VMIN:
+    case CSSPrimitiveValue.CSS_VMAX:
+    case CSSPrimitiveValue.CSS_DPPX:
+    case CSSPrimitiveValue.CSS_DPI:
+    case CSSPrimitiveValue.CSS_DPCM:
+        return true;
+    }
+    return false;
+}
+
+function getNumericValue(cssValue)
+{
+    if (hasFloatValue(cssValue.primitiveType))
+        return cssValue.getFloatValue(cssValue.primitiveType);
+
+    return -1;
+}
+
+function isCalcPrimitiveValue(value)
+{
+    switch (value.primitiveType) {
+    case 113: // CSSPrimitiveValue.CSS_CALC:
+    case 114: // CSSPrimitiveValue.CSS_CALC_PERCENTAGE_WITH_NUMBER:
+    case 115: // CSSPrimitiveValue.CSS_CALC_PERCENTAGE_WITH_LENGTH:
+    return true;
+    }
+    return false;
+}
+
+function extractNumbersFromCalcExpression(value, values)
+{
+    var calcRegexp = /^calc\((.+)\)$/;
+    var result = calcRegexp.exec(value.cssText);
+    var numberMatch = /([^\.\-0-9]*)(-?[\.0-9]+)/;
+    var remainder = result[1];
+    var match;
+    while ((match = numberMatch.exec(remainder)) !== null) {
+        var skipLength = match[1].length + match[2].length;
+        values.push(parseFloat(match[2]))
+        remainder = remainder.substr(skipLength + 1);
+    }
+}
+
 function checkExpectedValue(expected, index)
 {
     var time = expected[index][0];
@@ -109,33 +234,67 @@ function checkExpectedValue(expected, index)
     } else if (property == "lineHeight") {
         computedValue = parseInt(window.getComputedStyle(document.getElementById(elementId)).lineHeight);
         pass = isCloseEnough(computedValue, expectedValue, tolerance);
+    } else if (property == "background-image"
+               || property == "border-image-source"
+               || property == "border-image"
+               || property == "list-style-image"
+               || property == "-webkit-mask-image"
+               || property == "-webkit-mask-box-image") {
+        if (property == "border-image" || property == "-webkit-mask-image" || property == "-webkit-mask-box-image")
+            property += "-source";
+        
+        computedValue = window.getComputedStyle(document.getElementById(elementId)).getPropertyCSSValue(property).cssText;
+        computedCrossFade = parseCrossFade(computedValue);
+
+        if (!computedCrossFade) {
+            pass = false;
+        } else {
+            pass = isCloseEnough(computedCrossFade.percent, expectedValue, tolerance);
+        }
+    } else if (property == "-webkit-clip-path" || property == "-webkit-shape-outside") {
+        computedValue = window.getComputedStyle(document.getElementById(elementId)).getPropertyCSSValue(property).cssText;
+
+        var expectedValues = parseClipPath(expectedValue);
+        var values = parseClipPath(computedValue);
+        
+        pass = false;
+        if (values && values.length == expectedValues.length) {
+            pass = true
+            for (var i = 0; i < values.length; ++i)
+                pass &= isCloseEnough(values[i], expectedValues[i], tolerance);
+        }
     } else {
         var computedStyle = window.getComputedStyle(document.getElementById(elementId)).getPropertyCSSValue(property);
         if (computedStyle.cssValueType == CSSValue.CSS_VALUE_LIST) {
             var values = [];
             for (var i = 0; i < computedStyle.length; ++i) {
-                switch (computedStyle[i].cssValueType) {
+                var styleValue = computedStyle[i];
+                switch (styleValue.cssValueType) {
                   case CSSValue.CSS_PRIMITIVE_VALUE:
-                    values.push(computedStyle[i].getFloatValue(CSSPrimitiveValue.CSS_NUMBER));
+                    if (hasFloatValue(styleValue))
+                        values.push(styleValue.getFloatValue(CSSPrimitiveValue.CSS_NUMBER));
+                    else if (isCalcPrimitiveValue(styleValue))
+                        extractNumbersFromCalcExpression(styleValue, values);
                     break;
                   case CSSValue.CSS_CUSTOM:
                     // arbitrarily pick shadow-x and shadow-y
                     if (isShadow) {
-                      var shadowXY = getShadowXY(computedStyle[i]);
+                      var shadowXY = getShadowXY(styleValue);
                       values.push(shadowXY[0]);
                       values.push(shadowXY[1]);
                     } else
-                      values.push(computedStyle[i].cssText);
+                      values.push(styleValue.cssText);
                     break;
                 }
             }
             computedValue = values.join(',');
-            pass = true;
+            pass = values.length > 0;
             for (var i = 0; i < values.length; ++i)
                 pass &= isCloseEnough(values[i], expectedValue[i], tolerance);
         } else if (computedStyle.cssValueType == CSSValue.CSS_PRIMITIVE_VALUE) {
             switch (computedStyle.primitiveType) {
                 case CSSPrimitiveValue.CSS_STRING:
+                case CSSPrimitiveValue.CSS_IDENT:
                     computedValue = computedStyle.getStringValue();
                     pass = computedValue == expectedValue;
                     break;
@@ -182,8 +341,8 @@ function endTest()
 {
     document.getElementById('result').innerHTML = result;
 
-    if (window.layoutTestController)
-        layoutTestController.notifyDone();
+    if (window.testRunner)
+        testRunner.notifyDone();
 }
 
 function checkExpectedValueCallback(expected, index)
@@ -191,6 +350,31 @@ function checkExpectedValueCallback(expected, index)
     return function() { checkExpectedValue(expected, index); };
 }
 
+const prefix = "-webkit-";
+const propertiesRequiringPrefix = ["-webkit-text-stroke-color", "-webkit-text-fill-color"];
+
+function pauseTransitionAtTimeOnElement(transitionProperty, time, element)
+{
+    // If we haven't opted into CSS Animations and CSS Transitions as Web Animations, use the internal API.
+    if ('internals' in window && !internals.settings.webAnimationsCSSIntegrationEnabled())
+        return internals.pauseTransitionAtTimeOnElement(transitionProperty, time, element);
+
+    if (transitionProperty.startsWith(prefix) && !propertiesRequiringPrefix.includes(transitionProperty))
+        transitionProperty = transitionProperty.substr(prefix.length);
+
+    // Otherwise, use the Web Animations API.
+    const animations = element.getAnimations();
+    for (let animation of animations) {
+        if (animation instanceof CSSTransition && animation.transitionProperty == transitionProperty) {
+            animation.currentTime = time * 1000;
+            animation.pause();
+            return true;
+        }
+    }
+    console.log(`A transition for property ${transitionProperty} could not be found`);
+    return false;
+}
+
 function runTest(expected, usePauseAPI)
 {
     var maxTime = 0;
@@ -205,10 +389,10 @@ function runTest(expected, usePauseAPI)
         if (tryToPauseTransition === undefined)
           tryToPauseTransition = shouldBeTransitioning;
 
-        // We can only use the transition fast-forward mechanism if DRT implements pauseTransitionAtTimeOnElementWithId()
         if (hasPauseTransitionAPI && usePauseAPI) {
             if (tryToPauseTransition) {
-              if (!layoutTestController.pauseTransitionAtTimeOnElementWithId(property, time, elementId))
+              var element = document.getElementById(elementId);
+              if (!pauseTransitionAtTimeOnElement(property, time, element))
                 window.console.log("Failed to pause '" + property + "' transition on element '" + elementId + "'");
             }
             checkExpectedValue(expected, i);
@@ -249,16 +433,14 @@ function startTest(expected, usePauseAPI, callback)
 }
 
 var result = "";
-var hasPauseTransitionAPI;
+var hasPauseTransitionAPI = true;
 
 function runTransitionTest(expected, callback, usePauseAPI, doPixelTest)
 {
-    hasPauseTransitionAPI = ('layoutTestController' in window) && ('pauseTransitionAtTimeOnElementWithId' in layoutTestController);
-    
-    if (window.layoutTestController) {
+    if (window.testRunner) {
         if (!doPixelTest)
-            layoutTestController.dumpAsText();
-        layoutTestController.waitUntilDone();
+            testRunner.dumpAsText();
+        testRunner.waitUntilDone();
     }
     
     if (!expected)