michael@0: /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ michael@0: /* This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this michael@0: * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: #include "mozilla/DebugOnly.h" michael@0: michael@0: #include "mozilla/BasicEvents.h" michael@0: #include "mozilla/EventDispatcher.h" michael@0: #include "mozilla/dom/SVGAnimationElement.h" michael@0: #include "nsSMILTimedElement.h" michael@0: #include "nsAttrValueInlines.h" michael@0: #include "nsSMILAnimationFunction.h" michael@0: #include "nsSMILTimeValue.h" michael@0: #include "nsSMILTimeValueSpec.h" michael@0: #include "nsSMILInstanceTime.h" michael@0: #include "nsSMILParserUtils.h" michael@0: #include "nsSMILTimeContainer.h" michael@0: #include "nsGkAtoms.h" michael@0: #include "nsReadableUtils.h" michael@0: #include "nsMathUtils.h" michael@0: #include "nsThreadUtils.h" michael@0: #include "nsIPresShell.h" michael@0: #include "prdtoa.h" michael@0: #include "plstr.h" michael@0: #include "prtime.h" michael@0: #include "nsString.h" michael@0: #include "mozilla/AutoRestore.h" michael@0: #include "nsCharSeparatedTokenizer.h" michael@0: #include michael@0: michael@0: using namespace mozilla; michael@0: using namespace mozilla::dom; michael@0: michael@0: //---------------------------------------------------------------------- michael@0: // Helper class: InstanceTimeComparator michael@0: michael@0: // Upon inserting an instance time into one of our instance time lists we assign michael@0: // it a serial number. This allows us to sort the instance times in such a way michael@0: // that where we have several equal instance times, the ones added later will michael@0: // sort later. This means that when we call UpdateCurrentInterval during the michael@0: // waiting state we won't unnecessarily change the begin instance. michael@0: // michael@0: // The serial number also means that every instance time has an unambiguous michael@0: // position in the array so we can use RemoveElementSorted and the like. michael@0: bool michael@0: nsSMILTimedElement::InstanceTimeComparator::Equals( michael@0: const nsSMILInstanceTime* aElem1, michael@0: const nsSMILInstanceTime* aElem2) const michael@0: { michael@0: NS_ABORT_IF_FALSE(aElem1 && aElem2, michael@0: "Trying to compare null instance time pointers"); michael@0: NS_ABORT_IF_FALSE(aElem1->Serial() && aElem2->Serial(), michael@0: "Instance times have not been assigned serial numbers"); michael@0: NS_ABORT_IF_FALSE(aElem1 == aElem2 || aElem1->Serial() != aElem2->Serial(), michael@0: "Serial numbers are not unique"); michael@0: michael@0: return aElem1->Serial() == aElem2->Serial(); michael@0: } michael@0: michael@0: bool michael@0: nsSMILTimedElement::InstanceTimeComparator::LessThan( michael@0: const nsSMILInstanceTime* aElem1, michael@0: const nsSMILInstanceTime* aElem2) const michael@0: { michael@0: NS_ABORT_IF_FALSE(aElem1 && aElem2, michael@0: "Trying to compare null instance time pointers"); michael@0: NS_ABORT_IF_FALSE(aElem1->Serial() && aElem2->Serial(), michael@0: "Instance times have not been assigned serial numbers"); michael@0: michael@0: int8_t cmp = aElem1->Time().CompareTo(aElem2->Time()); michael@0: return cmp == 0 ? aElem1->Serial() < aElem2->Serial() : cmp < 0; michael@0: } michael@0: michael@0: //---------------------------------------------------------------------- michael@0: // Helper class: AsyncTimeEventRunner michael@0: michael@0: namespace michael@0: { michael@0: class AsyncTimeEventRunner : public nsRunnable michael@0: { michael@0: protected: michael@0: nsRefPtr mTarget; michael@0: uint32_t mMsg; michael@0: int32_t mDetail; michael@0: michael@0: public: michael@0: AsyncTimeEventRunner(nsIContent* aTarget, uint32_t aMsg, int32_t aDetail) michael@0: : mTarget(aTarget), mMsg(aMsg), mDetail(aDetail) michael@0: { michael@0: } michael@0: michael@0: NS_IMETHOD Run() michael@0: { michael@0: InternalUIEvent event(true, mMsg); michael@0: event.eventStructType = NS_SMIL_TIME_EVENT; michael@0: event.detail = mDetail; michael@0: michael@0: nsPresContext* context = nullptr; michael@0: nsIDocument* doc = mTarget->GetCurrentDoc(); michael@0: if (doc) { michael@0: nsCOMPtr shell = doc->GetShell(); michael@0: if (shell) { michael@0: context = shell->GetPresContext(); michael@0: } michael@0: } michael@0: michael@0: return EventDispatcher::Dispatch(mTarget, context, &event); michael@0: } michael@0: }; michael@0: } michael@0: michael@0: //---------------------------------------------------------------------- michael@0: // Helper class: AutoIntervalUpdateBatcher michael@0: michael@0: // Stack-based helper class to set the mDeferIntervalUpdates flag on an michael@0: // nsSMILTimedElement and perform the UpdateCurrentInterval when the object is michael@0: // destroyed. michael@0: // michael@0: // If several of these objects are allocated on the stack, the update will not michael@0: // be performed until the last object for a given nsSMILTimedElement is michael@0: // destroyed. michael@0: class MOZ_STACK_CLASS nsSMILTimedElement::AutoIntervalUpdateBatcher michael@0: { michael@0: public: michael@0: AutoIntervalUpdateBatcher(nsSMILTimedElement& aTimedElement) michael@0: : mTimedElement(aTimedElement), michael@0: mDidSetFlag(!aTimedElement.mDeferIntervalUpdates) michael@0: { michael@0: mTimedElement.mDeferIntervalUpdates = true; michael@0: } michael@0: michael@0: ~AutoIntervalUpdateBatcher() michael@0: { michael@0: if (!mDidSetFlag) michael@0: return; michael@0: michael@0: mTimedElement.mDeferIntervalUpdates = false; michael@0: michael@0: if (mTimedElement.mDoDeferredUpdate) { michael@0: mTimedElement.mDoDeferredUpdate = false; michael@0: mTimedElement.UpdateCurrentInterval(); michael@0: } michael@0: } michael@0: michael@0: private: michael@0: nsSMILTimedElement& mTimedElement; michael@0: bool mDidSetFlag; michael@0: }; michael@0: michael@0: //---------------------------------------------------------------------- michael@0: // Helper class: AutoIntervalUpdater michael@0: michael@0: // Stack-based helper class to call UpdateCurrentInterval when it is destroyed michael@0: // which helps avoid bugs where we forget to call UpdateCurrentInterval in the michael@0: // case of early returns (e.g. due to parse errors). michael@0: // michael@0: // This can be safely used in conjunction with AutoIntervalUpdateBatcher; any michael@0: // calls to UpdateCurrentInterval made by this class will simply be deferred if michael@0: // there is an AutoIntervalUpdateBatcher on the stack. michael@0: class MOZ_STACK_CLASS nsSMILTimedElement::AutoIntervalUpdater michael@0: { michael@0: public: michael@0: AutoIntervalUpdater(nsSMILTimedElement& aTimedElement) michael@0: : mTimedElement(aTimedElement) { } michael@0: michael@0: ~AutoIntervalUpdater() michael@0: { michael@0: mTimedElement.UpdateCurrentInterval(); michael@0: } michael@0: michael@0: private: michael@0: nsSMILTimedElement& mTimedElement; michael@0: }; michael@0: michael@0: //---------------------------------------------------------------------- michael@0: // Templated helper functions michael@0: michael@0: // Selectively remove elements from an array of type michael@0: // nsTArray > with O(n) performance. michael@0: template michael@0: void michael@0: nsSMILTimedElement::RemoveInstanceTimes(InstanceTimeList& aArray, michael@0: TestFunctor& aTest) michael@0: { michael@0: InstanceTimeList newArray; michael@0: for (uint32_t i = 0; i < aArray.Length(); ++i) { michael@0: nsSMILInstanceTime* item = aArray[i].get(); michael@0: if (aTest(item, i)) { michael@0: // As per bugs 665334 and 669225 we should be careful not to remove the michael@0: // instance time that corresponds to the previous interval's end time. michael@0: // michael@0: // Most functors supplied here fulfil this condition by checking if the michael@0: // instance time is marked as "ShouldPreserve" and if so, not deleting it. michael@0: // michael@0: // However, when filtering instance times, we sometimes need to drop even michael@0: // instance times marked as "ShouldPreserve". In that case we take special michael@0: // care not to delete the end instance time of the previous interval. michael@0: NS_ABORT_IF_FALSE(!GetPreviousInterval() || michael@0: item != GetPreviousInterval()->End(), michael@0: "Removing end instance time of previous interval"); michael@0: item->Unlink(); michael@0: } else { michael@0: newArray.AppendElement(item); michael@0: } michael@0: } michael@0: aArray.Clear(); michael@0: aArray.SwapElements(newArray); michael@0: } michael@0: michael@0: //---------------------------------------------------------------------- michael@0: // Static members michael@0: michael@0: nsAttrValue::EnumTable nsSMILTimedElement::sFillModeTable[] = { michael@0: {"remove", FILL_REMOVE}, michael@0: {"freeze", FILL_FREEZE}, michael@0: {nullptr, 0} michael@0: }; michael@0: michael@0: nsAttrValue::EnumTable nsSMILTimedElement::sRestartModeTable[] = { michael@0: {"always", RESTART_ALWAYS}, michael@0: {"whenNotActive", RESTART_WHENNOTACTIVE}, michael@0: {"never", RESTART_NEVER}, michael@0: {nullptr, 0} michael@0: }; michael@0: michael@0: const nsSMILMilestone nsSMILTimedElement::sMaxMilestone(INT64_MAX, false); michael@0: michael@0: // The thresholds at which point we start filtering intervals and instance times michael@0: // indiscriminately. michael@0: // See FilterIntervals and FilterInstanceTimes. michael@0: const uint8_t nsSMILTimedElement::sMaxNumIntervals = 20; michael@0: const uint8_t nsSMILTimedElement::sMaxNumInstanceTimes = 100; michael@0: michael@0: // Detect if we arrive in some sort of undetected recursive syncbase dependency michael@0: // relationship michael@0: const uint8_t nsSMILTimedElement::sMaxUpdateIntervalRecursionDepth = 20; michael@0: michael@0: //---------------------------------------------------------------------- michael@0: // Ctor, dtor michael@0: michael@0: nsSMILTimedElement::nsSMILTimedElement() michael@0: : michael@0: mAnimationElement(nullptr), michael@0: mFillMode(FILL_REMOVE), michael@0: mRestartMode(RESTART_ALWAYS), michael@0: mInstanceSerialIndex(0), michael@0: mClient(nullptr), michael@0: mCurrentInterval(nullptr), michael@0: mCurrentRepeatIteration(0), michael@0: mPrevRegisteredMilestone(sMaxMilestone), michael@0: mElementState(STATE_STARTUP), michael@0: mSeekState(SEEK_NOT_SEEKING), michael@0: mDeferIntervalUpdates(false), michael@0: mDoDeferredUpdate(false), michael@0: mDeleteCount(0), michael@0: mUpdateIntervalRecursionDepth(0) michael@0: { michael@0: mSimpleDur.SetIndefinite(); michael@0: mMin.SetMillis(0L); michael@0: mMax.SetIndefinite(); michael@0: } michael@0: michael@0: nsSMILTimedElement::~nsSMILTimedElement() michael@0: { michael@0: // Unlink all instance times from dependent intervals michael@0: for (uint32_t i = 0; i < mBeginInstances.Length(); ++i) { michael@0: mBeginInstances[i]->Unlink(); michael@0: } michael@0: mBeginInstances.Clear(); michael@0: for (uint32_t i = 0; i < mEndInstances.Length(); ++i) { michael@0: mEndInstances[i]->Unlink(); michael@0: } michael@0: mEndInstances.Clear(); michael@0: michael@0: // Notify anyone listening to our intervals that they're gone michael@0: // (We shouldn't get any callbacks from this because all our instance times michael@0: // are now disassociated with any intervals) michael@0: ClearIntervals(); michael@0: michael@0: // The following assertions are important in their own right (for checking michael@0: // correct behavior) but also because AutoIntervalUpdateBatcher holds pointers michael@0: // to class so if they fail there's the possibility we might have dangling michael@0: // pointers. michael@0: NS_ABORT_IF_FALSE(!mDeferIntervalUpdates, michael@0: "Interval updates should no longer be blocked when an nsSMILTimedElement " michael@0: "disappears"); michael@0: NS_ABORT_IF_FALSE(!mDoDeferredUpdate, michael@0: "There should no longer be any pending updates when an " michael@0: "nsSMILTimedElement disappears"); michael@0: } michael@0: michael@0: void michael@0: nsSMILTimedElement::SetAnimationElement(SVGAnimationElement* aElement) michael@0: { michael@0: NS_ABORT_IF_FALSE(aElement, "NULL owner element"); michael@0: NS_ABORT_IF_FALSE(!mAnimationElement, "Re-setting owner"); michael@0: mAnimationElement = aElement; michael@0: } michael@0: michael@0: nsSMILTimeContainer* michael@0: nsSMILTimedElement::GetTimeContainer() michael@0: { michael@0: return mAnimationElement ? mAnimationElement->GetTimeContainer() : nullptr; michael@0: } michael@0: michael@0: dom::Element* michael@0: nsSMILTimedElement::GetTargetElement() michael@0: { michael@0: return mAnimationElement ? michael@0: mAnimationElement->GetTargetElementContent() : michael@0: nullptr; michael@0: } michael@0: michael@0: //---------------------------------------------------------------------- michael@0: // nsIDOMElementTimeControl methods michael@0: // michael@0: // The definition of the ElementTimeControl interface differs between SMIL michael@0: // Animation and SVG 1.1. In SMIL Animation all methods have a void return michael@0: // type and the new instance time is simply added to the list and restart michael@0: // semantics are applied as with any other instance time. In the SVG definition michael@0: // the methods return a bool depending on the restart mode. michael@0: // michael@0: // This inconsistency has now been addressed by an erratum in SVG 1.1: michael@0: // michael@0: // http://www.w3.org/2003/01/REC-SVG11-20030114-errata#elementtimecontrol-interface michael@0: // michael@0: // which favours the definition in SMIL, i.e. instance times are just added michael@0: // without first checking the restart mode. michael@0: michael@0: nsresult michael@0: nsSMILTimedElement::BeginElementAt(double aOffsetSeconds) michael@0: { michael@0: nsSMILTimeContainer* container = GetTimeContainer(); michael@0: if (!container) michael@0: return NS_ERROR_FAILURE; michael@0: michael@0: nsSMILTime currentTime = container->GetCurrentTime(); michael@0: return AddInstanceTimeFromCurrentTime(currentTime, aOffsetSeconds, true); michael@0: } michael@0: michael@0: nsresult michael@0: nsSMILTimedElement::EndElementAt(double aOffsetSeconds) michael@0: { michael@0: nsSMILTimeContainer* container = GetTimeContainer(); michael@0: if (!container) michael@0: return NS_ERROR_FAILURE; michael@0: michael@0: nsSMILTime currentTime = container->GetCurrentTime(); michael@0: return AddInstanceTimeFromCurrentTime(currentTime, aOffsetSeconds, false); michael@0: } michael@0: michael@0: //---------------------------------------------------------------------- michael@0: // nsSVGAnimationElement methods michael@0: michael@0: nsSMILTimeValue michael@0: nsSMILTimedElement::GetStartTime() const michael@0: { michael@0: return mElementState == STATE_WAITING || mElementState == STATE_ACTIVE michael@0: ? mCurrentInterval->Begin()->Time() michael@0: : nsSMILTimeValue(); michael@0: } michael@0: michael@0: //---------------------------------------------------------------------- michael@0: // Hyperlinking support michael@0: michael@0: nsSMILTimeValue michael@0: nsSMILTimedElement::GetHyperlinkTime() const michael@0: { michael@0: nsSMILTimeValue hyperlinkTime; // Default ctor creates unresolved time michael@0: michael@0: if (mElementState == STATE_ACTIVE) { michael@0: hyperlinkTime = mCurrentInterval->Begin()->Time(); michael@0: } else if (!mBeginInstances.IsEmpty()) { michael@0: hyperlinkTime = mBeginInstances[0]->Time(); michael@0: } michael@0: michael@0: return hyperlinkTime; michael@0: } michael@0: michael@0: //---------------------------------------------------------------------- michael@0: // nsSMILTimedElement michael@0: michael@0: void michael@0: nsSMILTimedElement::AddInstanceTime(nsSMILInstanceTime* aInstanceTime, michael@0: bool aIsBegin) michael@0: { michael@0: NS_ABORT_IF_FALSE(aInstanceTime, "Attempting to add null instance time"); michael@0: michael@0: // Event-sensitivity: If an element is not active (but the parent time michael@0: // container is), then events are only handled for begin specifications. michael@0: if (mElementState != STATE_ACTIVE && !aIsBegin && michael@0: aInstanceTime->IsDynamic()) michael@0: { michael@0: // No need to call Unlink here--dynamic instance times shouldn't be linked michael@0: // to anything that's going to miss them michael@0: NS_ABORT_IF_FALSE(!aInstanceTime->GetBaseInterval(), michael@0: "Dynamic instance time has a base interval--we probably need to unlink" michael@0: " it if we're not going to use it"); michael@0: return; michael@0: } michael@0: michael@0: aInstanceTime->SetSerial(++mInstanceSerialIndex); michael@0: InstanceTimeList& instanceList = aIsBegin ? mBeginInstances : mEndInstances; michael@0: nsRefPtr* inserted = michael@0: instanceList.InsertElementSorted(aInstanceTime, InstanceTimeComparator()); michael@0: if (!inserted) { michael@0: NS_WARNING("Insufficient memory to insert instance time"); michael@0: return; michael@0: } michael@0: michael@0: UpdateCurrentInterval(); michael@0: } michael@0: michael@0: void michael@0: nsSMILTimedElement::UpdateInstanceTime(nsSMILInstanceTime* aInstanceTime, michael@0: nsSMILTimeValue& aUpdatedTime, michael@0: bool aIsBegin) michael@0: { michael@0: NS_ABORT_IF_FALSE(aInstanceTime, "Attempting to update null instance time"); michael@0: michael@0: // The reason we update the time here and not in the nsSMILTimeValueSpec is michael@0: // that it means we *could* re-sort more efficiently by doing a sorted remove michael@0: // and insert but currently this doesn't seem to be necessary given how michael@0: // infrequently we get these change notices. michael@0: aInstanceTime->DependentUpdate(aUpdatedTime); michael@0: InstanceTimeList& instanceList = aIsBegin ? mBeginInstances : mEndInstances; michael@0: instanceList.Sort(InstanceTimeComparator()); michael@0: michael@0: // Generally speaking, UpdateCurrentInterval makes changes to the current michael@0: // interval and sends changes notices itself. However, in this case because michael@0: // instance times are shared between the instance time list and the intervals michael@0: // we are effectively changing the current interval outside michael@0: // UpdateCurrentInterval so we need to explicitly signal that we've made michael@0: // a change. michael@0: // michael@0: // This wouldn't be necessary if we cloned instance times on adding them to michael@0: // the current interval but this introduces other complications (particularly michael@0: // detecting which instance time is being used to define the begin of the michael@0: // current interval when doing a Reset). michael@0: bool changedCurrentInterval = mCurrentInterval && michael@0: (mCurrentInterval->Begin() == aInstanceTime || michael@0: mCurrentInterval->End() == aInstanceTime); michael@0: michael@0: UpdateCurrentInterval(changedCurrentInterval); michael@0: } michael@0: michael@0: void michael@0: nsSMILTimedElement::RemoveInstanceTime(nsSMILInstanceTime* aInstanceTime, michael@0: bool aIsBegin) michael@0: { michael@0: NS_ABORT_IF_FALSE(aInstanceTime, "Attempting to remove null instance time"); michael@0: michael@0: // If the instance time should be kept (because it is or was the fixed end michael@0: // point of an interval) then just disassociate it from the creator. michael@0: if (aInstanceTime->ShouldPreserve()) { michael@0: aInstanceTime->Unlink(); michael@0: return; michael@0: } michael@0: michael@0: InstanceTimeList& instanceList = aIsBegin ? mBeginInstances : mEndInstances; michael@0: mozilla::DebugOnly found = michael@0: instanceList.RemoveElementSorted(aInstanceTime, InstanceTimeComparator()); michael@0: NS_ABORT_IF_FALSE(found, "Couldn't find instance time to delete"); michael@0: michael@0: UpdateCurrentInterval(); michael@0: } michael@0: michael@0: namespace michael@0: { michael@0: class MOZ_STACK_CLASS RemoveByCreator michael@0: { michael@0: public: michael@0: RemoveByCreator(const nsSMILTimeValueSpec* aCreator) : mCreator(aCreator) michael@0: { } michael@0: michael@0: bool operator()(nsSMILInstanceTime* aInstanceTime, uint32_t /*aIndex*/) michael@0: { michael@0: if (aInstanceTime->GetCreator() != mCreator) michael@0: return false; michael@0: michael@0: // If the instance time should be kept (because it is or was the fixed end michael@0: // point of an interval) then just disassociate it from the creator. michael@0: if (aInstanceTime->ShouldPreserve()) { michael@0: aInstanceTime->Unlink(); michael@0: return false; michael@0: } michael@0: michael@0: return true; michael@0: } michael@0: michael@0: private: michael@0: const nsSMILTimeValueSpec* mCreator; michael@0: }; michael@0: } michael@0: michael@0: void michael@0: nsSMILTimedElement::RemoveInstanceTimesForCreator( michael@0: const nsSMILTimeValueSpec* aCreator, bool aIsBegin) michael@0: { michael@0: NS_ABORT_IF_FALSE(aCreator, "Creator not set"); michael@0: michael@0: InstanceTimeList& instances = aIsBegin ? mBeginInstances : mEndInstances; michael@0: RemoveByCreator removeByCreator(aCreator); michael@0: RemoveInstanceTimes(instances, removeByCreator); michael@0: michael@0: UpdateCurrentInterval(); michael@0: } michael@0: michael@0: void michael@0: nsSMILTimedElement::SetTimeClient(nsSMILAnimationFunction* aClient) michael@0: { michael@0: // michael@0: // No need to check for nullptr. A nullptr parameter simply means to remove the michael@0: // previous client which we do by setting to nullptr anyway. michael@0: // michael@0: michael@0: mClient = aClient; michael@0: } michael@0: michael@0: void michael@0: nsSMILTimedElement::SampleAt(nsSMILTime aContainerTime) michael@0: { michael@0: // Milestones are cleared before a sample michael@0: mPrevRegisteredMilestone = sMaxMilestone; michael@0: michael@0: DoSampleAt(aContainerTime, false); michael@0: } michael@0: michael@0: void michael@0: nsSMILTimedElement::SampleEndAt(nsSMILTime aContainerTime) michael@0: { michael@0: // Milestones are cleared before a sample michael@0: mPrevRegisteredMilestone = sMaxMilestone; michael@0: michael@0: // If the current interval changes, we don't bother trying to remove any old michael@0: // milestones we'd registered. So it's possible to get a call here to end an michael@0: // interval at a time that no longer reflects the end of the current interval. michael@0: // michael@0: // For now we just check that we're actually in an interval but note that the michael@0: // initial sample we use to initialise the model is an end sample. This is michael@0: // because we want to resolve all the instance times before committing to an michael@0: // initial interval. Therefore an end sample from the startup state is also michael@0: // acceptable. michael@0: if (mElementState == STATE_ACTIVE || mElementState == STATE_STARTUP) { michael@0: DoSampleAt(aContainerTime, true); // End sample michael@0: } else { michael@0: // Even if this was an unnecessary milestone sample we want to be sure that michael@0: // our next real milestone is registered. michael@0: RegisterMilestone(); michael@0: } michael@0: } michael@0: michael@0: void michael@0: nsSMILTimedElement::DoSampleAt(nsSMILTime aContainerTime, bool aEndOnly) michael@0: { michael@0: NS_ABORT_IF_FALSE(mAnimationElement, michael@0: "Got sample before being registered with an animation element"); michael@0: NS_ABORT_IF_FALSE(GetTimeContainer(), michael@0: "Got sample without being registered with a time container"); michael@0: michael@0: // This could probably happen if we later implement externalResourcesRequired michael@0: // (bug 277955) and whilst waiting for those resources (and the animation to michael@0: // start) we transfer a node from another document fragment that has already michael@0: // started. In such a case we might receive milestone samples registered with michael@0: // the already active container. michael@0: if (GetTimeContainer()->IsPausedByType(nsSMILTimeContainer::PAUSE_BEGIN)) michael@0: return; michael@0: michael@0: // We use an end-sample to start animation since an end-sample lets us michael@0: // tentatively create an interval without committing to it (by transitioning michael@0: // to the ACTIVE state) and this is necessary because we might have michael@0: // dependencies on other animations that are yet to start. After these michael@0: // other animations start, it may be necessary to revise our initial interval. michael@0: // michael@0: // However, sometimes instead of an end-sample we can get a regular sample michael@0: // during STARTUP state. This can happen, for example, if we register michael@0: // a milestone before time t=0 and are then re-bound to the tree (which sends michael@0: // us back to the STARTUP state). In such a case we should just ignore the michael@0: // sample and wait for our real initial sample which will be an end-sample. michael@0: if (mElementState == STATE_STARTUP && !aEndOnly) michael@0: return; michael@0: michael@0: bool finishedSeek = false; michael@0: if (GetTimeContainer()->IsSeeking() && mSeekState == SEEK_NOT_SEEKING) { michael@0: mSeekState = mElementState == STATE_ACTIVE ? michael@0: SEEK_FORWARD_FROM_ACTIVE : michael@0: SEEK_FORWARD_FROM_INACTIVE; michael@0: } else if (mSeekState != SEEK_NOT_SEEKING && michael@0: !GetTimeContainer()->IsSeeking()) { michael@0: finishedSeek = true; michael@0: } michael@0: michael@0: bool stateChanged; michael@0: nsSMILTimeValue sampleTime(aContainerTime); michael@0: michael@0: do { michael@0: #ifdef DEBUG michael@0: // Check invariant michael@0: if (mElementState == STATE_STARTUP || mElementState == STATE_POSTACTIVE) { michael@0: NS_ABORT_IF_FALSE(!mCurrentInterval, michael@0: "Shouldn't have current interval in startup or postactive states"); michael@0: } else { michael@0: NS_ABORT_IF_FALSE(mCurrentInterval, michael@0: "Should have current interval in waiting and active states"); michael@0: } michael@0: #endif michael@0: michael@0: stateChanged = false; michael@0: michael@0: switch (mElementState) michael@0: { michael@0: case STATE_STARTUP: michael@0: { michael@0: nsSMILInterval firstInterval; michael@0: mElementState = GetNextInterval(nullptr, nullptr, nullptr, firstInterval) michael@0: ? STATE_WAITING michael@0: : STATE_POSTACTIVE; michael@0: stateChanged = true; michael@0: if (mElementState == STATE_WAITING) { michael@0: mCurrentInterval = new nsSMILInterval(firstInterval); michael@0: NotifyNewInterval(); michael@0: } michael@0: } michael@0: break; michael@0: michael@0: case STATE_WAITING: michael@0: { michael@0: if (mCurrentInterval->Begin()->Time() <= sampleTime) { michael@0: mElementState = STATE_ACTIVE; michael@0: mCurrentInterval->FixBegin(); michael@0: if (mClient) { michael@0: mClient->Activate(mCurrentInterval->Begin()->Time().GetMillis()); michael@0: } michael@0: if (mSeekState == SEEK_NOT_SEEKING) { michael@0: FireTimeEventAsync(NS_SMIL_BEGIN, 0); michael@0: } michael@0: if (HasPlayed()) { michael@0: Reset(); // Apply restart behaviour michael@0: // The call to Reset() may mean that the end point of our current michael@0: // interval should be changed and so we should update the interval michael@0: // now. However, calling UpdateCurrentInterval could result in the michael@0: // interval getting deleted (perhaps through some web of syncbase michael@0: // dependencies) therefore we make updating the interval the last michael@0: // thing we do. There is no guarantee that mCurrentInterval is set michael@0: // after this. michael@0: UpdateCurrentInterval(); michael@0: } michael@0: stateChanged = true; michael@0: } michael@0: } michael@0: break; michael@0: michael@0: case STATE_ACTIVE: michael@0: { michael@0: // Ending early will change the interval but we don't notify dependents michael@0: // of the change until we have closed off the current interval (since we michael@0: // don't want dependencies to un-end our early end). michael@0: bool didApplyEarlyEnd = ApplyEarlyEnd(sampleTime); michael@0: michael@0: if (mCurrentInterval->End()->Time() <= sampleTime) { michael@0: nsSMILInterval newInterval; michael@0: mElementState = michael@0: GetNextInterval(mCurrentInterval, nullptr, nullptr, newInterval) michael@0: ? STATE_WAITING michael@0: : STATE_POSTACTIVE; michael@0: if (mClient) { michael@0: mClient->Inactivate(mFillMode == FILL_FREEZE); michael@0: } michael@0: mCurrentInterval->FixEnd(); michael@0: if (mSeekState == SEEK_NOT_SEEKING) { michael@0: FireTimeEventAsync(NS_SMIL_END, 0); michael@0: } michael@0: mCurrentRepeatIteration = 0; michael@0: mOldIntervals.AppendElement(mCurrentInterval.forget()); michael@0: SampleFillValue(); michael@0: if (mElementState == STATE_WAITING) { michael@0: mCurrentInterval = new nsSMILInterval(newInterval); michael@0: } michael@0: // We are now in a consistent state to dispatch notifications michael@0: if (didApplyEarlyEnd) { michael@0: NotifyChangedInterval( michael@0: mOldIntervals[mOldIntervals.Length() - 1], false, true); michael@0: } michael@0: if (mElementState == STATE_WAITING) { michael@0: NotifyNewInterval(); michael@0: } michael@0: FilterHistory(); michael@0: stateChanged = true; michael@0: } else { michael@0: NS_ABORT_IF_FALSE(!didApplyEarlyEnd, michael@0: "We got an early end, but didn't end"); michael@0: nsSMILTime beginTime = mCurrentInterval->Begin()->Time().GetMillis(); michael@0: NS_ASSERTION(aContainerTime >= beginTime, michael@0: "Sample time should not precede current interval"); michael@0: nsSMILTime activeTime = aContainerTime - beginTime; michael@0: michael@0: // The 'min' attribute can cause the active interval to be longer than michael@0: // the 'repeating interval'. michael@0: // In that extended period we apply the fill mode. michael@0: if (GetRepeatDuration() <= nsSMILTimeValue(activeTime)) { michael@0: if (mClient && mClient->IsActive()) { michael@0: mClient->Inactivate(mFillMode == FILL_FREEZE); michael@0: } michael@0: SampleFillValue(); michael@0: } else { michael@0: SampleSimpleTime(activeTime); michael@0: michael@0: // We register our repeat times as milestones (except when we're michael@0: // seeking) so we should get a sample at exactly the time we repeat. michael@0: // (And even when we are seeking we want to update michael@0: // mCurrentRepeatIteration so we do that first before testing the michael@0: // seek state.) michael@0: uint32_t prevRepeatIteration = mCurrentRepeatIteration; michael@0: if ( michael@0: ActiveTimeToSimpleTime(activeTime, mCurrentRepeatIteration)==0 && michael@0: mCurrentRepeatIteration != prevRepeatIteration && michael@0: mCurrentRepeatIteration && michael@0: mSeekState == SEEK_NOT_SEEKING) { michael@0: FireTimeEventAsync(NS_SMIL_REPEAT, michael@0: static_cast(mCurrentRepeatIteration)); michael@0: } michael@0: } michael@0: } michael@0: } michael@0: break; michael@0: michael@0: case STATE_POSTACTIVE: michael@0: break; michael@0: } michael@0: michael@0: // Generally we continue driving the state machine so long as we have changed michael@0: // state. However, for end samples we only drive the state machine as far as michael@0: // the waiting or postactive state because we don't want to commit to any new michael@0: // interval (by transitioning to the active state) until all the end samples michael@0: // have finished and we then have complete information about the available michael@0: // instance times upon which to base our next interval. michael@0: } while (stateChanged && (!aEndOnly || (mElementState != STATE_WAITING && michael@0: mElementState != STATE_POSTACTIVE))); michael@0: michael@0: if (finishedSeek) { michael@0: DoPostSeek(); michael@0: } michael@0: RegisterMilestone(); michael@0: } michael@0: michael@0: void michael@0: nsSMILTimedElement::HandleContainerTimeChange() michael@0: { michael@0: // In future we could possibly introduce a separate change notice for time michael@0: // container changes and only notify those dependents who live in other time michael@0: // containers. For now we don't bother because when we re-resolve the time in michael@0: // the nsSMILTimeValueSpec we'll check if anything has changed and if not, we michael@0: // won't go any further. michael@0: if (mElementState == STATE_WAITING || mElementState == STATE_ACTIVE) { michael@0: NotifyChangedInterval(mCurrentInterval, false, false); michael@0: } michael@0: } michael@0: michael@0: namespace michael@0: { michael@0: bool michael@0: RemoveNonDynamic(nsSMILInstanceTime* aInstanceTime) michael@0: { michael@0: // Generally dynamically-generated instance times (DOM calls, event-based michael@0: // times) are not associated with their creator nsSMILTimeValueSpec since michael@0: // they may outlive them. michael@0: NS_ABORT_IF_FALSE(!aInstanceTime->IsDynamic() || michael@0: !aInstanceTime->GetCreator(), michael@0: "Dynamic instance time should be unlinked from its creator"); michael@0: return !aInstanceTime->IsDynamic() && !aInstanceTime->ShouldPreserve(); michael@0: } michael@0: } michael@0: michael@0: void michael@0: nsSMILTimedElement::Rewind() michael@0: { michael@0: NS_ABORT_IF_FALSE(mAnimationElement, michael@0: "Got rewind request before being attached to an animation element"); michael@0: michael@0: // It's possible to get a rewind request whilst we're already in the middle of michael@0: // a backwards seek. This can happen when we're performing tree surgery and michael@0: // seeking containers at the same time because we can end up requesting michael@0: // a local rewind on an element after binding it to a new container and then michael@0: // performing a rewind on that container as a whole without sampling in michael@0: // between. michael@0: // michael@0: // However, it should currently be impossible to get a rewind in the middle of michael@0: // a forwards seek since forwards seeks are detected and processed within the michael@0: // same (re)sample. michael@0: if (mSeekState == SEEK_NOT_SEEKING) { michael@0: mSeekState = mElementState == STATE_ACTIVE ? michael@0: SEEK_BACKWARD_FROM_ACTIVE : michael@0: SEEK_BACKWARD_FROM_INACTIVE; michael@0: } michael@0: NS_ABORT_IF_FALSE(mSeekState == SEEK_BACKWARD_FROM_INACTIVE || michael@0: mSeekState == SEEK_BACKWARD_FROM_ACTIVE, michael@0: "Rewind in the middle of a forwards seek?"); michael@0: michael@0: // Putting us in the startup state will ensure we skip doing any interval michael@0: // updates michael@0: mElementState = STATE_STARTUP; michael@0: ClearIntervals(); michael@0: michael@0: UnsetBeginSpec(RemoveNonDynamic); michael@0: UnsetEndSpec(RemoveNonDynamic); michael@0: michael@0: if (mClient) { michael@0: mClient->Inactivate(false); michael@0: } michael@0: michael@0: if (mAnimationElement->HasAnimAttr(nsGkAtoms::begin)) { michael@0: nsAutoString attValue; michael@0: mAnimationElement->GetAnimAttr(nsGkAtoms::begin, attValue); michael@0: SetBeginSpec(attValue, mAnimationElement, RemoveNonDynamic); michael@0: } michael@0: michael@0: if (mAnimationElement->HasAnimAttr(nsGkAtoms::end)) { michael@0: nsAutoString attValue; michael@0: mAnimationElement->GetAnimAttr(nsGkAtoms::end, attValue); michael@0: SetEndSpec(attValue, mAnimationElement, RemoveNonDynamic); michael@0: } michael@0: michael@0: mPrevRegisteredMilestone = sMaxMilestone; michael@0: RegisterMilestone(); michael@0: NS_ABORT_IF_FALSE(!mCurrentInterval, michael@0: "Current interval is set at end of rewind"); michael@0: } michael@0: michael@0: namespace michael@0: { michael@0: bool michael@0: RemoveNonDOM(nsSMILInstanceTime* aInstanceTime) michael@0: { michael@0: return !aInstanceTime->FromDOM() && !aInstanceTime->ShouldPreserve(); michael@0: } michael@0: } michael@0: michael@0: bool michael@0: nsSMILTimedElement::SetAttr(nsIAtom* aAttribute, const nsAString& aValue, michael@0: nsAttrValue& aResult, michael@0: Element* aContextNode, michael@0: nsresult* aParseResult) michael@0: { michael@0: bool foundMatch = true; michael@0: nsresult parseResult = NS_OK; michael@0: michael@0: if (aAttribute == nsGkAtoms::begin) { michael@0: parseResult = SetBeginSpec(aValue, aContextNode, RemoveNonDOM); michael@0: } else if (aAttribute == nsGkAtoms::dur) { michael@0: parseResult = SetSimpleDuration(aValue); michael@0: } else if (aAttribute == nsGkAtoms::end) { michael@0: parseResult = SetEndSpec(aValue, aContextNode, RemoveNonDOM); michael@0: } else if (aAttribute == nsGkAtoms::fill) { michael@0: parseResult = SetFillMode(aValue); michael@0: } else if (aAttribute == nsGkAtoms::max) { michael@0: parseResult = SetMax(aValue); michael@0: } else if (aAttribute == nsGkAtoms::min) { michael@0: parseResult = SetMin(aValue); michael@0: } else if (aAttribute == nsGkAtoms::repeatCount) { michael@0: parseResult = SetRepeatCount(aValue); michael@0: } else if (aAttribute == nsGkAtoms::repeatDur) { michael@0: parseResult = SetRepeatDur(aValue); michael@0: } else if (aAttribute == nsGkAtoms::restart) { michael@0: parseResult = SetRestart(aValue); michael@0: } else { michael@0: foundMatch = false; michael@0: } michael@0: michael@0: if (foundMatch) { michael@0: aResult.SetTo(aValue); michael@0: if (aParseResult) { michael@0: *aParseResult = parseResult; michael@0: } michael@0: } michael@0: michael@0: return foundMatch; michael@0: } michael@0: michael@0: bool michael@0: nsSMILTimedElement::UnsetAttr(nsIAtom* aAttribute) michael@0: { michael@0: bool foundMatch = true; michael@0: michael@0: if (aAttribute == nsGkAtoms::begin) { michael@0: UnsetBeginSpec(RemoveNonDOM); michael@0: } else if (aAttribute == nsGkAtoms::dur) { michael@0: UnsetSimpleDuration(); michael@0: } else if (aAttribute == nsGkAtoms::end) { michael@0: UnsetEndSpec(RemoveNonDOM); michael@0: } else if (aAttribute == nsGkAtoms::fill) { michael@0: UnsetFillMode(); michael@0: } else if (aAttribute == nsGkAtoms::max) { michael@0: UnsetMax(); michael@0: } else if (aAttribute == nsGkAtoms::min) { michael@0: UnsetMin(); michael@0: } else if (aAttribute == nsGkAtoms::repeatCount) { michael@0: UnsetRepeatCount(); michael@0: } else if (aAttribute == nsGkAtoms::repeatDur) { michael@0: UnsetRepeatDur(); michael@0: } else if (aAttribute == nsGkAtoms::restart) { michael@0: UnsetRestart(); michael@0: } else { michael@0: foundMatch = false; michael@0: } michael@0: michael@0: return foundMatch; michael@0: } michael@0: michael@0: //---------------------------------------------------------------------- michael@0: // Setters and unsetters michael@0: michael@0: nsresult michael@0: nsSMILTimedElement::SetBeginSpec(const nsAString& aBeginSpec, michael@0: Element* aContextNode, michael@0: RemovalTestFunction aRemove) michael@0: { michael@0: return SetBeginOrEndSpec(aBeginSpec, aContextNode, true /*isBegin*/, michael@0: aRemove); michael@0: } michael@0: michael@0: void michael@0: nsSMILTimedElement::UnsetBeginSpec(RemovalTestFunction aRemove) michael@0: { michael@0: ClearSpecs(mBeginSpecs, mBeginInstances, aRemove); michael@0: UpdateCurrentInterval(); michael@0: } michael@0: michael@0: nsresult michael@0: nsSMILTimedElement::SetEndSpec(const nsAString& aEndSpec, michael@0: Element* aContextNode, michael@0: RemovalTestFunction aRemove) michael@0: { michael@0: return SetBeginOrEndSpec(aEndSpec, aContextNode, false /*!isBegin*/, michael@0: aRemove); michael@0: } michael@0: michael@0: void michael@0: nsSMILTimedElement::UnsetEndSpec(RemovalTestFunction aRemove) michael@0: { michael@0: ClearSpecs(mEndSpecs, mEndInstances, aRemove); michael@0: UpdateCurrentInterval(); michael@0: } michael@0: michael@0: nsresult michael@0: nsSMILTimedElement::SetSimpleDuration(const nsAString& aDurSpec) michael@0: { michael@0: // Update the current interval before returning michael@0: AutoIntervalUpdater updater(*this); michael@0: michael@0: nsSMILTimeValue duration; michael@0: const nsAString& dur = nsSMILParserUtils::TrimWhitespace(aDurSpec); michael@0: michael@0: // SVG-specific: "For SVG's animation elements, if "media" is specified, the michael@0: // attribute will be ignored." (SVG 1.1, section 19.2.6) michael@0: if (dur.EqualsLiteral("media") || dur.EqualsLiteral("indefinite")) { michael@0: duration.SetIndefinite(); michael@0: } else { michael@0: if (!nsSMILParserUtils::ParseClockValue(dur, &duration) || michael@0: duration.GetMillis() == 0L) { michael@0: mSimpleDur.SetIndefinite(); michael@0: return NS_ERROR_FAILURE; michael@0: } michael@0: } michael@0: // mSimpleDur should never be unresolved. ParseClockValue will either set michael@0: // duration to resolved or will return false. michael@0: NS_ABORT_IF_FALSE(duration.IsResolved(), michael@0: "Setting unresolved simple duration"); michael@0: michael@0: mSimpleDur = duration; michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: void michael@0: nsSMILTimedElement::UnsetSimpleDuration() michael@0: { michael@0: mSimpleDur.SetIndefinite(); michael@0: UpdateCurrentInterval(); michael@0: } michael@0: michael@0: nsresult michael@0: nsSMILTimedElement::SetMin(const nsAString& aMinSpec) michael@0: { michael@0: // Update the current interval before returning michael@0: AutoIntervalUpdater updater(*this); michael@0: michael@0: nsSMILTimeValue duration; michael@0: const nsAString& min = nsSMILParserUtils::TrimWhitespace(aMinSpec); michael@0: michael@0: if (min.EqualsLiteral("media")) { michael@0: duration.SetMillis(0L); michael@0: } else { michael@0: if (!nsSMILParserUtils::ParseClockValue(min, &duration)) { michael@0: mMin.SetMillis(0L); michael@0: return NS_ERROR_FAILURE; michael@0: } michael@0: } michael@0: michael@0: NS_ABORT_IF_FALSE(duration.GetMillis() >= 0L, "Invalid duration"); michael@0: michael@0: mMin = duration; michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: void michael@0: nsSMILTimedElement::UnsetMin() michael@0: { michael@0: mMin.SetMillis(0L); michael@0: UpdateCurrentInterval(); michael@0: } michael@0: michael@0: nsresult michael@0: nsSMILTimedElement::SetMax(const nsAString& aMaxSpec) michael@0: { michael@0: // Update the current interval before returning michael@0: AutoIntervalUpdater updater(*this); michael@0: michael@0: nsSMILTimeValue duration; michael@0: const nsAString& max = nsSMILParserUtils::TrimWhitespace(aMaxSpec); michael@0: michael@0: if (max.EqualsLiteral("media") || max.EqualsLiteral("indefinite")) { michael@0: duration.SetIndefinite(); michael@0: } else { michael@0: if (!nsSMILParserUtils::ParseClockValue(max, &duration) || michael@0: duration.GetMillis() == 0L) { michael@0: mMax.SetIndefinite(); michael@0: return NS_ERROR_FAILURE; michael@0: } michael@0: NS_ABORT_IF_FALSE(duration.GetMillis() > 0L, "Invalid duration"); michael@0: } michael@0: michael@0: mMax = duration; michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: void michael@0: nsSMILTimedElement::UnsetMax() michael@0: { michael@0: mMax.SetIndefinite(); michael@0: UpdateCurrentInterval(); michael@0: } michael@0: michael@0: nsresult michael@0: nsSMILTimedElement::SetRestart(const nsAString& aRestartSpec) michael@0: { michael@0: nsAttrValue temp; michael@0: bool parseResult michael@0: = temp.ParseEnumValue(aRestartSpec, sRestartModeTable, true); michael@0: mRestartMode = parseResult michael@0: ? nsSMILRestartMode(temp.GetEnumValue()) michael@0: : RESTART_ALWAYS; michael@0: UpdateCurrentInterval(); michael@0: return parseResult ? NS_OK : NS_ERROR_FAILURE; michael@0: } michael@0: michael@0: void michael@0: nsSMILTimedElement::UnsetRestart() michael@0: { michael@0: mRestartMode = RESTART_ALWAYS; michael@0: UpdateCurrentInterval(); michael@0: } michael@0: michael@0: nsresult michael@0: nsSMILTimedElement::SetRepeatCount(const nsAString& aRepeatCountSpec) michael@0: { michael@0: // Update the current interval before returning michael@0: AutoIntervalUpdater updater(*this); michael@0: michael@0: nsSMILRepeatCount newRepeatCount; michael@0: michael@0: if (nsSMILParserUtils::ParseRepeatCount(aRepeatCountSpec, newRepeatCount)) { michael@0: mRepeatCount = newRepeatCount; michael@0: return NS_OK; michael@0: } michael@0: mRepeatCount.Unset(); michael@0: return NS_ERROR_FAILURE; michael@0: } michael@0: michael@0: void michael@0: nsSMILTimedElement::UnsetRepeatCount() michael@0: { michael@0: mRepeatCount.Unset(); michael@0: UpdateCurrentInterval(); michael@0: } michael@0: michael@0: nsresult michael@0: nsSMILTimedElement::SetRepeatDur(const nsAString& aRepeatDurSpec) michael@0: { michael@0: // Update the current interval before returning michael@0: AutoIntervalUpdater updater(*this); michael@0: michael@0: nsSMILTimeValue duration; michael@0: michael@0: const nsAString& repeatDur = michael@0: nsSMILParserUtils::TrimWhitespace(aRepeatDurSpec); michael@0: michael@0: if (repeatDur.EqualsLiteral("indefinite")) { michael@0: duration.SetIndefinite(); michael@0: } else { michael@0: if (!nsSMILParserUtils::ParseClockValue(repeatDur, &duration)) { michael@0: mRepeatDur.SetUnresolved(); michael@0: return NS_ERROR_FAILURE; michael@0: } michael@0: } michael@0: michael@0: mRepeatDur = duration; michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: void michael@0: nsSMILTimedElement::UnsetRepeatDur() michael@0: { michael@0: mRepeatDur.SetUnresolved(); michael@0: UpdateCurrentInterval(); michael@0: } michael@0: michael@0: nsresult michael@0: nsSMILTimedElement::SetFillMode(const nsAString& aFillModeSpec) michael@0: { michael@0: uint16_t previousFillMode = mFillMode; michael@0: michael@0: nsAttrValue temp; michael@0: bool parseResult = michael@0: temp.ParseEnumValue(aFillModeSpec, sFillModeTable, true); michael@0: mFillMode = parseResult michael@0: ? nsSMILFillMode(temp.GetEnumValue()) michael@0: : FILL_REMOVE; michael@0: michael@0: // Update fill mode of client michael@0: if (mFillMode != previousFillMode && HasClientInFillRange()) { michael@0: mClient->Inactivate(mFillMode == FILL_FREEZE); michael@0: SampleFillValue(); michael@0: } michael@0: michael@0: return parseResult ? NS_OK : NS_ERROR_FAILURE; michael@0: } michael@0: michael@0: void michael@0: nsSMILTimedElement::UnsetFillMode() michael@0: { michael@0: uint16_t previousFillMode = mFillMode; michael@0: mFillMode = FILL_REMOVE; michael@0: if (previousFillMode == FILL_FREEZE && HasClientInFillRange()) { michael@0: mClient->Inactivate(false); michael@0: } michael@0: } michael@0: michael@0: void michael@0: nsSMILTimedElement::AddDependent(nsSMILTimeValueSpec& aDependent) michael@0: { michael@0: // There's probably no harm in attempting to register a dependent michael@0: // nsSMILTimeValueSpec twice, but we're not expecting it to happen. michael@0: NS_ABORT_IF_FALSE(!mTimeDependents.GetEntry(&aDependent), michael@0: "nsSMILTimeValueSpec is already registered as a dependency"); michael@0: mTimeDependents.PutEntry(&aDependent); michael@0: michael@0: // Add current interval. We could add historical intervals too but that would michael@0: // cause unpredictable results since some intervals may have been filtered. michael@0: // SMIL doesn't say what to do here so for simplicity and consistency we michael@0: // simply add the current interval if there is one. michael@0: // michael@0: // It's not necessary to call SyncPauseTime since we're dealing with michael@0: // historical instance times not newly added ones. michael@0: if (mCurrentInterval) { michael@0: aDependent.HandleNewInterval(*mCurrentInterval, GetTimeContainer()); michael@0: } michael@0: } michael@0: michael@0: void michael@0: nsSMILTimedElement::RemoveDependent(nsSMILTimeValueSpec& aDependent) michael@0: { michael@0: mTimeDependents.RemoveEntry(&aDependent); michael@0: } michael@0: michael@0: bool michael@0: nsSMILTimedElement::IsTimeDependent(const nsSMILTimedElement& aOther) const michael@0: { michael@0: const nsSMILInstanceTime* thisBegin = GetEffectiveBeginInstance(); michael@0: const nsSMILInstanceTime* otherBegin = aOther.GetEffectiveBeginInstance(); michael@0: michael@0: if (!thisBegin || !otherBegin) michael@0: return false; michael@0: michael@0: return thisBegin->IsDependentOn(*otherBegin); michael@0: } michael@0: michael@0: void michael@0: nsSMILTimedElement::BindToTree(nsIContent* aContextNode) michael@0: { michael@0: // Reset previously registered milestone since we may be registering with michael@0: // a different time container now. michael@0: mPrevRegisteredMilestone = sMaxMilestone; michael@0: michael@0: // If we were already active then clear all our timing information and start michael@0: // afresh michael@0: if (mElementState != STATE_STARTUP) { michael@0: mSeekState = SEEK_NOT_SEEKING; michael@0: Rewind(); michael@0: } michael@0: michael@0: // Scope updateBatcher to last only for the ResolveReferences calls: michael@0: { michael@0: AutoIntervalUpdateBatcher updateBatcher(*this); michael@0: michael@0: // Resolve references to other parts of the tree michael@0: uint32_t count = mBeginSpecs.Length(); michael@0: for (uint32_t i = 0; i < count; ++i) { michael@0: mBeginSpecs[i]->ResolveReferences(aContextNode); michael@0: } michael@0: michael@0: count = mEndSpecs.Length(); michael@0: for (uint32_t j = 0; j < count; ++j) { michael@0: mEndSpecs[j]->ResolveReferences(aContextNode); michael@0: } michael@0: } michael@0: michael@0: RegisterMilestone(); michael@0: } michael@0: michael@0: void michael@0: nsSMILTimedElement::HandleTargetElementChange(Element* aNewTarget) michael@0: { michael@0: AutoIntervalUpdateBatcher updateBatcher(*this); michael@0: michael@0: uint32_t count = mBeginSpecs.Length(); michael@0: for (uint32_t i = 0; i < count; ++i) { michael@0: mBeginSpecs[i]->HandleTargetElementChange(aNewTarget); michael@0: } michael@0: michael@0: count = mEndSpecs.Length(); michael@0: for (uint32_t j = 0; j < count; ++j) { michael@0: mEndSpecs[j]->HandleTargetElementChange(aNewTarget); michael@0: } michael@0: } michael@0: michael@0: void michael@0: nsSMILTimedElement::Traverse(nsCycleCollectionTraversalCallback* aCallback) michael@0: { michael@0: uint32_t count = mBeginSpecs.Length(); michael@0: for (uint32_t i = 0; i < count; ++i) { michael@0: nsSMILTimeValueSpec* beginSpec = mBeginSpecs[i]; michael@0: NS_ABORT_IF_FALSE(beginSpec, michael@0: "null nsSMILTimeValueSpec in list of begin specs"); michael@0: beginSpec->Traverse(aCallback); michael@0: } michael@0: michael@0: count = mEndSpecs.Length(); michael@0: for (uint32_t j = 0; j < count; ++j) { michael@0: nsSMILTimeValueSpec* endSpec = mEndSpecs[j]; michael@0: NS_ABORT_IF_FALSE(endSpec, "null nsSMILTimeValueSpec in list of end specs"); michael@0: endSpec->Traverse(aCallback); michael@0: } michael@0: } michael@0: michael@0: void michael@0: nsSMILTimedElement::Unlink() michael@0: { michael@0: AutoIntervalUpdateBatcher updateBatcher(*this); michael@0: michael@0: // Remove dependencies on other elements michael@0: uint32_t count = mBeginSpecs.Length(); michael@0: for (uint32_t i = 0; i < count; ++i) { michael@0: nsSMILTimeValueSpec* beginSpec = mBeginSpecs[i]; michael@0: NS_ABORT_IF_FALSE(beginSpec, michael@0: "null nsSMILTimeValueSpec in list of begin specs"); michael@0: beginSpec->Unlink(); michael@0: } michael@0: michael@0: count = mEndSpecs.Length(); michael@0: for (uint32_t j = 0; j < count; ++j) { michael@0: nsSMILTimeValueSpec* endSpec = mEndSpecs[j]; michael@0: NS_ABORT_IF_FALSE(endSpec, "null nsSMILTimeValueSpec in list of end specs"); michael@0: endSpec->Unlink(); michael@0: } michael@0: michael@0: ClearIntervals(); michael@0: michael@0: // Make sure we don't notify other elements of new intervals michael@0: mTimeDependents.Clear(); michael@0: } michael@0: michael@0: //---------------------------------------------------------------------- michael@0: // Implementation helpers michael@0: michael@0: nsresult michael@0: nsSMILTimedElement::SetBeginOrEndSpec(const nsAString& aSpec, michael@0: Element* aContextNode, michael@0: bool aIsBegin, michael@0: RemovalTestFunction aRemove) michael@0: { michael@0: TimeValueSpecList& timeSpecsList = aIsBegin ? mBeginSpecs : mEndSpecs; michael@0: InstanceTimeList& instances = aIsBegin ? mBeginInstances : mEndInstances; michael@0: michael@0: ClearSpecs(timeSpecsList, instances, aRemove); michael@0: michael@0: AutoIntervalUpdateBatcher updateBatcher(*this); michael@0: michael@0: nsCharSeparatedTokenizer tokenizer(aSpec, ';'); michael@0: if (!tokenizer.hasMoreTokens()) { // Empty list michael@0: return NS_ERROR_FAILURE; michael@0: } michael@0: michael@0: nsresult rv = NS_OK; michael@0: while (tokenizer.hasMoreTokens() && NS_SUCCEEDED(rv)) { michael@0: nsAutoPtr michael@0: spec(new nsSMILTimeValueSpec(*this, aIsBegin)); michael@0: rv = spec->SetSpec(tokenizer.nextToken(), aContextNode); michael@0: if (NS_SUCCEEDED(rv)) { michael@0: timeSpecsList.AppendElement(spec.forget()); michael@0: } michael@0: } michael@0: michael@0: if (NS_FAILED(rv)) { michael@0: ClearSpecs(timeSpecsList, instances, aRemove); michael@0: } michael@0: michael@0: return rv; michael@0: } michael@0: michael@0: namespace michael@0: { michael@0: // Adaptor functor for RemoveInstanceTimes that allows us to use function michael@0: // pointers instead. michael@0: // Without this we'd have to either templatize ClearSpecs and all its callers michael@0: // or pass bool flags around to specify which removal function to use here. michael@0: class MOZ_STACK_CLASS RemoveByFunction michael@0: { michael@0: public: michael@0: RemoveByFunction(nsSMILTimedElement::RemovalTestFunction aFunction) michael@0: : mFunction(aFunction) { } michael@0: bool operator()(nsSMILInstanceTime* aInstanceTime, uint32_t /*aIndex*/) michael@0: { michael@0: return mFunction(aInstanceTime); michael@0: } michael@0: michael@0: private: michael@0: nsSMILTimedElement::RemovalTestFunction mFunction; michael@0: }; michael@0: } michael@0: michael@0: void michael@0: nsSMILTimedElement::ClearSpecs(TimeValueSpecList& aSpecs, michael@0: InstanceTimeList& aInstances, michael@0: RemovalTestFunction aRemove) michael@0: { michael@0: AutoIntervalUpdateBatcher updateBatcher(*this); michael@0: michael@0: for (uint32_t i = 0; i < aSpecs.Length(); ++i) { michael@0: aSpecs[i]->Unlink(); michael@0: } michael@0: aSpecs.Clear(); michael@0: michael@0: RemoveByFunction removeByFunction(aRemove); michael@0: RemoveInstanceTimes(aInstances, removeByFunction); michael@0: } michael@0: michael@0: void michael@0: nsSMILTimedElement::ClearIntervals() michael@0: { michael@0: if (mElementState != STATE_STARTUP) { michael@0: mElementState = STATE_POSTACTIVE; michael@0: } michael@0: mCurrentRepeatIteration = 0; michael@0: ResetCurrentInterval(); michael@0: michael@0: // Remove old intervals michael@0: for (int32_t i = mOldIntervals.Length() - 1; i >= 0; --i) { michael@0: mOldIntervals[i]->Unlink(); michael@0: } michael@0: mOldIntervals.Clear(); michael@0: } michael@0: michael@0: bool michael@0: nsSMILTimedElement::ApplyEarlyEnd(const nsSMILTimeValue& aSampleTime) michael@0: { michael@0: // This should only be called within DoSampleAt as a helper function michael@0: NS_ABORT_IF_FALSE(mElementState == STATE_ACTIVE, michael@0: "Unexpected state to try to apply an early end"); michael@0: michael@0: bool updated = false; michael@0: michael@0: // Only apply an early end if we're not already ending. michael@0: if (mCurrentInterval->End()->Time() > aSampleTime) { michael@0: nsSMILInstanceTime* earlyEnd = CheckForEarlyEnd(aSampleTime); michael@0: if (earlyEnd) { michael@0: if (earlyEnd->IsDependent()) { michael@0: // Generate a new instance time for the early end since the michael@0: // existing instance time is part of some dependency chain that we michael@0: // don't want to participate in. michael@0: nsRefPtr newEarlyEnd = michael@0: new nsSMILInstanceTime(earlyEnd->Time()); michael@0: mCurrentInterval->SetEnd(*newEarlyEnd); michael@0: } else { michael@0: mCurrentInterval->SetEnd(*earlyEnd); michael@0: } michael@0: updated = true; michael@0: } michael@0: } michael@0: return updated; michael@0: } michael@0: michael@0: namespace michael@0: { michael@0: class MOZ_STACK_CLASS RemoveReset michael@0: { michael@0: public: michael@0: RemoveReset(const nsSMILInstanceTime* aCurrentIntervalBegin) michael@0: : mCurrentIntervalBegin(aCurrentIntervalBegin) { } michael@0: bool operator()(nsSMILInstanceTime* aInstanceTime, uint32_t /*aIndex*/) michael@0: { michael@0: // SMIL 3.0 section 5.4.3, 'Resetting element state': michael@0: // Any instance times associated with past Event-values, Repeat-values, michael@0: // Accesskey-values or added via DOM method calls are removed from the michael@0: // dependent begin and end instance times lists. In effect, all events michael@0: // and DOM methods calls in the past are cleared. This does not apply to michael@0: // an instance time that defines the begin of the current interval. michael@0: return aInstanceTime->IsDynamic() && michael@0: !aInstanceTime->ShouldPreserve() && michael@0: (!mCurrentIntervalBegin || aInstanceTime != mCurrentIntervalBegin); michael@0: } michael@0: michael@0: private: michael@0: const nsSMILInstanceTime* mCurrentIntervalBegin; michael@0: }; michael@0: } michael@0: michael@0: void michael@0: nsSMILTimedElement::Reset() michael@0: { michael@0: RemoveReset resetBegin(mCurrentInterval ? mCurrentInterval->Begin() : nullptr); michael@0: RemoveInstanceTimes(mBeginInstances, resetBegin); michael@0: michael@0: RemoveReset resetEnd(nullptr); michael@0: RemoveInstanceTimes(mEndInstances, resetEnd); michael@0: } michael@0: michael@0: void michael@0: nsSMILTimedElement::DoPostSeek() michael@0: { michael@0: // Finish backwards seek michael@0: if (mSeekState == SEEK_BACKWARD_FROM_INACTIVE || michael@0: mSeekState == SEEK_BACKWARD_FROM_ACTIVE) { michael@0: // Previously some dynamic instance times may have been marked to be michael@0: // preserved because they were endpoints of an historic interval (which may michael@0: // or may not have been filtered). Now that we've finished a seek we should michael@0: // clear that flag for those instance times whose intervals are no longer michael@0: // historic. michael@0: UnpreserveInstanceTimes(mBeginInstances); michael@0: UnpreserveInstanceTimes(mEndInstances); michael@0: michael@0: // Now that the times have been unmarked perform a reset. This might seem michael@0: // counter-intuitive when we're only doing a seek within an interval but michael@0: // SMIL seems to require this. SMIL 3.0, 'Hyperlinks and timing': michael@0: // Resolved end times associated with events, Repeat-values, michael@0: // Accesskey-values or added via DOM method calls are cleared when seeking michael@0: // to time earlier than the resolved end time. michael@0: Reset(); michael@0: UpdateCurrentInterval(); michael@0: } michael@0: michael@0: switch (mSeekState) michael@0: { michael@0: case SEEK_FORWARD_FROM_ACTIVE: michael@0: case SEEK_BACKWARD_FROM_ACTIVE: michael@0: if (mElementState != STATE_ACTIVE) { michael@0: FireTimeEventAsync(NS_SMIL_END, 0); michael@0: } michael@0: break; michael@0: michael@0: case SEEK_FORWARD_FROM_INACTIVE: michael@0: case SEEK_BACKWARD_FROM_INACTIVE: michael@0: if (mElementState == STATE_ACTIVE) { michael@0: FireTimeEventAsync(NS_SMIL_BEGIN, 0); michael@0: } michael@0: break; michael@0: michael@0: case SEEK_NOT_SEEKING: michael@0: /* Do nothing */ michael@0: break; michael@0: } michael@0: michael@0: mSeekState = SEEK_NOT_SEEKING; michael@0: } michael@0: michael@0: void michael@0: nsSMILTimedElement::UnpreserveInstanceTimes(InstanceTimeList& aList) michael@0: { michael@0: const nsSMILInterval* prevInterval = GetPreviousInterval(); michael@0: const nsSMILInstanceTime* cutoff = mCurrentInterval ? michael@0: mCurrentInterval->Begin() : michael@0: prevInterval ? prevInterval->Begin() : nullptr; michael@0: uint32_t count = aList.Length(); michael@0: for (uint32_t i = 0; i < count; ++i) { michael@0: nsSMILInstanceTime* instance = aList[i].get(); michael@0: if (!cutoff || cutoff->Time().CompareTo(instance->Time()) < 0) { michael@0: instance->UnmarkShouldPreserve(); michael@0: } michael@0: } michael@0: } michael@0: michael@0: void michael@0: nsSMILTimedElement::FilterHistory() michael@0: { michael@0: // We should filter the intervals first, since instance times still used in an michael@0: // interval won't be filtered. michael@0: FilterIntervals(); michael@0: FilterInstanceTimes(mBeginInstances); michael@0: FilterInstanceTimes(mEndInstances); michael@0: } michael@0: michael@0: void michael@0: nsSMILTimedElement::FilterIntervals() michael@0: { michael@0: // We can filter old intervals that: michael@0: // michael@0: // a) are not the previous interval; AND michael@0: // b) are not in the middle of a dependency chain; AND michael@0: // c) are not the first interval michael@0: // michael@0: // Condition (a) is necessary since the previous interval is used for applying michael@0: // fill effects and updating the current interval. michael@0: // michael@0: // Condition (b) is necessary since even if this interval itself is not michael@0: // active, it may be part of a dependency chain that includes active michael@0: // intervals. Such chains are used to establish priorities within the michael@0: // animation sandwich. michael@0: // michael@0: // Condition (c) is necessary to support hyperlinks that target animations michael@0: // since in some cases the defined behavior is to seek the document back to michael@0: // the first resolved begin time. Presumably the intention here is not michael@0: // actually to use the first resolved begin time, the michael@0: // _the_first_resolved_begin_time_that_produced_an_interval. That is, michael@0: // if we have begin="-5s; -3s; 1s; 3s" with a duration on 1s, we should seek michael@0: // to 1s. The spec doesn't say this but I'm pretty sure that is the intention. michael@0: // It seems negative times were simply not considered. michael@0: // michael@0: // Although the above conditions allow us to safely filter intervals for most michael@0: // scenarios they do not cover all cases and there will still be scenarios michael@0: // that generate intervals indefinitely. In such a case we simply set michael@0: // a maximum number of intervals and drop any intervals beyond that threshold. michael@0: michael@0: uint32_t threshold = mOldIntervals.Length() > sMaxNumIntervals ? michael@0: mOldIntervals.Length() - sMaxNumIntervals : michael@0: 0; michael@0: IntervalList filteredList; michael@0: for (uint32_t i = 0; i < mOldIntervals.Length(); ++i) michael@0: { michael@0: nsSMILInterval* interval = mOldIntervals[i].get(); michael@0: if (i != 0 && /*skip first interval*/ michael@0: i + 1 < mOldIntervals.Length() && /*skip previous interval*/ michael@0: (i < threshold || !interval->IsDependencyChainLink())) { michael@0: interval->Unlink(true /*filtered, not deleted*/); michael@0: } else { michael@0: filteredList.AppendElement(mOldIntervals[i].forget()); michael@0: } michael@0: } michael@0: mOldIntervals.Clear(); michael@0: mOldIntervals.SwapElements(filteredList); michael@0: } michael@0: michael@0: namespace michael@0: { michael@0: class MOZ_STACK_CLASS RemoveFiltered michael@0: { michael@0: public: michael@0: RemoveFiltered(nsSMILTimeValue aCutoff) : mCutoff(aCutoff) { } michael@0: bool operator()(nsSMILInstanceTime* aInstanceTime, uint32_t /*aIndex*/) michael@0: { michael@0: // We can filter instance times that: michael@0: // a) Precede the end point of the previous interval; AND michael@0: // b) Are NOT syncbase times that might be updated to a time after the end michael@0: // point of the previous interval; AND michael@0: // c) Are NOT fixed end points in any remaining interval. michael@0: return aInstanceTime->Time() < mCutoff && michael@0: aInstanceTime->IsFixedTime() && michael@0: !aInstanceTime->ShouldPreserve(); michael@0: } michael@0: michael@0: private: michael@0: nsSMILTimeValue mCutoff; michael@0: }; michael@0: michael@0: class MOZ_STACK_CLASS RemoveBelowThreshold michael@0: { michael@0: public: michael@0: RemoveBelowThreshold(uint32_t aThreshold, michael@0: nsTArray& aTimesToKeep) michael@0: : mThreshold(aThreshold), michael@0: mTimesToKeep(aTimesToKeep) { } michael@0: bool operator()(nsSMILInstanceTime* aInstanceTime, uint32_t aIndex) michael@0: { michael@0: return aIndex < mThreshold && !mTimesToKeep.Contains(aInstanceTime); michael@0: } michael@0: michael@0: private: michael@0: uint32_t mThreshold; michael@0: nsTArray& mTimesToKeep; michael@0: }; michael@0: } michael@0: michael@0: void michael@0: nsSMILTimedElement::FilterInstanceTimes(InstanceTimeList& aList) michael@0: { michael@0: if (GetPreviousInterval()) { michael@0: RemoveFiltered removeFiltered(GetPreviousInterval()->End()->Time()); michael@0: RemoveInstanceTimes(aList, removeFiltered); michael@0: } michael@0: michael@0: // As with intervals it is possible to create a document that, even despite michael@0: // our most aggressive filtering, will generate instance times indefinitely michael@0: // (e.g. cyclic dependencies with TimeEvents---we can't filter such times as michael@0: // they're unpredictable due to the possibility of seeking the document which michael@0: // may prevent some events from being generated). Therefore we introduce michael@0: // a hard cutoff at which point we just drop the oldest instance times. michael@0: if (aList.Length() > sMaxNumInstanceTimes) { michael@0: uint32_t threshold = aList.Length() - sMaxNumInstanceTimes; michael@0: // There are a few instance times we should keep though, notably: michael@0: // - the current interval begin time, michael@0: // - the previous interval end time (see note in RemoveInstanceTimes) michael@0: // - the first interval begin time (see note in FilterIntervals) michael@0: nsTArray timesToKeep; michael@0: if (mCurrentInterval) { michael@0: timesToKeep.AppendElement(mCurrentInterval->Begin()); michael@0: } michael@0: const nsSMILInterval* prevInterval = GetPreviousInterval(); michael@0: if (prevInterval) { michael@0: timesToKeep.AppendElement(prevInterval->End()); michael@0: } michael@0: if (!mOldIntervals.IsEmpty()) { michael@0: timesToKeep.AppendElement(mOldIntervals[0]->Begin()); michael@0: } michael@0: RemoveBelowThreshold removeBelowThreshold(threshold, timesToKeep); michael@0: RemoveInstanceTimes(aList, removeBelowThreshold); michael@0: } michael@0: } michael@0: michael@0: // michael@0: // This method is based on the pseudocode given in the SMILANIM spec. michael@0: // michael@0: // See: michael@0: // http://www.w3.org/TR/2001/REC-smil-animation-20010904/#Timing-BeginEnd-LC-Start michael@0: // michael@0: bool michael@0: nsSMILTimedElement::GetNextInterval(const nsSMILInterval* aPrevInterval, michael@0: const nsSMILInterval* aReplacedInterval, michael@0: const nsSMILInstanceTime* aFixedBeginTime, michael@0: nsSMILInterval& aResult) const michael@0: { michael@0: NS_ABORT_IF_FALSE(!aFixedBeginTime || aFixedBeginTime->Time().IsDefinite(), michael@0: "Unresolved or indefinite begin time specified for interval start"); michael@0: static const nsSMILTimeValue zeroTime(0L); michael@0: michael@0: if (mRestartMode == RESTART_NEVER && aPrevInterval) michael@0: return false; michael@0: michael@0: // Calc starting point michael@0: nsSMILTimeValue beginAfter; michael@0: bool prevIntervalWasZeroDur = false; michael@0: if (aPrevInterval) { michael@0: beginAfter = aPrevInterval->End()->Time(); michael@0: prevIntervalWasZeroDur michael@0: = aPrevInterval->End()->Time() == aPrevInterval->Begin()->Time(); michael@0: } else { michael@0: beginAfter.SetMillis(INT64_MIN); michael@0: } michael@0: michael@0: nsRefPtr tempBegin; michael@0: nsRefPtr tempEnd; michael@0: michael@0: while (true) { michael@0: // Calculate begin time michael@0: if (aFixedBeginTime) { michael@0: if (aFixedBeginTime->Time() < beginAfter) { michael@0: return false; michael@0: } michael@0: // our ref-counting is not const-correct michael@0: tempBegin = const_cast(aFixedBeginTime); michael@0: } else if ((!mAnimationElement || michael@0: !mAnimationElement->HasAnimAttr(nsGkAtoms::begin)) && michael@0: beginAfter <= zeroTime) { michael@0: tempBegin = new nsSMILInstanceTime(nsSMILTimeValue(0)); michael@0: } else { michael@0: int32_t beginPos = 0; michael@0: do { michael@0: tempBegin = michael@0: GetNextGreaterOrEqual(mBeginInstances, beginAfter, beginPos); michael@0: if (!tempBegin || !tempBegin->Time().IsDefinite()) { michael@0: return false; michael@0: } michael@0: // If we're updating the current interval then skip any begin time that is michael@0: // dependent on the current interval's begin time. e.g. michael@0: // GetBaseTime() == aReplacedInterval->Begin()); michael@0: } michael@0: NS_ABORT_IF_FALSE(tempBegin && tempBegin->Time().IsDefinite() && michael@0: tempBegin->Time() >= beginAfter, michael@0: "Got a bad begin time while fetching next interval"); michael@0: michael@0: // Calculate end time michael@0: { michael@0: int32_t endPos = 0; michael@0: do { michael@0: tempEnd = michael@0: GetNextGreaterOrEqual(mEndInstances, tempBegin->Time(), endPos); michael@0: michael@0: // SMIL doesn't allow for coincident zero-duration intervals, so if the michael@0: // previous interval was zero-duration, and tempEnd is going to give us michael@0: // another zero duration interval, then look for another end to use michael@0: // instead. michael@0: if (tempEnd && prevIntervalWasZeroDur && michael@0: tempEnd->Time() == beginAfter) { michael@0: tempEnd = GetNextGreater(mEndInstances, tempBegin->Time(), endPos); michael@0: } michael@0: // As above with begin times, avoid creating self-referential loops michael@0: // between instance times by checking that the newly found end instance michael@0: // time is not already dependent on the end of the current interval. michael@0: } while (tempEnd && aReplacedInterval && michael@0: tempEnd->GetBaseTime() == aReplacedInterval->End()); michael@0: michael@0: if (!tempEnd) { michael@0: // If all the ends are before the beginning we have a bad interval michael@0: // UNLESS: michael@0: // a) We never had any end attribute to begin with (the SMIL pseudocode michael@0: // places this condition earlier in the flow but that fails to allow michael@0: // for DOM calls when no "indefinite" condition is given), OR michael@0: // b) We never had any end instance times to begin with, OR michael@0: // c) We have end events which leave the interval open-ended. michael@0: bool openEndedIntervalOk = mEndSpecs.IsEmpty() || michael@0: mEndInstances.IsEmpty() || michael@0: EndHasEventConditions(); michael@0: michael@0: // The above conditions correspond with the SMIL pseudocode but SMIL michael@0: // doesn't address self-dependent instance times which we choose to michael@0: // ignore. michael@0: // michael@0: // Therefore we add a qualification of (b) above that even if michael@0: // there are end instance times but they all depend on the end of the michael@0: // current interval we should act as if they didn't exist and allow the michael@0: // open-ended interval. michael@0: // michael@0: // In the following condition we don't use |= because it doesn't provide michael@0: // short-circuit behavior. michael@0: openEndedIntervalOk = openEndedIntervalOk || michael@0: (aReplacedInterval && michael@0: AreEndTimesDependentOn(aReplacedInterval->End())); michael@0: michael@0: if (!openEndedIntervalOk) { michael@0: return false; // Bad interval michael@0: } michael@0: } michael@0: michael@0: nsSMILTimeValue intervalEnd = tempEnd michael@0: ? tempEnd->Time() : nsSMILTimeValue(); michael@0: nsSMILTimeValue activeEnd = CalcActiveEnd(tempBegin->Time(), intervalEnd); michael@0: michael@0: if (!tempEnd || intervalEnd != activeEnd) { michael@0: tempEnd = new nsSMILInstanceTime(activeEnd); michael@0: } michael@0: } michael@0: NS_ABORT_IF_FALSE(tempEnd, "Failed to get end point for next interval"); michael@0: michael@0: // When we choose the interval endpoints, we don't allow coincident michael@0: // zero-duration intervals, so if we arrive here and we have a zero-duration michael@0: // interval starting at the same point as a previous zero-duration interval, michael@0: // then it must be because we've applied constraints to the active duration. michael@0: // In that case, we will potentially run into an infinite loop, so we break michael@0: // it by searching for the next interval that starts AFTER our current michael@0: // zero-duration interval. michael@0: if (prevIntervalWasZeroDur && tempEnd->Time() == beginAfter) { michael@0: if (prevIntervalWasZeroDur) { michael@0: beginAfter.SetMillis(tempBegin->Time().GetMillis() + 1); michael@0: prevIntervalWasZeroDur = false; michael@0: continue; michael@0: } michael@0: } michael@0: prevIntervalWasZeroDur = tempBegin->Time() == tempEnd->Time(); michael@0: michael@0: // Check for valid interval michael@0: if (tempEnd->Time() > zeroTime || michael@0: (tempBegin->Time() == zeroTime && tempEnd->Time() == zeroTime)) { michael@0: aResult.Set(*tempBegin, *tempEnd); michael@0: return true; michael@0: } michael@0: michael@0: if (mRestartMode == RESTART_NEVER) { michael@0: // tempEnd <= 0 so we're going to loop which effectively means restarting michael@0: return false; michael@0: } michael@0: michael@0: beginAfter = tempEnd->Time(); michael@0: } michael@0: NS_NOTREACHED("Hmm... we really shouldn't be here"); michael@0: michael@0: return false; michael@0: } michael@0: michael@0: nsSMILInstanceTime* michael@0: nsSMILTimedElement::GetNextGreater(const InstanceTimeList& aList, michael@0: const nsSMILTimeValue& aBase, michael@0: int32_t& aPosition) const michael@0: { michael@0: nsSMILInstanceTime* result = nullptr; michael@0: while ((result = GetNextGreaterOrEqual(aList, aBase, aPosition)) && michael@0: result->Time() == aBase) { } michael@0: return result; michael@0: } michael@0: michael@0: nsSMILInstanceTime* michael@0: nsSMILTimedElement::GetNextGreaterOrEqual(const InstanceTimeList& aList, michael@0: const nsSMILTimeValue& aBase, michael@0: int32_t& aPosition) const michael@0: { michael@0: nsSMILInstanceTime* result = nullptr; michael@0: int32_t count = aList.Length(); michael@0: michael@0: for (; aPosition < count && !result; ++aPosition) { michael@0: nsSMILInstanceTime* val = aList[aPosition].get(); michael@0: NS_ABORT_IF_FALSE(val, "NULL instance time in list"); michael@0: if (val->Time() >= aBase) { michael@0: result = val; michael@0: } michael@0: } michael@0: michael@0: return result; michael@0: } michael@0: michael@0: /** michael@0: * @see SMILANIM 3.3.4 michael@0: */ michael@0: nsSMILTimeValue michael@0: nsSMILTimedElement::CalcActiveEnd(const nsSMILTimeValue& aBegin, michael@0: const nsSMILTimeValue& aEnd) const michael@0: { michael@0: nsSMILTimeValue result; michael@0: michael@0: NS_ABORT_IF_FALSE(mSimpleDur.IsResolved(), michael@0: "Unresolved simple duration in CalcActiveEnd"); michael@0: NS_ABORT_IF_FALSE(aBegin.IsDefinite(), michael@0: "Indefinite or unresolved begin time in CalcActiveEnd"); michael@0: michael@0: result = GetRepeatDuration(); michael@0: michael@0: if (aEnd.IsDefinite()) { michael@0: nsSMILTime activeDur = aEnd.GetMillis() - aBegin.GetMillis(); michael@0: michael@0: if (result.IsDefinite()) { michael@0: result.SetMillis(std::min(result.GetMillis(), activeDur)); michael@0: } else { michael@0: result.SetMillis(activeDur); michael@0: } michael@0: } michael@0: michael@0: result = ApplyMinAndMax(result); michael@0: michael@0: if (result.IsDefinite()) { michael@0: nsSMILTime activeEnd = result.GetMillis() + aBegin.GetMillis(); michael@0: result.SetMillis(activeEnd); michael@0: } michael@0: michael@0: return result; michael@0: } michael@0: michael@0: nsSMILTimeValue michael@0: nsSMILTimedElement::GetRepeatDuration() const michael@0: { michael@0: nsSMILTimeValue multipliedDuration; michael@0: if (mRepeatCount.IsDefinite() && mSimpleDur.IsDefinite()) { michael@0: multipliedDuration.SetMillis( michael@0: nsSMILTime(mRepeatCount * double(mSimpleDur.GetMillis()))); michael@0: } else { michael@0: multipliedDuration.SetIndefinite(); michael@0: } michael@0: michael@0: nsSMILTimeValue repeatDuration; michael@0: michael@0: if (mRepeatDur.IsResolved()) { michael@0: repeatDuration = std::min(multipliedDuration, mRepeatDur); michael@0: } else if (mRepeatCount.IsSet()) { michael@0: repeatDuration = multipliedDuration; michael@0: } else { michael@0: repeatDuration = mSimpleDur; michael@0: } michael@0: michael@0: return repeatDuration; michael@0: } michael@0: michael@0: nsSMILTimeValue michael@0: nsSMILTimedElement::ApplyMinAndMax(const nsSMILTimeValue& aDuration) const michael@0: { michael@0: if (!aDuration.IsResolved()) { michael@0: return aDuration; michael@0: } michael@0: michael@0: if (mMax < mMin) { michael@0: return aDuration; michael@0: } michael@0: michael@0: nsSMILTimeValue result; michael@0: michael@0: if (aDuration > mMax) { michael@0: result = mMax; michael@0: } else if (aDuration < mMin) { michael@0: result = mMin; michael@0: } else { michael@0: result = aDuration; michael@0: } michael@0: michael@0: return result; michael@0: } michael@0: michael@0: nsSMILTime michael@0: nsSMILTimedElement::ActiveTimeToSimpleTime(nsSMILTime aActiveTime, michael@0: uint32_t& aRepeatIteration) michael@0: { michael@0: nsSMILTime result; michael@0: michael@0: NS_ABORT_IF_FALSE(mSimpleDur.IsResolved(), michael@0: "Unresolved simple duration in ActiveTimeToSimpleTime"); michael@0: NS_ABORT_IF_FALSE(aActiveTime >= 0, "Expecting non-negative active time"); michael@0: // Note that a negative aActiveTime will give us a negative value for michael@0: // aRepeatIteration, which is bad because aRepeatIteration is unsigned michael@0: michael@0: if (mSimpleDur.IsIndefinite() || mSimpleDur.GetMillis() == 0L) { michael@0: aRepeatIteration = 0; michael@0: result = aActiveTime; michael@0: } else { michael@0: result = aActiveTime % mSimpleDur.GetMillis(); michael@0: aRepeatIteration = (uint32_t)(aActiveTime / mSimpleDur.GetMillis()); michael@0: } michael@0: michael@0: return result; michael@0: } michael@0: michael@0: // michael@0: // Although in many cases it would be possible to check for an early end and michael@0: // adjust the current interval well in advance the SMIL Animation spec seems to michael@0: // indicate that we should only apply an early end at the latest possible michael@0: // moment. In particular, this paragraph from section 3.6.8: michael@0: // michael@0: // 'If restart is set to "always", then the current interval will end early if michael@0: // there is an instance time in the begin list that is before (i.e. earlier michael@0: // than) the defined end for the current interval. Ending in this manner will michael@0: // also send a changed time notice to all time dependents for the current michael@0: // interval end.' michael@0: // michael@0: nsSMILInstanceTime* michael@0: nsSMILTimedElement::CheckForEarlyEnd( michael@0: const nsSMILTimeValue& aContainerTime) const michael@0: { michael@0: NS_ABORT_IF_FALSE(mCurrentInterval, michael@0: "Checking for an early end but the current interval is not set"); michael@0: if (mRestartMode != RESTART_ALWAYS) michael@0: return nullptr; michael@0: michael@0: int32_t position = 0; michael@0: nsSMILInstanceTime* nextBegin = michael@0: GetNextGreater(mBeginInstances, mCurrentInterval->Begin()->Time(), michael@0: position); michael@0: michael@0: if (nextBegin && michael@0: nextBegin->Time() > mCurrentInterval->Begin()->Time() && michael@0: nextBegin->Time() < mCurrentInterval->End()->Time() && michael@0: nextBegin->Time() <= aContainerTime) { michael@0: return nextBegin; michael@0: } michael@0: michael@0: return nullptr; michael@0: } michael@0: michael@0: void michael@0: nsSMILTimedElement::UpdateCurrentInterval(bool aForceChangeNotice) michael@0: { michael@0: // Check if updates are currently blocked (batched) michael@0: if (mDeferIntervalUpdates) { michael@0: mDoDeferredUpdate = true; michael@0: return; michael@0: } michael@0: michael@0: // We adopt the convention of not resolving intervals until the first michael@0: // sample. Otherwise, every time each attribute is set we'll re-resolve the michael@0: // current interval and notify all our time dependents of the change. michael@0: // michael@0: // The disadvantage of deferring resolving the interval is that DOM calls to michael@0: // to getStartTime will throw an INVALID_STATE_ERR exception until the michael@0: // document timeline begins since the start time has not yet been resolved. michael@0: if (mElementState == STATE_STARTUP) michael@0: return; michael@0: michael@0: // Although SMIL gives rules for detecting cycles in change notifications, michael@0: // some configurations can lead to create-delete-create-delete-etc. cycles michael@0: // which SMIL does not consider. michael@0: // michael@0: // In order to provide consistent behavior in such cases, we detect two michael@0: // deletes in a row and then refuse to create any further intervals. That is, michael@0: // we say the configuration is invalid. michael@0: if (mDeleteCount > 1) { michael@0: // When we update the delete count we also set the state to post active, so michael@0: // if we're not post active here then something other than michael@0: // UpdateCurrentInterval has updated the element state in between and all michael@0: // bets are off. michael@0: NS_ABORT_IF_FALSE(mElementState == STATE_POSTACTIVE, michael@0: "Expected to be in post-active state after performing double delete"); michael@0: return; michael@0: } michael@0: michael@0: // Check that we aren't stuck in infinite recursion updating some syncbase michael@0: // dependencies. Generally such situations should be detected in advance and michael@0: // the chain broken in a sensible and predictable manner, so if we're hitting michael@0: // this assertion we need to work out how to detect the case that's causing michael@0: // it. In release builds, just bail out before we overflow the stack. michael@0: AutoRestore depthRestorer(mUpdateIntervalRecursionDepth); michael@0: if (++mUpdateIntervalRecursionDepth > sMaxUpdateIntervalRecursionDepth) { michael@0: NS_ABORT_IF_FALSE(false, michael@0: "Update current interval recursion depth exceeded threshold"); michael@0: return; michael@0: } michael@0: michael@0: // If the interval is active the begin time is fixed. michael@0: const nsSMILInstanceTime* beginTime = mElementState == STATE_ACTIVE michael@0: ? mCurrentInterval->Begin() michael@0: : nullptr; michael@0: nsSMILInterval updatedInterval; michael@0: if (GetNextInterval(GetPreviousInterval(), mCurrentInterval, michael@0: beginTime, updatedInterval)) { michael@0: michael@0: if (mElementState == STATE_POSTACTIVE) { michael@0: michael@0: NS_ABORT_IF_FALSE(!mCurrentInterval, michael@0: "In postactive state but the interval has been set"); michael@0: mCurrentInterval = new nsSMILInterval(updatedInterval); michael@0: mElementState = STATE_WAITING; michael@0: NotifyNewInterval(); michael@0: michael@0: } else { michael@0: michael@0: bool beginChanged = false; michael@0: bool endChanged = false; michael@0: michael@0: if (mElementState != STATE_ACTIVE && michael@0: !updatedInterval.Begin()->SameTimeAndBase( michael@0: *mCurrentInterval->Begin())) { michael@0: mCurrentInterval->SetBegin(*updatedInterval.Begin()); michael@0: beginChanged = true; michael@0: } michael@0: michael@0: if (!updatedInterval.End()->SameTimeAndBase(*mCurrentInterval->End())) { michael@0: mCurrentInterval->SetEnd(*updatedInterval.End()); michael@0: endChanged = true; michael@0: } michael@0: michael@0: if (beginChanged || endChanged || aForceChangeNotice) { michael@0: NotifyChangedInterval(mCurrentInterval, beginChanged, endChanged); michael@0: } michael@0: } michael@0: michael@0: // There's a chance our next milestone has now changed, so update the time michael@0: // container michael@0: RegisterMilestone(); michael@0: } else { // GetNextInterval failed: Current interval is no longer valid michael@0: if (mElementState == STATE_ACTIVE) { michael@0: // The interval is active so we can't just delete it, instead trim it so michael@0: // that begin==end. michael@0: if (!mCurrentInterval->End()->SameTimeAndBase(*mCurrentInterval->Begin())) michael@0: { michael@0: mCurrentInterval->SetEnd(*mCurrentInterval->Begin()); michael@0: NotifyChangedInterval(mCurrentInterval, false, true); michael@0: } michael@0: // The transition to the postactive state will take place on the next michael@0: // sample (along with firing end events, clearing intervals etc.) michael@0: RegisterMilestone(); michael@0: } else if (mElementState == STATE_WAITING) { michael@0: AutoRestore deleteCountRestorer(mDeleteCount); michael@0: ++mDeleteCount; michael@0: mElementState = STATE_POSTACTIVE; michael@0: ResetCurrentInterval(); michael@0: } michael@0: } michael@0: } michael@0: michael@0: void michael@0: nsSMILTimedElement::SampleSimpleTime(nsSMILTime aActiveTime) michael@0: { michael@0: if (mClient) { michael@0: uint32_t repeatIteration; michael@0: nsSMILTime simpleTime = michael@0: ActiveTimeToSimpleTime(aActiveTime, repeatIteration); michael@0: mClient->SampleAt(simpleTime, mSimpleDur, repeatIteration); michael@0: } michael@0: } michael@0: michael@0: void michael@0: nsSMILTimedElement::SampleFillValue() michael@0: { michael@0: if (mFillMode != FILL_FREEZE || !mClient) michael@0: return; michael@0: michael@0: nsSMILTime activeTime; michael@0: michael@0: if (mElementState == STATE_WAITING || mElementState == STATE_POSTACTIVE) { michael@0: const nsSMILInterval* prevInterval = GetPreviousInterval(); michael@0: NS_ABORT_IF_FALSE(prevInterval, michael@0: "Attempting to sample fill value but there is no previous interval"); michael@0: NS_ABORT_IF_FALSE(prevInterval->End()->Time().IsDefinite() && michael@0: prevInterval->End()->IsFixedTime(), michael@0: "Attempting to sample fill value but the endpoint of the previous " michael@0: "interval is not resolved and fixed"); michael@0: michael@0: activeTime = prevInterval->End()->Time().GetMillis() - michael@0: prevInterval->Begin()->Time().GetMillis(); michael@0: michael@0: // If the interval's repeat duration was shorter than its active duration, michael@0: // use the end of the repeat duration to determine the frozen animation's michael@0: // state. michael@0: nsSMILTimeValue repeatDuration = GetRepeatDuration(); michael@0: if (repeatDuration.IsDefinite()) { michael@0: activeTime = std::min(repeatDuration.GetMillis(), activeTime); michael@0: } michael@0: } else { michael@0: MOZ_ASSERT(mElementState == STATE_ACTIVE, michael@0: "Attempting to sample fill value when we're in an unexpected state " michael@0: "(probably STATE_STARTUP)"); michael@0: michael@0: // If we are being asked to sample the fill value while active we *must* michael@0: // have a repeat duration shorter than the active duration so use that. michael@0: MOZ_ASSERT(GetRepeatDuration().IsDefinite(), michael@0: "Attempting to sample fill value of an active animation with " michael@0: "an indefinite repeat duration"); michael@0: activeTime = GetRepeatDuration().GetMillis(); michael@0: } michael@0: michael@0: uint32_t repeatIteration; michael@0: nsSMILTime simpleTime = michael@0: ActiveTimeToSimpleTime(activeTime, repeatIteration); michael@0: michael@0: if (simpleTime == 0L && repeatIteration) { michael@0: mClient->SampleLastValue(--repeatIteration); michael@0: } else { michael@0: mClient->SampleAt(simpleTime, mSimpleDur, repeatIteration); michael@0: } michael@0: } michael@0: michael@0: nsresult michael@0: nsSMILTimedElement::AddInstanceTimeFromCurrentTime(nsSMILTime aCurrentTime, michael@0: double aOffsetSeconds, bool aIsBegin) michael@0: { michael@0: double offset = aOffsetSeconds * PR_MSEC_PER_SEC; michael@0: michael@0: // Check we won't overflow the range of nsSMILTime michael@0: if (aCurrentTime + NS_round(offset) > INT64_MAX) michael@0: return NS_ERROR_ILLEGAL_VALUE; michael@0: michael@0: nsSMILTimeValue timeVal(aCurrentTime + int64_t(NS_round(offset))); michael@0: michael@0: nsRefPtr instanceTime = michael@0: new nsSMILInstanceTime(timeVal, nsSMILInstanceTime::SOURCE_DOM); michael@0: michael@0: AddInstanceTime(instanceTime, aIsBegin); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: void michael@0: nsSMILTimedElement::RegisterMilestone() michael@0: { michael@0: nsSMILTimeContainer* container = GetTimeContainer(); michael@0: if (!container) michael@0: return; michael@0: NS_ABORT_IF_FALSE(mAnimationElement, michael@0: "Got a time container without an owning animation element"); michael@0: michael@0: nsSMILMilestone nextMilestone; michael@0: if (!GetNextMilestone(nextMilestone)) michael@0: return; michael@0: michael@0: // This method is called every time we might possibly have updated our michael@0: // current interval, but since nsSMILTimeContainer makes no attempt to filter michael@0: // out redundant milestones we do some rudimentary filtering here. It's not michael@0: // perfect, but unnecessary samples are fairly cheap. michael@0: if (nextMilestone >= mPrevRegisteredMilestone) michael@0: return; michael@0: michael@0: container->AddMilestone(nextMilestone, *mAnimationElement); michael@0: mPrevRegisteredMilestone = nextMilestone; michael@0: } michael@0: michael@0: bool michael@0: nsSMILTimedElement::GetNextMilestone(nsSMILMilestone& aNextMilestone) const michael@0: { michael@0: // Return the next key moment in our lifetime. michael@0: // michael@0: // XXX It may be possible in future to optimise this so that we only register michael@0: // for milestones if: michael@0: // a) We have time dependents, or michael@0: // b) We are dependent on events or syncbase relationships, or michael@0: // c) There are registered listeners for our events michael@0: // michael@0: // Then for the simple case where everything uses offset values we could michael@0: // ignore milestones altogether. michael@0: // michael@0: // We'd need to be careful, however, that if one of those conditions became michael@0: // true in between samples that we registered our next milestone at that michael@0: // point. michael@0: michael@0: switch (mElementState) michael@0: { michael@0: case STATE_STARTUP: michael@0: // All elements register for an initial end sample at t=0 where we resolve michael@0: // our initial interval. michael@0: aNextMilestone.mIsEnd = true; // Initial sample should be an end sample michael@0: aNextMilestone.mTime = 0; michael@0: return true; michael@0: michael@0: case STATE_WAITING: michael@0: NS_ABORT_IF_FALSE(mCurrentInterval, michael@0: "In waiting state but the current interval has not been set"); michael@0: aNextMilestone.mIsEnd = false; michael@0: aNextMilestone.mTime = mCurrentInterval->Begin()->Time().GetMillis(); michael@0: return true; michael@0: michael@0: case STATE_ACTIVE: michael@0: { michael@0: // Work out what comes next: the interval end or the next repeat iteration michael@0: nsSMILTimeValue nextRepeat; michael@0: if (mSeekState == SEEK_NOT_SEEKING && mSimpleDur.IsDefinite()) { michael@0: nsSMILTime nextRepeatActiveTime = michael@0: (mCurrentRepeatIteration + 1) * mSimpleDur.GetMillis(); michael@0: // Check that the repeat fits within the repeat duration michael@0: if (nsSMILTimeValue(nextRepeatActiveTime) < GetRepeatDuration()) { michael@0: nextRepeat.SetMillis(mCurrentInterval->Begin()->Time().GetMillis() + michael@0: nextRepeatActiveTime); michael@0: } michael@0: } michael@0: nsSMILTimeValue nextMilestone = michael@0: std::min(mCurrentInterval->End()->Time(), nextRepeat); michael@0: michael@0: // Check for an early end before that time michael@0: nsSMILInstanceTime* earlyEnd = CheckForEarlyEnd(nextMilestone); michael@0: if (earlyEnd) { michael@0: aNextMilestone.mIsEnd = true; michael@0: aNextMilestone.mTime = earlyEnd->Time().GetMillis(); michael@0: return true; michael@0: } michael@0: michael@0: // Apply the previously calculated milestone michael@0: if (nextMilestone.IsDefinite()) { michael@0: aNextMilestone.mIsEnd = nextMilestone != nextRepeat; michael@0: aNextMilestone.mTime = nextMilestone.GetMillis(); michael@0: return true; michael@0: } michael@0: michael@0: return false; michael@0: } michael@0: michael@0: case STATE_POSTACTIVE: michael@0: return false; michael@0: } michael@0: MOZ_CRASH("Invalid element state"); michael@0: } michael@0: michael@0: void michael@0: nsSMILTimedElement::NotifyNewInterval() michael@0: { michael@0: NS_ABORT_IF_FALSE(mCurrentInterval, michael@0: "Attempting to notify dependents of a new interval but the interval " michael@0: "is not set"); michael@0: michael@0: nsSMILTimeContainer* container = GetTimeContainer(); michael@0: if (container) { michael@0: container->SyncPauseTime(); michael@0: } michael@0: michael@0: NotifyTimeDependentsParams params = { this, container }; michael@0: mTimeDependents.EnumerateEntries(NotifyNewIntervalCallback, ¶ms); michael@0: } michael@0: michael@0: void michael@0: nsSMILTimedElement::NotifyChangedInterval(nsSMILInterval* aInterval, michael@0: bool aBeginObjectChanged, michael@0: bool aEndObjectChanged) michael@0: { michael@0: NS_ABORT_IF_FALSE(aInterval, "Null interval for change notification"); michael@0: michael@0: nsSMILTimeContainer* container = GetTimeContainer(); michael@0: if (container) { michael@0: container->SyncPauseTime(); michael@0: } michael@0: michael@0: // Copy the instance times list since notifying the instance times can result michael@0: // in a chain reaction whereby our own interval gets deleted along with its michael@0: // instance times. michael@0: InstanceTimeList times; michael@0: aInterval->GetDependentTimes(times); michael@0: michael@0: for (uint32_t i = 0; i < times.Length(); ++i) { michael@0: times[i]->HandleChangedInterval(container, aBeginObjectChanged, michael@0: aEndObjectChanged); michael@0: } michael@0: } michael@0: michael@0: void michael@0: nsSMILTimedElement::FireTimeEventAsync(uint32_t aMsg, int32_t aDetail) michael@0: { michael@0: if (!mAnimationElement) michael@0: return; michael@0: michael@0: nsCOMPtr event = michael@0: new AsyncTimeEventRunner(mAnimationElement, aMsg, aDetail); michael@0: NS_DispatchToMainThread(event, NS_DISPATCH_NORMAL); michael@0: } michael@0: michael@0: const nsSMILInstanceTime* michael@0: nsSMILTimedElement::GetEffectiveBeginInstance() const michael@0: { michael@0: switch (mElementState) michael@0: { michael@0: case STATE_STARTUP: michael@0: return nullptr; michael@0: michael@0: case STATE_ACTIVE: michael@0: return mCurrentInterval->Begin(); michael@0: michael@0: case STATE_WAITING: michael@0: case STATE_POSTACTIVE: michael@0: { michael@0: const nsSMILInterval* prevInterval = GetPreviousInterval(); michael@0: return prevInterval ? prevInterval->Begin() : nullptr; michael@0: } michael@0: } michael@0: MOZ_CRASH("Invalid element state"); michael@0: } michael@0: michael@0: const nsSMILInterval* michael@0: nsSMILTimedElement::GetPreviousInterval() const michael@0: { michael@0: return mOldIntervals.IsEmpty() michael@0: ? nullptr michael@0: : mOldIntervals[mOldIntervals.Length()-1].get(); michael@0: } michael@0: michael@0: bool michael@0: nsSMILTimedElement::HasClientInFillRange() const michael@0: { michael@0: // Returns true if we have a client that is in the range where it will fill michael@0: return mClient && michael@0: ((mElementState != STATE_ACTIVE && HasPlayed()) || michael@0: (mElementState == STATE_ACTIVE && !mClient->IsActive())); michael@0: } michael@0: michael@0: bool michael@0: nsSMILTimedElement::EndHasEventConditions() const michael@0: { michael@0: for (uint32_t i = 0; i < mEndSpecs.Length(); ++i) { michael@0: if (mEndSpecs[i]->IsEventBased()) michael@0: return true; michael@0: } michael@0: return false; michael@0: } michael@0: michael@0: bool michael@0: nsSMILTimedElement::AreEndTimesDependentOn( michael@0: const nsSMILInstanceTime* aBase) const michael@0: { michael@0: if (mEndInstances.IsEmpty()) michael@0: return false; michael@0: michael@0: for (uint32_t i = 0; i < mEndInstances.Length(); ++i) { michael@0: if (mEndInstances[i]->GetBaseTime() != aBase) { michael@0: return false; michael@0: } michael@0: } michael@0: return true; michael@0: } michael@0: michael@0: //---------------------------------------------------------------------- michael@0: // Hashtable callback functions michael@0: michael@0: /* static */ PLDHashOperator michael@0: nsSMILTimedElement::NotifyNewIntervalCallback(TimeValueSpecPtrKey* aKey, michael@0: void* aData) michael@0: { michael@0: NS_ABORT_IF_FALSE(aKey, "Null hash key for time container hash table"); michael@0: NS_ABORT_IF_FALSE(aKey->GetKey(), michael@0: "null nsSMILTimeValueSpec in set of time dependents"); michael@0: michael@0: NotifyTimeDependentsParams* params = michael@0: static_cast(aData); michael@0: NS_ABORT_IF_FALSE(params, "null data ptr while enumerating hashtable"); michael@0: nsSMILInterval* interval = params->mTimedElement->mCurrentInterval; michael@0: // It's possible that in notifying one new time dependent of a new interval michael@0: // that a chain reaction is triggered which results in the original interval michael@0: // disappearing. If that's the case we can skip sending further notifications. michael@0: if (!interval) michael@0: return PL_DHASH_STOP; michael@0: michael@0: nsSMILTimeValueSpec* spec = aKey->GetKey(); michael@0: spec->HandleNewInterval(*interval, params->mTimeContainer); michael@0: return PL_DHASH_NEXT; michael@0: }