Bug 474739. Handle zero-duration intervals properly in SMIL. r=roc

--HG--
extra : rebase_source : 8ad07bedbb6b808dd36ce8949dcb186ba1fd7ca1
This commit is contained in:
Brian Birtles 2009-08-05 14:39:04 +12:00
parent 1e621e52ea
commit 43ff7295f1
5 changed files with 447 additions and 53 deletions

View File

@ -234,11 +234,8 @@ nsSMILTimedElement::SampleAt(nsSMILTime aDocumentTime)
{
case STATE_STARTUP:
{
nsSMILTimeValue beginAfter;
beginAfter.SetMillis(LL_MININT);
mElementState =
(NS_SUCCEEDED(GetNextInterval(beginAfter, PR_TRUE, mCurrentInterval)))
(NS_SUCCEEDED(GetNextInterval(nsnull, mCurrentInterval)))
? STATE_WAITING
: STATE_POSTACTIVE;
stateChanged = PR_TRUE;
@ -263,9 +260,7 @@ nsSMILTimedElement::SampleAt(nsSMILTime aDocumentTime)
if (mCurrentInterval.mEnd.CompareTo(docTime) <= 0) {
nsSMILInterval newInterval;
mElementState =
(NS_SUCCEEDED(GetNextInterval(mCurrentInterval.mEnd,
PR_FALSE,
newInterval)))
(NS_SUCCEEDED(GetNextInterval(&mCurrentInterval, newInterval)))
? STATE_WAITING
: STATE_POSTACTIVE;
if (mClient) {
@ -706,30 +701,28 @@ nsSMILTimedElement::SetBeginOrEndSpec(const nsAString& aSpec,
// http://www.w3.org/TR/2001/REC-smil-animation-20010904/#Timing-BeginEnd-LC-Start
//
nsresult
nsSMILTimedElement::GetNextInterval(const nsSMILTimeValue& aBeginAfter,
PRBool aFirstInterval,
nsSMILTimedElement::GetNextInterval(const nsSMILInterval* aPrevInterval,
nsSMILInterval& aResult)
{
static nsSMILTimeValue zeroTime;
zeroTime.SetMillis(0L);
nsSMILTimeValue beginAfter = aBeginAfter;
if (mRestartMode == RESTART_NEVER && aPrevInterval)
return NS_ERROR_FAILURE;
// Calc starting point
nsSMILTimeValue beginAfter;
PRBool prevIntervalWasZeroDur = PR_FALSE;
if (aPrevInterval) {
beginAfter = aPrevInterval->mEnd;
prevIntervalWasZeroDur
= (aPrevInterval->mEnd.CompareTo(aPrevInterval->mBegin) == 0);
} else {
beginAfter.SetMillis(LL_MININT);
}
nsSMILTimeValue tempBegin;
nsSMILTimeValue tempEnd;
PRInt32 beginPos = 0;
PRInt32 endPos = 0;
//
// This is to handle the special case when a we are calculating the first
// interval and we have a non-0-duration interval immediately after
// a 0-duration in which case but we have to be careful not to re-use an end
// that has already been used in another interval. See the pseudocode in
// SMILANIM 3.6.8 for getFirstInterval.
//
PRInt32 endMaxPos = 0;
if (mRestartMode == RESTART_NEVER && !aFirstInterval)
return NS_ERROR_FAILURE;
nsSMILInstanceTime::Comparator comparator;
mBeginInstances.Sort(comparator);
@ -739,6 +732,7 @@ nsSMILTimedElement::GetNextInterval(const nsSMILTimeValue& aBeginAfter,
if (!mBeginSpecSet && beginAfter.CompareTo(zeroTime) <= 0) {
tempBegin.SetMillis(0);
} else {
PRInt32 beginPos = 0;
PRBool beginFound = GetNextGreaterOrEqual(mBeginInstances, beginAfter,
beginPos, tempBegin);
if (!beginFound)
@ -751,23 +745,16 @@ nsSMILTimedElement::GetNextInterval(const nsSMILTimeValue& aBeginAfter,
tempEnd = CalcActiveEnd(tempBegin, indefiniteEnd);
} else {
//
// Start searching from the beginning again.
//
endPos = 0;
PRInt32 endPos = 0;
PRBool endFound = GetNextGreaterOrEqual(mEndInstances, tempBegin,
endPos, tempEnd);
if ((!aFirstInterval && tempEnd.CompareTo(aBeginAfter) == 0) ||
(aFirstInterval && tempEnd.CompareTo(tempBegin) == 0 &&
endPos <= endMaxPos)) {
endFound =
GetNextGreaterOrEqual(mEndInstances, tempBegin, endPos, tempEnd);
// If the last interval ended at the same point and was zero-duration and
// this one is too, look for another end to use instead
if (tempEnd.CompareTo(tempBegin) == 0 && prevIntervalWasZeroDur) {
endFound = GetNextGreater(mEndInstances, tempBegin, endPos, tempEnd);
}
endMaxPos = endPos;
if (!endFound) {
if (mEndHasEventConditions || mEndInstances.Length() == 0) {
tempEnd.SetUnresolved();
@ -785,11 +772,25 @@ nsSMILTimedElement::GetNextInterval(const nsSMILTimeValue& aBeginAfter,
tempEnd = CalcActiveEnd(tempBegin, tempEnd);
}
if (tempEnd.CompareTo(zeroTime) > 0) {
// If we get two zero-length intervals in a row we will potentially have an
// infinite loop so we break it here by searching for the next begin time
// greater than tempEnd on the next time around.
if (tempEnd.IsResolved() && tempBegin.CompareTo(tempEnd) == 0) {
if (prevIntervalWasZeroDur) {
beginAfter.SetMillis(tempEnd.GetMillis()+1);
prevIntervalWasZeroDur = PR_FALSE;
continue;
}
prevIntervalWasZeroDur = PR_TRUE;
}
if (tempEnd.CompareTo(zeroTime) > 0 ||
(tempBegin.CompareTo(zeroTime) == 0 && tempEnd.CompareTo(zeroTime) == 0)) {
aResult.mBegin = tempBegin;
aResult.mEnd = tempEnd;
return NS_OK;
} else if (mRestartMode == RESTART_NEVER) {
// tempEnd <= 0 so we're going to loop which effectively means restarting
return NS_ERROR_FAILURE;
} else {
beginAfter = tempEnd;
@ -800,6 +801,19 @@ nsSMILTimedElement::GetNextInterval(const nsSMILTimeValue& aBeginAfter,
return NS_ERROR_FAILURE;
}
PRBool
nsSMILTimedElement::GetNextGreater(
const nsTArray<nsSMILInstanceTime>& aList,
const nsSMILTimeValue& aBase,
PRInt32 &aPosition,
nsSMILTimeValue& aResult)
{
PRBool found;
while ((found = GetNextGreaterOrEqual(aList, aBase, aPosition, aResult))
&& aResult.CompareTo(aBase) == 0);
return found;
}
PRBool
nsSMILTimedElement::GetNextGreaterOrEqual(
const nsTArray<nsSMILInstanceTime>& aList,
@ -957,9 +971,7 @@ nsSMILTimedElement::CheckForEarlyEnd(const nsSMILTimeValue& aDocumentTime)
nsSMILTimeValue nextBegin;
PRInt32 position = 0;
while (GetNextGreaterOrEqual(mBeginInstances, mCurrentInterval.mBegin,
position, nextBegin)
&& nextBegin.CompareTo(mCurrentInterval.mBegin) == 0);
GetNextGreater(mBeginInstances, mCurrentInterval.mBegin, position, nextBegin);
if (nextBegin.IsResolved() &&
nextBegin.CompareTo(mCurrentInterval.mBegin) > 0 &&
@ -973,16 +985,10 @@ void
nsSMILTimedElement::UpdateCurrentInterval()
{
nsSMILInterval updatedInterval;
PRBool isFirstInterval = mOldIntervals.IsEmpty();
nsSMILTimeValue beginAfter;
if (!isFirstInterval) {
beginAfter = mOldIntervals[mOldIntervals.Length() - 1].mEnd;
} else {
beginAfter.SetMillis(LL_MININT);
}
nsresult rv = GetNextInterval(beginAfter, isFirstInterval, updatedInterval);
nsSMILInterval* prevInterval = mOldIntervals.IsEmpty()
? nsnull
: &mOldIntervals[mOldIntervals.Length() - 1];
nsresult rv = GetNextInterval(prevInterval, updatedInterval);
if (NS_SUCCEEDED(rv)) {
@ -1049,7 +1055,7 @@ nsSMILTimedElement::SampleFillValue()
nsSMILTime simpleTime =
ActiveTimeToSimpleTime(activeTime, repeatIteration);
if (simpleTime == 0L) {
if (simpleTime == 0L && repeatIteration) {
mClient->SampleLastValue(--repeatIteration);
} else {
mClient->SampleAt(simpleTime, mSimpleDur, repeatIteration);

View File

@ -251,9 +251,12 @@ protected:
*
* @see SMILANIM 3.6.8
*/
nsresult GetNextInterval(const nsSMILTimeValue& aBeginAfter,
PRBool aFirstInstance,
nsresult GetNextInterval(const nsSMILInterval* aPrevInterval,
nsSMILInterval& aResult);
PRBool GetNextGreater(const nsTArray<nsSMILInstanceTime>& aList,
const nsSMILTimeValue& aBase,
PRInt32& aPosition,
nsSMILTimeValue& aResult);
PRBool GetNextGreaterOrEqual(
const nsTArray<nsSMILInstanceTime>& aList,
const nsSMILTimeValue& aBase,

View File

@ -20,6 +20,7 @@
#
# Contributor(s):
# Daniel Holbert <dholbert@mozilla.com>
# Brian Birtles <bbirtles@gmail.com>
#
# Alternatively, the contents of this file may be used under the terms of
# either of the GNU General Public License Version 2 or later (the "GPL"),
@ -49,6 +50,8 @@ _TEST_FILES = test_smilRestart.xhtml \
test_smilSetCurrentTime.xhtml \
test_smilSync.xhtml \
test_smilSyncTransform.xhtml \
test_smilTiming.xhtml \
test_smilTimingZeroIntervals.xhtml \
$(NULL)
libs:: $(_TEST_FILES)

View File

@ -0,0 +1,111 @@
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<title>Test for SMIL timing</title>
<script type="text/javascript" src="/MochiKit/packed.js"></script>
<script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
</head>
<body>
<p id="display"></p>
<div id="content" style="display: none">
<svg id="svg" xmlns="http://www.w3.org/2000/svg" width="120px" height="120px">
<circle cx="-100" cy="20" r="15" fill="blue" id="circle"/>
</svg>
</div>
<pre id="test">
<script class="testbody" type="text/javascript">
<![CDATA[
/** Test for SMIL timing **/
/* Global Variables */
const svgns="http://www.w3.org/2000/svg";
var svg = document.getElementById("svg");
var circle = document.getElementById('circle');
SimpleTest.waitForExplicitFinish();
function createAnim() {
var anim = document.createElementNS(svgns,'animate');
anim.setAttribute('attributeName','cx');
anim.setAttribute('from','0');
anim.setAttribute('to','100');
anim.setAttribute('dur','10s');
anim.setAttribute('begin','indefinite');
return circle.appendChild(anim);
}
function removeAnim(anim) {
anim.parentNode.removeChild(anim);
}
function main() {
svg.pauseAnimations();
var tests =
[ testOffsetStartup,
testMultipleBegins,
testNegativeBegins,
testSorting
];
for (var i = 0; i < tests.length; i++) {
var anim = createAnim();
svg.setCurrentTime(0);
tests[i](anim);
removeAnim(anim);
}
SimpleTest.finish();
}
function checkSample(time, expectedValue) {
svg.setCurrentTime(time);
is(circle.cx.animVal.value, expectedValue);
}
function testOffsetStartup(anim) {
anim.setAttribute('begin', '3s');
checkSample(0,-100);
checkSample(4,10);
}
function testMultipleBegins(anim) {
anim.setAttribute('begin', '2s; 6s');
anim.setAttribute('dur', ' 2s');
checkSample(0,-100);
checkSample(3,50);
checkSample(4,-100);
checkSample(7,50);
checkSample(8,-100);
}
function testNegativeBegins(anim) {
anim.setAttribute('begin', '-3s; 1s ; 4s');
anim.setAttribute('dur', '2s ');
anim.setAttribute('fill', 'freeze');
checkSample(0,-100);
checkSample(0.5,-100);
checkSample(1,0);
checkSample(2,50);
checkSample(3,100);
checkSample(5,50);
}
function testSorting(anim) {
anim.setAttribute('begin', '-3s; 110s; 1s; 4s; -5s; -10s');
anim.setAttribute('end', '111s; -5s; -15s; 6s; -5s; 1.2s');
anim.setAttribute('dur', '2s ');
anim.setAttribute('fill', 'freeze');
checkSample(0,-100);
checkSample(1,0);
checkSample(2,10);
checkSample(4,0);
checkSample(5,50);
checkSample(109,100);
checkSample(110,0);
checkSample(112,50);
}
window.addEventListener("load", main, false);
]]>
</script>
</pre>
</body>
</html>

View File

@ -0,0 +1,271 @@
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<title>Test for SMIL timing with zero-duration intervals</title>
<script type="text/javascript" src="/MochiKit/packed.js"></script>
<script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
</head>
<body>
<p id="display"></p>
<div id="content" style="display: none">
<svg id="svg" xmlns="http://www.w3.org/2000/svg" width="120px" height="120px">
<circle cx="-100" cy="20" r="15" fill="blue" id="circle"/>
</svg>
</div>
<pre id="test">
<script class="testbody" type="text/javascript">
<![CDATA[
/** Test for SMIL timing with zero-duration intervals **/
/* Global Variables */
const svgns="http://www.w3.org/2000/svg";
var svg = document.getElementById("svg");
var circle = document.getElementById('circle');
SimpleTest.waitForExplicitFinish();
function createAnim() {
var anim = document.createElementNS(svgns,'animate');
anim.setAttribute('attributeName','cx');
anim.setAttribute('from','0');
anim.setAttribute('to','100');
anim.setAttribute('dur','10s');
anim.setAttribute('begin','indefinite');
return circle.appendChild(anim);
}
function removeAnim(anim) {
anim.parentNode.removeChild(anim);
}
function main() {
svg.pauseAnimations();
var tests =
[ testZeroDurationIntervalsA,
testZeroDurationIntervalsB,
testZeroDurationIntervalsC,
testZeroDurationIntervalsD,
testZeroDurationIntervalsE,
testZeroDurationIntervalsF,
testZeroDurationIntervalsG,
testZeroDurationIntervalsH,
testZeroDurationIntervalsI,
testZeroDurationIntervalsJ,
testZeroDurationIntervalsK,
testZeroDurationIntervalsL,
testZeroDurationIntervalsM,
testZeroDurationIntervalsN,
testZeroDurationIntervalsO
];
for (var i = 0; i < tests.length; i++) {
var anim = createAnim();
svg.setCurrentTime(0);
tests[i](anim);
removeAnim(anim);
}
SimpleTest.finish();
}
function checkSample(time, expectedValue) {
svg.setCurrentTime(time);
is(circle.cx.animVal.value, expectedValue);
}
function testZeroDurationIntervalsA(anim) {
// The zero-duration interval should play, followed by a second interval
// starting at the same point. There is no end for the interval
// at 4s so it should not play.
anim.setAttribute('begin', '1s ;4s');
anim.setAttribute('end', '1s; 2s');
anim.setAttribute('dur', '2s ');
anim.setAttribute('fill', 'freeze');
checkSample(0,-100);
checkSample(1,0);
checkSample(1.1,5);
checkSample(2,50);
checkSample(3,50);
checkSample(4,50);
checkSample(5,50);
checkSample(6,50);
}
function testZeroDurationIntervalsB(anim) {
// This interval should however actually restart as there is a valid end-point
anim.setAttribute('begin', '1s ;4s');
anim.setAttribute('end', '1.1s; indefinite');
anim.setAttribute('dur', '2s ');
anim.setAttribute('fill', 'freeze');
checkSample(0,-100);
checkSample(1,0);
checkSample(1.1,5);
checkSample(2,5);
checkSample(4,0);
checkSample(5,50);
}
function testZeroDurationIntervalsC(anim) {
// -0.5s has already been used as the endpoint of one interval so don't use it
// a second time
anim.setAttribute('begin', '-2s; -0.5s');
anim.setAttribute('end', '-0.5s; 1s');
anim.setAttribute('dur', '2s');
anim.setAttribute('fill', 'freeze');
checkSample(0,25);
checkSample(1.5,75);
}
function testZeroDurationIntervalsD(anim) {
// Two end points that could make a zero-length interval
anim.setAttribute('begin', '-2s; -0.5s');
anim.setAttribute('end', '-0.5s; -0.5s; 1s');
anim.setAttribute('dur', '2s');
anim.setAttribute('fill', 'freeze');
checkSample(0,25);
checkSample(1.5,75);
}
function testZeroDurationIntervalsE(anim) {
// Should give us 1s-1s, 1s-5s
anim.setAttribute('begin', '1s');
anim.setAttribute('end', '1s; 5s');
anim.setAttribute('fill', 'freeze');
is(anim.getStartTime(),1);
checkSample(0,-100);
checkSample(1,0);
checkSample(6,40);
}
function testZeroDurationIntervalsF(anim) {
// Should give us 1s-1s
anim.setAttribute('begin', '1s');
anim.setAttribute('end', '1s');
anim.setAttribute('fill', 'freeze');
is(anim.getStartTime(),1);
checkSample(0,-100);
checkSample(1,0);
checkSample(2,0);
is(anim.getStartTime(),1);
}
function testZeroDurationIntervalsG(anim) {
// Test a non-zero interval after a zero interval
// Should give us 1-2s, 3-3s, 3-4s
anim.setAttribute('begin', '1s; 3s');
anim.setAttribute('end', '3s; 5s');
anim.setAttribute('dur', '1s');
anim.setAttribute('fill', 'freeze');
checkSample(0,-100);
checkSample(1,0);
checkSample(2,100);
checkSample(3,0);
checkSample(5,100);
}
function testZeroDurationIntervalsH(anim) {
// Test multiple non-adjacent zero-intervals
// Should give us 1-1s, 1-2s, 3-3s, 3-4s
anim.setAttribute('begin', '1s; 3s');
anim.setAttribute('end', '1s; 3s; 5s');
anim.setAttribute('dur', '1s');
anim.setAttribute('fill', 'freeze');
checkSample(0,-100);
checkSample(1,0);
checkSample(2,100);
checkSample(3,0);
checkSample(5,100);
}
function testZeroDurationIntervalsI(anim) {
// Test skipping values that are the same
// Should give us 1-1s, 1-2s
anim.setAttribute('begin', '1s; 1s');
anim.setAttribute('end', '1s; 1s; 2s');
anim.setAttribute('fill', 'freeze');
is(anim.getStartTime(),1);
checkSample(0,-100);
checkSample(1,0);
checkSample(2,10);
checkSample(3,10);
}
function testZeroDurationIntervalsJ(anim) {
// Should give us 0-0.5s, 1-1s, 1-3s
anim.setAttribute('begin', '0s; 1s; 1s');
anim.setAttribute('end', '1s; 3s');
anim.setAttribute('dur', '0.5s');
anim.setAttribute('fill', 'freeze');
is(anim.getStartTime(),0);
checkSample(0,0);
checkSample(0.6,100);
checkSample(1,0);
checkSample(2,100);
}
function testZeroDurationIntervalsK(anim) {
// Should give us -0.5-1s
anim.setAttribute('begin', '-0.5s');
anim.setAttribute('end', '-0.5s; 1s');
anim.setAttribute('fill', 'freeze');
is(anim.getStartTime(),-0.5);
checkSample(0,5);
checkSample(1,15);
checkSample(2,15);
}
function testZeroDurationIntervalsL(anim) {
// Test that multiple end values are ignored
// Should give us 1-1s, 1-3s
anim.setAttribute('begin', '1s');
anim.setAttribute('end', '1s; 1s; 1s; 3s');
anim.setAttribute('fill', 'freeze');
is(anim.getStartTime(),1);
checkSample(0,-100);
checkSample(1,0);
checkSample(2,10);
checkSample(4,20);
}
function testZeroDurationIntervalsM(anim) {
// Test 0-duration interval at start
anim.setAttribute('begin', '0s');
anim.setAttribute('end', '0s');
anim.setAttribute('fill', 'freeze');
is(anim.getStartTime(),0);
checkSample(0,0);
checkSample(1,0);
}
function testZeroDurationIntervalsN(anim) {
// Test 0-active-duration interval at start (different code path to above)
anim.setAttribute('begin', '0s');
anim.setAttribute('repeatDur', '0s');
anim.setAttribute('fill', 'freeze');
is(anim.getStartTime(),0);
checkSample(0,0);
checkSample(1,0);
}
function testZeroDurationIntervalsO(anim) {
// Make a zero-duration interval by constraining the active duration
// We should not loop infinitely but should look for the next begin time after
// that (in this case that is 2s, which would otherwise have been skipped
// because restart=whenNotActive)
// Should give us 1-1s, 2-2s
anim.setAttribute('begin', '1s; 2s');
anim.setAttribute('repeatDur', '0s');
anim.setAttribute('restart', 'whenNotActive');
anim.setAttribute('fill', 'freeze');
is(anim.getStartTime(),1);
checkSample(0,-100);
checkSample(1,0);
checkSample(1.5,0);
checkSample(3,0);
is(anim.getStartTime(),2);
}
window.addEventListener("load", main, false);
]]>
</script>
</pre>
</body>
</html>