Bug 948245 part 2 - Allow the min attribute to extend the active duration; r=dholbert

The min attribute on an animation element can extend the active duration making
it longer than the "repeat duration" (the amount of the time the animation runs
including all repeats). For the remaining time after the repeat duration has
completed until the end of the active duration the animation should apply its
fill behavior.

Previously this was not implemented and min could not extend the active
duration. Allowing this effectively introduces an additional kind of state where
we are both within the active interval but not animating. In this case we
set the animation function (referred to as the "client" for historical reasons)
to inactive so that effectively the timing model is active but the animation
model is inactive.

(In the future we will come up with something a little easier to understand when
we rework this in terms of Web Animations components.)
This commit is contained in:
Brian Birtles 2013-12-13 13:41:52 +09:00
parent 881e4d0e52
commit 2bacbb339e
7 changed files with 202 additions and 31 deletions

View File

@ -164,6 +164,15 @@ public:
return (mIsActive || mIsFrozen);
}
/**
* Indicates if the animation is active.
*
* @return true if the animation is active, false otherwise.
*/
bool IsActive() const {
return mIsActive;
}
/**
* Indicates if this animation will replace the passed in result rather than
* adding to it. Animations that replace the underlying value may be called

View File

@ -667,19 +667,32 @@ nsSMILTimedElement::DoSampleAt(nsSMILTime aContainerTime, bool aEndOnly)
NS_ASSERTION(aContainerTime >= beginTime,
"Sample time should not precede current interval");
nsSMILTime activeTime = aContainerTime - beginTime;
SampleSimpleTime(activeTime);
// We register our repeat times as milestones (except when we're
// seeking) so we should get a sample at exactly the time we repeat.
// (And even when we are seeking we want to update
// mCurrentRepeatIteration so we do that first before testing the seek
// state.)
uint32_t prevRepeatIteration = mCurrentRepeatIteration;
if (ActiveTimeToSimpleTime(activeTime, mCurrentRepeatIteration)==0 &&
// The 'min' attribute can cause the active interval to be longer than
// the 'repeating interval'.
// In that extended period we apply the fill mode.
if (GetRepeatDuration() <= nsSMILTimeValue(activeTime)) {
if (mClient && mClient->IsActive()) {
mClient->Inactivate(mFillMode == FILL_FREEZE);
}
SampleFillValue();
} else {
SampleSimpleTime(activeTime);
// We register our repeat times as milestones (except when we're
// seeking) so we should get a sample at exactly the time we repeat.
// (And even when we are seeking we want to update
// mCurrentRepeatIteration so we do that first before testing the
// seek state.)
uint32_t prevRepeatIteration = mCurrentRepeatIteration;
if (
ActiveTimeToSimpleTime(activeTime, mCurrentRepeatIteration)==0 &&
mCurrentRepeatIteration != prevRepeatIteration &&
mCurrentRepeatIteration &&
mSeekState == SEEK_NOT_SEEKING) {
FireTimeEventAsync(NS_SMIL_REPEAT,
static_cast<int32_t>(mCurrentRepeatIteration));
FireTimeEventAsync(NS_SMIL_REPEAT,
static_cast<int32_t>(mCurrentRepeatIteration));
}
}
}
}
@ -1084,12 +1097,8 @@ nsSMILTimedElement::SetFillMode(const nsAString& aFillModeSpec)
? nsSMILFillMode(temp.GetEnumValue())
: FILL_REMOVE;
// Check if we're in a fill-able state: i.e. we've played at least one
// interval and are now between intervals or at the end of all intervals
bool isFillable = HasPlayed() &&
(mElementState == STATE_WAITING || mElementState == STATE_POSTACTIVE);
if (mClient && mFillMode != previousFillMode && isFillable) {
// Update fill mode of client
if (mFillMode != previousFillMode && HasClientInFillRange()) {
mClient->Inactivate(mFillMode == FILL_FREEZE);
SampleFillValue();
}
@ -1102,9 +1111,9 @@ nsSMILTimedElement::UnsetFillMode()
{
uint16_t previousFillMode = mFillMode;
mFillMode = FILL_REMOVE;
if ((mElementState == STATE_WAITING || mElementState == STATE_POSTACTIVE) &&
previousFillMode == FILL_FREEZE && mClient && HasPlayed())
if (previousFillMode == FILL_FREEZE && HasClientInFillRange()) {
mClient->Inactivate(false);
}
}
void
@ -1865,8 +1874,7 @@ nsSMILTimedElement::ApplyMinAndMax(const nsSMILTimeValue& aDuration) const
if (aDuration > mMax) {
result = mMax;
} else if (aDuration < mMin) {
nsSMILTimeValue repeatDur = GetRepeatDuration();
result = mMin > repeatDur ? repeatDur : mMin;
result = mMin;
} else {
result = aDuration;
}
@ -2060,16 +2068,35 @@ nsSMILTimedElement::SampleFillValue()
if (mFillMode != FILL_FREEZE || !mClient)
return;
const nsSMILInterval* prevInterval = GetPreviousInterval();
NS_ABORT_IF_FALSE(prevInterval,
"Attempting to sample fill value but there is no previous interval");
NS_ABORT_IF_FALSE(prevInterval->End()->Time().IsDefinite() &&
prevInterval->End()->IsFixedTime(),
"Attempting to sample fill value but the endpoint of the previous "
"interval is not resolved and fixed");
nsSMILTime activeTime;
nsSMILTime activeTime = prevInterval->End()->Time().GetMillis() -
prevInterval->Begin()->Time().GetMillis();
if (mElementState == STATE_WAITING || mElementState == STATE_POSTACTIVE) {
const nsSMILInterval* prevInterval = GetPreviousInterval();
NS_ABORT_IF_FALSE(prevInterval,
"Attempting to sample fill value but there is no previous interval");
NS_ABORT_IF_FALSE(prevInterval->End()->Time().IsDefinite() &&
prevInterval->End()->IsFixedTime(),
"Attempting to sample fill value but the endpoint of the previous "
"interval is not resolved and fixed");
activeTime = prevInterval->End()->Time().GetMillis() -
prevInterval->Begin()->Time().GetMillis();
// If the interval's repeat duration was shorter than its active duration,
// use the end of the repeat duration to determine the frozen animation's
// state.
nsSMILTimeValue repeatDuration = GetRepeatDuration();
if (repeatDuration.IsDefinite()) {
activeTime = std::min(repeatDuration.GetMillis(), activeTime);
}
} else if (mElementState == STATE_ACTIVE) {
// If we are being asked to sample the fill value while active we *must*
// have a repeat duration shorter than the active duration so use that.
MOZ_ASSERT(GetRepeatDuration().IsDefinite(),
"Attempting to sample fill value of an active animation with "
"an indefinite repeat duration");
activeTime = GetRepeatDuration().GetMillis();
}
uint32_t repeatIteration;
nsSMILTime simpleTime =
@ -2165,8 +2192,13 @@ nsSMILTimedElement::GetNextMilestone(nsSMILMilestone& aNextMilestone) const
// Work out what comes next: the interval end or the next repeat iteration
nsSMILTimeValue nextRepeat;
if (mSeekState == SEEK_NOT_SEEKING && mSimpleDur.IsDefinite()) {
nextRepeat.SetMillis(mCurrentInterval->Begin()->Time().GetMillis() +
(mCurrentRepeatIteration + 1) * mSimpleDur.GetMillis());
nsSMILTime nextRepeatActiveTime =
(mCurrentRepeatIteration + 1) * mSimpleDur.GetMillis();
// Check that the repeat fits within the repeat duration
if (nsSMILTimeValue(nextRepeatActiveTime) < GetRepeatDuration()) {
nextRepeat.SetMillis(mCurrentInterval->Begin()->Time().GetMillis() +
nextRepeatActiveTime);
}
}
nsSMILTimeValue nextMilestone =
std::min(mCurrentInterval->End()->Time(), nextRepeat);
@ -2275,6 +2307,15 @@ nsSMILTimedElement::GetPreviousInterval() const
: mOldIntervals[mOldIntervals.Length()-1].get();
}
bool
nsSMILTimedElement::HasClientInFillRange() const
{
// Returns true if we have a client that is in the range where it will fill
return mClient &&
((mElementState != STATE_ACTIVE && HasPlayed()) ||
(mElementState == STATE_ACTIVE && !mClient->IsActive()));
}
bool
nsSMILTimedElement::EndHasEventConditions() const
{

View File

@ -512,6 +512,7 @@ protected:
const nsSMILInstanceTime* GetEffectiveBeginInstance() const;
const nsSMILInterval* GetPreviousInterval() const;
bool HasPlayed() const { return !mOldIntervals.IsEmpty(); }
bool HasClientInFillRange() const;
bool EndHasEventConditions() const;
bool AreEndTimesDependentOn(
const nsSMILInstanceTime* aBase) const;

View File

@ -38,6 +38,7 @@ support-files =
[test_smilMappedAttrFromBy.xhtml]
[test_smilMappedAttrFromTo.xhtml]
[test_smilMappedAttrPaced.xhtml]
[test_smilMinTiming.html]
[test_smilRepeatDuration.html]
[test_smilRepeatTiming.xhtml]
[test_smilReset.xhtml]

View File

@ -0,0 +1,93 @@
<!doctype html>
<html>
<!--
https://bugzilla.mozilla.org/show_bug.cgi?id=948245
-->
<head>
<meta charset="utf-8">
<title>Test for Bug 948245</title>
<script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
</head>
<body>
<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=948245">Mozilla Bug 948245</a>
<p id="display"></p>
<div id="content" style="display: none">
<svg id="svg" onload="this.pauseAnimations()">
<rect fill="red" id="rect" x="0">
<animate attributeName="x" to="100" id="animation" dur="100s" min="200s"/>
</rect>
</svg>
</div>
<pre id="test">
<script class="testbody" type="text/javascript">
// The 'min' attribute introduces a kind of additional state into the SMIL
// model. If the 'min' attribute extends the active duration, the additional
// time between the amount of time the animation normally runs for (called the
// 'repeat duration') and the extended active duration is filled using the
// fill mode.
//
// Below we refer to this period of time between the end of the repeat
// duration and the end of the active duration as the 'extended period'.
//
// This test verifies that as we jump in and out of these states we produce
// the correct values.
//
// The test animation above produces an active interval that is longer than
// the 'repeating duration' of the animation.
var rect = $('rect'),
animation = $('animation');
// Animation doesn't start until onload
SimpleTest.waitForExplicitFinish();
window.addEventListener("load", runTests, false);
function runTests() {
ok($('svg').animationsPaused(), "should be paused by <svg> load handler");
// In the extended period (t=150s) we should not be animating or filling
// since the default fill mode is "none".
animation.ownerSVGElement.setCurrentTime(150);
ise(rect.x.animVal.value, 0,
"Shouldn't fill in extended period with fill='none'");
// If we set the fill mode we should start filling.
animation.setAttribute("fill", "freeze");
ise(rect.x.animVal.value, 100,
"Should fill in extended period with fill='freeze'");
// If we unset the fill attribute we should stop filling.
animation.removeAttribute("fill");
ise(rect.x.animVal.value, 0, "Shouldn't fill after unsetting fill");
// If we jump back into the repeated interval (at t=50s) we should be
// animating.
animation.ownerSVGElement.setCurrentTime(50);
ise(rect.x.animVal.value, 50, "Should be active in repeating interval");
// If we jump to the boundary at the start of the extended period we should
// not be filling (since we removed the fill attribute above).
animation.ownerSVGElement.setCurrentTime(100);
ise(rect.x.animVal.value, 0,
"Shouldn't fill after seeking to boundary of extended period");
// If we apply a fill mode at this boundary point we should do regular fill
// behavior of using the last value in the interpolation range.
animation.setAttribute("fill", "freeze");
ise(rect.x.animVal.value, 100,
"Should fill at boundary to extended period");
// Check that if we seek past the interval we fill with the value at the end
// of the _repeat_duration_ not the value at the end of the
// _active_duration_.
animation.setAttribute("repeatCount", "1.5");
animation.ownerSVGElement.setCurrentTime(225);
ise(rect.x.animVal.value, 50,
"Should fill with the end of the repeat duration value");
SimpleTest.finish();
}
</script>
</pre>
</body>
</html>

View File

@ -0,0 +1,24 @@
<svg xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink"
class="reftest-wait"
onload="setTimeAndSnapshot(115, true)">
<script xlink:href="smil-util.js" type="text/javascript"/>
<!-- Test min behavior
Set up is as follows:
1. There is a rectangle with a lime fill.
2. A <set> animation (with default fill="none") sets the fill to
orange at t=100s.
It has a simple duration of 10s and a min duration of 20s.
3. A further <set> animation sets the fill to red when the first
animation finishes (using syncbase timing).
At time t=115s we should still be in the first animation's active
interval with its fill mode of 'none' applied which should mean the
original lime fill is used. -->
<rect width="100%" height="100%" fill="lime">
<set attributeName="fill" to="orange" dur="10s" min="20s" begin="100s"
id="a"/>
<set attributeName="fill" to="red" begin="a.end"/>
</rect>
</svg>

After

Width:  |  Height:  |  Size: 1017 B

View File

@ -251,6 +251,8 @@ fuzzy-if(cocoaWidget&&layersGPUAccelerated,1,2) == anim-gradient-attr-presence-0
# interaction between xml mapped attributes and their css equivalents
== mapped-attr-vs-css-prop-1.svg lime.svg
== min-1.svg lime.svg
== smil-transitions-interaction-1a.svg lime.svg
== smil-transitions-interaction-1b.svg lime.svg
== smil-transitions-interaction-2a.svg lime.svg