Bug 527270: Implement SMIL TimeEvents. r=dholbert,smaug; sr=roc; a=blocking-betaN

This commit is contained in:
Brian Birtles 2010-07-31 16:02:52 +09:00
parent 8662f7a74d
commit 8b6e3c3cc7
23 changed files with 766 additions and 26 deletions

View File

@ -157,6 +157,7 @@ enum EventNameType {
EventNameType_XUL = 0x0002,
EventNameType_SVGGraphic = 0x0004, // svg graphic elements
EventNameType_SVGSVG = 0x0008, // the svg element
EventNameType_SMIL = 0x0016, // smil elements
EventNameType_HTMLXUL = 0x0003,
EventNameType_All = 0xFFFF

View File

@ -575,6 +575,14 @@ nsContentUtils::InitializeEventTable() {
// This is a bit hackish, but SVG's event names are weird.
{ nsGkAtoms::onzoom, NS_SVG_ZOOM, EventNameType_SVGSVG, NS_EVENT_NULL },
#endif // MOZ_SVG
#ifdef MOZ_SMIL
{ nsGkAtoms::onbegin, NS_SMIL_BEGIN, EventNameType_SMIL, NS_EVENT_NULL },
{ nsGkAtoms::onbeginEvent, NS_SMIL_BEGIN, EventNameType_None, NS_SMIL_TIME_EVENT },
{ nsGkAtoms::onend, NS_SMIL_END, EventNameType_SMIL, NS_EVENT_NULL },
{ nsGkAtoms::onendEvent, NS_SMIL_END, EventNameType_None, NS_SMIL_TIME_EVENT },
{ nsGkAtoms::onrepeat, NS_SMIL_REPEAT, EventNameType_SMIL, NS_EVENT_NULL },
{ nsGkAtoms::onrepeatEvent, NS_SMIL_REPEAT, EventNameType_None, NS_SMIL_TIME_EVENT },
#endif // MOZ_SMIL
#ifdef MOZ_MEDIA
{ nsGkAtoms::onloadstart, NS_LOADSTART, EventNameType_HTML, NS_EVENT_NULL },
{ nsGkAtoms::onprogress, NS_PROGRESS, EventNameType_HTML, NS_EVENT_NULL },

View File

@ -1309,6 +1309,12 @@ GK_ATOM(keyPoints, "keyPoints")
GK_ATOM(keySplines, "keySplines")
GK_ATOM(keyTimes, "keyTimes")
GK_ATOM(mozAnimateMotionDummyAttr, "_mozAnimateMotionDummyAttr")
GK_ATOM(onbegin, "onbegin")
GK_ATOM(onbeginEvent, "onbeginEvent")
GK_ATOM(onend, "onend")
GK_ATOM(onendEvent, "onendEvent")
GK_ATOM(onrepeat, "onrepeat")
GK_ATOM(onrepeatEvent, "onrepeatEvent")
GK_ATOM(repeatCount, "repeatCount")
GK_ATOM(repeatDur, "repeatDur")
GK_ATOM(restart, "restart")

View File

@ -103,6 +103,10 @@ NS_NewDOMSVGEvent(nsIDOMEvent** aResult, nsPresContext* aPresContext, class nsEv
nsresult
NS_NewDOMSVGZoomEvent(nsIDOMEvent** aResult, nsPresContext* aPresContext, class nsGUIEvent* aEvent);
#endif // MOZ_SVG
#ifdef MOZ_SMIL
nsresult
NS_NewDOMTimeEvent(nsIDOMEvent** aResult, nsPresContext* aPresContext, class nsEvent* aEvent);
#endif // MOZ_SMIL
nsresult
NS_NewDOMXULCommandEvent(nsIDOMEvent** aResult, nsPresContext* aPresContext, class nsInputEvent* aEvent);
nsresult

View File

@ -81,6 +81,9 @@ static const char* const sEventNames[] = {
"SVGLoad", "SVGUnload", "SVGAbort", "SVGError", "SVGResize", "SVGScroll",
"SVGZoom",
#endif // MOZ_SVG
#ifdef MOZ_SMIL
"beginEvent", "endEvent", "repeatEvent",
#endif // MOZ_SMIL
#ifdef MOZ_MEDIA
"loadstart", "progress", "suspend", "emptied", "stalled", "play", "pause",
"loadedmetadata", "loadeddata", "waiting", "playing", "canplay",
@ -773,6 +776,15 @@ NS_METHOD nsDOMEvent::DuplicatePrivateData()
break;
}
#endif // MOZ_SVG
#ifdef MOZ_SMIL
case NS_SMIL_TIME_EVENT:
{
newEvent = new nsUIEvent(PR_FALSE, msg, 0);
NS_ENSURE_TRUE(newEvent, NS_ERROR_OUT_OF_MEMORY);
newEvent->eventStructType = NS_SMIL_TIME_EVENT;
break;
}
#endif // MOZ_SMIL
case NS_SIMPLE_GESTURE_EVENT:
{
nsSimpleGestureEvent* oldSimpleGestureEvent = static_cast<nsSimpleGestureEvent*>(mEvent);
@ -1225,6 +1237,14 @@ const char* nsDOMEvent::GetEventName(PRUint32 aEventType)
case NS_SVG_ZOOM:
return sEventNames[eDOMEvents_SVGZoom];
#endif // MOZ_SVG
#ifdef MOZ_SMIL
case NS_SMIL_BEGIN:
return sEventNames[eDOMEvents_beginEvent];
case NS_SMIL_END:
return sEventNames[eDOMEvents_endEvent];
case NS_SMIL_REPEAT:
return sEventNames[eDOMEvents_repeatEvent];
#endif // MOZ_SMIL
#ifdef MOZ_MEDIA
case NS_LOADSTART:
return sEventNames[eDOMEvents_loadstart];

View File

@ -142,6 +142,11 @@ public:
eDOMEvents_SVGScroll,
eDOMEvents_SVGZoom,
#endif // MOZ_SVG
#ifdef MOZ_SMIL
eDOMEvents_beginEvent,
eDOMEvents_endEvent,
eDOMEvents_repeatEvent,
#endif // MOZ_SMIL
#ifdef MOZ_MEDIA
eDOMEvents_loadstart,
eDOMEvents_progress,

View File

@ -742,6 +742,10 @@ nsEventDispatcher::CreateEvent(nsPresContext* aPresContext,
return NS_NewDOMSVGZoomEvent(aDOMEvent, aPresContext,
static_cast<nsGUIEvent*>(aEvent));
#endif // MOZ_SVG
#ifdef MOZ_SMIL
case NS_SMIL_TIME_EVENT:
return NS_NewDOMTimeEvent(aDOMEvent, aPresContext, aEvent);
#endif // MOZ_SMIL
case NS_COMMAND_EVENT:
return NS_NewDOMCommandEvent(aDOMEvent, aPresContext,
@ -797,6 +801,11 @@ nsEventDispatcher::CreateEvent(nsPresContext* aPresContext,
aEventType.LowerCaseEqualsLiteral("svgzoomevents"))
return NS_NewDOMSVGZoomEvent(aDOMEvent, aPresContext, nsnull);
#endif // MOZ_SVG
#ifdef MOZ_SMIL
if (aEventType.LowerCaseEqualsLiteral("timeevent") ||
aEventType.LowerCaseEqualsLiteral("timeevents"))
return NS_NewDOMTimeEvent(aDOMEvent, aPresContext, nsnull);
#endif // MOZ_SMIL
if (aEventType.LowerCaseEqualsLiteral("xulcommandevent") ||
aEventType.LowerCaseEqualsLiteral("xulcommandevents"))
return NS_NewDOMXULCommandEvent(aDOMEvent, aPresContext, nsnull);

View File

@ -983,6 +983,14 @@ nsEventListenerManager::CompileEventHandlerInternal(nsIScriptContext *aContext,
else if (aName == nsGkAtoms::onSVGZoom)
attrName = nsGkAtoms::onzoom;
#endif // MOZ_SVG
#ifdef MOZ_SMIL
else if (aName == nsGkAtoms::onbeginEvent)
attrName = nsGkAtoms::onbegin;
else if (aName == nsGkAtoms::onrepeatEvent)
attrName = nsGkAtoms::onrepeat;
else if (aName == nsGkAtoms::onendEvent)
attrName = nsGkAtoms::onend;
#endif // MOZ_SMIL
content->GetAttr(kNameSpaceID_None, attrName, handlerBody);

View File

@ -54,6 +54,7 @@ EXPORTS = nsSMILKeySpline.h
ifdef MOZ_SMIL
CPPSRCS += \
nsDOMTimeEvent.cpp \
nsSMILAnimationController.cpp \
nsSMILAnimationFunction.cpp \
nsSMILCompositor.cpp \
@ -105,6 +106,7 @@ EXPORTS += \
INCLUDES += \
-I$(srcdir)/../base/src \
-I$(srcdir)/../../layout/style \
-I$(srcdir)/../events/src \
$(NULL)
endif # MOZ_SMIL

View File

@ -0,0 +1,130 @@
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* ***** BEGIN LICENSE BLOCK *****
* Version: MPL 1.1/GPL 2.0/LGPL 2.1
*
* The contents of this file are subject to the Mozilla Public License Version
* 1.1 (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
* http://www.mozilla.org/MPL/
*
* Software distributed under the License is distributed on an "AS IS" basis,
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
* for the specific language governing rights and limitations under the
* License.
*
* The Original Code is the Mozilla SMIL Module.
*
* The Initial Developer of the Original Code is Mozilla Foundation.
* Portions created by the Initial Developer are Copyright (C) 2010
* the Initial Developer. All Rights Reserved.
*
* Contributor(s):
* Brian Birtles <birtles@gmail.com>
*
* Alternatively, the contents of this file may be used under the terms of
* either the GNU General Public License Version 2 or later (the "GPL"), or
* the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
* in which case the provisions of the GPL or the LGPL are applicable instead
* of those above. If you wish to allow use of your version of this file only
* under the terms of either the GPL or the LGPL, and not to allow others to
* use your version of this file under the terms of the MPL, indicate your
* decision by deleting the provisions above and replace them with the notice
* and other provisions required by the GPL or the LGPL. If you do not delete
* the provisions above, a recipient may use your version of this file under
* the terms of any one of the MPL, the GPL or the LGPL.
*
* ***** END LICENSE BLOCK ***** */
#include "nsDOMTimeEvent.h"
#include "nsGUIEvent.h"
#include "nsPresContext.h"
#include "nsIInterfaceRequestorUtils.h"
#include "nsIDOMAbstractView.h"
nsDOMTimeEvent::nsDOMTimeEvent(nsPresContext* aPresContext, nsEvent* aEvent)
: nsDOMEvent(aPresContext, aEvent ? aEvent : new nsUIEvent(PR_FALSE, 0, 0)),
mDetail(0)
{
if (aEvent) {
mEventIsInternal = PR_FALSE;
} else {
mEventIsInternal = PR_TRUE;
mEvent->eventStructType = NS_SMIL_TIME_EVENT;
}
if (mEvent->eventStructType == NS_SMIL_TIME_EVENT) {
nsUIEvent* event = static_cast<nsUIEvent*>(mEvent);
mDetail = event->detail;
}
mEvent->flags |= NS_EVENT_FLAG_CANT_BUBBLE |
NS_EVENT_FLAG_CANT_CANCEL;
if (mPresContext) {
nsCOMPtr<nsISupports> container = mPresContext->GetContainer();
if (container) {
nsCOMPtr<nsIDOMWindowInternal> window = do_GetInterface(container);
if (window) {
mView = do_QueryInterface(window);
}
}
}
}
NS_IMPL_CYCLE_COLLECTION_CLASS(nsDOMTimeEvent)
NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(nsDOMTimeEvent, nsDOMEvent)
NS_IMPL_CYCLE_COLLECTION_UNLINK_NSCOMPTR(mView)
NS_IMPL_CYCLE_COLLECTION_UNLINK_END
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(nsDOMTimeEvent, nsDOMEvent)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_NSCOMPTR(mView)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
NS_IMPL_ADDREF_INHERITED(nsDOMTimeEvent, nsDOMEvent)
NS_IMPL_RELEASE_INHERITED(nsDOMTimeEvent, nsDOMEvent)
DOMCI_DATA(TimeEvent, nsDOMTimeEvent)
NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION_INHERITED(nsDOMTimeEvent)
NS_INTERFACE_MAP_ENTRY(nsIDOMTimeEvent)
NS_DOM_INTERFACE_MAP_ENTRY_CLASSINFO(TimeEvent)
NS_INTERFACE_MAP_END_INHERITING(nsDOMEvent)
NS_IMETHODIMP
nsDOMTimeEvent::GetView(nsIDOMAbstractView** aView)
{
*aView = mView;
NS_IF_ADDREF(*aView);
return NS_OK;
}
NS_IMETHODIMP
nsDOMTimeEvent::GetDetail(PRInt32* aDetail)
{
*aDetail = mDetail;
return NS_OK;
}
NS_IMETHODIMP
nsDOMTimeEvent::InitTimeEvent(const nsAString& aTypeArg,
nsIDOMAbstractView* aViewArg,
PRInt32 aDetailArg)
{
nsresult rv = nsDOMEvent::InitEvent(aTypeArg, PR_FALSE /*doesn't bubble*/,
PR_FALSE /*can't cancel*/);
NS_ENSURE_SUCCESS(rv, rv);
mDetail = aDetailArg;
mView = aViewArg;
return NS_OK;
}
nsresult NS_NewDOMTimeEvent(nsIDOMEvent** aInstancePtrResult,
nsPresContext* aPresContext,
nsEvent* aEvent)
{
nsDOMTimeEvent* it = new nsDOMTimeEvent(aPresContext, aEvent);
return CallQueryInterface(it, aInstancePtrResult);
}

View File

@ -0,0 +1,65 @@
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* ***** BEGIN LICENSE BLOCK *****
* Version: MPL 1.1/GPL 2.0/LGPL 2.1
*
* The contents of this file are subject to the Mozilla Public License Version
* 1.1 (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
* http://www.mozilla.org/MPL/
*
* Software distributed under the License is distributed on an "AS IS" basis,
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
* for the specific language governing rights and limitations under the
* License.
*
* The Original Code is the Mozilla SMIL Module.
*
* The Initial Developer of the Original Code is Mozilla Foundation.
* Portions created by the Initial Developer are Copyright (C) 2010
* the Initial Developer. All Rights Reserved.
*
* Contributor(s):
* Brian Birtles <birtles@gmail.com>
*
* Alternatively, the contents of this file may be used under the terms of
* either the GNU General Public License Version 2 or later (the "GPL"), or
* the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
* in which case the provisions of the GPL or the LGPL are applicable instead
* of those above. If you wish to allow use of your version of this file only
* under the terms of either the GPL or the LGPL, and not to allow others to
* use your version of this file under the terms of the MPL, indicate your
* decision by deleting the provisions above and replace them with the notice
* and other provisions required by the GPL or the LGPL. If you do not delete
* the provisions above, a recipient may use your version of this file under
* the terms of any one of the MPL, the GPL or the LGPL.
*
* ***** END LICENSE BLOCK ***** */
#ifndef NS_DOMTIMEEVENT_H_
#define NS_DOMTIMEEVENT_H_
#include "nsIDOMTimeEvent.h"
#include "nsDOMEvent.h"
class nsDOMTimeEvent : public nsDOMEvent,
public nsIDOMTimeEvent
{
public:
nsDOMTimeEvent(nsPresContext* aPresContext, nsEvent* aEvent);
// nsISupports interface:
NS_DECL_ISUPPORTS_INHERITED
NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(nsDOMTimeEvent, nsDOMEvent)
// nsIDOMTimeEvent interface:
NS_DECL_NSIDOMTIMEEVENT
// Forward to base class
NS_FORWARD_TO_NSDOMEVENT
private:
nsCOMPtr<nsIDOMAbstractView> mView;
PRInt32 mDetail;
};
#endif // NS_DOMTIMEEVENT_H_

View File

@ -43,15 +43,19 @@
#include "nsSMILParserUtils.h"
#include "nsSMILTimeContainer.h"
#include "nsGkAtoms.h"
#include "nsGUIEvent.h"
#include "nsEventDispatcher.h"
#include "nsReadableUtils.h"
#include "nsMathUtils.h"
#include "nsThreadUtils.h"
#include "nsIPresShell.h"
#include "prdtoa.h"
#include "plstr.h"
#include "prtime.h"
#include "nsString.h"
//----------------------------------------------------------------------
// Helper classes -- InstanceTimeComparator
// Helper class: InstanceTimeComparator
// Upon inserting an instance time into one of our instance time lists we assign
// it a serial number. This allows us to sort the instance times in such a way
@ -90,6 +94,43 @@ nsSMILTimedElement::InstanceTimeComparator::LessThan(
return cmp == 0 ? aElem1->Serial() < aElem2->Serial() : cmp < 0;
}
//----------------------------------------------------------------------
// Helper class: AsyncTimeEventRunner
namespace
{
class AsyncTimeEventRunner : public nsRunnable
{
protected:
nsRefPtr<nsIContent> mTarget;
PRUint32 mMsg;
PRInt32 mDetail;
public:
AsyncTimeEventRunner(nsIContent* aTarget, PRUint32 aMsg, PRInt32 aDetail)
: mTarget(aTarget), mMsg(aMsg), mDetail(aDetail)
{
}
NS_IMETHOD Run()
{
nsUIEvent event(PR_TRUE, mMsg, mDetail);
event.eventStructType = NS_SMIL_TIME_EVENT;
nsPresContext* context = nsnull;
nsIDocument* doc = mTarget->GetCurrentDoc();
if (doc) {
nsCOMPtr<nsIPresShell> shell = doc->GetShell();
if (shell) {
context = shell->GetPresContext();
}
}
return nsEventDispatcher::Dispatch(mTarget, context, &event);
}
};
}
//----------------------------------------------------------------------
// Templated helper functions
@ -150,6 +191,7 @@ nsSMILTimedElement::nsSMILTimedElement()
mInstanceSerialIndex(0),
mClient(nsnull),
mCurrentInterval(nsnull),
mCurrentRepeatIteration(0),
mPrevRegisteredMilestone(sMaxMilestone),
mElementState(STATE_STARTUP),
mSeekState(SEEK_NOT_SEEKING)
@ -506,20 +548,21 @@ nsSMILTimedElement::DoSampleAt(nsSMILTime aContainerTime, PRBool aEndOnly)
if (mCurrentInterval->Begin()->Time() <= sampleTime) {
mElementState = STATE_ACTIVE;
mCurrentInterval->FixBegin();
if (HasPlayed()) {
Reset(); // Apply restart behaviour
}
if (mClient) {
mClient->Activate(mCurrentInterval->Begin()->Time().GetMillis());
}
if (mSeekState == SEEK_NOT_SEEKING) {
FireTimeEventAsync(NS_SMIL_BEGIN, 0);
}
if (HasPlayed()) {
Reset(); // Apply restart behaviour
// The call to Reset() may mean that the end point of our current
// interval should be changed and so we should update the interval
// now. However, calling UpdateCurrentInterval could result in the
// interval getting deleted (perhaps through some web of syncbase
// dependencies) therefore we make updating the interval the last
// thing we do. There is no guarantee that mCurrentInterval.IsSet()
// is true after this.
// thing we do. There is no guarantee that mCurrentInterval is set
// after this.
UpdateCurrentInterval();
}
stateChanged = PR_TRUE;
@ -541,6 +584,10 @@ nsSMILTimedElement::DoSampleAt(nsSMILTime aContainerTime, PRBool aEndOnly)
mClient->Inactivate(mFillMode == FILL_FREEZE);
}
mCurrentInterval->FixEnd();
if (mSeekState == SEEK_NOT_SEEKING) {
FireTimeEventAsync(NS_SMIL_END, 0);
}
mCurrentRepeatIteration = 0;
mOldIntervals.AppendElement(mCurrentInterval.forget());
// We must update mOldIntervals before calling SampleFillValue
SampleFillValue();
@ -556,6 +603,19 @@ nsSMILTimedElement::DoSampleAt(nsSMILTime aContainerTime, PRBool aEndOnly)
"Sample time should not precede current interval");
nsSMILTime activeTime = aContainerTime - beginTime;
SampleSimpleTime(activeTime);
// We register our repeat times as milestones (except when we're
// seeking) so we should get a sample at exactly the time we repeat.
// (And even when we are seeking we want to update
// mCurrentRepeatIteration so we do that first before testing the seek
// state.)
PRUint32 prevRepeatIteration = mCurrentRepeatIteration;
if (ActiveTimeToSimpleTime(activeTime, mCurrentRepeatIteration)==0 &&
mCurrentRepeatIteration != prevRepeatIteration &&
mCurrentRepeatIteration &&
mSeekState == SEEK_NOT_SEEKING) {
FireTimeEventAsync(NS_SMIL_REPEAT,
static_cast<PRInt32>(mCurrentRepeatIteration));
}
}
}
break;
@ -607,6 +667,7 @@ nsSMILTimedElement::Rewind()
// Set the STARTUP state first so that if we get any callbacks we won't waste
// time recalculating the current interval
mElementState = STATE_STARTUP;
mCurrentRepeatIteration = 0;
// Clear the intervals and instance times except those instance times we can't
// regenerate (DOM calls etc.)
@ -1238,13 +1299,6 @@ nsSMILTimedElement::Reset()
void
nsSMILTimedElement::DoPostSeek()
{
// XXX When implementing TimeEvents we'll need to compare mElementState with
// mSeekState and dispatch events as follows:
// ACTIVE->INACTIVE: End event
// INACTIVE->ACTIVE: Begin event
// ACTIVE->ACTIVE: Nothing (even if they're different intervals)
// INACTIVE->INACTIVE: Nothing (even if we've skipped intervals)
// Finish backwards seek
if (mSeekState == SEEK_BACKWARD_FROM_INACTIVE ||
mSeekState == SEEK_BACKWARD_FROM_ACTIVE) {
@ -1266,6 +1320,34 @@ nsSMILTimedElement::DoPostSeek()
UpdateCurrentInterval();
}
// XXX
// Note that SMIL gives the very cryptic description:
// The associated time for the event is the document time before the seek.
// This action does not resolve any times in the instance times list for end
// times.
//
// The second sentence was added as a clarification in a SMIL 2.0 erratum.
// Presumably the intention is that we fire the event as implemented below but
// don't act on it. This makes sense at least for dependencies within the same
// time container. So we'll probably need to set a flag here to ensure we
// don't actually act on it when we implement event-based timing.
switch (mSeekState)
{
case SEEK_FORWARD_FROM_ACTIVE:
case SEEK_BACKWARD_FROM_ACTIVE:
if (mElementState != STATE_ACTIVE) {
FireTimeEventAsync(NS_SMIL_END, 0);
}
break;
case SEEK_FORWARD_FROM_INACTIVE:
case SEEK_BACKWARD_FROM_INACTIVE:
if (mElementState == STATE_ACTIVE) {
FireTimeEventAsync(NS_SMIL_BEGIN, 0);
}
break;
}
mSeekState = SEEK_NOT_SEEKING;
}
@ -1861,10 +1943,6 @@ nsSMILTimedElement::GetNextMilestone(nsSMILMilestone& aNextMilestone) const
{
// Return the next key moment in our lifetime.
//
// XXX Once we implement TimeEvents and event based timing we might need to
// include repeat times too, particularly if it's important to get them in
// order.
//
// XXX It may be possible in future to optimise this so that we only register
// for milestones if:
// a) We have time dependents, or
@ -1896,22 +1974,27 @@ nsSMILTimedElement::GetNextMilestone(nsSMILMilestone& aNextMilestone) const
case STATE_ACTIVE:
{
// XXX When we implement TimeEvents, we may need to consider what comes
// next: the interval end or an interval repeat.
// Work out what comes next: the interval end or the next repeat iteration
nsSMILTimeValue nextRepeat;
if (mSeekState == SEEK_NOT_SEEKING && mSimpleDur.IsResolved()) {
nextRepeat.SetMillis(mCurrentInterval->Begin()->Time().GetMillis() +
(mCurrentRepeatIteration + 1) * mSimpleDur.GetMillis());
}
nsSMILTimeValue nextMilestone =
NS_MIN(mCurrentInterval->End()->Time(), nextRepeat);
// Check for an early end
nsSMILInstanceTime* earlyEnd =
CheckForEarlyEnd(mCurrentInterval->End()->Time());
// Check for an early end before that time
nsSMILInstanceTime* earlyEnd = CheckForEarlyEnd(nextMilestone);
if (earlyEnd) {
aNextMilestone.mIsEnd = PR_TRUE;
aNextMilestone.mTime = earlyEnd->Time().GetMillis();
return PR_TRUE;
}
// Otherwise it's just the next interval end
if (mCurrentInterval->End()->Time().IsResolved()) {
aNextMilestone.mIsEnd = PR_TRUE;
aNextMilestone.mTime = mCurrentInterval->End()->Time().GetMillis();
// Apply the previously calculated milestone
if (nextMilestone.IsResolved()) {
aNextMilestone.mIsEnd = nextMilestone != nextRepeat;
aNextMilestone.mTime = nextMilestone.GetMillis();
return PR_TRUE;
}
@ -1958,6 +2041,17 @@ nsSMILTimedElement::NotifyChangedInterval()
mCurrentInterval->NotifyChanged(container);
}
void
nsSMILTimedElement::FireTimeEventAsync(PRUint32 aMsg, PRInt32 aDetail)
{
if (!mAnimationElement)
return;
nsCOMPtr<nsIRunnable> event =
new AsyncTimeEventRunner(&mAnimationElement->Content(), aMsg, aDetail);
NS_DispatchToMainThread(event, NS_DISPATCH_NORMAL);
}
const nsSMILInstanceTime*
nsSMILTimedElement::GetEffectiveBeginInstance() const
{

View File

@ -482,6 +482,7 @@ protected:
void NotifyNewInterval();
void NotifyChangedInterval();
void FireTimeEventAsync(PRUint32 aMsg, PRInt32 aDetail);
const nsSMILInstanceTime* GetEffectiveBeginInstance() const;
const nsSMILInterval* GetPreviousInterval() const;
PRBool HasPlayed() const { return !mOldIntervals.IsEmpty(); }
@ -539,6 +540,7 @@ protected:
nsSMILAnimationFunction* mClient;
nsAutoPtr<nsSMILInterval> mCurrentInterval;
IntervalList mOldIntervals;
PRUint32 mCurrentRepeatIteration;
nsSMILMilestone mPrevRegisteredMilestone;
static const nsSMILMilestone sMaxMilestone;
static const PRUint8 sMaxNumIntervals;

View File

@ -82,6 +82,7 @@ _TEST_FILES = \
test_smilSyncbaseTarget.xhtml \
test_smilSyncTransform.xhtml \
test_smilTextZoom.xhtml \
test_smilTimeEvents.xhtml \
test_smilTiming.xhtml \
test_smilTimingZeroIntervals.xhtml \
test_smilUpdatedInterval.xhtml \

View File

@ -0,0 +1,292 @@
<html xmlns="http://www.w3.org/1999/xhtml">
<!--
https://bugzilla.mozilla.org/show_bug.cgi?id=572270
-->
<head>
<title>Test TimeEvents dispatching</title>
<script type="text/javascript" src="/MochiKit/packed.js"></script>
<script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
</head>
<body>
<a target="_blank"
href="https://bugzilla.mozilla.org/show_bug.cgi?id=572270">Mozilla Bug
572270</a>
<p id="display"></p>
<div id="content" style="display: none">
<svg id="svg" xmlns="http://www.w3.org/2000/svg" width="100px" height="100px">
<g font-size="10px">
<circle cx="0" cy="0" r="15" fill="blue" id="circle"
onbegin="parentHandler(evt)" onrepeat="parentHandler(evt)"
onend="parentHandler(evt)">
<animate attributeName="cy" from="0" to="100" dur="60s" begin="2s"
id="anim" repeatCount="2"
onbegin="handleOnBegin(evt)" onrepeat="handleOnRepeat(evt)"
onend="handleOnEnd(evt)"/>
</circle>
</g>
</svg>
</div>
<pre id="test">
<script class="testbody" type="text/javascript">
<![CDATA[
/** Test SMIL TimeEvents dispatching **/
/* Global Variables */
const gTimeoutDur = 5000; // Time until we give up waiting for events in ms
var gSvg = document.getElementById("svg");
var gAnim = document.getElementById('anim');
var gCircle = document.getElementById('circle');
var gExpectedEvents = new Array();
var gTimeoutID;
var gTestStages =
[ testPlaybackBegin,
testPlaybackRepeat,
testPlaybackEnd,
testForwardsSeekToMid,
testForwardsSeekToNextInterval,
testForwardsSeekPastEnd,
testBackwardsSeekToMid,
testBackwardsSeekToStart,
testCreateEvent,
testRegistration
];
SimpleTest.waitForExplicitFinish();
function continueTest()
{
if (gTestStages.length == 0) {
SimpleTest.finish();
return;
}
gTestStages.shift()();
}
function testPlaybackBegin()
{
// Test events are dispatched through normal playback
gSvg.pauseAnimations();
gSvg.setCurrentTime(1.99);
gExpectedEvents.push("beginEvent", "beginEvent"); // Two registered handlers
gTimeoutID = setTimeout(timeoutFail, gTimeoutDur);
gSvg.unpauseAnimations();
}
function testPlaybackRepeat()
{
gSvg.pauseAnimations();
gSvg.setCurrentTime(61.99);
gExpectedEvents.push(["repeatEvent", 1], ["repeatEvent", 1]);
gTimeoutID = setTimeout(timeoutFail, gTimeoutDur);
gSvg.unpauseAnimations();
}
function testPlaybackEnd()
{
gSvg.pauseAnimations();
gSvg.setCurrentTime(121.99);
gExpectedEvents.push("endEvent", "endEvent");
gTimeoutID = setTimeout(timeoutFail, gTimeoutDur);
gSvg.unpauseAnimations();
}
function testForwardsSeekToMid()
{
gSvg.pauseAnimations();
// Set animation parameters to something that repeats a lot
gSvg.setCurrentTime(0);
gAnim.setAttribute('begin', '2s; 102s');
gAnim.setAttribute('dur', '15s');
gAnim.setAttribute('repeatCount', '6');
gSvg.setCurrentTime(46.99);
gExpectedEvents.push("beginEvent", "beginEvent",
["repeatEvent", 3], ["repeatEvent", 3]);
gTimeoutID = setTimeout(timeoutFail, gTimeoutDur);
gSvg.unpauseAnimations();
}
function testForwardsSeekToNextInterval()
{
// Skip to next interval -- we shouldn't get any additional begin or end
// events in between
gSvg.pauseAnimations();
gSvg.setCurrentTime(131.99);
gExpectedEvents.push(["repeatEvent", 2], ["repeatEvent", 2]);
gTimeoutID = setTimeout(timeoutFail, gTimeoutDur);
gSvg.unpauseAnimations();
}
function testForwardsSeekPastEnd()
{
gSvg.pauseAnimations();
gSvg.setCurrentTime(200);
gExpectedEvents.push("endEvent", "endEvent");
gTimeoutID = setTimeout(timeoutFail, gTimeoutDur);
gSvg.unpauseAnimations();
}
function testBackwardsSeekToMid()
{
gSvg.pauseAnimations();
gSvg.setCurrentTime(31.99);
gExpectedEvents.push("beginEvent", "beginEvent",
["repeatEvent", 2], ["repeatEvent", 2]);
gTimeoutID = setTimeout(timeoutFail, gTimeoutDur);
gSvg.unpauseAnimations();
}
function testBackwardsSeekToStart()
{
gSvg.pauseAnimations();
gExpectedEvents.push("endEvent", "endEvent");
gTimeoutID = setTimeout(timeoutFail, gTimeoutDur);
gSvg.setCurrentTime(0);
}
function testCreateEvent()
{
var evt;
try {
evt = document.createEvent("TimeEvents");
} catch (e) {
ok(false, "Failed to create TimeEvent via script: " + e);
return;
}
evt.initTimeEvent("repeatEvent", null, 3);
is(evt.type, "repeatEvent", "Unexpected type for user-generated event");
is(evt.detail, 3, "Unexpected detail for user-generated event");
is(evt.target, null, "Unexpected event target");
is(evt.currentTarget, null, "Unexpected event current target");
is(evt.eventPhase, evt.AT_TARGET);
is(evt.bubbles, false, "Event should not bubble");
is(evt.cancelable, false, "Event should not be cancelable");
is(evt.view, null, "Event view should be null");
// Prior to dispatch we should be able to change the event type
evt.initTimeEvent("beginEvent", document.defaultView, 0);
is(evt.type, "beginEvent", "Failed to update event type before dispatch");
is(evt.detail, 0, "Failed to update event detail before dispatch");
is(evt.view, document.defaultView, "Event view should be set");
// But not directly as it's readonly
try {
evt.type = "endEvent";
} catch(e) { }
is(evt.type, "beginEvent", "Event type should be readonly");
// Likewise the detail field should be readonly
try {
evt.detail = "8";
} catch(e) { }
is(evt.detail, 0, "Event detail should be readonly");
// Dispatch
gExpectedEvents.push("beginEvent", "beginEvent");
gTimeoutID = setTimeout(timeoutFail, gTimeoutDur);
gAnim.dispatchEvent(evt);
}
function testRegistration()
{
gSvg.pauseAnimations();
// Reset animation to something simple
gSvg.setCurrentTime(0);
gAnim.setAttribute('begin', '2s');
gAnim.setAttribute('dur', '50s');
// Remove attribute handler
gAnim.removeAttribute('onbegin');
// Add bogus handlers
gAnim.setAttribute('onbeginElement', 'handleOnBegin(evt)');
gAnim.addEventListener("begin", handleOnBegin, false);
gAnim.addEventListener("onbegin", handleOnBegin, false);
// We should now have just one legitimate listener: the one registered to
// handle 'beginElement'
gSvg.setCurrentTime(1.99);
gExpectedEvents.push("beginEvent");
gTimeoutID = setTimeout(timeoutFail, gTimeoutDur);
gSvg.unpauseAnimations();
}
function handleOnBegin(evt)
{
is(evt.type, "beginEvent", "Expected begin event but got " + evt.type);
checkExpectedEvent(evt);
}
function handleOnRepeat(evt)
{
is(evt.type, "repeatEvent", "Expected repeat event but got " + evt.type);
checkExpectedEvent(evt);
}
function handleOnEnd(evt)
{
is(evt.type, "endEvent", "Expected end event but got " + evt.type);
checkExpectedEvent(evt);
}
function sanityCheckEvent(evt)
{
is(evt.target, gAnim, "Unexpected event target");
is(evt.currentTarget, gAnim, "Unexpected event current target");
is(evt.eventPhase, evt.AT_TARGET);
is(evt.bubbles, false, "Event should not bubble");
is(evt.cancelable, false, "Event should not be cancelable");
// Currently we set event timestamps to 0 which DOM 2 allows. This isn't
// correct since SMIL uses this field to avoid synchronisation slew but first
// we need to fix bug 323039 and bug 77992 which involve storing timestamps as
// 64-bit integers and deciding whether those timestamps should be related to
// the epoch or system start.
is(evt.timeStamp, 0, "Event timeStamp should be 0");
ok(evt.view !== null, "Event view not set");
}
function checkExpectedEvent(evt)
{
sanityCheckEvent(evt);
ok(gExpectedEvents.length > 0, "Unexpected event: " + evt.type);
if (gExpectedEvents.length == 0) return;
var expected = gExpectedEvents.shift();
if (typeof expected == 'string') {
is(evt.type, expected, "Unexpected event type");
is(evt.detail, 0, "Unexpected event detail (repeat iteration)");
} else {
is(evt.type, expected[0], "Unexpected event type");
is(evt.detail, expected[1], "Unexpected event detail (repeat iteration)");
}
if (gExpectedEvents.length == 0) {
clearTimeout(gTimeoutID);
continueTest();
}
}
function timeoutFail()
{
ok(false, "Timed out waiting for events: " + gExpectedEvents.join(', '));
SimpleTest.finish(); // No point continuing
}
function parentHandler(evt)
{
ok(false, "Handler on parent got called but event shouldn't bubble.");
}
window.addEventListener("load", continueTest, false);
// Register event handlers *in addition* to the handlers already added via the
// "onbegin", "onend", "onrepeat" attributes on the <animate> and <circle>
// elements. This is to test that both types of registration work.
gAnim.addEventListener("beginEvent", handleOnBegin, false);
gAnim.addEventListener("repeatEvent", handleOnRepeat, false);
gAnim.addEventListener("endEvent", handleOnEnd, false);
gCircle.addEventListener("beginEvent", parentHandler, false);
]]>
</script>
</pre>
</body>
</html>

View File

@ -476,6 +476,12 @@ nsSVGAnimationElement::EndElementAt(float offset)
return NS_OK;
}
PRBool
nsSVGAnimationElement::IsEventName(nsIAtom* aName)
{
return nsContentUtils::IsEventAttributeName(aName, EventNameType_SMIL);
}
void
nsSVGAnimationElement::UpdateHrefTarget(nsIContent* aNodeForContext,
const nsAString& aHrefStr)

View File

@ -98,6 +98,9 @@ public:
virtual nsSMILTimeContainer* GetTimeContainer();
protected:
// nsSVGElement overrides
PRBool IsEventName(nsIAtom* aName);
void UpdateHrefTarget(nsIContent* aNodeForContext,
const nsAString& aHrefStr);

View File

@ -1364,6 +1364,14 @@ nsIAtom* nsSVGElement::GetEventNameForAttr(nsIAtom* aAttr)
return nsGkAtoms::onSVGScroll;
if (aAttr == nsGkAtoms::onzoom)
return nsGkAtoms::onSVGZoom;
#ifdef MOZ_SMIL
if (aAttr == nsGkAtoms::onbegin)
return nsGkAtoms::onbeginEvent;
if (aAttr == nsGkAtoms::onrepeat)
return nsGkAtoms::onrepeatEvent;
if (aAttr == nsGkAtoms::onend)
return nsGkAtoms::onendEvent;
#endif // MOZ_SMIL
return aAttr;
}

View File

@ -377,6 +377,7 @@
#include "nsIDOMSVGSetElement.h"
#include "nsIDOMSVGAnimationElement.h"
#include "nsIDOMElementTimeControl.h"
#include "nsIDOMTimeEvent.h"
#endif // MOZ_SMIL
#include "nsIDOMSVGAnimTransformList.h"
#include "nsIDOMSVGCircleElement.h"
@ -1003,6 +1004,8 @@ static nsDOMClassInfoData sClassInfoData[] = {
ELEMENT_SCRIPTABLE_FLAGS)
NS_DEFINE_CLASSINFO_DATA(SVGSetElement, nsElementSH,
ELEMENT_SCRIPTABLE_FLAGS)
NS_DEFINE_CLASSINFO_DATA(TimeEvent, nsDOMGenericSH,
DOM_DEFAULT_SCRIPTABLE_FLAGS)
#endif // MOZ_SMIL
NS_DEFINE_CLASSINFO_DATA(SVGCircleElement, nsElementSH,
ELEMENT_SCRIPTABLE_FLAGS)
@ -3041,6 +3044,10 @@ nsDOMClassInfo::Init()
DOM_CLASSINFO_SVG_ELEMENT_MAP_ENTRIES
DOM_CLASSINFO_MAP_END
DOM_CLASSINFO_MAP_BEGIN(TimeEvent, nsIDOMTimeEvent)
DOM_CLASSINFO_MAP_ENTRY(nsIDOMTimeEvent)
DOM_CLASSINFO_EVENT_MAP_ENTRIES
DOM_CLASSINFO_MAP_END
#endif // MOZ_SMIL
DOM_CLASSINFO_MAP_BEGIN(SVGCircleElement, nsIDOMSVGCircleElement)

View File

@ -240,6 +240,7 @@ DOMCI_CLASS(SVGAnimateTransformElement)
DOMCI_CLASS(SVGAnimateMotionElement)
DOMCI_CLASS(SVGMpathElement)
DOMCI_CLASS(SVGSetElement)
DOMCI_CLASS(TimeEvent)
#endif // MOZ_SMIL
DOMCI_CLASS(SVGCircleElement)
DOMCI_CLASS(SVGClipPathElement)

View File

@ -48,6 +48,7 @@ XPIDL_MODULE = dom_smil
XPIDLSRCS = \
nsIDOMElementTimeControl.idl \
nsIDOMTimeEvent.idl \
$(NULL)
include $(topsrcdir)/config/rules.mk

View File

@ -0,0 +1,57 @@
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* ***** BEGIN LICENSE BLOCK *****
* Version: MPL 1.1/GPL 2.0/LGPL 2.1
*
* The contents of this file are subject to the Mozilla Public License Version
* 1.1 (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
* http://www.mozilla.org/MPL/
*
* Software distributed under the License is distributed on an "AS IS" basis,
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
* for the specific language governing rights and limitations under the
* License.
*
* The Original Code is the Mozilla SMIL Module.
*
* The Initial Developer of the Original Code is Mozilla Foundation.
* Portions created by the Initial Developer are Copyright (C) 2010
* the Initial Developer. All Rights Reserved.
*
* Contributor(s):
* Brian Birtles <birtles@gmail.com>
*
* Alternatively, the contents of this file may be used under the terms of
* either the GNU General Public License Version 2 or later (the "GPL"), or
* the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
* in which case the provisions of the GPL or the LGPL are applicable instead
* of those above. If you wish to allow use of your version of this file only
* under the terms of either the GPL or the LGPL, and not to allow others to
* use your version of this file under the terms of the MPL, indicate your
* decision by deleting the provisions above and replace them with the notice
* and other provisions required by the GPL or the LGPL. If you do not delete
* the provisions above, a recipient may use your version of this file under
* the terms of any one of the MPL, the GPL or the LGPL.
*
* ***** END LICENSE BLOCK ***** */
#include "nsIDOMEvent.idl"
/**
* The SMIL TimeEvent interface.
*
* For more information please refer to:
* http://www.w3.org/TR/SMIL/smil-timing.html#Events-TimeEvent
* http://www.w3.org/TR/SVG/animate.html#InterfaceTimeEvent
*/
[scriptable, uuid(0d309c26-ddbb-44cb-9af1-3008972349e3)]
interface nsIDOMTimeEvent : nsIDOMEvent
{
readonly attribute long detail;
readonly attribute nsIDOMAbstractView view;
void initTimeEvent(in DOMString typeArg,
in nsIDOMAbstractView viewArg,
in long detailArg);
};

View File

@ -101,6 +101,9 @@ class nsHashKey;
#define NS_SVG_EVENT 30
#define NS_SVGZOOM_EVENT 31
#endif // MOZ_SVG
#ifdef MOZ_SMIL
#define NS_SMIL_TIME_EVENT 32
#endif // MOZ_SMIL
#define NS_QUERY_CONTENT_EVENT 33
@ -460,6 +463,13 @@ class nsHashKey;
#define NS_TRANSITION_EVENT_START 4200
#define NS_TRANSITION_END (NS_TRANSITION_EVENT_START)
#ifdef MOZ_SMIL
#define NS_SMIL_TIME_EVENT_START 4300
#define NS_SMIL_BEGIN (NS_SMIL_TIME_EVENT_START)
#define NS_SMIL_END (NS_SMIL_TIME_EVENT_START + 1)
#define NS_SMIL_REPEAT (NS_SMIL_TIME_EVENT_START + 2)
#endif // MOZ_SMIL
/**
* Return status for event processors, nsEventStatus, is defined in
* nsEvent.h.