Add new timer APIs to NPAPI, NPN_ScheduleTimer and NPN_UnscheduleTimer. b=499921 r/sr=jst
authorJosh Aas <joshmoz@gmail.com>
Wed, 01 Jul 2009 11:09:49 -0400
changeset 29937 6135c784b4582a4ea887537bb34787cbd2a32c6c
parent 29936 0ad55acfa9794e3a52407d7e73ce3f3e17ffd7b5
child 29938 35afe1f6703657575464305262bc1e3d0d6ebfcd
child 29942 0d648609ce8112412325203c586fcb1e302f26f2
push idunknown
push userunknown
push dateunknown
bugs499921
milestone1.9.2a1pre
Add new timer APIs to NPAPI, NPN_ScheduleTimer and NPN_UnscheduleTimer. b=499921 r/sr=jst
modules/plugin/base/public/npapi.h
modules/plugin/base/public/npfunctions.h
modules/plugin/base/src/nsNPAPIPlugin.cpp
modules/plugin/base/src/nsNPAPIPlugin.h
modules/plugin/base/src/nsNPAPIPluginInstance.cpp
modules/plugin/base/src/nsNPAPIPluginInstance.h
modules/plugin/test/mochitest/Makefile.in
modules/plugin/test/mochitest/test_npapi_timers.xul
modules/plugin/test/testplugin/nptest.cpp
modules/plugin/test/testplugin/nptest.h
--- a/modules/plugin/base/public/npapi.h
+++ b/modules/plugin/base/public/npapi.h
@@ -670,16 +670,18 @@ NPError     NP_LOADDS NPN_SetValueForURL
 NPError     NP_LOADDS NPN_GetAuthenticationInfo(NPP instance,
                                                 const char *protocol,
                                                 const char *host, int32_t port,
                                                 const char *scheme,
                                                 const char *realm,
                                                 char **username, uint32_t *ulen,
                                                 char **password,
                                                 uint32_t *plen);
+uint32_t    NPN_ScheduleTimer(NPP instance, uint32_t interval, NPBool repeat, void (*timerFunc)(NPP npp, uint32_t timerID));
+void        NPN_UnscheduleTimer(NPP instance, uint32_t timerID);
 
 #ifdef __cplusplus
 }  /* end extern "C" */
 #endif
 
 #endif /* RC_INVOKED */
 #ifdef __OS2__
 #pragma pack()
--- a/modules/plugin/base/public/npfunctions.h
+++ b/modules/plugin/base/public/npfunctions.h
@@ -111,16 +111,18 @@ typedef void         (*NPN_SetExceptionP
 typedef bool         (*NPN_PushPopupsEnabledStateProcPtr)(NPP npp, NPBool enabled);
 typedef bool         (*NPN_PopPopupsEnabledStateProcPtr)(NPP npp);
 typedef bool         (*NPN_EnumerateProcPtr)(NPP npp, NPObject *obj, NPIdentifier **identifier, uint32_t *count);
 typedef void         (*NPN_PluginThreadAsyncCallProcPtr)(NPP instance, void (*func)(void *), void *userData);
 typedef bool         (*NPN_ConstructProcPtr)(NPP npp, NPObject* obj, const NPVariant *args, uint32_t argCount, NPVariant *result);
 typedef NPError      (*NPN_GetValueForURLPtr)(NPP npp, NPNURLVariable variable, const char *url, char **value, uint32_t *len);
 typedef NPError      (*NPN_SetValueForURLPtr)(NPP npp, NPNURLVariable variable, const char *url, const char *value, uint32_t len);
 typedef NPError      (*NPN_GetAuthenticationInfoPtr)(NPP npp, const char *protocol, const char *host, int32_t port, const char *scheme, const char *realm, char **username, uint32_t *ulen, char **password, uint32_t *plen);
+typedef uint32_t     (*NPN_ScheduleTimerPtr)(NPP instance, uint32_t interval, NPBool repeat, void (*timerFunc)(NPP npp, uint32_t timerID));
+typedef void         (*NPN_UnscheduleTimerPtr)(NPP instance, uint32_t timerID);
 
 typedef struct _NPPluginFuncs {
   uint16_t size;
   uint16_t version;
   NPP_NewProcPtr newp;
   NPP_DestroyProcPtr destroy;
   NPP_SetWindowProcPtr setwindow;
   NPP_NewStreamProcPtr newstream;
@@ -182,16 +184,18 @@ typedef struct _NPNetscapeFuncs {
   NPN_PushPopupsEnabledStateProcPtr pushpopupsenabledstate;
   NPN_PopPopupsEnabledStateProcPtr poppopupsenabledstate;
   NPN_EnumerateProcPtr enumerate;
   NPN_PluginThreadAsyncCallProcPtr pluginthreadasynccall;
   NPN_ConstructProcPtr construct;
   NPN_GetValueForURLPtr getvalueforurl;
   NPN_SetValueForURLPtr setvalueforurl;
   NPN_GetAuthenticationInfoPtr getauthenticationinfo;
+  NPN_ScheduleTimerPtr scheduletimer;
+  NPN_UnscheduleTimerPtr unscheduletimer;
 } NPNetscapeFuncs;
 
 #ifdef XP_MACOSX
 /*
  * Mac OS X version(s) of NP_GetMIMEDescription(const char *)
  * These can be called to retreive MIME information from the plugin dynamically
  *
  * Note: For compatibility with Quicktime, BPSupportedMIMEtypes is another way
--- a/modules/plugin/base/src/nsNPAPIPlugin.cpp
+++ b/modules/plugin/base/src/nsNPAPIPlugin.cpp
@@ -248,16 +248,18 @@ nsNPAPIPlugin::CheckClassInitialized(voi
   CALLBACKS.releasevariantvalue = ((NPN_ReleaseVariantValueProcPtr)_releasevariantvalue);
   CALLBACKS.setexception = ((NPN_SetExceptionProcPtr)_setexception);
   CALLBACKS.pushpopupsenabledstate = ((NPN_PushPopupsEnabledStateProcPtr)_pushpopupsenabledstate);
   CALLBACKS.poppopupsenabledstate = ((NPN_PopPopupsEnabledStateProcPtr)_poppopupsenabledstate);
   CALLBACKS.pluginthreadasynccall = ((NPN_PluginThreadAsyncCallProcPtr)_pluginthreadasynccall);
   CALLBACKS.getvalueforurl = ((NPN_GetValueForURLPtr)_getvalueforurl);
   CALLBACKS.setvalueforurl = ((NPN_SetValueForURLPtr)_setvalueforurl);
   CALLBACKS.getauthenticationinfo = ((NPN_GetAuthenticationInfoPtr)_getauthenticationinfo);
+  CALLBACKS.scheduletimer = ((NPN_ScheduleTimerPtr)_scheduletimer);
+  CALLBACKS.unscheduletimer = ((NPN_UnscheduleTimerPtr)_unscheduletimer);
 
   if (!sPluginThreadAsyncCallLock)
     sPluginThreadAsyncCallLock = nsAutoLock::NewLock("sPluginThreadAsyncCallLock");
 
   initialized = PR_TRUE;
 
   NPN_PLUGIN_LOG(PLUGIN_LOG_NORMAL,("NPN callbacks initialized\n"));
 }
@@ -2484,16 +2486,36 @@ NPError NP_CALLBACK
   *ulen = *username ? uname8.Length() : 0;
 
   *password = ToNewCString(pwd8);
   *plen = *password ? pwd8.Length() : 0;
 
   return NPERR_NO_ERROR;
 }
 
+uint32_t NP_CALLBACK
+_scheduletimer(NPP instance, uint32_t interval, NPBool repeat, void (*timerFunc)(NPP npp, uint32_t timerID))
+{
+  nsNPAPIPluginInstance *inst = (nsNPAPIPluginInstance *)instance->ndata;
+  if (!inst)
+    return 0;
+
+  return inst->ScheduleTimer(interval, repeat, timerFunc);
+}
+
+void NP_CALLBACK
+_unscheduletimer(NPP instance, uint32_t timerID)
+{
+  nsNPAPIPluginInstance *inst = (nsNPAPIPluginInstance *)instance->ndata;
+  if (!inst)
+    return;
+
+  inst->UnscheduleTimer(timerID);
+}
+
 void
 OnPluginDestroy(NPP instance)
 {
   if (!sPluginThreadAsyncCallLock) {
     return;
   }
 
   {
--- a/modules/plugin/base/src/nsNPAPIPlugin.h
+++ b/modules/plugin/base/src/nsNPAPIPlugin.h
@@ -207,16 +207,22 @@ NPError NP_CALLBACK
                 const char *value, uint32_t len);
 
 NPError NP_CALLBACK
 _getauthenticationinfo(NPP instance, const char *protocol, const char *host,
                        int32_t port, const char *scheme, const char *realm,
                        char **username, uint32_t *ulen, char **password,
                        uint32_t *plen);
 
+uint32_t NP_CALLBACK
+_scheduletimer(NPP instance, uint32_t interval, NPBool repeat, void (*timerFunc)(NPP npp, uint32_t timerID));
+
+void NP_CALLBACK
+_unscheduletimer(NPP instance, uint32_t timerID);
+
 PR_END_EXTERN_C
 
 const char *
 PeekException();
 
 void
 PopException();
 
--- a/modules/plugin/base/src/nsNPAPIPluginInstance.cpp
+++ b/modules/plugin/base/src/nsNPAPIPluginInstance.cpp
@@ -962,16 +962,20 @@ NS_IMETHODIMP nsNPAPIPluginInstance::Sto
       window->PopPopupControlState(openAbused);
     }
   }
 
   if (!mStarted) {
     return NS_OK;
   }
 
+  // clean up all outstanding timers
+  for (PRUint32 i = mTimers.Length(); i > 0; i--)
+    UnscheduleTimer(mTimers[i - 1]->id);
+
   // If there's code from this plugin instance on the stack, delay the
   // destroy.
   if (PluginDestructionGuard::DelayDestroy(this)) {
     return NS_OK;
   }
 
   // Make sure we lock while we're writing to mStarted after we've
   // started as other threads might be checking that inside a lock.
@@ -1604,17 +1608,18 @@ nsNPAPIPluginInstance::PopPopupsEnabledS
 }
 
 PRUint16
 nsNPAPIPluginInstance::GetPluginAPIVersion()
 {
   return fCallbacks->version;
 }
 
-nsresult nsNPAPIPluginInstance::PrivateModeStateChanged()
+nsresult
+nsNPAPIPluginInstance::PrivateModeStateChanged()
 {
   if (!mStarted)
     return NS_OK;
   
   PLUGIN_LOG(PLUGIN_LOG_NORMAL, ("nsNPAPIPluginInstance informing plugin of private mode state change this=%p\n",this));
   
   if (fCallbacks->setvalue) {
     PluginDestructionGuard guard(this);
@@ -1629,16 +1634,102 @@ nsresult nsNPAPIPluginInstance::PrivateM
       NPError error;
       NS_TRY_SAFE_CALL_RETURN(error, (*fCallbacks->setvalue)(&fNPP, NPNVprivateModeBool, &pme), fLibrary, this);
       return (error == NPERR_NO_ERROR) ? NS_OK : NS_ERROR_FAILURE;
     }
   }
   return NS_ERROR_FAILURE;
 }
 
+static void
+PluginTimerCallback(nsITimer *aTimer, void *aClosure)
+{
+  nsNPAPITimer* t = (nsNPAPITimer*)aClosure;
+  NPP npp = t->npp;
+  uint32_t id = t->id;
+
+  (*(t->callback))(npp, id);
+
+  // Make sure we still have an instance and the timer is still alive
+  // after the callback.
+  nsNPAPIPluginInstance *inst = (nsNPAPIPluginInstance*)npp->ndata;
+  if (!inst || !inst->TimerWithID(id, NULL))
+    return;
+
+  // use UnscheduleTimer to clean up if this is a one-shot timer
+  PRUint32 timerType;
+  t->timer->GetType(&timerType);
+  if (timerType == nsITimer::TYPE_ONE_SHOT)
+      inst->UnscheduleTimer(id);
+}
+
+nsNPAPITimer*
+nsNPAPIPluginInstance::TimerWithID(uint32_t id, PRUint32* index)
+{
+  PRUint32 len = mTimers.Length();
+  for (PRUint32 i = 0; i < len; i++) {
+    if (mTimers[i]->id == id) {
+      if (index)
+        *index = i;
+      return mTimers[i];
+    }
+  }
+  return nsnull;
+}
+
+uint32_t
+nsNPAPIPluginInstance::ScheduleTimer(uint32_t interval, NPBool repeat, void (*timerFunc)(NPP npp, uint32_t timerID))
+{
+  nsNPAPITimer *newTimer = new nsNPAPITimer();
+
+  newTimer->npp = &fNPP;
+
+  // generate ID that is unique to this instance
+  uint32_t uniqueID = mTimers.Length();
+  while ((uniqueID == 0) || TimerWithID(uniqueID, NULL))
+    uniqueID++;
+  newTimer->id = uniqueID;
+
+  // create new xpcom timer, scheduled correctly
+  nsresult rv;
+  nsCOMPtr<nsITimer> xpcomTimer = do_CreateInstance(NS_TIMER_CONTRACTID, &rv);
+  if (NS_FAILED(rv))
+    return 0;
+  const short timerType = (repeat ? (short)nsITimer::TYPE_REPEATING_SLACK : (short)nsITimer::TYPE_ONE_SHOT);
+  xpcomTimer->InitWithFuncCallback(PluginTimerCallback, newTimer, interval, timerType);
+  newTimer->timer = xpcomTimer;
+
+  // save callback function
+  newTimer->callback = timerFunc;
+
+  // add timer to timers array
+  mTimers.AppendElement(newTimer);
+
+  return newTimer->id;
+}
+
+void
+nsNPAPIPluginInstance::UnscheduleTimer(uint32_t timerID)
+{
+  // find the timer struct by ID
+  PRUint32 index;
+  nsNPAPITimer* t = TimerWithID(timerID, &index);
+  if (!t)
+    return;
+
+  // cancel the timer
+  t->timer->Cancel();
+
+  // remove timer struct from array
+  mTimers.RemoveElementAt(index);
+
+  // delete timer
+  delete t;
+}
+
 nsresult
 nsNPAPIPluginInstance::GetDOMElement(nsIDOMElement* *result)
 {
   if (!mOwner) {
     *result = nsnull;
     return NS_ERROR_FAILURE;
   }
 
--- a/modules/plugin/base/src/nsNPAPIPluginInstance.h
+++ b/modules/plugin/base/src/nsNPAPIPluginInstance.h
@@ -43,137 +43,151 @@
 #include "nsCOMPtr.h"
 #include "nsTArray.h"
 #include "nsIPlugin.h"
 #include "nsIPluginInstance.h"
 #include "nsIPluginTagInfo2.h"
 #include "nsIPluginInstanceInternal.h"
 #include "nsPIDOMWindow.h"
 #include "nsIPluginInstanceOwner.h"
+#include "nsITimer.h"
 
 #include "npfunctions.h"
 #include "prlink.h"
 
 class nsNPAPIPluginStreamListener;
 class nsPIDOMWindow;
 
 struct nsInstanceStream
 {
-    nsInstanceStream *mNext;
-    nsNPAPIPluginStreamListener *mPluginStreamListener;
+  nsInstanceStream *mNext;
+  nsNPAPIPluginStreamListener *mPluginStreamListener;
+
+  nsInstanceStream();
+  ~nsInstanceStream();
+};
 
-    nsInstanceStream();
-    ~nsInstanceStream();
+class nsNPAPITimer
+{
+public:
+  NPP npp;
+  uint32_t id;
+  nsCOMPtr<nsITimer> timer;
+  void (*callback)(NPP npp, uint32_t timerID);
 };
 
 class nsNPAPIPluginInstance : public nsIPluginInstance,
                               public nsIPluginInstanceInternal
 {
 public:
+  NS_DECL_ISUPPORTS
+  NS_DECL_NSIPLUGININSTANCE
 
-    NS_DECL_ISUPPORTS
-    NS_DECL_NSIPLUGININSTANCE
+  // nsIPluginInstanceInternal methods
 
-    // nsIPluginInstanceInternal methods
+  virtual JSObject *GetJSObject(JSContext *cx);
 
-    virtual JSObject *GetJSObject(JSContext *cx);
-
-    virtual nsresult GetFormValue(nsAString& aValue);
+  virtual nsresult GetFormValue(nsAString& aValue);
 
-    virtual void PushPopupsEnabledState(PRBool aEnabled);
-    virtual void PopPopupsEnabledState();
+  virtual void PushPopupsEnabledState(PRBool aEnabled);
+  virtual void PopPopupsEnabledState();
 
-    virtual PRUint16 GetPluginAPIVersion();
+  virtual PRUint16 GetPluginAPIVersion();
 
-    virtual void DefineJavaProperties();
+  virtual void DefineJavaProperties();
 
-    // nsNPAPIPluginInstance-specific methods
+  // nsNPAPIPluginInstance-specific methods
 
-    nsresult GetNPP(NPP * aNPP);
+  nsresult GetNPP(NPP * aNPP);
 
-    // Return the callbacks for the plugin instance.
-    nsresult GetCallbacks(const NPPluginFuncs ** aCallbacks);
+  // Return the callbacks for the plugin instance.
+  nsresult GetCallbacks(const NPPluginFuncs ** aCallbacks);
 
-    NPError SetWindowless(PRBool aWindowless);
+  NPError SetWindowless(PRBool aWindowless);
 
-    NPError SetTransparent(PRBool aTransparent);
+  NPError SetTransparent(PRBool aTransparent);
 
-    NPError SetWantsAllNetworkStreams(PRBool aWantsAllNetworkStreams);
+  NPError SetWantsAllNetworkStreams(PRBool aWantsAllNetworkStreams);
 
 #ifdef XP_MACOSX
-    void SetDrawingModel(NPDrawingModel aModel);
-    NPDrawingModel GetDrawingModel();
+  void SetDrawingModel(NPDrawingModel aModel);
+  NPDrawingModel GetDrawingModel();
 #endif
 
-    nsresult NewNotifyStream(nsIPluginStreamListener** listener, 
-                             void* notifyData, 
-                             PRBool aCallNotify,
-                             const char * aURL);
+  nsresult NewNotifyStream(nsIPluginStreamListener** listener, 
+                           void* notifyData, 
+                           PRBool aCallNotify,
+                           const char * aURL);
 
-    nsNPAPIPluginInstance(NPPluginFuncs* callbacks, PRLibrary* aLibrary);
+  nsNPAPIPluginInstance(NPPluginFuncs* callbacks, PRLibrary* aLibrary);
 
-    // Use Release() to destroy this
-    virtual ~nsNPAPIPluginInstance(void);
+  // Use Release() to destroy this
+  virtual ~nsNPAPIPluginInstance();
 
-    // returns the state of mStarted
-    PRBool IsStarted(void);
+  // returns the state of mStarted
+  PRBool IsStarted();
 
-    // cache this NPAPI plugin like an XPCOM plugin
-    nsresult SetCached(PRBool aCache) { mCached = aCache; return NS_OK; }
+  // cache this NPAPI plugin like an XPCOM plugin
+  nsresult SetCached(PRBool aCache) { mCached = aCache; return NS_OK; }
 
-    already_AddRefed<nsPIDOMWindow> GetDOMWindow();
+  already_AddRefed<nsPIDOMWindow> GetDOMWindow();
 
-    nsresult PrivateModeStateChanged();
+  nsresult PrivateModeStateChanged();
 
-    nsresult GetDOMElement(nsIDOMElement* *result);
+  nsresult GetDOMElement(nsIDOMElement* *result);
 
+  nsNPAPITimer* TimerWithID(uint32_t id, PRUint32* index);
+  uint32_t      ScheduleTimer(uint32_t interval, NPBool repeat, void (*timerFunc)(NPP npp, uint32_t timerID));
+  void          UnscheduleTimer(uint32_t timerID);
 protected:
+  nsresult InitializePlugin();
 
-    nsresult InitializePlugin();
-
-    // Calls NPP_GetValue
-    nsresult GetValueInternal(NPPVariable variable, void* value);
+  // Calls NPP_GetValue
+  nsresult GetValueInternal(NPPVariable variable, void* value);
 
-    nsresult GetTagType(nsPluginTagType *result);
-    nsresult GetAttributes(PRUint16& n, const char*const*& names,
-                           const char*const*& values);
-    nsresult GetParameters(PRUint16& n, const char*const*& names,
-                           const char*const*& values);
-    nsresult GetMode(nsPluginMode *result);
+  nsresult GetTagType(nsPluginTagType *result);
+  nsresult GetAttributes(PRUint16& n, const char*const*& names,
+                         const char*const*& values);
+  nsresult GetParameters(PRUint16& n, const char*const*& names,
+                         const char*const*& values);
+  nsresult GetMode(nsPluginMode *result);
 
-    // A pointer to the plugin's callback functions. This information
-    // is actually stored in the plugin class (<b>nsPluginClass</b>),
-    // and is common for all plugins of the class.
-    NPPluginFuncs* fCallbacks;
+  // A pointer to the plugin's callback functions. This information
+  // is actually stored in the plugin class (<b>nsPluginClass</b>),
+  // and is common for all plugins of the class.
+  NPPluginFuncs* fCallbacks;
 
-    // The structure used to communicate between the plugin instance and
-    // the browser.
-    NPP_t fNPP;
+  // The structure used to communicate between the plugin instance and
+  // the browser.
+  NPP_t fNPP;
 
 #ifdef XP_MACOSX
-    NPDrawingModel mDrawingModel;
+  NPDrawingModel mDrawingModel;
 #endif
 
-    // these are used to store the windowless properties
-    // which the browser will later query
-    PRPackedBool  mWindowless;
-    PRPackedBool  mTransparent;
-    PRPackedBool  mStarted;
-    PRPackedBool  mCached;
-    PRPackedBool  mIsJavaPlugin;
-    PRPackedBool  mWantsAllNetworkStreams;
+  // these are used to store the windowless properties
+  // which the browser will later query
+  PRPackedBool  mWindowless;
+  PRPackedBool  mTransparent;
+  PRPackedBool  mStarted;
+  PRPackedBool  mCached;
+  PRPackedBool  mIsJavaPlugin;
+  PRPackedBool  mWantsAllNetworkStreams;
 
 public:
-    // True while creating the plugin, or calling NPP_SetWindow() on it.
-    PRPackedBool  mInPluginInitCall;
-    PRLibrary* fLibrary;
-    nsInstanceStream *mStreams;
+  // True while creating the plugin, or calling NPP_SetWindow() on it.
+  PRPackedBool mInPluginInitCall;
+  PRLibrary* fLibrary;
+  nsInstanceStream *mStreams;
+
+private:
+  nsTArray<PopupControlState> mPopupStates;
 
-    nsTArray<PopupControlState> mPopupStates;
-
-    nsMIMEType mMIMEType;
+  nsMIMEType mMIMEType;
 
-    // Weak pointer to the owner. The owner nulls this out (by calling
-    // InvalidateOwner()) when it's no longer our owner.
-    nsIPluginInstanceOwner  *mOwner;
+  // Weak pointer to the owner. The owner nulls this out (by calling
+  // InvalidateOwner()) when it's no longer our owner.
+  nsIPluginInstanceOwner *mOwner;
+
+  nsTArray<nsNPAPITimer*> mTimers;
 };
 
 #endif // nsNPAPIPluginInstance_h_
--- a/modules/plugin/test/mochitest/Makefile.in
+++ b/modules/plugin/test/mochitest/Makefile.in
@@ -44,12 +44,13 @@ relativesrcdir  = modules/plugin/test
 include $(DEPTH)/config/autoconf.mk
 include $(topsrcdir)/config/rules.mk
 
 _TEST_FILES = \
 		test_bug479979.xul \
 		test_npruntime.xul   \
 		test_privatemode.xul \
 		test_wmode.xul \
+		test_npapi_timers.xul \
 		$(NULL)
 
 libs:: $(_TEST_FILES)
 	$(INSTALL) $^ $(DEPTH)/_tests/testing/mochitest/chrome/$(relativesrcdir)
new file mode 100644
--- /dev/null
+++ b/modules/plugin/test/mochitest/test_npapi_timers.xul
@@ -0,0 +1,59 @@
+<?xml version="1.0"?>
+<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
+<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css"
+                 type="text/css"?>
+<window title="NPAPI Timer Tests"
+  xmlns:html="http://www.w3.org/1999/xhtml"
+  xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+  <title>NPAPI Timer Tests</title>
+  <script type="application/javascript" 
+   src="chrome://mochikit/content/MochiKit/packed.js"></script>
+  <script type="application/javascript"
+          src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js" />
+<body xmlns="http://www.w3.org/1999/xhtml" onload="runTests()">
+<embed id="plugin" type="application/x-test" width="50" height="50"></embed>
+</body>
+<script class="testbody" type="application/javascript">
+<![CDATA[
+SimpleTest.waitForExplicitFinish();
+
+var uniqueIDTestFinished = false;
+var shortOneShotTimerFiredOnce = false;
+var shortOneShotTimerFiredTwice = false;
+var longRepeatingTimerFiredOnce = false;
+var longRepeatingTimerFiredTwice = false;
+
+function uniqueID(success) {
+  is(success, true, "Timers incorrectly given non-unique IDs.");
+  uniqueIDTestFinished = true;
+}
+
+function shortTimerFired() {
+  is(uniqueIDTestFinished, true, "Unique ID test should have reported back before short timer fired.");
+  if (shortOneShotTimerFiredOnce)
+    shortOneShotTimerFiredTwice = true;
+  else
+    shortOneShotTimerFiredOnce = true;
+}
+
+function longTimerFired() {
+  is(shortOneShotTimerFiredOnce, true, "Short one-shot timer did not fire.");
+  is(shortOneShotTimerFiredTwice, false, "Short one-shot timer fired multiple times.");
+
+  if (longRepeatingTimerFiredOnce) {
+    var p = document.getElementById("plugin");
+    p.unscheduleAllTimers();
+    SimpleTest.finish();
+  }
+  else {
+    longRepeatingTimerFiredOnce = true;
+  }
+}
+
+function runTests() {
+  var p = document.getElementById("plugin");
+  p.timerTest();
+}
+]]>
+</script>
+</window>
--- a/modules/plugin/test/testplugin/nptest.cpp
+++ b/modules/plugin/test/testplugin/nptest.cpp
@@ -57,52 +57,58 @@ static NPClass sNPClass;
 // identifiers
 //
 
 typedef bool (* ScriptableFunction)
   (NPObject* npobj, const NPVariant* args, uint32_t argCount, NPVariant* result);
 
 static bool setUndefinedValueTest(NPObject* npobj, const NPVariant* args, uint32_t argCount, NPVariant* result);
 static bool identifierToStringTest(NPObject* npobj, const NPVariant* args, uint32_t argCount, NPVariant* result);
+static bool timerTest(NPObject* npobj, const NPVariant* args, uint32_t argCount, NPVariant* result);
 static bool queryPrivateModeState(NPObject* npobj, const NPVariant* args, uint32_t argCount, NPVariant* result);
 static bool lastReportedPrivateModeState(NPObject* npobj, const NPVariant* args, uint32_t argCount, NPVariant* result);
 static bool hasWidget(NPObject* npobj, const NPVariant* args, uint32_t argCount, NPVariant* result);
 static bool getEdge(NPObject* npobj, const NPVariant* args, uint32_t argCount, NPVariant* result);
 static bool getClipRegionRectCount(NPObject* npobj, const NPVariant* args, uint32_t argCount, NPVariant* result);
 static bool getClipRegionRectEdge(NPObject* npobj, const NPVariant* args, uint32_t argCount, NPVariant* result);
 static bool startWatchingInstanceCount(NPObject* npobj, const NPVariant* args, uint32_t argCount, NPVariant* result);
 static bool getInstanceCount(NPObject* npobj, const NPVariant* args, uint32_t argCount, NPVariant* result);
 static bool stopWatchingInstanceCount(NPObject* npobj, const NPVariant* args, uint32_t argCount, NPVariant* result);
+static bool unscheduleAllTimers(NPObject* npobj, const NPVariant* args, uint32_t argCount, NPVariant* result);
 
 static const NPUTF8* sPluginMethodIdentifierNames[] = {
   "setUndefinedValueTest",
   "identifierToStringTest",
+  "timerTest",
   "queryPrivateModeState",
   "lastReportedPrivateModeState",
   "hasWidget",
   "getEdge",
   "getClipRegionRectCount",
   "getClipRegionRectEdge",
   "startWatchingInstanceCount",
   "getInstanceCount",
   "stopWatchingInstanceCount",
+  "unscheduleAllTimers",
 };
 static NPIdentifier sPluginMethodIdentifiers[ARRAY_LENGTH(sPluginMethodIdentifierNames)];
 static const ScriptableFunction sPluginMethodFunctions[ARRAY_LENGTH(sPluginMethodIdentifierNames)] = {
   setUndefinedValueTest,
   identifierToStringTest,
+  timerTest,
   queryPrivateModeState,
   lastReportedPrivateModeState,
   hasWidget,
   getEdge,
   getClipRegionRectCount,
   getClipRegionRectEdge,
   startWatchingInstanceCount,
   getInstanceCount,
   stopWatchingInstanceCount,
+  unscheduleAllTimers,
 };
 
 static bool sIdentifiersInitialized = false;
 
 /**
  * Incremented for every startWatchingInstanceCount.
  */
 static int32_t sCurrentInstanceCountWatchGeneration = 0;
@@ -510,16 +516,22 @@ NPN_HasProperty(NPP instance, NPObject* 
 }
 
 NPObject*
 NPN_CreateObject(NPP instance, NPClass* aClass)
 {
   return sBrowserFuncs->createobject(instance, aClass);
 }
 
+bool
+NPN_Invoke(NPP npp, NPObject* obj, NPIdentifier methodName, const NPVariant *args, uint32_t argCount, NPVariant *result)
+{
+  return sBrowserFuncs->invoke(npp, obj, methodName, args, argCount, result);
+}
+
 const char*
 NPN_UserAgent(NPP instance)
 {
   return sBrowserFuncs->uagent(instance);
 }
 
 NPObject*
 NPN_RetainObject(NPObject* obj)
@@ -540,16 +552,34 @@ NPN_MemAlloc(uint32_t size)
 }
 
 void
 NPN_MemFree(void* ptr)
 {
   return sBrowserFuncs->memfree(ptr);
 }
 
+uint32_t
+NPN_ScheduleTimer(NPP instance, uint32_t interval, NPBool repeat, void (*timerFunc)(NPP npp, uint32_t timerID))
+{
+  return sBrowserFuncs->scheduletimer(instance, interval, repeat, timerFunc);
+}
+
+void
+NPN_UnscheduleTimer(NPP instance, uint32_t timerID)
+{
+  return sBrowserFuncs->unscheduletimer(instance, timerID);
+}
+
+void
+NPN_ReleaseVariantValue(NPVariant *variant)
+{
+  return sBrowserFuncs->releasevariantvalue(variant);
+}
+
 //
 // npruntime object functions
 //
 
 NPObject*
 scriptableAllocate(NPP npp, NPClass* aClass)
 {
   TestNPObject* object = (TestNPObject*)NPN_MemAlloc(sizeof(TestNPObject));
@@ -656,16 +686,59 @@ identifierToStringTest(NPObject* npobj, 
 
   NPUTF8* utf8String = NPN_UTF8FromIdentifier(identifier);
   if (!utf8String)
     return false;
   STRINGZ_TO_NPVARIANT(utf8String, *result);
   return true;
 }
 
+static void timerCallback(NPP npp, uint32_t timerID)
+{
+  InstanceData* id = static_cast<InstanceData*>(npp->pdata);
+
+  NPObject* windowObject;
+  NPN_GetValue(npp, NPNVWindowNPObject, &windowObject);
+  if (!windowObject)
+    return;
+
+  NPVariant rval;
+  if (timerID == id->timerID1)
+    NPN_Invoke(npp, windowObject, NPN_GetStringIdentifier("shortTimerFired"), NULL, 0, &rval);
+  else if (timerID == id->timerID2)
+    NPN_Invoke(npp, windowObject, NPN_GetStringIdentifier("longTimerFired"), NULL, 0, &rval);
+
+  NPN_ReleaseObject(windowObject);
+}
+
+static bool
+timerTest(NPObject* npobj, const NPVariant* args, uint32_t argCount, NPVariant* result)
+{
+  NPP npp = static_cast<TestNPObject*>(npobj)->npp;
+  InstanceData* id = static_cast<InstanceData*>(npp->pdata);
+
+  NPObject* windowObject;
+  NPN_GetValue(npp, NPNVWindowNPObject, &windowObject);
+  if (!windowObject)
+    return false;
+
+  id->timerID1 = NPN_ScheduleTimer(npp, 50, false, timerCallback);
+  id->timerID2 = NPN_ScheduleTimer(npp, 150, true, timerCallback);
+
+  NPVariant rval;
+  NPVariant uniqueIDArgs[1];
+  BOOLEAN_TO_NPVARIANT((id->timerID1 != id->timerID2), uniqueIDArgs[0]);
+  NPN_Invoke(npp, windowObject, NPN_GetStringIdentifier("uniqueID"), uniqueIDArgs, 1, &rval);
+  NPN_ReleaseVariantValue(&uniqueIDArgs[0]);
+
+  NPN_ReleaseObject(windowObject);
+
+  return true;
+}
+
 static bool
 queryPrivateModeState(NPObject* npobj, const NPVariant* args, uint32_t argCount, NPVariant* result)
 {
   if (argCount != 0)
     return false;
 
   NPBool pms = false;
   NPN_GetValue(static_cast<TestNPObject*>(npobj)->npp, NPNVprivateModeBool, &pms);
@@ -784,8 +857,22 @@ stopWatchingInstanceCount(NPObject* npob
   if (argCount != 0)
     return false;
   if (!sWatchingInstanceCount)
     return false;
 
   sWatchingInstanceCount = false;
   return true;
 }
+
+static bool
+unscheduleAllTimers(NPObject* npobj, const NPVariant* args, uint32_t argCount, NPVariant* result)
+{
+  NPP npp = static_cast<TestNPObject*>(npobj)->npp;
+  InstanceData* id = static_cast<InstanceData*>(npp->pdata);
+
+  NPN_UnscheduleTimer(npp, id->timerID1);
+  id->timerID1 = 0;
+  NPN_UnscheduleTimer(npp, id->timerID2);
+  id->timerID2 = 0;
+
+  return true;
+}
--- a/modules/plugin/test/testplugin/nptest.h
+++ b/modules/plugin/test/testplugin/nptest.h
@@ -57,11 +57,13 @@ typedef struct _PlatformData PlatformDat
 typedef struct InstanceData {
   NPP npp;
   NPWindow window;
   TestNPObject* scriptableObject;
   PlatformData* platformData;
   uint32_t instanceCountWatchGeneration;
   bool lastReportedPrivateModeState;
   bool hasWidget;
+  uint32_t timerID1;
+  uint32_t timerID2;
 } InstanceData;
 
 #endif // nptest_h_