mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-11-28 07:13:20 +00:00
971b645fe3
Differential Revision: https://phabricator.services.mozilla.com/D87865
371 lines
12 KiB
C++
371 lines
12 KiB
C++
/* -*- 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
|