Bug 492458 - SVG SMIL: Implement backwards seeking - Part 1 - interval and instance time filtering. r=dholbert, sr=roc

This commit is contained in:
Brian Birtles 2010-07-03 14:52:50 +09:00
parent a5966c5522
commit dada1e0fb4
12 changed files with 557 additions and 170 deletions

View File

@ -74,9 +74,9 @@ nsSMILInstanceTime::nsSMILInstanceTime(const nsSMILTimeValue& aTime,
nsSMILInterval* aBaseInterval)
: mTime(aTime),
mFlags(0),
mSerial(0),
mVisited(PR_FALSE),
mChainEnd(PR_FALSE),
mFixedEndpointRefCnt(0),
mSerial(0),
mCreator(aCreator),
mBaseInterval(nsnull) // This will get set to aBaseInterval in a call to
// SetBaseInterval() at end of constructor
@ -87,7 +87,7 @@ nsSMILInstanceTime::nsSMILInstanceTime(const nsSMILTimeValue& aTime,
break;
case SOURCE_DOM:
mFlags = kClearOnReset | kFromDOM;
mFlags = kDynamic | kFromDOM;
break;
case SOURCE_SYNCBASE:
@ -95,7 +95,7 @@ nsSMILInstanceTime::nsSMILInstanceTime(const nsSMILTimeValue& aTime,
break;
case SOURCE_EVENT:
mFlags = kClearOnReset;
mFlags = kDynamic;
break;
}
@ -106,6 +106,9 @@ nsSMILInstanceTime::~nsSMILInstanceTime()
{
NS_ABORT_IF_FALSE(!mBaseInterval && !mCreator,
"Destroying instance time without first calling Unlink()");
NS_ABORT_IF_FALSE(mFixedEndpointRefCnt == 0,
"Destroying instance time that is still used as the fixed endpoint of an "
"interval");
}
void
@ -129,12 +132,9 @@ nsSMILInstanceTime::HandleChangedInterval(
"Got call to HandleChangedInterval on an independent instance time.");
NS_ABORT_IF_FALSE(mCreator, "Base interval is set but creator is not.");
if (mVisited || mChainEnd) {
// We're breaking the cycle here but we need to ensure that if we later
// receive a change notice in a different context (e.g. due to a time
// container change) that we don't end up following the chain further and so
// we set a flag to that effect.
mChainEnd = PR_TRUE;
if (mVisited) {
// Break the cycle here
Unlink();
return;
}
@ -152,20 +152,48 @@ void
nsSMILInstanceTime::HandleDeletedInterval()
{
NS_ABORT_IF_FALSE(mBaseInterval,
"Got call to HandleDeletedInterval on an independent instance time.");
NS_ABORT_IF_FALSE(mCreator, "Base interval is set but creator is not.");
"Got call to HandleDeletedInterval on an independent instance time");
NS_ABORT_IF_FALSE(mCreator, "Base interval is set but creator is not");
mBaseInterval = nsnull;
mFlags &= ~kMayUpdate; // Can't update without a base interval
nsRefPtr<nsSMILInstanceTime> deathGrip(this);
mCreator->HandleDeletedInstanceTime(*this);
mCreator = nsnull;
}
PRBool
nsSMILInstanceTime::IsDependent(const nsSMILInstanceTime& aOther) const
void
nsSMILInstanceTime::HandleFilteredInterval()
{
if (mVisited || mChainEnd)
NS_ABORT_IF_FALSE(mBaseInterval,
"Got call to HandleFilteredInterval on an independent instance time");
mBaseInterval = nsnull;
mFlags &= ~kMayUpdate; // Can't update without a base interval
mCreator = nsnull;
}
void
nsSMILInstanceTime::AddRefFixedEndpoint()
{
NS_ABORT_IF_FALSE(mFixedEndpointRefCnt < PR_UINT16_MAX,
"Fixed endpoint reference count upper limit reached");
++mFixedEndpointRefCnt;
mFlags &= ~kMayUpdate; // Once fixed, always fixed
}
void
nsSMILInstanceTime::ReleaseFixedEndpoint()
{
NS_ABORT_IF_FALSE(mFixedEndpointRefCnt > 0, "Duplicate release");
--mFixedEndpointRefCnt;
}
PRBool
nsSMILInstanceTime::IsDependentOn(const nsSMILInstanceTime& aOther) const
{
if (mVisited)
return PR_FALSE;
const nsSMILInstanceTime* myBaseTime = GetBaseTime();
@ -177,7 +205,7 @@ nsSMILInstanceTime::IsDependent(const nsSMILInstanceTime& aOther) const
// mVisited is mutable
AutoBoolSetter setVisited(const_cast<nsSMILInstanceTime*>(this)->mVisited);
return myBaseTime->IsDependent(aOther);
return myBaseTime->IsDependentOn(aOther);
}
void

View File

@ -92,24 +92,29 @@ public:
PRBool aBeginObjectChanged,
PRBool aEndObjectChanged);
void HandleDeletedInterval();
void HandleFilteredInterval();
const nsSMILTimeValue& Time() const { return mTime; }
const nsSMILTimeValueSpec* GetCreator() const { return mCreator; }
PRBool ClearOnReset() const { return !!(mFlags & kClearOnReset); }
PRBool MayUpdate() const { return !!(mFlags & kMayUpdate); }
PRBool IsDynamic() const { return !!(mFlags & kDynamic); }
PRBool IsFixedTime() const { return !(mFlags & kMayUpdate); }
PRBool FromDOM() const { return !!(mFlags & kFromDOM); }
PRBool IsUsedAsFixedEndpoint() const { return mFixedEndpointRefCnt > 0; }
void MarkNoLongerUpdating() { mFlags &= ~kMayUpdate; }
void AddRefFixedEndpoint();
void ReleaseFixedEndpoint();
void DependentUpdate(const nsSMILTimeValue& aNewTime)
{
NS_ABORT_IF_FALSE(MayUpdate(),
NS_ABORT_IF_FALSE(!IsFixedTime(),
"Updating an instance time that is not expected to be updated");
mTime = aNewTime;
}
PRBool IsDependent(const nsSMILInstanceTime& aOther) const;
PRBool IsDependent() const { return !!mBaseInterval; }
PRBool IsDependentOn(const nsSMILInstanceTime& aOther) const;
const nsSMILInterval* GetBaseInterval() const { return mBaseInterval; }
PRBool SameTimeAndBase(const nsSMILInstanceTime& aOther) const
{
@ -131,15 +136,16 @@ protected:
// Internal flags used to represent the behaviour of different instance times
enum {
// Indicates if this instance time should be removed when the owning timed
// element is reset. True for events and DOM calls.
kClearOnReset = 1,
// Indicates that this instance time was generated by an event or a DOM
// call. Such instance times require special handling when (i) the owning
// element is reset, and (ii) when a backwards seek is performed and the
// timing model is reconstructed.
kDynamic = 1,
// Indicates that this instance time is referred to by an
// nsSMILTimeValueSpec and as such may be updated. Such instance time should
// not be filtered out by the nsSMILTimedElement even if they appear to be
// in the past as they may be updated to a future time. Initially set for
// syncbase-generated times until they are frozen.
// in the past as they may be updated to a future time.
kMayUpdate = 2,
// Indicates that this instance time was generated from the DOM as opposed
@ -149,15 +155,26 @@ protected:
// DOM.
kFromDOM = 4
};
PRUint8 mFlags; // Combination of kClearOnReset, kMayUpdate, etc.
PRUint8 mFlags; // Combination of kDynamic, kMayUpdate, etc.
PRPackedBool mVisited; // (mutable) Cycle tracking
// Additional reference count to determine if this instance time is currently
// used as a fixed endpoint in any intervals. Instance times that are used in
// this way should not be removed when the owning nsSMILTimedElement removes
// instance times in response to a restart or in an attempt to free up memory
// by filtering out old instance times.
//
// Instance times are only shared in a few cases, namely:
// a) early ends,
// b) zero-duration intervals, and
// c) momentarily whilst establishing new intervals and updating the current
// interval
// Hence the limited range of a PRUint16 should be more than adequate.
PRUint16 mFixedEndpointRefCnt;
PRUint32 mSerial; // A serial number used by the containing class to
// specify the sort order for instance times with the
// same mTime.
PRPackedBool mVisited; // (mutable) Cycle tracking
PRPackedBool mChainEnd; // Flag to indicate that this instance time is part
// of some cyclic dependency and that in order to
// avoid infinite recursion the cycle should not be
// followed any further than this point.
nsSMILTimeValueSpec* mCreator; // The nsSMILTimeValueSpec object that created
// us. (currently only needed for syncbase

View File

@ -39,6 +39,8 @@
nsSMILInterval::nsSMILInterval()
:
mBeginFixed(PR_FALSE),
mEndFixed(PR_FALSE),
mBeginObjectChanged(PR_FALSE),
mEndObjectChanged(PR_FALSE)
{
@ -48,19 +50,28 @@ nsSMILInterval::nsSMILInterval(const nsSMILInterval& aOther)
:
mBegin(aOther.mBegin),
mEnd(aOther.mEnd),
mBeginFixed(PR_FALSE),
mEndFixed(PR_FALSE),
mBeginObjectChanged(PR_FALSE),
mEndObjectChanged(PR_FALSE)
{
NS_ABORT_IF_FALSE(aOther.mDependentTimes.IsEmpty(),
"Attempting to copy-construct an interval with dependent times, "
"this will lead to instance times being shared between intervals.");
// For the time being we don't allow intervals with fixed endpoints to be
// copied since we only ever copy-construct to establish a new current
// interval. If we ever need to copy historical intervals we may need to move
// the ReleaseFixedEndpoint calls from Unlink to the dtor.
NS_ABORT_IF_FALSE(!aOther.mBeginFixed && !aOther.mEndFixed,
"Attempting to copy-construct an interval with fixed endpoints");
}
nsSMILInterval::~nsSMILInterval()
{
NS_ABORT_IF_FALSE(mDependentTimes.IsEmpty(),
"Destroying interval without disassociating dependent instance times. "
"NotifyDeleting was not called.");
"Unlink was not called");
}
void
@ -76,12 +87,24 @@ nsSMILInterval::NotifyChanged(const nsSMILTimeContainer* aContainer)
}
void
nsSMILInterval::NotifyDeleting()
nsSMILInterval::Unlink(PRBool aFiltered)
{
for (PRInt32 i = mDependentTimes.Length() - 1; i >= 0; --i) {
mDependentTimes[i]->HandleDeletedInterval();
if (aFiltered) {
mDependentTimes[i]->HandleFilteredInterval();
} else {
mDependentTimes[i]->HandleDeletedInterval();
}
}
mDependentTimes.Clear();
if (mBegin && mBeginFixed) {
mBegin->ReleaseFixedEndpoint();
}
mBegin = nsnull;
if (mEnd && mEndFixed) {
mEnd->ReleaseFixedEndpoint();
}
mEnd = nsnull;
}
nsSMILInstanceTime*
@ -104,7 +127,9 @@ void
nsSMILInterval::SetBegin(nsSMILInstanceTime& aBegin)
{
NS_ABORT_IF_FALSE(aBegin.Time().IsResolved(),
"Attempting to set unresolved begin time on interval.");
"Attempting to set unresolved begin time on interval");
NS_ABORT_IF_FALSE(!mBeginFixed,
"Attempting to set begin time but the begin point is fixed");
if (mBegin == &aBegin)
return;
@ -116,6 +141,9 @@ nsSMILInterval::SetBegin(nsSMILInstanceTime& aBegin)
void
nsSMILInterval::SetEnd(nsSMILInstanceTime& aEnd)
{
NS_ABORT_IF_FALSE(!mEndFixed,
"Attempting to set end time but the end point is fixed");
if (mEnd == &aEnd)
return;
@ -123,6 +151,28 @@ nsSMILInterval::SetEnd(nsSMILInstanceTime& aEnd)
mEndObjectChanged = PR_TRUE;
}
void
nsSMILInterval::FixBegin()
{
NS_ABORT_IF_FALSE(mBegin && mEnd,
"Fixing begin point on un-initialized interval");
NS_ABORT_IF_FALSE(!mBeginFixed, "Duplicate calls to FixBegin()");
mBeginFixed = PR_TRUE;
mBegin->AddRefFixedEndpoint();
}
void
nsSMILInterval::FixEnd()
{
NS_ABORT_IF_FALSE(mBegin && mEnd,
"Fixing end point on un-initialized interval");
NS_ABORT_IF_FALSE(mBeginFixed,
"Fixing the end of an interval without a fixed begin");
NS_ABORT_IF_FALSE(!mEndFixed, "Duplicate calls to FixEnd()");
mEndFixed = PR_TRUE;
mEnd->AddRefFixedEndpoint();
}
void
nsSMILInterval::AddDependentTime(nsSMILInstanceTime& aTime)
{
@ -142,3 +192,19 @@ nsSMILInterval::RemoveDependentTime(const nsSMILInstanceTime& aTime)
mDependentTimes.RemoveElementSorted(&aTime);
NS_ABORT_IF_FALSE(found, "Couldn't find instance time to delete.");
}
PRBool
nsSMILInterval::IsDependencyChainLink() const
{
if (!mBegin || !mEnd)
return PR_FALSE; // Not yet initialised so it can't be part of a chain
if (mDependentTimes.IsEmpty())
return PR_FALSE; // No dependents, chain end
// So we have dependents, but we're still only a link in the chain (as opposed
// to the end of the chain) if one of our endpoints is dependent on an
// interval other than ourselves.
return (mBegin->IsDependent() && mBegin->GetBaseInterval() != this) ||
(mEnd->IsDependent() && mEnd->GetBaseInterval() != this);
}

View File

@ -57,7 +57,7 @@ public:
nsSMILInterval(const nsSMILInterval& aOther);
~nsSMILInterval();
void NotifyChanged(const nsSMILTimeContainer* aContainer);
void NotifyDeleting();
void Unlink(PRBool aFiltered = PR_FALSE);
const nsSMILInstanceTime* Begin() const
{
@ -83,37 +83,15 @@ public:
SetEnd(aEnd);
}
void FreezeBegin()
{
NS_ABORT_IF_FALSE(mBegin && mEnd,
"Freezing Begin() on un-initialized instance time");
mBegin->MarkNoLongerUpdating();
}
void FreezeEnd()
{
NS_ABORT_IF_FALSE(mBegin && mEnd,
"Freezing End() on un-initialized instance time");
NS_ABORT_IF_FALSE(!mBegin->MayUpdate(),
"Freezing the end of an interval without a fixed begin");
mEnd->MarkNoLongerUpdating();
}
// XXX Backwards seeking support (bug 492458)
void Unfreeze()
{
// XXX
UnfreezeEnd();
}
void UnfreezeEnd()
{
// XXX
}
void FixBegin();
void FixEnd();
void AddDependentTime(nsSMILInstanceTime& aTime);
void RemoveDependentTime(const nsSMILInstanceTime& aTime);
// Cue for assessing if this interval can be filtered
PRBool IsDependencyChainLink() const;
private:
nsRefPtr<nsSMILInstanceTime> mBegin;
nsRefPtr<nsSMILInstanceTime> mEnd;
@ -123,6 +101,18 @@ private:
// nsSMILInstanceTimes to notify when this interval is changed or deleted.
InstanceTimeList mDependentTimes;
// Indicates if the end points of the interval are fixed or not.
//
// Note that this is not the same as having an end point whose TIME is fixed
// (i.e. nsSMILInstanceTime::IsFixed() returns PR_TRUE). This is because it is
// possible to have an end point with a fixed TIME and yet still update the
// end point to refer to a different nsSMILInstanceTime object.
//
// However, if mBegin/EndFixed is PR_TRUE, then BOTH the nsSMILInstanceTime
// OBJECT returned for that end point and its TIME value will not change.
PRPackedBool mBeginFixed;
PRPackedBool mEndFixed;
// When change notifications are passed around the timing model we try to
// filter out all changes where there is no observable difference to an
// instance time. Changes that may produce an observable difference are:

View File

@ -160,8 +160,8 @@ nsSMILTimeValueSpec::HandleChangedInstanceTime(
PRBool aObjectChanged)
{
// If the instance time is fixed (e.g. because it's being used as the begin
// time of an active interval) we just ignore the change.
if (!aInstanceTimeToUpdate.MayUpdate())
// time of an active or postactive interval) we just ignore the change.
if (aInstanceTimeToUpdate.IsFixedTime())
return;
nsSMILTimeValue updatedTime =

View File

@ -90,6 +90,29 @@ nsSMILTimedElement::InstanceTimeComparator::LessThan(
return cmp == 0 ? aElem1->Serial() < aElem2->Serial() : cmp < 0;
}
//----------------------------------------------------------------------
// Templated helper functions
// Selectively remove elements from an array of type
// nsTArray<nsRefPtr<nsSMILInstanceTime> > with O(n) performance.
template <class TestFunctor>
void
nsSMILTimedElement::RemoveInstanceTimes(InstanceTimeList& aArray,
TestFunctor& aTest)
{
InstanceTimeList newArray;
for (PRUint32 i = 0; i < aArray.Length(); ++i) {
nsSMILInstanceTime* item = aArray[i].get();
if (aTest(item, i)) {
item->Unlink();
} else {
newArray.AppendElement(item);
}
}
aArray.Clear();
aArray.SwapElements(newArray);
}
//----------------------------------------------------------------------
// Static members
@ -108,6 +131,12 @@ nsAttrValue::EnumTable nsSMILTimedElement::sRestartModeTable[] = {
const nsSMILMilestone nsSMILTimedElement::sMaxMilestone(LL_MAXINT, PR_FALSE);
// The thresholds at which point we start filtering intervals and instance times
// indiscriminately.
// See FilterIntervals and FilterInstanceTimes.
const PRUint8 nsSMILTimedElement::sMaxNumIntervals = 20;
const PRUint8 nsSMILTimedElement::sMaxNumInstanceTimes = 100;
//----------------------------------------------------------------------
// Ctor, dtor
@ -149,12 +178,12 @@ nsSMILTimedElement::~nsSMILTimedElement()
// (We shouldn't get any callbacks from this because all our instance times
// are now disassociated with any intervals)
if (mCurrentInterval) {
mCurrentInterval->NotifyDeleting();
mCurrentInterval->Unlink();
mCurrentInterval = nsnull;
}
for (PRInt32 i = mOldIntervals.Length() - 1; i >= 0; --i) {
mOldIntervals[i]->NotifyDeleting();
mOldIntervals[i]->Unlink();
}
mOldIntervals.Clear();
}
@ -332,22 +361,33 @@ nsSMILTimedElement::RemoveInstanceTime(nsSMILInstanceTime* aInstanceTime,
UpdateCurrentInterval();
}
namespace
{
class RemoveByCreator
{
public:
RemoveByCreator(const nsSMILTimeValueSpec* aCreator) : mCreator(aCreator)
{ }
PRBool operator()(nsSMILInstanceTime* aInstanceTime, PRUint32 /*aIndex*/)
{
return aInstanceTime->GetCreator() == mCreator;
}
private:
const nsSMILTimeValueSpec* mCreator;
};
}
void
nsSMILTimedElement::RemoveInstanceTimesForCreator(
const nsSMILTimeValueSpec* aCreator, PRBool aIsBegin)
{
NS_ABORT_IF_FALSE(aCreator, "Creator not set");
InstanceTimeList& instances = aIsBegin ? mBeginInstances : mEndInstances;
PRInt32 count = instances.Length();
for (PRInt32 i = count - 1; i >= 0; --i) {
nsSMILInstanceTime* instance = instances[i].get();
NS_ABORT_IF_FALSE(instance, "NULL instance in instances array");
if (instance->GetCreator() == aCreator) {
instance->Unlink();
instances.RemoveElementAt(i);
}
}
InstanceTimeList& instances = aIsBegin ? mBeginInstances : mEndInstances;
RemoveByCreator removeByCreator(aCreator);
RemoveInstanceTimes(instances, removeByCreator);
UpdateCurrentInterval();
}
@ -445,12 +485,7 @@ nsSMILTimedElement::DoSampleAt(nsSMILTime aContainerTime, PRBool aEndOnly)
stateChanged = PR_TRUE;
if (mElementState == STATE_WAITING) {
mCurrentInterval = new nsSMILInterval(firstInterval);
if (!mCurrentInterval) {
NS_WARNING("Failed to allocate memory for new interval");
mElementState = STATE_POSTACTIVE;
} else {
NotifyNewInterval();
}
NotifyNewInterval();
}
}
break;
@ -459,7 +494,7 @@ nsSMILTimedElement::DoSampleAt(nsSMILTime aContainerTime, PRBool aEndOnly)
{
if (mCurrentInterval->Begin()->Time() <= sampleTime) {
mElementState = STATE_ACTIVE;
mCurrentInterval->FreezeBegin();
mCurrentInterval->FixBegin();
if (HasPlayed()) {
Reset(); // Apply restart behaviour
}
@ -483,14 +518,7 @@ nsSMILTimedElement::DoSampleAt(nsSMILTime aContainerTime, PRBool aEndOnly)
case STATE_ACTIVE:
{
// Only apply an early end if we're not already ending.
if (mCurrentInterval->End()->Time() > sampleTime) {
nsSMILInstanceTime* earlyEnd = CheckForEarlyEnd(sampleTime);
if (earlyEnd) {
mCurrentInterval->SetEnd(*earlyEnd);
NotifyChangedInterval();
}
}
ApplyEarlyEnd(sampleTime);
if (mCurrentInterval->End()->Time() <= sampleTime) {
nsSMILInterval newInterval;
@ -501,19 +529,15 @@ nsSMILTimedElement::DoSampleAt(nsSMILTime aContainerTime, PRBool aEndOnly)
if (mClient) {
mClient->Inactivate(mFillMode == FILL_FREEZE);
}
mCurrentInterval->FreezeEnd();
mCurrentInterval->FixEnd();
mOldIntervals.AppendElement(mCurrentInterval.forget());
// We must update mOldIntervals before calling SampleFillValue
SampleFillValue();
if (mElementState == STATE_WAITING) {
mCurrentInterval = new nsSMILInterval(newInterval);
if (!mCurrentInterval) {
NS_WARNING("Failed to allocate memory for new interval");
mElementState = STATE_POSTACTIVE;
} else {
NotifyNewInterval();
}
NotifyNewInterval();
}
FilterHistory();
stateChanged = PR_TRUE;
} else {
nsSMILTime beginTime = mCurrentInterval->Begin()->Time().GetMillis();
@ -552,37 +576,6 @@ nsSMILTimedElement::HandleContainerTimeChange()
}
}
void
nsSMILTimedElement::Reset()
{
// SMIL 3.0 section 5.4.3, 'Resetting element state':
// Any instance times associated with past Event-values, Repeat-values,
// Accesskey-values or added via DOM method calls are removed from the
// dependent begin and end instance times lists. In effect, all events and
// DOM methods calls in the past are cleared. This does not apply to an
// instance time that defines the begin of the current interval.
PRInt32 count = mBeginInstances.Length();
for (PRInt32 i = count - 1; i >= 0; --i) {
nsSMILInstanceTime* instance = mBeginInstances[i].get();
NS_ABORT_IF_FALSE(instance, "NULL instance in begin instances array");
if (instance->ClearOnReset() &&
(!mCurrentInterval || instance != mCurrentInterval->Begin())) {
instance->Unlink();
mBeginInstances.RemoveElementAt(i);
}
}
count = mEndInstances.Length();
for (PRInt32 j = count - 1; j >= 0; --j) {
nsSMILInstanceTime* instance = mEndInstances[j].get();
NS_ABORT_IF_FALSE(instance, "NULL instance in end instances array");
if (instance->ClearOnReset()) {
instance->Unlink();
mEndInstances.RemoveElementAt(j);
}
}
}
PRBool
nsSMILTimedElement::SetAttr(nsIAtom* aAttribute, const nsAString& aValue,
nsAttrValue& aResult, nsIContent* aContextNode,
@ -922,16 +915,15 @@ nsSMILTimedElement::AddDependent(nsSMILTimeValueSpec& aDependent)
"nsSMILTimeValueSpec is already registered as a dependency");
mTimeDependents.PutEntry(&aDependent);
// Add old and current intervals
// Add current interval. We could add historical intervals too but that would
// cause unpredictable results since some intervals may have been filtered.
// SMIL doesn't say what to do here so for simplicity and consistency we
// simply add the current interval if there is one.
//
// It's not necessary to call SyncPauseTime since we're dealing with
// historical instance times not newly added ones.
nsSMILTimeContainer* container = GetTimeContainer();
for (PRUint32 i = 0; i < mOldIntervals.Length(); ++i) {
aDependent.HandleNewInterval(*mOldIntervals[i], container);
}
if (mCurrentInterval) {
aDependent.HandleNewInterval(*mCurrentInterval, container);
aDependent.HandleNewInterval(*mCurrentInterval, GetTimeContainer());
}
}
@ -950,7 +942,7 @@ nsSMILTimedElement::IsTimeDependent(const nsSMILTimedElement& aOther) const
if (!thisBegin || !otherBegin)
return PR_FALSE;
return thisBegin->IsDependent(*otherBegin);
return thisBegin->IsDependentOn(*otherBegin);
}
void
@ -1054,6 +1046,18 @@ nsSMILTimedElement::SetBeginOrEndSpec(const nsAString& aSpec,
return rv;
}
namespace
{
class RemoveNonDOM
{
public:
PRBool operator()(nsSMILInstanceTime* aInstanceTime, PRUint32 /*aIndex*/)
{
return !aInstanceTime->FromDOM();
}
};
}
void
nsSMILTimedElement::ClearBeginOrEndSpecs(PRBool aIsBegin)
{
@ -1063,17 +1067,184 @@ nsSMILTimedElement::ClearBeginOrEndSpecs(PRBool aIsBegin)
// Remove only those instance times generated by the attribute, not those from
// DOM calls.
InstanceTimeList& instances = aIsBegin ? mBeginInstances : mEndInstances;
PRInt32 count = instances.Length();
for (PRInt32 i = count - 1; i >= 0; --i) {
nsSMILInstanceTime* instance = instances[i].get();
NS_ABORT_IF_FALSE(instance, "NULL instance in instances array");
if (!instance->FromDOM()) {
instance->Unlink();
instances.RemoveElementAt(i);
RemoveNonDOM removeNonDOM;
RemoveInstanceTimes(instances, removeNonDOM);
}
void
nsSMILTimedElement::ApplyEarlyEnd(const nsSMILTimeValue& aSampleTime)
{
// This should only be called within DoSampleAt as a helper function
NS_ABORT_IF_FALSE(mElementState == STATE_ACTIVE,
"Unexpected state to try to apply an early end");
// Only apply an early end if we're not already ending.
if (mCurrentInterval->End()->Time() > aSampleTime) {
nsSMILInstanceTime* earlyEnd = CheckForEarlyEnd(aSampleTime);
if (earlyEnd) {
if (earlyEnd->IsDependent()) {
// Generate a new instance time for the early end since the
// existing instance time is part of some dependency chain that we
// don't want to participate in.
nsRefPtr<nsSMILInstanceTime> newEarlyEnd =
new nsSMILInstanceTime(earlyEnd->Time());
mCurrentInterval->SetEnd(*newEarlyEnd);
} else {
mCurrentInterval->SetEnd(*earlyEnd);
}
NotifyChangedInterval();
}
}
}
namespace
{
class RemoveReset
{
public:
RemoveReset(const nsSMILInstanceTime* aCurrentIntervalBegin)
: mCurrentIntervalBegin(aCurrentIntervalBegin) { }
PRBool operator()(nsSMILInstanceTime* aInstanceTime, PRUint32 /*aIndex*/)
{
// SMIL 3.0 section 5.4.3, 'Resetting element state':
// Any instance times associated with past Event-values, Repeat-values,
// Accesskey-values or added via DOM method calls are removed from the
// dependent begin and end instance times lists. In effect, all events
// and DOM methods calls in the past are cleared. This does not apply to
// an instance time that defines the begin of the current interval.
return aInstanceTime->IsDynamic() &&
!aInstanceTime->IsUsedAsFixedEndpoint() &&
(!mCurrentIntervalBegin || aInstanceTime != mCurrentIntervalBegin);
}
private:
const nsSMILInstanceTime* mCurrentIntervalBegin;
};
}
void
nsSMILTimedElement::Reset()
{
RemoveReset resetBegin(mCurrentInterval ? mCurrentInterval->Begin() : nsnull);
RemoveInstanceTimes(mBeginInstances, resetBegin);
RemoveReset resetEnd(nsnull);
RemoveInstanceTimes(mEndInstances, resetEnd);
}
void
nsSMILTimedElement::FilterHistory()
{
// We should filter the intervals first, since instance times still used in an
// interval won't be filtered.
FilterIntervals();
FilterInstanceTimes(mBeginInstances);
FilterInstanceTimes(mEndInstances);
}
void
nsSMILTimedElement::FilterIntervals()
{
// We can filter old intervals that:
//
// a) are not the previous interval; AND
// b) are not in the middle of a dependency chain
//
// Condition (a) is necessary since the previous interval is used for applying
// fill effects and updating the current interval.
//
// Condition (b) is necessary since even if this interval itself is not
// active, it may be part of a dependency chain that includes active
// intervals. Such chains are used to establish priorities within the
// animation sandwich.
//
// Although the above conditions allow us to safely filter intervals for most
// scenarios they do not cover all cases and there will still be scenarios
// that generate intervals indefinitely. In such a case we simply set
// a maximum number of intervals and drop any intervals beyond that threshold.
PRUint32 threshold = mOldIntervals.Length() > sMaxNumIntervals ?
mOldIntervals.Length() - sMaxNumIntervals :
0;
IntervalList filteredList;
for (PRUint32 i = 0; i < mOldIntervals.Length(); ++i)
{
nsSMILInterval* interval = mOldIntervals[i].get();
if (i + 1 < mOldIntervals.Length() /*skip previous interval*/ &&
(i < threshold || !interval->IsDependencyChainLink())) {
interval->Unlink(PR_TRUE /*filtered, not deleted*/);
} else {
filteredList.AppendElement(mOldIntervals[i].forget());
}
}
mOldIntervals.Clear();
mOldIntervals.SwapElements(filteredList);
}
namespace
{
class RemoveFiltered
{
public:
RemoveFiltered(nsSMILTimeValue aCutoff) : mCutoff(aCutoff) { }
PRBool operator()(nsSMILInstanceTime* aInstanceTime, PRUint32 /*aIndex*/)
{
// We can filter instance times that:
// a) Precede the end point of the previous interval; AND
// b) Are NOT syncbase times that might be updated to a time after the end
// point of the previous interval; AND
// c) Are NOT fixed end points in any remaining interval.
return aInstanceTime->Time() < mCutoff &&
aInstanceTime->IsFixedTime() &&
!aInstanceTime->IsUsedAsFixedEndpoint();
}
private:
nsSMILTimeValue mCutoff;
};
class RemoveBelowThreshold
{
public:
RemoveBelowThreshold(PRUint32 aThreshold,
const nsSMILInstanceTime* aCurrentIntervalBegin)
: mThreshold(aThreshold),
mCurrentIntervalBegin(aCurrentIntervalBegin) { }
PRBool operator()(nsSMILInstanceTime* aInstanceTime, PRUint32 aIndex)
{
return aInstanceTime != mCurrentIntervalBegin && aIndex < mThreshold;
}
private:
PRUint32 mThreshold;
const nsSMILInstanceTime* mCurrentIntervalBegin;
};
}
void
nsSMILTimedElement::FilterInstanceTimes(InstanceTimeList& aList)
{
if (GetPreviousInterval()) {
RemoveFiltered removeFiltered(GetPreviousInterval()->End()->Time());
RemoveInstanceTimes(aList, removeFiltered);
}
// As with intervals it is possible to create a document that, even despite
// our most aggressive filtering, will generate instance times indefinitely
// (e.g. cyclic dependencies with TimeEvents---we can't filter such times as
// they're unpredictable due to the possibility of seeking the document which
// may prevent some events from being generated). Therefore we introduce
// a hard cutoff at which point we just drop the oldest instance times.
if (aList.Length() > sMaxNumInstanceTimes) {
PRUint32 threshold = aList.Length() - sMaxNumInstanceTimes;
// We should still preserve the current interval begin time however
const nsSMILInstanceTime* currentIntervalBegin = mCurrentInterval ?
mCurrentInterval->Begin() : nsnull;
RemoveBelowThreshold removeBelowThreshold(threshold, currentIntervalBegin);
RemoveInstanceTimes(aList, removeBelowThreshold);
}
}
//
// This method is based on the pseudocode given in the SMILANIM spec.
//
@ -1119,8 +1290,6 @@ nsSMILTimedElement::GetNextInterval(const nsSMILInterval* aPrevInterval,
tempBegin = const_cast<nsSMILInstanceTime*>(aFixedBeginTime);
} else if (!mBeginSpecSet && beginAfter <= zeroTime) {
tempBegin = new nsSMILInstanceTime(nsSMILTimeValue(0));
if (!tempBegin)
return NS_ERROR_OUT_OF_MEMORY;
} else {
PRInt32 beginPos = 0;
tempBegin = GetNextGreaterOrEqual(mBeginInstances, beginAfter, beginPos);
@ -1166,8 +1335,6 @@ nsSMILTimedElement::GetNextInterval(const nsSMILInterval* aPrevInterval,
if (!tempEnd || intervalEnd != activeEnd) {
tempEnd = new nsSMILInstanceTime(activeEnd);
}
if (!tempEnd)
return NS_ERROR_OUT_OF_MEMORY;
}
NS_ABORT_IF_FALSE(tempEnd, "Failed to get end point for next interval");
@ -1409,10 +1576,6 @@ nsSMILTimedElement::UpdateCurrentInterval(PRBool aForceChangeNotice)
NS_ABORT_IF_FALSE(!mCurrentInterval,
"In postactive state but the interval has been set");
mCurrentInterval = new nsSMILInterval(updatedInterval);
if (!mCurrentInterval) {
NS_WARNING("Failed to allocate memory for new interval.");
return;
}
mElementState = STATE_WAITING;
NotifyNewInterval();
@ -1450,7 +1613,7 @@ nsSMILTimedElement::UpdateCurrentInterval(PRBool aForceChangeNotice)
if (mElementState == STATE_ACTIVE || mElementState == STATE_WAITING) {
mElementState = STATE_POSTACTIVE;
mCurrentInterval->NotifyDeleting();
mCurrentInterval->Unlink();
mCurrentInterval = nsnull;
}
}
@ -1477,9 +1640,9 @@ nsSMILTimedElement::SampleFillValue()
NS_ABORT_IF_FALSE(prevInterval,
"Attempting to sample fill value but there is no previous interval");
NS_ABORT_IF_FALSE(prevInterval->End()->Time().IsResolved() &&
!prevInterval->End()->MayUpdate(),
prevInterval->End()->IsFixedTime(),
"Attempting to sample fill value but the endpoint of the previous "
"interval is not resolved and frozen");
"interval is not resolved and fixed");
nsSMILTime activeTime = prevInterval->End()->Time().GetMillis() -
prevInterval->Begin()->Time().GetMillis();
@ -1508,10 +1671,6 @@ nsSMILTimedElement::AddInstanceTimeFromCurrentTime(nsSMILTime aCurrentTime,
// so we don't end up setting SOURCE_DOM for event-based times.
nsRefPtr<nsSMILInstanceTime> instanceTime =
new nsSMILInstanceTime(timeVal, nsSMILInstanceTime::SOURCE_DOM);
if (!instanceTime) {
NS_WARNING("Insufficient memory to create instance time");
return;
}
AddInstanceTime(instanceTime, aIsBegin);
}

View File

@ -228,12 +228,6 @@ public:
*/
void HandleContainerTimeChange();
/**
* Reset the element's internal state. As described in SMILANIM 3.3.7, all
* instance times associated with DOM calls, events, etc. are cleared.
*/
void Reset();
/**
* Attempts to set an attribute on this timed element.
*
@ -345,6 +339,10 @@ protected:
nsSMILTimeContainer* mTimeContainer;
};
// Templated helper functions
template <class TestFunctor>
void RemoveInstanceTimes(InstanceTimeList& aArray, TestFunctor& aTest);
//
// Implementation helpers
//
@ -377,6 +375,40 @@ protected:
void ClearBeginOrEndSpecs(PRBool aIsBegin);
void DoSampleAt(nsSMILTime aContainerTime, PRBool aEndOnly);
/**
* Helper function to check for an early end and, if necessary, update the
* current interval accordingly.
*
* See SMIL 3.0, section 5.4.5, Element life cycle, "Active Time - Playing an
* interval" for a description of ending early.
*
* @param aSampleTime The current sample time. Early ends should only be
* applied at the last possible moment (i.e. if they are at
* or before the current sample time) and only if the
* current interval is not already ending.
*/
void ApplyEarlyEnd(const nsSMILTimeValue& aSampleTime);
/**
* Clears certain state in response to the element restarting.
*
* This state is described in SMIL 3.0, section 5.4.3, Resetting element state
*/
void Reset();
/**
* Helper function to iterate through this element's accumulated timing
* information (specifically old nsSMILIntervals and nsSMILTimeInstanceTimes)
* and discard items that are no longer needed or exceed some threshold of
* accumulated state.
*/
void FilterHistory();
// Helper functions for FilterHistory to clear old nsSMILIntervals and
// nsSMILInstanceTimes respectively.
void FilterIntervals();
void FilterInstanceTimes(InstanceTimeList& aList);
/**
* Calculates the next acceptable interval for this element after the
* specified interval, or, if no previous interval is specified, it will be
@ -483,6 +515,8 @@ protected:
IntervalList mOldIntervals;
nsSMILMilestone mPrevRegisteredMilestone;
static const nsSMILMilestone sMaxMilestone;
static const PRUint8 sMaxNumIntervals;
static const PRUint8 sMaxNumInstanceTimes;
// Set of dependent time value specs to be notified when establishing a new
// current interval. Change notifications and delete notifications are handled

View File

@ -60,11 +60,11 @@ function main() {
is(anim.getStartTime(), 6);
// Rebind
// At this point all the old intervals should be re-added to anim. If they're
// not and only the current interval is added to anim we'll get a start time
// of 4s instead of 2s.
// At this point only the current interval will be re-added to anim (this is
// for consistency since old intervals may or may not have been filtered).
// Therefore the start time should be 4s instead of 2s.
circle.appendChild(anim);
is(anim.getStartTime(), 2);
is(anim.getStartTime(), 4);
SimpleTest.finish();
}

View File

@ -0,0 +1,63 @@
<svg xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink"
class="reftest-wait"
onload="go()">
<!-- Instance time filtering involves removing instance times that are no
longer needed. However, under some arrangements an excessive number of
instance times may be generated that will never be cleaned up since they
might potentially still affect the behavior of the timing model.
For example, consider the case where we have a valid cyclic dependency
(e.g. a ping-pong effect) between TimeEvents. For example,
a.begin=b.endEvent and b.begin=a.endEvent. The times generated by this
arrangement won't be cleared by regular filtering since they're
technically unpredictable (e.g. seeking the document will cause some
events to be suppressed) and so we preserve them to provide correct
backwards seeking support.
Therefore, after reaching a certain threshold, old instance times are
simply discarded indiscriminantly to avoid consuming memory in unbounded
fashion as the animation progresses.
This test checks this second stage of instance time filtering. -->
<script>
function go() {
var svg = document.documentElement;
var anim = document.getElementById('anim');
// To begin with we have an animation from 0s-2s
svg.pauseAnimations();
svg.setCurrentTime(1.0); // Seek to mid-interval
// Generate a lot of instance times beyond the interval end at t=2s
// The threshold will be something like 100 but just in case it's 200
// let's make 210 instance times.
for (var i = 0; i &lt; 210; i++) {
// The first instance time will be at t=3s and then we'll generate lots
// of times following on from there
anim.beginElementAt(2 + i * 0.1);
}
// Seek past the interval end -- this will cause the filtering to kick in.
// The first stage of filtering will only filter instance times before the
// end of the previous interval (i.e. before t=2s in this case).
// The second stage of filtering should take care of the rest.
svg.setCurrentTime(2.5);
// The second stage of filtering will clear out the oldest times first.
// However, since first time we generated at t=3s is now used as begin of
// the yet-to-begin current interval it should not be cleared.
// So if we force the current interval to be updated by adding another
// instance time the next interval should still start at t=3s.
anim.beginElementAt(100);
// Now when we go to do a snapshot at t=3s, the animation should be in
// effect.
svg.setCurrentTime(3.0);
svg.removeAttribute("class");
}
</script>
<rect id="blueRect" x="100" y="15" width="200" height="200" fill="blue">
<set id="anim" attributeName="x" to="15" begin="0s" dur="2s"/>
</rect>
</svg>

After

Width:  |  Height:  |  Size: 2.9 KiB

View File

@ -175,3 +175,6 @@ fails == anim-strokecolor-1.svg anim-standard-ref.svg # bug 436296
== smil-transitions-interaction-3b.svg lime.svg
== smil-transitions-interaction-4a.svg lime.svg
== smil-transitions-interaction-4b.svg lime.svg
# Test filtering of excessive times
== filtered-instance-time-1.svg anim-standard-ref.svg

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(2, true)">
<script xlink:href="../smil-util.js" type="text/javascript"/>
<!--
This test ensures that even if we have an old interval, if it is part of an
active dependency chain it should not be filtered.
-->
<rect width="100" height="100" fill="orange">
<!-- This animation is indirectly dependent on anim 'b' and hence, even
though it appears earlier in the document it should be given
priority. -->
<set attributeName="fill" to="green" begin="a.begin"/>
<!-- This will generate a series of short intervals such that by t=2s the
interval via which the first animation depends on b might be considered
as a candidate for filtering. -->
<set attributeName="width" to="100" begin="b.begin-2s; a.begin+0.2s"
dur="0.1s" id="a"/>
<set attributeName="fill" to="red" begin="2s" id="b"/>
</rect>
</svg>

After

Width:  |  Height:  |  Size: 1.0 KiB

View File

@ -89,3 +89,6 @@
== cross-container-1.xhtml green-box-ref.xhtml
== cross-container-2.xhtml green-box-ref.xhtml
== cross-container-3.xhtml green-box-ref.xhtml
# Filtering
== filtered-interval-1.svg green-box-ref.svg