summaryrefslogtreecommitdiffstats
path: root/dom/smil/SMILTimeValueSpec.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'dom/smil/SMILTimeValueSpec.cpp')
-rw-r--r--dom/smil/SMILTimeValueSpec.cpp370
1 files changed, 370 insertions, 0 deletions
diff --git a/dom/smil/SMILTimeValueSpec.cpp b/dom/smil/SMILTimeValueSpec.cpp
new file mode 100644
index 0000000000..3ce58e9598
--- /dev/null
+++ b/dom/smil/SMILTimeValueSpec.cpp
@@ -0,0 +1,370 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "mozilla/EventListenerManager.h"
+#include "mozilla/SMILInstanceTime.h"
+#include "mozilla/SMILInterval.h"
+#include "mozilla/SMILParserUtils.h"
+#include "mozilla/SMILTimeContainer.h"
+#include "mozilla/SMILTimedElement.h"
+#include "mozilla/SMILTimeValueSpec.h"
+#include "mozilla/SMILTimeValue.h"
+#include "mozilla/dom/Document.h"
+#include "mozilla/dom/Event.h"
+#include "mozilla/dom/SVGAnimationElement.h"
+#include "mozilla/dom/TimeEvent.h"
+#include "nsString.h"
+#include <limits>
+
+using namespace mozilla::dom;
+
+namespace mozilla {
+
+//----------------------------------------------------------------------
+// Nested class: EventListener
+
+NS_IMPL_ISUPPORTS(SMILTimeValueSpec::EventListener, nsIDOMEventListener)
+
+NS_IMETHODIMP
+SMILTimeValueSpec::EventListener::HandleEvent(Event* aEvent) {
+ if (mSpec) {
+ mSpec->HandleEvent(aEvent);
+ }
+ return NS_OK;
+}
+
+//----------------------------------------------------------------------
+// Implementation
+
+SMILTimeValueSpec::SMILTimeValueSpec(SMILTimedElement& aOwner, bool aIsBegin)
+ : mOwner(&aOwner), mIsBegin(aIsBegin), mReferencedElement(this) {}
+
+SMILTimeValueSpec::~SMILTimeValueSpec() {
+ UnregisterFromReferencedElement(mReferencedElement.get());
+ if (mEventListener) {
+ mEventListener->Disconnect();
+ mEventListener = nullptr;
+ }
+}
+
+nsresult SMILTimeValueSpec::SetSpec(const nsAString& aStringSpec,
+ Element& aContextElement) {
+ SMILTimeValueSpecParams params;
+
+ if (!SMILParserUtils::ParseTimeValueSpecParams(aStringSpec, params))
+ return NS_ERROR_FAILURE;
+
+ mParams = params;
+
+ // According to SMIL 3.0:
+ // The special value "indefinite" does not yield an instance time in the
+ // begin list. It will, however yield a single instance with the value
+ // "indefinite" in an end list. This value is not removed by a reset.
+ if (mParams.mType == SMILTimeValueSpecParams::OFFSET ||
+ (!mIsBegin && mParams.mType == SMILTimeValueSpecParams::INDEFINITE)) {
+ mOwner->AddInstanceTime(new SMILInstanceTime(mParams.mOffset), mIsBegin);
+ }
+
+ // Fill in the event symbol to simplify handling later
+ if (mParams.mType == SMILTimeValueSpecParams::REPEAT) {
+ mParams.mEventSymbol = nsGkAtoms::repeatEvent;
+ }
+
+ ResolveReferences(aContextElement);
+
+ return NS_OK;
+}
+
+void SMILTimeValueSpec::ResolveReferences(Element& aContextElement) {
+ if (mParams.mType != SMILTimeValueSpecParams::SYNCBASE && !IsEventBased()) {
+ return;
+ }
+
+ // If we're not bound to the document yet, don't worry, we'll get called again
+ // when that happens
+ if (!aContextElement.IsInComposedDoc()) return;
+
+ // Hold ref to the old element so that it isn't destroyed in between resetting
+ // the referenced element and using the pointer to update the referenced
+ // element.
+ RefPtr<Element> oldReferencedElement = mReferencedElement.get();
+
+ if (mParams.mDependentElemID) {
+ mReferencedElement.ResetWithID(aContextElement, mParams.mDependentElemID);
+ } else if (mParams.mType == SMILTimeValueSpecParams::EVENT) {
+ Element* target = mOwner->GetTargetElement();
+ mReferencedElement.ResetWithElement(target);
+ } else {
+ MOZ_ASSERT(false, "Syncbase or repeat spec without ID");
+ }
+ UpdateReferencedElement(oldReferencedElement, mReferencedElement.get());
+}
+
+bool SMILTimeValueSpec::IsEventBased() const {
+ return mParams.mType == SMILTimeValueSpecParams::EVENT ||
+ mParams.mType == SMILTimeValueSpecParams::REPEAT;
+}
+
+void SMILTimeValueSpec::HandleNewInterval(
+ SMILInterval& aInterval, const SMILTimeContainer* aSrcContainer) {
+ const SMILInstanceTime& baseInstance =
+ mParams.mSyncBegin ? *aInterval.Begin() : *aInterval.End();
+ SMILTimeValue newTime =
+ ConvertBetweenTimeContainers(baseInstance.Time(), aSrcContainer);
+
+ // Apply offset
+ if (!ApplyOffset(newTime)) {
+ NS_WARNING("New time overflows SMILTime, ignoring");
+ return;
+ }
+
+ // Create the instance time and register it with the interval
+ RefPtr<SMILInstanceTime> newInstance = new SMILInstanceTime(
+ newTime, SMILInstanceTime::SOURCE_SYNCBASE, this, &aInterval);
+ mOwner->AddInstanceTime(newInstance, mIsBegin);
+}
+
+void SMILTimeValueSpec::HandleTargetElementChange(Element* aNewTarget) {
+ if (!IsEventBased() || mParams.mDependentElemID) return;
+
+ mReferencedElement.ResetWithElement(aNewTarget);
+}
+
+void SMILTimeValueSpec::HandleChangedInstanceTime(
+ const SMILInstanceTime& aBaseTime, const SMILTimeContainer* aSrcContainer,
+ SMILInstanceTime& aInstanceTimeToUpdate, bool aObjectChanged) {
+ // If the instance time is fixed (e.g. because it's being used as the begin
+ // time of an active or postactive interval) we just ignore the change.
+ if (aInstanceTimeToUpdate.IsFixedTime()) return;
+
+ SMILTimeValue updatedTime =
+ ConvertBetweenTimeContainers(aBaseTime.Time(), aSrcContainer);
+
+ // Apply offset
+ if (!ApplyOffset(updatedTime)) {
+ NS_WARNING("Updated time overflows SMILTime, ignoring");
+ return;
+ }
+
+ // The timed element that owns the instance time does the updating so it can
+ // re-sort its array of instance times more efficiently
+ if (aInstanceTimeToUpdate.Time() != updatedTime || aObjectChanged) {
+ mOwner->UpdateInstanceTime(&aInstanceTimeToUpdate, updatedTime, mIsBegin);
+ }
+}
+
+void SMILTimeValueSpec::HandleDeletedInstanceTime(
+ SMILInstanceTime& aInstanceTime) {
+ mOwner->RemoveInstanceTime(&aInstanceTime, mIsBegin);
+}
+
+bool SMILTimeValueSpec::DependsOnBegin() const { return mParams.mSyncBegin; }
+
+void SMILTimeValueSpec::Traverse(
+ nsCycleCollectionTraversalCallback* aCallback) {
+ mReferencedElement.Traverse(aCallback);
+}
+
+void SMILTimeValueSpec::Unlink() {
+ UnregisterFromReferencedElement(mReferencedElement.get());
+ mReferencedElement.Unlink();
+}
+
+//----------------------------------------------------------------------
+// Implementation helpers
+
+void SMILTimeValueSpec::UpdateReferencedElement(Element* aFrom, Element* aTo) {
+ if (aFrom == aTo) return;
+
+ UnregisterFromReferencedElement(aFrom);
+
+ switch (mParams.mType) {
+ case SMILTimeValueSpecParams::SYNCBASE: {
+ SMILTimedElement* to = GetTimedElement(aTo);
+ if (to) {
+ to->AddDependent(*this);
+ }
+ } break;
+
+ case SMILTimeValueSpecParams::EVENT:
+ case SMILTimeValueSpecParams::REPEAT:
+ RegisterEventListener(aTo);
+ break;
+
+ default:
+ // not a referencing-type
+ break;
+ }
+}
+
+void SMILTimeValueSpec::UnregisterFromReferencedElement(Element* aElement) {
+ if (!aElement) return;
+
+ if (mParams.mType == SMILTimeValueSpecParams::SYNCBASE) {
+ SMILTimedElement* timedElement = GetTimedElement(aElement);
+ if (timedElement) {
+ timedElement->RemoveDependent(*this);
+ }
+ mOwner->RemoveInstanceTimesForCreator(this, mIsBegin);
+ } else if (IsEventBased()) {
+ UnregisterEventListener(aElement);
+ }
+}
+
+SMILTimedElement* SMILTimeValueSpec::GetTimedElement(Element* aElement) {
+ nsCOMPtr<SVGAnimationElement> animationElement = do_QueryInterface(aElement);
+ return animationElement ? &animationElement->TimedElement() : nullptr;
+}
+
+// Indicates whether we're allowed to register an event-listener
+// when scripting is disabled.
+bool SMILTimeValueSpec::IsWhitelistedEvent() {
+ // The category of (SMIL-specific) "repeat(n)" events are allowed.
+ if (mParams.mType == SMILTimeValueSpecParams::REPEAT) {
+ return true;
+ }
+
+ // A specific list of other SMIL-related events are allowed, too.
+ if (mParams.mType == SMILTimeValueSpecParams::EVENT &&
+ (mParams.mEventSymbol == nsGkAtoms::repeat ||
+ mParams.mEventSymbol == nsGkAtoms::repeatEvent ||
+ mParams.mEventSymbol == nsGkAtoms::beginEvent ||
+ mParams.mEventSymbol == nsGkAtoms::endEvent)) {
+ return true;
+ }
+
+ return false;
+}
+
+void SMILTimeValueSpec::RegisterEventListener(Element* aTarget) {
+ MOZ_ASSERT(IsEventBased(),
+ "Attempting to register event-listener for unexpected "
+ "SMILTimeValueSpec type");
+ MOZ_ASSERT(mParams.mEventSymbol,
+ "Attempting to register event-listener but there is no event "
+ "name");
+
+ if (!aTarget) return;
+
+ // When script is disabled, only allow registration for whitelisted events.
+ if (!aTarget->GetOwnerDocument()->IsScriptEnabled() &&
+ !IsWhitelistedEvent()) {
+ return;
+ }
+
+ if (!mEventListener) {
+ mEventListener = new EventListener(this);
+ }
+
+ EventListenerManager* elm = aTarget->GetOrCreateListenerManager();
+ if (!elm) {
+ return;
+ }
+
+ elm->AddEventListenerByType(mEventListener,
+ nsDependentAtomString(mParams.mEventSymbol),
+ AllEventsAtSystemGroupBubble());
+}
+
+void SMILTimeValueSpec::UnregisterEventListener(Element* aTarget) {
+ if (!aTarget || !mEventListener) {
+ return;
+ }
+
+ EventListenerManager* elm = aTarget->GetOrCreateListenerManager();
+ if (!elm) {
+ return;
+ }
+
+ elm->RemoveEventListenerByType(mEventListener,
+ nsDependentAtomString(mParams.mEventSymbol),
+ AllEventsAtSystemGroupBubble());
+}
+
+void SMILTimeValueSpec::HandleEvent(Event* aEvent) {
+ MOZ_ASSERT(mEventListener, "Got event without an event listener");
+ MOZ_ASSERT(IsEventBased(), "Got event for non-event SMILTimeValueSpec");
+ MOZ_ASSERT(aEvent, "No event supplied");
+
+ // XXX In the long run we should get the time from the event itself which will
+ // store the time in global document time which we'll need to convert to our
+ // time container
+ SMILTimeContainer* container = mOwner->GetTimeContainer();
+ if (!container) return;
+
+ if (mParams.mType == SMILTimeValueSpecParams::REPEAT &&
+ !CheckRepeatEventDetail(aEvent)) {
+ return;
+ }
+
+ SMILTime currentTime = container->GetCurrentTimeAsSMILTime();
+ SMILTimeValue newTime(currentTime);
+ if (!ApplyOffset(newTime)) {
+ NS_WARNING("New time generated from event overflows SMILTime, ignoring");
+ return;
+ }
+
+ RefPtr<SMILInstanceTime> newInstance =
+ new SMILInstanceTime(newTime, SMILInstanceTime::SOURCE_EVENT);
+ mOwner->AddInstanceTime(newInstance, mIsBegin);
+}
+
+bool SMILTimeValueSpec::CheckRepeatEventDetail(Event* aEvent) {
+ TimeEvent* timeEvent = aEvent->AsTimeEvent();
+ if (!timeEvent) {
+ NS_WARNING("Received a repeat event that was not a DOMTimeEvent");
+ return false;
+ }
+
+ int32_t detail = timeEvent->Detail();
+ return detail > 0 && (uint32_t)detail == mParams.mRepeatIteration;
+}
+
+SMILTimeValue SMILTimeValueSpec::ConvertBetweenTimeContainers(
+ const SMILTimeValue& aSrcTime, const SMILTimeContainer* aSrcContainer) {
+ // If the source time is either indefinite or unresolved the result is going
+ // to be the same
+ if (!aSrcTime.IsDefinite()) return aSrcTime;
+
+ // Convert from source time container to our parent time container
+ const SMILTimeContainer* dstContainer = mOwner->GetTimeContainer();
+ if (dstContainer == aSrcContainer) return aSrcTime;
+
+ // If one of the elements is not attached to a time container then we can't do
+ // any meaningful conversion
+ if (!aSrcContainer || !dstContainer) return SMILTimeValue(); // unresolved
+
+ SMILTimeValue docTime =
+ aSrcContainer->ContainerToParentTime(aSrcTime.GetMillis());
+
+ if (docTime.IsIndefinite())
+ // This will happen if the source container is paused and we have a future
+ // time. Just return the indefinite time.
+ return docTime;
+
+ MOZ_ASSERT(docTime.IsDefinite(),
+ "ContainerToParentTime gave us an unresolved or indefinite time");
+
+ return dstContainer->ParentToContainerTime(docTime.GetMillis());
+}
+
+bool SMILTimeValueSpec::ApplyOffset(SMILTimeValue& aTime) const {
+ // indefinite + offset = indefinite. Likewise for unresolved times.
+ if (!aTime.IsDefinite()) {
+ return true;
+ }
+
+ double resultAsDouble =
+ (double)aTime.GetMillis() + mParams.mOffset.GetMillis();
+ if (resultAsDouble > double(std::numeric_limits<SMILTime>::max()) ||
+ resultAsDouble < double(std::numeric_limits<SMILTime>::min())) {
+ return false;
+ }
+ aTime.SetMillis(aTime.GetMillis() + mParams.mOffset.GetMillis());
+ return true;
+}
+
+} // namespace mozilla