+2009-01-06 Chris Marrin <cmarrin@apple.com>
+
+ Reviewed by David Hyatt.
+
+ * animations/animation-test-helpers.js:
+ (runAnimationTest.matrixStringToArray):
+ (runAnimationTest.checkExpectedValue):
+ * animations/simultaneous-start-left-expected.txt: Added.
+ * animations/simultaneous-start-left.html: Added.
+ * animations/simultaneous-start-transform-expected.txt: Added.
+ * animations/simultaneous-start-transform.html: Added.
+
2009-01-06 Dean Jackson <dino@apple.com>
Reviewed by Dave Hyatt.
Each sub-array must contain these items in this order:
- the name of the CSS animation (may be null) [1]
- the time in seconds at which to snapshot the CSS property
- - the id of the element on which to get the CSS property value
- - the name of the CSS property to get [2]
+ - the id of the element on which to get the CSS property value [2]
+ - the name of the CSS property to get [3]
- the expected value for the CSS property
- the tolerance to use when comparing the effective CSS property value with its expected value
[1] If null is passed, a regular setTimeout() will be used instead to snapshot the animated property in the future,
- instead of fast forwarding using the pauseAnimationAtTimeOnElementWithId() JS API from DRT
+ instead of fast forwarding using the pauseAnimationAtTimeOnElementWithId() JS API from DRT
+
+ [2] If a single string is passed, it is the id of the element to test. If an array with 2 elements is passed they
+ are the ids of 2 elements, whose values are compared for equality. In this case the expected value is ignored
+ but the tolerance is used in the comparison.
- [2] If the CSS property name is "webkitTransform", expected value must be an array of 1 or more numbers corresponding to the matrix elements,
+ [3] If the CSS property name is "webkitTransform", expected value must be an array of 1 or more numbers corresponding to the matrix elements,
or a string which will be compared directly (useful if the expected value is "none")
If the CSS property name is "webkitTransform.N", expected value must be a number corresponding to the Nth element of the matrix
var diff = Math.abs(actual - desired);
return diff <= tolerance;
}
-
+
+ function matrixStringToArray(s)
+ {
+ var m = s.split("(");
+ m = m[1].split(")");
+ return m[0].split(",");
+ }
+
function checkExpectedValue(expected, index)
{
var animationName = expected[index][0];
var expectedValue = expected[index][4];
var tolerance = expected[index][5];
+ // Check for a pair of element Ids
+ var compareElements = false;
+ var elementId2;
+ if (typeof elementId != "string") {
+ if (elementId.length != 2)
+ return;
+
+ elementId2 = elementId[1];
+ elementId = elementId[0];
+ compareElements = true;
+ }
+
if (animationName && hasPauseAnimationAPI && !layoutTestController.pauseAnimationAtTimeOnElementWithId(animationName, time, elementId)) {
result += "FAIL - animation \"" + animationName + "\" is not running" + "<br>";
return;
}
-
- var computedValue;
- var pass;
+
+ if (compareElements && animationName && hasPauseAnimationAPI && !layoutTestController.pauseAnimationAtTimeOnElementWithId(animationName, time, elementId2)) {
+ result += "FAIL - animation \"" + animationName + "\" is not running" + "<br>";
+ return;
+ }
+
+ var computedValue, computedValue2;
+ var pass = true;
if (!property.indexOf("webkitTransform")) {
computedValue = window.getComputedStyle(document.getElementById(elementId)).webkitTransform;
-
- if (typeof expectedValue == "string")
- pass = (computedValue == expectedValue);
- else if (typeof expectedValue == "number") {
- var m = computedValue.split("(");
- var m = m[1].split(",");
- pass = isCloseEnough(parseFloat(m[parseInt(property.substring(16))]), expectedValue, tolerance);
- } else {
- var m = computedValue.split("(");
- var m = m[1].split(",");
- for (i = 0; i < expectedValue.length; ++i) {
- pass = isCloseEnough(parseFloat(m[i]), expectedValue[i], tolerance);
+ if (compareElements) {
+ computedValue2 = window.getComputedStyle(document.getElementById(elementId2)).webkitTransform;
+ var m1 = matrixStringToArray(computedValue);
+ var m2 = matrixStringToArray(computedValue2);
+
+ // for now we assume that both matrices are either both 2D or both 3D
+ var count = (computedValue.substring(0, 7) == "matrix3d") ? 16 : 6;
+ for (var i = 0; i < count; ++i) {
+ pass = isCloseEnough(parseFloat(m1[i]), m2[i], tolerance);
if (!pass)
break;
+ }
+ } else {
+ if (typeof expectedValue == "string")
+ pass = (computedValue == expectedValue);
+ else if (typeof expectedValue == "number") {
+ var m = matrixStringToArray(computedValue);
+ pass = isCloseEnough(parseFloat(m[parseInt(property.substring(16))]), expectedValue, tolerance);
+ } else {
+ var m = matrixStringToArray(computedValue);
+ for (i = 0; i < expectedValue.length; ++i) {
+ pass = isCloseEnough(parseFloat(m[i]), expectedValue[i], tolerance);
+ if (!pass)
+ break;
+ }
}
}
} else if (property == "lineHeight") {
computedValue = parseInt(window.getComputedStyle(document.getElementById(elementId)).lineHeight);
- pass = isCloseEnough(computedValue, expectedValue, tolerance);
- } else {
+ if (compareElements) {
+ computedValue2 = parseInt(window.getComputedStyle(document.getElementById(elementId2)).lineHeight);
+ pass = isCloseEnough(computedValue, computedValue2, tolerance);
+ }
+ else
+ pass = isCloseEnough(computedValue, expectedValue, tolerance);
+ } else {
var computedStyle = window.getComputedStyle(document.getElementById(elementId)).getPropertyCSSValue(property);
computedValue = computedStyle.getFloatValue(CSSPrimitiveValue.CSS_NUMBER);
- pass = isCloseEnough(computedValue, expectedValue, tolerance);
+ if (compareElements) {
+ var computedStyle2 = window.getComputedStyle(document.getElementById(elementId2)).getPropertyCSSValue(property);
+ computedValue2 = computedStyle2.getFloatValue(CSSPrimitiveValue.CSS_NUMBER);
+ pass = isCloseEnough(computedValue, computedValue2, tolerance);
+ }
+ else
+ pass = isCloseEnough(computedValue, expectedValue, tolerance);
}
- if (pass)
- result += "PASS - \"" + property + "\" property for \"" + elementId + "\" element at " + time + "s saw something close to: " + expectedValue + "<br>";
- else
- result += "FAIL - \"" + property + "\" property for \"" + elementId + "\" element at " + time + "s expected: " + expectedValue + " but saw: " + computedValue + "<br>";
+ if (compareElements) {
+ if (pass)
+ result += "PASS - \"" + property + "\" property for \"" + elementId + "\" and \"" + elementId2 + "\" elements at " + time + "s are close enough to each other" + "<br>";
+ else
+ result += "FAIL - \"" + property + "\" property for \"" + elementId + "\" and \"" + elementId2 + "\" elements at " + time + "s saw: \"" + computedValue + "\" and \"" + computedValue2 + "\" which are not close enough to each other" + "<br>";
+ } else {
+ if (pass)
+ result += "PASS - \"" + property + "\" property for \"" + elementId + "\" element at " + time + "s saw something close to: " + expectedValue + "<br>";
+ else
+ result += "FAIL - \"" + property + "\" property for \"" + elementId + "\" element at " + time + "s expected: " + expectedValue + " but saw: " + computedValue + "<br>";
+ }
}
function endTest()
--- /dev/null
+This test performs an animation of the left property. It animates over 10 seconds. It takes 3 snapshots and expects each result to be within a specified range.
+PASS - "left" property for "box1" element at 2s saw something close to: 170
+PASS - "left" property for "box2" element at 2s saw something close to: 170
+PASS - "left" property for "box1" and "box2" elements at 2s are close enough to each other
+PASS - "left" property for "box1" element at 5s saw something close to: 410
+PASS - "left" property for "box2" element at 5s saw something close to: 410
+PASS - "left" property for "box1" and "box2" elements at 5s are close enough to each other
+PASS - "left" property for "box1" element at 8s saw something close to: 650
+PASS - "left" property for "box2" element at 8s saw something close to: 650
+PASS - "left" property for "box1" and "box2" elements at 8s are close enough to each other
+
--- /dev/null
+<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
+ "http://www.w3.org/TR/html4/loose.dtd">
+
+<html lang="en">
+<head>
+ <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
+ <title>Test simultaneous starting of two animations</title>
+ <style type="text/css" media="screen">
+ .box {
+ position: relative;
+ left: 100px;
+ height: 100px;
+ width: 100px;
+ background-color: blue;
+ -webkit-animation-duration: 10s;
+ -webkit-animation-timing-function: linear;
+ -webkit-animation-name: "anim";
+ }
+ @-webkit-keyframes "anim" {
+ from { left: 10px; }
+ to { left: 810px; }
+ }
+ #box1 {
+ top: 10px;
+ background-color: blue;
+ }
+ #box2 {
+ top: 10px;
+ background-color: red;
+ }
+ </style>
+ <script src="animation-test-helpers.js" type="text/javascript" charset="utf-8"></script>
+ <script type="text/javascript" charset="utf-8">
+
+ const expectedValues = [
+ // [animation-name, time, element-id, property, expected-value, tolerance]
+ ["anim", 2, "box1", "left", 170, 5],
+ ["anim", 2, "box2", "left", 170, 5],
+ ["anim", 2, ["box1", "box2"], "left", "", 0],
+ ["anim", 5, "box1", "left", 410, 5],
+ ["anim", 5, "box2", "left", 410, 5],
+ ["anim", 5, ["box1", "box2"], "left", "", 0],
+ ["anim", 8, "box1", "left", 650, 5],
+ ["anim", 8, "box2", "left", 650, 5],
+ ["anim", 8, ["box1", "box2"], "left", "", 0],
+ ];
+
+ runAnimationTest(expectedValues);
+
+ </script>
+</head>
+<body>
+This test performs an animation of the left property. It animates over 10 seconds.
+It takes 3 snapshots and expects each result to be within a specified range.
+<div id="box1" class="box">
+</div>
+<div id="box2" class="box">
+</div>
+<div id="result">
+</div>
+</body>
+</html>
--- /dev/null
+This test performs an animation of the transform property. It animates over 10 seconds. It takes 3 snapshots and expects each result to be within a specified range. PASS - "webkitTransform" property for "box1" element at 2s saw something close to: 0.309017,0.951057
+PASS - "webkitTransform" property for "box2" element at 2s saw something close to: 0.309017,0.951057
+PASS - "webkitTransform" property for "box1" and "box2" elements at 2s are close enough to each other
+PASS - "webkitTransform" property for "box1" element at 5s saw something close to: -1,0
+PASS - "webkitTransform" property for "box2" element at 5s saw something close to: -1,0
+PASS - "webkitTransform" property for "box1" and "box2" elements at 5s are close enough to each other
+PASS - "webkitTransform" property for "box1" element at 8s saw something close to: 0.309017,-0.951057
+PASS - "webkitTransform" property for "box2" element at 8s saw something close to: 0.309017,-0.951057
+PASS - "webkitTransform" property for "box1" and "box2" elements at 8s are close enough to each other
+
--- /dev/null
+<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
+ "http://www.w3.org/TR/html4/loose.dtd">
+
+<html lang="en">
+<head>
+ <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
+ <title>Test simultaneous starting of two animations</title>
+ <style type="text/css" media="screen">
+ .box {
+ position: absolute;
+ top: 40px;
+ height: 100px;
+ width: 100px;
+ background-color: blue;
+ -webkit-animation-duration: 10s;
+ -webkit-animation-timing-function: linear;
+ -webkit-animation-name: "anim";
+ }
+ @-webkit-keyframes "anim" {
+ from { -webkit-transform: rotate(0deg); }
+ to { -webkit-transform: rotate(360deg); }
+ }
+ #box1 {
+ left: 40px;
+ background-color: blue;
+ }
+ #box2 {
+ left: 160px;
+ background-color: red;
+ }
+ </style>
+ <script src="animation-test-helpers.js" type="text/javascript" charset="utf-8"></script>
+ <script type="text/javascript" charset="utf-8">
+
+ const expectedValues = [
+ // [animation-name, time, element-id, property, expected-value, tolerance]
+ ["anim", 2, "box1", "webkitTransform", [ 0.309017, 0.951057 ], 0.05],
+ ["anim", 2, "box2", "webkitTransform", [ 0.309017, 0.951057 ], 0.05],
+ ["anim", 2, ["box1", "box2"], "webkitTransform", "", 0],
+ ["anim", 5, "box1", "webkitTransform", [ -1, 0 ], 0.05],
+ ["anim", 5, "box2", "webkitTransform", [ -1, 0 ], 0.05],
+ ["anim", 5, ["box1", "box2"], "webkitTransform", "", 0],
+ ["anim", 8, "box1", "webkitTransform", [ 0.309017, -0.951057 ], 0.05],
+ ["anim", 8, "box2", "webkitTransform", [ 0.309017, -0.951057 ], 0.05],
+ ["anim", 8, ["box1", "box2"], "webkitTransform", "", 0],
+ ];
+
+ runAnimationTest(expectedValues);
+
+ </script>
+</head>
+<body>
+This test performs an animation of the transform property. It animates over 10 seconds.
+It takes 3 snapshots and expects each result to be within a specified range.
+<div id="box1" class="box">
+</div>
+<div id="box2" class="box">
+</div>
+<div id="result" style="position:absolute; top:150px">
+</div>
+</body>
+</html>
+2009-01-06 Chris Marrin <cmarrin@apple.com>
+
+ Reviewed by David Hyatt.
+
+ Tests: animations/simultaneous-start-left.html
+ animations/simultaneous-start-transform.html
+
+ Fixed https://bugs.webkit.org/show_bug.cgi?id=22870
+
+ I added calls beginAnimationUpdate() and endAnimationUpdate() calls
+ to AnimationController. These are called by Document at the start
+ and end of the recalcStyle cycle. Right now, I'm just using the
+ beginAnimationUpdate() method to reset an animation time value.
+ The first time the animation time is accessed after this reset I set
+ it to the currentTime. So all animations in that cycle get the same
+ start time.
+
+ The test cases checked in test this, but in the case of the 'left'
+ test it actually doesn't make any difference in most cases. This is
+ because values are clamped to whole pixels, so the start times would
+ have to be pretty far off for the test to fail using the old
+ currentTime() model. Still, under really heavy load, it's possible for
+ the test to fail without these changes.
+
+ The 'transform' test is another story. It animates to the full resolution
+ of a floating point number, so the test fails miserably without this
+ fix.
+
+ * dom/Document.cpp:
+ (WebCore::Document::recalcStyle):
+ * page/animation/AnimationBase.cpp:
+ (WebCore::AnimationBase::updateStateMachine):
+ (WebCore::AnimationBase::fireAnimationEventsIfNeeded):
+ (WebCore::AnimationBase::willNeedService):
+ (WebCore::AnimationBase::progress):
+ (WebCore::AnimationBase::goIntoEndingOrLoopingState):
+ (WebCore::AnimationBase::beginAnimationUpdateTime):
+ * page/animation/AnimationBase.h:
+ * page/animation/AnimationController.cpp:
+ (WebCore::AnimationControllerPrivate::beginAnimationUpdateTime):
+ (WebCore::AnimationControllerPrivate::setBeginAnimationUpdateTime):
+ (WebCore::AnimationControllerPrivate::AnimationControllerPrivate):
+ (WebCore::AnimationController::updateAnimations):
+ (WebCore::AnimationController::beginAnimationUpdateTime):
+ (WebCore::AnimationController::beginAnimationUpdate):
+ (WebCore::AnimationController::endAnimationUpdate):
+ * page/animation/AnimationController.h:
+ * page/animation/KeyframeAnimation.cpp:
+ (WebCore::KeyframeAnimation::animate):
+
2009-01-06 Julien Chaffraix <jchaffraix@webkit.org>
Reviewed by Nikolas Zimmermann.
if (m_inStyleRecalc)
return; // Guard against re-entrancy. -dwh
-
+
+ m_frame->animation()->beginAnimationUpdate();
+
m_inStyleRecalc = true;
suspendPostAttachCallbacks();
m_closeAfterStyleRecalc = false;
implicitClose();
}
+
+ m_frame->animation()->endAnimationUpdate();
}
void Document::updateRendering()
// If we are in AnimationStateStartWaitResponse, the animation will get canceled before
// we get a response, so move to the next state.
endAnimation(false);
- updateStateMachine(AnimationStateInputStartTimeSet, currentTime());
+ updateStateMachine(AnimationStateInputStartTimeSet, beginAnimationUpdateTime());
}
return;
}
ASSERT(input == AnimationStateInputStartAnimation || input == AnimationStateInputPlayStateRunnning || input == AnimationStateInputPlayStatePaused);
if (input == AnimationStateInputStartAnimation || input == AnimationStateInputPlayStateRunnning) {
m_waitedForResponse = false;
- m_requestedStartTime = currentTime();
+ m_requestedStartTime = beginAnimationUpdateTime();
m_animState = AnimationStateStartWaitTimer;
}
break;
} else {
ASSERT(!paused());
// We're waiting for the start timer to fire and we got a pause. Cancel the timer, pause and wait
- m_pauseTime = currentTime();
+ m_pauseTime = beginAnimationUpdateTime();
m_animState = AnimationStatePausedWaitTimer;
}
break;
if (overridden() || !startAnimation(0)) {
// We're not going to get a startTime callback, so fire the start time here
m_animState = AnimationStateStartWaitResponse;
- updateStateMachine(AnimationStateInputStartTimeSet, currentTime());
+ updateStateMachine(AnimationStateInputStartTimeSet, beginAnimationUpdateTime());
} else
m_waitedForResponse = true;
break;
goIntoEndingOrLoopingState();
} else {
// We are pausing while running. Cancel the animation and wait
- m_pauseTime = currentTime();
+ m_pauseTime = beginAnimationUpdateTime();
endAnimation(false);
m_animState = AnimationStatePausedRun;
}
}
} else {
// We are pausing while running. Cancel the animation and wait
- m_pauseTime = currentTime();
+ m_pauseTime = beginAnimationUpdateTime();
endAnimation(false);
m_animState = AnimationStatePausedRun;
}
ASSERT(input == AnimationStateInputPlayStateRunnning);
ASSERT(paused());
// Update the times
- m_startTime += currentTime() - m_pauseTime;
+ m_startTime += beginAnimationUpdateTime() - m_pauseTime;
m_pauseTime = -1;
// we were waiting for the start timer to fire, go back and wait again
ASSERT(paused());
// Update the times
if (m_animState == AnimationStatePausedRun)
- m_startTime += currentTime() - m_pauseTime;
+ m_startTime += beginAnimationUpdateTime() - m_pauseTime;
else
m_startTime = 0;
m_pauseTime = -1;
// Start the animation
if (overridden() || !startAnimation(m_startTime)) {
// We're not going to get a startTime callback, so fire the start time here
- updateStateMachine(AnimationStateInputStartTimeSet, currentTime());
+ updateStateMachine(AnimationStateInputStartTimeSet, beginAnimationUpdateTime());
} else
m_waitedForResponse = true;
break;
// Check for start timeout
if (m_animState == AnimationStateStartWaitTimer) {
- if (currentTime() - m_requestedStartTime >= m_animation->delay())
+ if (beginAnimationUpdateTime() - m_requestedStartTime >= m_animation->delay())
updateStateMachine(AnimationStateInputStartTimerFired, 0);
return;
}
- double elapsedDuration = currentTime() - m_startTime;
+ double elapsedDuration = beginAnimationUpdateTime() - m_startTime;
ASSERT(elapsedDuration >= 0);
// Check for end timeout
return -1;
if (m_animState == AnimationStateStartWaitTimer) {
- double timeFromNow = m_animation->delay() - (currentTime() - m_requestedStartTime);
+ double timeFromNow = m_animation->delay() - (beginAnimationUpdateTime() - m_requestedStartTime);
return (float) ((timeFromNow > 0) ? timeFromNow : 0);
}
if (preActive())
return 0;
- double elapsedTime = running() && !paused() ? (currentTime() - m_startTime) : (m_pauseTime - m_startTime);
+ double elapsedTime = running() && !paused() ? (beginAnimationUpdateTime() - m_startTime) : (m_pauseTime - m_startTime);
if (running() && elapsedTime < 0)
return 0;
if (m_animation->iterationCount() > 0)
totalDuration = m_animation->duration() * m_animation->iterationCount();
- const double elapsedDuration = currentTime() - m_startTime;
+ const double elapsedDuration = beginAnimationUpdateTime() - m_startTime;
ASSERT(elapsedDuration >= 0);
double durationLeft = 0;
double nextIterationTime = totalDuration;
m_pauseTime = m_startTime + t - m_animation->delay();
}
+double AnimationBase::beginAnimationUpdateTime() const
+{
+ return m_compAnim->animationController()->beginAnimationUpdateTime();
+}
+
} // namespace WebCore
void pauseAtTime(double t);
+ double beginAnimationUpdateTime() const;
+
protected:
virtual void overrideAnimations() { }
virtual void resumeOverriddenAnimations() { }
#include "CSSParser.h"
#include "EventNames.h"
#include "Frame.h"
+#include "SystemTime.h"
#include "Timer.h"
namespace WebCore {
static const double cAnimationTimerDelay = 0.025;
+static const double cBeginAnimationUpdateTimeNotSet = -1;
class AnimationControllerPrivate {
public:
bool pauseAnimationAtTime(RenderObject*, const String& name, double t);
bool pauseTransitionAtTime(RenderObject*, const String& property, double t);
+ double beginAnimationUpdateTime()
+ {
+ if (m_beginAnimationUpdateTime == cBeginAnimationUpdateTimeNotSet)
+ m_beginAnimationUpdateTime = currentTime();
+ return m_beginAnimationUpdateTime;
+ }
+
+ void setBeginAnimationUpdateTime(double t) { m_beginAnimationUpdateTime = t; }
+
private:
typedef HashMap<RenderObject*, RefPtr<CompositeAnimation> > RenderObjectAnimationMap;
};
Vector<EventToDispatch> m_eventsToDispatch;
+
+ double m_beginAnimationUpdateTime;
};
AnimationControllerPrivate::AnimationControllerPrivate(Frame* frame)
: m_animationTimer(this, &AnimationControllerPrivate::animationTimerFired)
, m_updateRenderingDispatcher(this, &AnimationControllerPrivate::updateRenderingDispatcherFired)
, m_frame(frame)
+ , m_beginAnimationUpdateTime(cBeginAnimationUpdateTimeNotSet)
{
}
}
PassRefPtr<RenderStyle> AnimationController::updateAnimations(RenderObject* renderer, RenderStyle* newStyle)
-{
+{
// Don't do anything if we're in the cache
if (!renderer->document() || renderer->document()->inPageCache())
return newStyle;
m_data->styleAvailable();
}
+double AnimationController::beginAnimationUpdateTime()
+{
+ return m_data->beginAnimationUpdateTime();
+}
+
+void AnimationController::beginAnimationUpdate()
+{
+ m_data->setBeginAnimationUpdateTime(cBeginAnimationUpdateTimeNotSet);
+}
+
+void AnimationController::endAnimationUpdate()
+{
+}
+
} // namespace WebCore
else
m_numStyleAvailableWaiters--;
}
+
+ double beginAnimationUpdateTime();
+
+ void beginAnimationUpdate();
+ void endAnimationUpdate();
private:
AnimationControllerPrivate* m_data;
// We should cache the last pair or something.
// Find the first key
- double elapsedTime = (m_startTime > 0 || m_pauseTime > 0) ? ((!paused() ? currentTime() : m_pauseTime) - m_startTime) : 0;
+ double elapsedTime = (m_startTime > 0 || m_pauseTime > 0) ? ((!paused() ? beginAnimationUpdateTime() : m_pauseTime) - m_startTime) : 0;
if (elapsedTime < 0)
elapsedTime = 0;