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 "nsSMILTimeContainer.h" michael@0: #include "nsSMILTimeValue.h" michael@0: #include "nsSMILTimedElement.h" michael@0: #include michael@0: michael@0: nsSMILTimeContainer::nsSMILTimeContainer() michael@0: : michael@0: mParent(nullptr), michael@0: mCurrentTime(0L), michael@0: mParentOffset(0L), michael@0: mPauseStart(0L), michael@0: mNeedsPauseSample(false), michael@0: mNeedsRewind(false), michael@0: mIsSeeking(false), michael@0: mPauseState(PAUSE_BEGIN) michael@0: { michael@0: } michael@0: michael@0: nsSMILTimeContainer::~nsSMILTimeContainer() michael@0: { michael@0: if (mParent) { michael@0: mParent->RemoveChild(*this); michael@0: } michael@0: } michael@0: michael@0: nsSMILTimeValue michael@0: nsSMILTimeContainer::ContainerToParentTime(nsSMILTime aContainerTime) const michael@0: { michael@0: // If we're paused, then future times are indefinite michael@0: if (IsPaused() && aContainerTime > mCurrentTime) michael@0: return nsSMILTimeValue::Indefinite(); michael@0: michael@0: return nsSMILTimeValue(aContainerTime + mParentOffset); michael@0: } michael@0: michael@0: nsSMILTimeValue michael@0: nsSMILTimeContainer::ParentToContainerTime(nsSMILTime aParentTime) const michael@0: { michael@0: // If we're paused, then any time after when we paused is indefinite michael@0: if (IsPaused() && aParentTime > mPauseStart) michael@0: return nsSMILTimeValue::Indefinite(); michael@0: michael@0: return nsSMILTimeValue(aParentTime - mParentOffset); michael@0: } michael@0: michael@0: void michael@0: nsSMILTimeContainer::Begin() michael@0: { michael@0: Resume(PAUSE_BEGIN); michael@0: if (mPauseState) { michael@0: mNeedsPauseSample = true; michael@0: } michael@0: michael@0: // This is a little bit complicated here. Ideally we'd just like to call michael@0: // Sample() and force an initial sample but this turns out to be a bad idea michael@0: // because this may mean that NeedsSample() no longer reports true and so when michael@0: // we come to the first real sample our parent will skip us over altogether. michael@0: // So we force the time to be updated and adopt the policy to never call michael@0: // Sample() ourselves but to always leave that to our parent or client. michael@0: michael@0: UpdateCurrentTime(); michael@0: } michael@0: michael@0: void michael@0: nsSMILTimeContainer::Pause(uint32_t aType) michael@0: { michael@0: bool didStartPause = false; michael@0: michael@0: if (!mPauseState && aType) { michael@0: mPauseStart = GetParentTime(); michael@0: mNeedsPauseSample = true; michael@0: didStartPause = true; michael@0: } michael@0: michael@0: mPauseState |= aType; michael@0: michael@0: if (didStartPause) { michael@0: NotifyTimeChange(); michael@0: } michael@0: } michael@0: michael@0: void michael@0: nsSMILTimeContainer::Resume(uint32_t aType) michael@0: { michael@0: if (!mPauseState) michael@0: return; michael@0: michael@0: mPauseState &= ~aType; michael@0: michael@0: if (!mPauseState) { michael@0: nsSMILTime extraOffset = GetParentTime() - mPauseStart; michael@0: mParentOffset += extraOffset; michael@0: NotifyTimeChange(); michael@0: } michael@0: } michael@0: michael@0: nsSMILTime michael@0: nsSMILTimeContainer::GetCurrentTime() const michael@0: { michael@0: // The following behaviour is consistent with: michael@0: // http://www.w3.org/2003/01/REC-SVG11-20030114-errata michael@0: // #getCurrentTime_setCurrentTime_undefined_before_document_timeline_begin michael@0: // which says that if GetCurrentTime is called before the document timeline michael@0: // has begun we should just return 0. michael@0: if (IsPausedByType(PAUSE_BEGIN)) michael@0: return 0L; michael@0: michael@0: return mCurrentTime; michael@0: } michael@0: michael@0: void michael@0: nsSMILTimeContainer::SetCurrentTime(nsSMILTime aSeekTo) michael@0: { michael@0: // SVG 1.1 doesn't specify what to do for negative times so we adopt SVGT1.2's michael@0: // behaviour of clamping negative times to 0. michael@0: aSeekTo = std::max(0, aSeekTo); michael@0: michael@0: // The following behaviour is consistent with: michael@0: // http://www.w3.org/2003/01/REC-SVG11-20030114-errata michael@0: // #getCurrentTime_setCurrentTime_undefined_before_document_timeline_begin michael@0: // which says that if SetCurrentTime is called before the document timeline michael@0: // has begun we should still adjust the offset. michael@0: nsSMILTime parentTime = GetParentTime(); michael@0: mParentOffset = parentTime - aSeekTo; michael@0: mIsSeeking = true; michael@0: michael@0: if (IsPaused()) { michael@0: mNeedsPauseSample = true; michael@0: mPauseStart = parentTime; michael@0: } michael@0: michael@0: if (aSeekTo < mCurrentTime) { michael@0: // Backwards seek michael@0: mNeedsRewind = true; michael@0: ClearMilestones(); michael@0: } michael@0: michael@0: // Force an update to the current time in case we get a call to GetCurrentTime michael@0: // before another call to Sample(). michael@0: UpdateCurrentTime(); michael@0: michael@0: NotifyTimeChange(); michael@0: } michael@0: michael@0: nsSMILTime michael@0: nsSMILTimeContainer::GetParentTime() const michael@0: { michael@0: if (mParent) michael@0: return mParent->GetCurrentTime(); michael@0: michael@0: return 0L; michael@0: } michael@0: michael@0: void michael@0: nsSMILTimeContainer::SyncPauseTime() michael@0: { michael@0: if (IsPaused()) { michael@0: nsSMILTime parentTime = GetParentTime(); michael@0: nsSMILTime extraOffset = parentTime - mPauseStart; michael@0: mParentOffset += extraOffset; michael@0: mPauseStart = parentTime; michael@0: } michael@0: } michael@0: michael@0: void michael@0: nsSMILTimeContainer::Sample() michael@0: { michael@0: if (!NeedsSample()) michael@0: return; michael@0: michael@0: UpdateCurrentTime(); michael@0: DoSample(); michael@0: michael@0: mNeedsPauseSample = false; michael@0: } michael@0: michael@0: nsresult michael@0: nsSMILTimeContainer::SetParent(nsSMILTimeContainer* aParent) michael@0: { michael@0: if (mParent) { michael@0: mParent->RemoveChild(*this); michael@0: // When we're not attached to a parent time container, GetParentTime() will michael@0: // return 0. We need to adjust our pause state information to be relative to michael@0: // this new time base. michael@0: // Note that since "current time = parent time - parent offset" setting the michael@0: // parent offset and pause start as follows preserves our current time even michael@0: // while parent time = 0. michael@0: mParentOffset = -mCurrentTime; michael@0: mPauseStart = 0L; michael@0: } michael@0: michael@0: mParent = aParent; michael@0: michael@0: nsresult rv = NS_OK; michael@0: if (mParent) { michael@0: rv = mParent->AddChild(*this); michael@0: } michael@0: michael@0: return rv; michael@0: } michael@0: michael@0: bool michael@0: nsSMILTimeContainer::AddMilestone(const nsSMILMilestone& aMilestone, michael@0: mozilla::dom::SVGAnimationElement& aElement) michael@0: { michael@0: // We record the milestone time and store it along with the element but this michael@0: // time may change (e.g. if attributes are changed on the timed element in michael@0: // between samples). If this happens, then we may do an unecessary sample michael@0: // but that's pretty cheap. michael@0: return mMilestoneEntries.Push(MilestoneEntry(aMilestone, aElement)); michael@0: } michael@0: michael@0: void michael@0: nsSMILTimeContainer::ClearMilestones() michael@0: { michael@0: mMilestoneEntries.Clear(); michael@0: } michael@0: michael@0: bool michael@0: nsSMILTimeContainer::GetNextMilestoneInParentTime( michael@0: nsSMILMilestone& aNextMilestone) const michael@0: { michael@0: if (mMilestoneEntries.IsEmpty()) michael@0: return false; michael@0: michael@0: nsSMILTimeValue parentTime = michael@0: ContainerToParentTime(mMilestoneEntries.Top().mMilestone.mTime); michael@0: if (!parentTime.IsDefinite()) michael@0: return false; michael@0: michael@0: aNextMilestone = nsSMILMilestone(parentTime.GetMillis(), michael@0: mMilestoneEntries.Top().mMilestone.mIsEnd); michael@0: michael@0: return true; michael@0: } michael@0: michael@0: bool michael@0: nsSMILTimeContainer::PopMilestoneElementsAtMilestone( michael@0: const nsSMILMilestone& aMilestone, michael@0: AnimElemArray& aMatchedElements) michael@0: { michael@0: if (mMilestoneEntries.IsEmpty()) michael@0: return false; michael@0: michael@0: nsSMILTimeValue containerTime = ParentToContainerTime(aMilestone.mTime); michael@0: if (!containerTime.IsDefinite()) michael@0: return false; michael@0: michael@0: nsSMILMilestone containerMilestone(containerTime.GetMillis(), michael@0: aMilestone.mIsEnd); michael@0: michael@0: NS_ABORT_IF_FALSE(mMilestoneEntries.Top().mMilestone >= containerMilestone, michael@0: "Trying to pop off earliest times but we have earlier ones that were " michael@0: "overlooked"); michael@0: michael@0: bool gotOne = false; michael@0: while (!mMilestoneEntries.IsEmpty() && michael@0: mMilestoneEntries.Top().mMilestone == containerMilestone) michael@0: { michael@0: aMatchedElements.AppendElement(mMilestoneEntries.Pop().mTimebase); michael@0: gotOne = true; michael@0: } michael@0: michael@0: return gotOne; michael@0: } michael@0: michael@0: void michael@0: nsSMILTimeContainer::Traverse(nsCycleCollectionTraversalCallback* aCallback) michael@0: { michael@0: const MilestoneEntry* p = mMilestoneEntries.Elements(); michael@0: while (p < mMilestoneEntries.Elements() + mMilestoneEntries.Length()) { michael@0: NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(*aCallback, "mTimebase"); michael@0: aCallback->NoteXPCOMChild(static_cast(p->mTimebase.get())); michael@0: ++p; michael@0: } michael@0: } michael@0: michael@0: void michael@0: nsSMILTimeContainer::Unlink() michael@0: { michael@0: mMilestoneEntries.Clear(); michael@0: } michael@0: michael@0: void michael@0: nsSMILTimeContainer::UpdateCurrentTime() michael@0: { michael@0: nsSMILTime now = IsPaused() ? mPauseStart : GetParentTime(); michael@0: mCurrentTime = now - mParentOffset; michael@0: NS_ABORT_IF_FALSE(mCurrentTime >= 0, "Container has negative time"); michael@0: } michael@0: michael@0: void michael@0: nsSMILTimeContainer::NotifyTimeChange() michael@0: { michael@0: // Called when the container time is changed with respect to the document michael@0: // time. When this happens time dependencies in other time containers need to michael@0: // re-resolve their times because begin and end times are stored in container michael@0: // time. michael@0: // michael@0: // To get the list of timed elements with dependencies we simply re-use the michael@0: // milestone elements. This is because any timed element with dependents and michael@0: // with significant transitions yet to fire should have their next milestone michael@0: // registered. Other timed elements don't matter. michael@0: const MilestoneEntry* p = mMilestoneEntries.Elements(); michael@0: #if DEBUG michael@0: uint32_t queueLength = mMilestoneEntries.Length(); michael@0: #endif michael@0: while (p < mMilestoneEntries.Elements() + mMilestoneEntries.Length()) { michael@0: mozilla::dom::SVGAnimationElement* elem = p->mTimebase.get(); michael@0: elem->TimedElement().HandleContainerTimeChange(); michael@0: NS_ABORT_IF_FALSE(queueLength == mMilestoneEntries.Length(), michael@0: "Call to HandleContainerTimeChange resulted in a change to the " michael@0: "queue of milestones"); michael@0: ++p; michael@0: } michael@0: }