Bug 485157: SMIL event timing, part 6 repeat timing, r=dholbert, sr=roc, a=roc
authorBrian Birtles <birtles@gmail.com>
Wed, 18 Aug 2010 19:20:24 +0900
changeset 50807 32b5f913f72c
parent 50806 fd27d7619098
child 50808 ca457b5758e0
push id15162
push userbbirtles@mozilla.com
push date2010-08-18 10:24 +0000
treeherdermozilla-central@ca457b5758e0 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersdholbert, roc, roc
bugs485157
milestone2.0b5pre
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 485157: SMIL event timing, part 6 repeat timing, r=dholbert, sr=roc, a=roc
content/base/src/nsGkAtomList.h
content/smil/nsSMILParserUtils.cpp
content/smil/nsSMILTimeValueSpec.cpp
content/smil/nsSMILTimeValueSpec.h
content/smil/test/Makefile.in
content/smil/test/test_smilRepeatTiming.xhtml
--- a/content/base/src/nsGkAtomList.h
+++ b/content/base/src/nsGkAtomList.h
@@ -1315,16 +1315,17 @@ GK_ATOM(mozAnimateMotionDummyAttr, "_moz
 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(repeatEvent, "repeatEvent")
 GK_ATOM(restart, "restart")
 GK_ATOM(to, "to")
 GK_ATOM(XML, "XML")
 #endif
 
 #ifdef MOZ_MATHML
 // internal MathML attributes: different from columnalign_, columnlines_,
 // fontstyle_, rowalign_ and rowlines_
--- a/content/smil/nsSMILParserUtils.cpp
+++ b/content/smil/nsSMILParserUtils.cpp
@@ -55,17 +55,19 @@
 
 namespace {
 
 const PRUint32 MSEC_PER_SEC  = 1000;
 const PRUint32 MSEC_PER_MIN  = 1000 * 60;
 const PRUint32 MSEC_PER_HOUR = 1000 * 60 * 60;
 const PRInt32  DECIMAL_BASE  = 10;
 
-#define ACCESSKEY_PREFIX NS_LITERAL_STRING("accesskey(")
+// XXX SVG/SMIL Animation use 'accessKey' whilst SMIL3 uses 'accesskey'
+//     We should allow both
+#define ACCESSKEY_PREFIX NS_LITERAL_STRING("accessKey(")
 #define REPEAT_PREFIX    NS_LITERAL_STRING("repeat(")
 #define WALLCLOCK_PREFIX NS_LITERAL_STRING("wallclock(")
 
 // NS_IS_SPACE relies on isspace which may return true for \xB and \xC but
 // SMILANIM does not consider these characters to be whitespace.
 inline PRBool
 IsSpace(const PRUnichar c)
 {
@@ -358,16 +360,19 @@ ParseElementBaseTimeValueSpec(const nsAS
   // The spec will probably look something like one of these
   //
   // element-name.begin
   // element-name.event-name
   // event-name
   // element-name.repeat(3)
   // event\.name
   //
+  // Technically `repeat(3)' is permitted but the behaviour in this case is not
+  // defined (for SMIL Animation) so we don't support it here.
+  //
 
   const PRUnichar* tokenStart = aSpec.BeginReading();
   const PRUnichar* tokenEnd = GetTokenEnd(aSpec, PR_TRUE);
   nsAutoString token(Substring(tokenStart, tokenEnd));
   Unescape(token);
 
   if (token.IsEmpty())
     return NS_ERROR_FAILURE;
--- a/content/smil/nsSMILTimeValueSpec.cpp
+++ b/content/smil/nsSMILTimeValueSpec.cpp
@@ -42,16 +42,17 @@
 #include "nsSMILTimedElement.h"
 #include "nsSMILInstanceTime.h"
 #include "nsSMILParserUtils.h"
 #include "nsISMILAnimationElement.h"
 #include "nsContentUtils.h"
 #include "nsIEventListenerManager.h"
 #include "nsIDOMEventGroup.h"
 #include "nsGUIEvent.h"
+#include "nsIDOMTimeEvent.h"
 #include "nsString.h"
 
 using namespace mozilla::dom;
 
 //----------------------------------------------------------------------
 // Nested class: EventListener
 
 NS_IMPL_ISUPPORTS1(nsSMILTimeValueSpec::EventListener, nsIDOMEventListener)
@@ -112,26 +113,32 @@ nsSMILTimeValueSpec::SetSpec(const nsASt
   //   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 == nsSMILTimeValueSpecParams::OFFSET ||
       (!mIsBegin && mParams.mType == nsSMILTimeValueSpecParams::INDEFINITE)) {
     mOwner->AddInstanceTime(new nsSMILInstanceTime(mParams.mOffset), mIsBegin);
   }
 
+  // Fill in the event symbol to simplify handling later
+  if (mParams.mType == nsSMILTimeValueSpecParams::REPEAT) {
+    mParams.mEventSymbol = nsGkAtoms::repeatEvent;
+  }
+
   ResolveReferences(aContextNode);
 
   return rv;
 }
 
 void
 nsSMILTimeValueSpec::ResolveReferences(nsIContent* aContextNode)
 {
   if (mParams.mType != nsSMILTimeValueSpecParams::SYNCBASE &&
-      !IsEventBased())
+      mParams.mType != nsSMILTimeValueSpecParams::EVENT &&
+      mParams.mType != nsSMILTimeValueSpecParams::REPEAT)
     return;
 
   NS_ABORT_IF_FALSE(aContextNode,
       "null context node for resolving timing references against");
 
   // If we're not bound to the document yet, don't worry, we'll get called again
   // when that happens
   if (!aContextNode->IsInDoc())
@@ -140,21 +147,21 @@ nsSMILTimeValueSpec::ResolveReferences(n
   // 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.
   nsRefPtr<Element> oldReferencedElement = mReferencedElement.get();
 
   if (mParams.mDependentElemID) {
     mReferencedElement.ResetWithID(aContextNode,
         nsDependentAtomString(mParams.mDependentElemID));
-  } else if (IsEventBased()) {
+  } else if (mParams.mType == nsSMILTimeValueSpecParams::EVENT) {
     Element* target = mOwner->GetTargetElement();
     mReferencedElement.ResetWithElement(target);
   } else {
-    NS_ABORT_IF_FALSE(PR_FALSE, "Syncbase element without ID");
+    NS_ABORT_IF_FALSE(PR_FALSE, "Syncbase or repeat spec without ID");
   }
   UpdateReferencedElement(oldReferencedElement, mReferencedElement.get());
 }
 
 PRBool
 nsSMILTimeValueSpec::IsEventBased() const
 {
   return mParams.mType == nsSMILTimeValueSpecParams::EVENT ||
@@ -252,39 +259,51 @@ nsSMILTimeValueSpec::Unlink()
 void
 nsSMILTimeValueSpec::UpdateReferencedElement(Element* aFrom, Element* aTo)
 {
   if (aFrom == aTo)
     return;
 
   UnregisterFromReferencedElement(aFrom);
 
-  if (mParams.mType == nsSMILTimeValueSpecParams::SYNCBASE) {
-    nsSMILTimedElement* to = GetTimedElement(aTo);
-    if (to) {
-      to->AddDependent(*this);
+  switch (mParams.mType)
+  {
+  case nsSMILTimeValueSpecParams::SYNCBASE:
+    {
+      nsSMILTimedElement* to = GetTimedElement(aTo);
+      if (to) {
+        to->AddDependent(*this);
+      }
     }
-  } else if (mParams.mType == nsSMILTimeValueSpecParams::EVENT) {
+    break;
+
+  case nsSMILTimeValueSpecParams::EVENT:
+  case nsSMILTimeValueSpecParams::REPEAT:
     RegisterEventListener(aTo);
+    break;
+
+  default:
+    // not a referencing-type or not yet supported
+    break;
   }
 }
 
 void
 nsSMILTimeValueSpec::UnregisterFromReferencedElement(Element* aElement)
 {
   if (!aElement)
     return;
 
   if (mParams.mType == nsSMILTimeValueSpecParams::SYNCBASE) {
     nsSMILTimedElement* timedElement = GetTimedElement(aElement);
     if (timedElement) {
       timedElement->RemoveDependent(*this);
     }
     mOwner->RemoveInstanceTimesForCreator(this, mIsBegin);
-  } else if (mParams.mType == nsSMILTimeValueSpecParams::EVENT) {
+  } else if (IsEventBased()) {
     UnregisterEventListener(aElement);
   }
 }
 
 nsSMILTimedElement*
 nsSMILTimeValueSpec::GetTimedElement(Element* aElement)
 {
   if (!aElement)
@@ -295,18 +314,20 @@ nsSMILTimeValueSpec::GetTimedElement(Ele
     return nsnull;
 
   return &animElement->TimedElement();
 }
 
 void
 nsSMILTimeValueSpec::RegisterEventListener(Element* aTarget)
 {
-  NS_ABORT_IF_FALSE(mParams.mType == nsSMILTimeValueSpecParams::EVENT,
-    "Attempting to register event-listener for non-event nsSMILTimeValueSpec");
+  NS_ABORT_IF_FALSE(mParams.mType == nsSMILTimeValueSpecParams::EVENT ||
+                    mParams.mType == nsSMILTimeValueSpecParams::REPEAT,
+    "Attempting to register event-listener for unexpected nsSMILTimeValueSpec"
+    " type");
   NS_ABORT_IF_FALSE(mParams.mEventSymbol,
     "Attempting to register event-listener but there is no event name");
 
   if (!aTarget)
     return;
 
   if (!mEventListener) {
     mEventListener = new EventListener(this);
@@ -362,34 +383,55 @@ nsSMILTimeValueSpec::GetEventListenerMan
 
   return elm;
 }
 
 void
 nsSMILTimeValueSpec::HandleEvent(nsIDOMEvent* aEvent)
 {
   NS_ABORT_IF_FALSE(mEventListener, "Got event without an event listener");
-  NS_ABORT_IF_FALSE(mParams.mType == nsSMILTimeValueSpecParams::EVENT,
-    "Got event for non-event nsSMILTimeValueSpec");
+  NS_ABORT_IF_FALSE(IsEventBased(),
+                    "Got event for non-event nsSMILTimeValueSpec");
+  NS_ABORT_IF_FALSE(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
   nsSMILTimeContainer* container = mOwner->GetTimeContainer();
   if (!container)
     return;
 
+  if (!CheckEventDetail(aEvent))
+    return;
+
   nsSMILTime currentTime = container->GetCurrentTime();
   nsSMILTimeValue newTime(currentTime + mParams.mOffset.GetMillis());
 
   nsRefPtr<nsSMILInstanceTime> newInstance =
     new nsSMILInstanceTime(newTime, nsSMILInstanceTime::SOURCE_EVENT);
   mOwner->AddInstanceTime(newInstance, mIsBegin);
 }
 
+PRBool
+nsSMILTimeValueSpec::CheckEventDetail(nsIDOMEvent *aEvent)
+{
+  if (mParams.mType != nsSMILTimeValueSpecParams::REPEAT)
+    return PR_TRUE;
+
+  nsCOMPtr<nsIDOMTimeEvent> timeEvent = do_QueryInterface(aEvent);
+  if (!timeEvent) {
+    NS_WARNING("Received a repeat event that was not a DOMTimeEvent");
+    return PR_FALSE;
+  }
+
+  PRInt32 detail;
+  timeEvent->GetDetail(&detail);
+  return detail == mParams.mRepeatIterationOrAccessKey;
+}
+
 nsSMILTimeValue
 nsSMILTimeValueSpec::ConvertBetweenTimeContainers(
     const nsSMILTimeValue& aSrcTime,
     const nsSMILTimeContainer* aSrcContainer)
 {
   // If the source time is either indefinite or unresolved the result is going
   // to be the same
   if (!aSrcTime.IsResolved())
--- a/content/smil/nsSMILTimeValueSpec.h
+++ b/content/smil/nsSMILTimeValueSpec.h
@@ -93,16 +93,17 @@ protected:
   void UpdateReferencedElement(Element* aFrom, Element* aTo);
   void UnregisterFromReferencedElement(Element* aElement);
   nsSMILTimedElement* GetTimedElement(Element* aElement);
   void RegisterEventListener(Element* aElement);
   void UnregisterEventListener(Element* aElement);
   nsIEventListenerManager* GetEventListenerManager(Element* aElement,
       nsIDOMEventGroup** aSystemGroup);
   void HandleEvent(nsIDOMEvent* aEvent);
+  PRBool CheckEventDetail(nsIDOMEvent* aEvent);
   nsSMILTimeValue ConvertBetweenTimeContainers(const nsSMILTimeValue& aSrcTime,
                                       const nsSMILTimeContainer* aSrcContainer);
 
   nsSMILTimedElement*           mOwner;
   PRPackedBool                  mIsBegin; // Indicates if *we* are a begin spec,
                                           // not to be confused with
                                           // mParams.mSyncBegin which indicates
                                           // if we're synced with the begin of
--- a/content/smil/test/Makefile.in
+++ b/content/smil/test/Makefile.in
@@ -72,16 +72,17 @@ include $(topsrcdir)/config/rules.mk
 	  test_smilMappedAttrPaced.xhtml \
 	  test_smilReset.xhtml \
 	  test_smilRestart.xhtml \
 	  test_smilFillMode.xhtml \
 	  test_smilGetStartTime.xhtml \
 	  test_smilGetSimpleDuration.xhtml \
 	  test_smilKeySplines.xhtml \
 	  test_smilKeyTimesPacedMode.xhtml \
+	  test_smilRepeatTiming.xhtml \
 	  test_smilSetCurrentTime.xhtml \
 	  test_smilSync.xhtml \
 	  test_smilSyncbaseTarget.xhtml \
 	  test_smilSyncTransform.xhtml \
 	  test_smilTextZoom.xhtml \
 	  test_smilTimeEvents.xhtml \
 	  test_smilTiming.xhtml \
 	  test_smilTimingZeroIntervals.xhtml \
new file mode 100644
--- /dev/null
+++ b/content/smil/test/test_smilRepeatTiming.xhtml
@@ -0,0 +1,96 @@
+<html xmlns="http://www.w3.org/1999/xhtml">
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=485157
+-->
+<head>
+  <title>Test repeat timing</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=485157">Mozilla Bug
+  485157</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">
+  <rect width="100" height="100" fill="green">
+    <set attributeName="width" to="100" dur="20s" repeatCount="5" begin="0s"
+      id="a" onrepeat="startWaiting(evt)"/>
+    <set attributeName="fill" attributeType="CSS" to="green"
+      begin="a.repeat(1)" onbegin="expectedBegin()" dur="20s"/>
+    <set attributeName="x" to="100"
+      begin="a.repeat(2)" onbegin="unexpectedBegin(this)" dur="20s"/>
+    <set attributeName="y" to="100"
+      begin="a.repeat(0)" onbegin="unexpectedBegin(this)" dur="20s"/>
+    <set attributeName="width" to="100"
+      begin="a.repeat(-1)" onbegin="unexpectedBegin(this)" dur="20s"/>
+    <set attributeName="height" to="100"
+      begin="a.repeat(a)" onbegin="unexpectedBegin(this)" dur="20s"/>
+  </rect>
+</svg>
+</div>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+<![CDATA[
+/** Test SMIL repeat timing **/
+
+/* Global Variables */
+const gTimeoutDur = 5000; // Time until we give up waiting for events in ms
+var gSvg  = document.getElementById('svg');
+var gRect = document.getElementById('circle');
+var gTimeoutID;
+var gGotBegin = false;
+
+SimpleTest.waitForExplicitFinish();
+
+function testBegin()
+{
+  gSvg.setCurrentTime(19.999);
+}
+
+function startWaiting(evt)
+{
+  is(evt.detail, 1, "Unexpected repeat event received: test broken");
+  if (gGotBegin)
+    return;
+
+  gTimeoutID = setTimeout(timeoutFail, gTimeoutDur);
+}
+
+function timeoutFail()
+{
+  ok(false, "Timed out waiting for begin event");
+  finish();
+}
+
+function expectedBegin()
+{
+  is(gGotBegin, false,
+     "Got begin event more than once for non-repeating animation");
+  gGotBegin = true;
+  clearTimeout(gTimeoutID);
+  // Wait a moment before finishing in case there are erroneous events waiting
+  // to be processed.
+  setTimeout(finish, 10);
+}
+
+function unexpectedBegin(elem)
+{
+  ok(false, "Got unexpected begin from animation with spec: " +
+            elem.getAttribute('begin'));
+}
+
+function finish()
+{
+  gSvg.pauseAnimations();
+  SimpleTest.finish();
+}
+
+window.addEventListener("load", testBegin, false);
+]]>
+</script>
+</pre>
+</body>
+</html>