Bug 506961 - Add a method to get jsdIValue from JS implemented event listeners, r=bz
authorOlli Pettay <Olli.Pettay@helsinki.fi>
Sat, 17 Oct 2009 17:40:44 +0300
changeset 33989 06cecc61ffeb269dc03b86272cfb33e4c0479626
parent 33988 f8a2075294dc852a6140c0aa6219dc699ad82421
child 33990 092b0f9e39a3e3ff5dcc6b92146e0f3a8ecefa88
child 33996 c884003bbe7f3d211dcd7738ec55f4ba91aecc0e
push id1
push userroot
push dateTue, 26 Apr 2011 22:38:44 +0000
treeherdermozilla-beta@bfdb6e623a36 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersbz
bugs506961
milestone1.9.3a1pre
Bug 506961 - Add a method to get jsdIValue from JS implemented event listeners, r=bz
content/events/public/nsIEventListenerService.idl
content/events/src/Makefile.in
content/events/src/nsEventListenerService.cpp
content/events/src/nsEventListenerService.h
content/events/test/test_bug448602.html
dom/base/nsIJSEventListener.h
dom/src/events/nsJSEventListener.cpp
dom/src/events/nsJSEventListener.h
js/jsd/idl/jsdIDebuggerService.idl
js/jsd/jsd_xpc.cpp
--- a/content/events/public/nsIEventListenerService.idl
+++ b/content/events/public/nsIEventListenerService.idl
@@ -38,32 +38,38 @@
 #include "nsIDOMEventListener.idl"
 
 interface nsIDOMEventTarget;
 
 /**
  * An instance of this interface describes how an event listener
  * was added to an event target.
  */
-[scriptable, uuid(4f132988-4709-44e5-985c-9e16d0f7c954)]
+[scriptable, uuid(cbc2ea6e-4043-4435-ba8f-64cbf6638622)]
 interface nsIEventListenerInfo : nsISupports
 {
   /**
    * The type of the event for which the listener was added.
    */
   readonly attribute AString type;
   readonly attribute boolean capturing;
   readonly attribute boolean allowsUntrusted;
   readonly attribute boolean inSystemEventGroup;
   /**
    * Tries to serialize event listener to a string.
    * Returns null if serialization isn't possible
    * (for example with C++ listeners).
    */
   AString toSource();
+
+  /**
+   * If jsdIDebuggerService is active and the listener is implemented in JS,
+   * this returns the listener as a jsdIValue. Otherwise null.
+   */
+  nsISupports getDebugObject();
 };
 
 [scriptable, uuid(551cac0f-31ed-45e0-8d67-bc0d6e117b31)]
 interface nsIEventListenerService : nsISupports
 {
   /**
    * Returns an array of nsIEventListenerInfo objects.
    * If aEventTarget doesn't have any listeners, this returns null.
--- a/content/events/src/Makefile.in
+++ b/content/events/src/Makefile.in
@@ -95,8 +95,14 @@ LOCAL_INCLUDES	= \
              -I$(srcdir)/../../xml/content/src \
              -I$(srcdir)/../../../dom/base \
              -I$(srcdir)/../../../layout/generic \
              -I$(srcdir)/../../../layout/xul/base/src \
              -I$(srcdir)/../../../layout/xul/base/src/tree/src \
              $(NULL)
 
 DEFINES += -D_IMPL_NS_LAYOUT
+
+ifdef MOZ_JSDEBUGGER
+DEFINES += -DMOZ_JSDEBUGGER
+endif
+
+
--- a/content/events/src/nsEventListenerService.cpp
+++ b/content/events/src/nsEventListenerService.cpp
@@ -46,16 +46,19 @@
 #include "nsIDOMWindow.h"
 #include "nsPIDOMWindow.h"
 #include "nsJSUtils.h"
 #include "nsIPrivateDOMEvent.h"
 #include "nsIJSContextStack.h"
 #include "nsGUIEvent.h"
 #include "nsEventDispatcher.h"
 #include "nsIJSEventListener.h"
+#ifdef MOZ_JSDEBUGGER
+#include "jsdIDebuggerService.h"
+#endif
 
 NS_IMPL_CYCLE_COLLECTION_1(nsEventListenerInfo, mListener)
 
 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(nsEventListenerInfo)
   NS_INTERFACE_MAP_ENTRY(nsIEventListenerInfo)
   NS_INTERFACE_MAP_ENTRY(nsISupports)
   NS_INTERFACE_MAP_ENTRY_CONTENT_CLASSINFO(EventListenerInfo)
 NS_INTERFACE_MAP_END
@@ -88,47 +91,95 @@ NS_IMETHODIMP
 nsEventListenerInfo::GetInSystemEventGroup(PRBool* aInSystemEventGroup)
 {
   *aInSystemEventGroup = mInSystemEventGroup;
   return NS_OK;
 }
 
 NS_IMPL_ISUPPORTS1(nsEventListenerService, nsIEventListenerService)
 
-NS_IMETHODIMP
-nsEventListenerInfo::ToSource(nsAString& aResult)
+// Caller must root *aJSVal!
+PRBool
+nsEventListenerInfo::GetJSVal(jsval* aJSVal)
 {
-  aResult.SetIsVoid(PR_TRUE);
+  *aJSVal = JSVAL_NULL;
   nsCOMPtr<nsIXPConnectWrappedJS> wrappedJS = do_QueryInterface(mListener);
   if (wrappedJS) {
     JSObject* object = nsnull;
     wrappedJS->GetJSObject(&object);
-    if (object) {
-      nsCOMPtr<nsIThreadJSContextStack> stack =
-        nsContentUtils::ThreadJSContextStack();
-      if (stack) {
-        JSContext* cx = nsnull;
-        stack->GetSafeJSContext(&cx);
-        if (cx && NS_SUCCEEDED(stack->Push(cx))) {
-          JSAutoRequest ar(cx);
-          jsval v = OBJECT_TO_JSVAL(object);
-          JSString* str = JS_ValueToSource(cx, v);
-          if (str) {
-            aResult.Assign(nsDependentJSString(str));
-          }
-          stack->Pop(&cx);
+    *aJSVal = OBJECT_TO_JSVAL(object);
+    return PR_TRUE;
+  }
+
+  nsCOMPtr<nsIJSEventListener> jsl = do_QueryInterface(mListener);
+  if (jsl) {
+    nsresult rv = jsl->GetJSVal(mType, aJSVal);
+    if (NS_SUCCEEDED(rv)) {
+      return PR_TRUE;
+    }
+  }
+  return PR_FALSE;
+}
+
+NS_IMETHODIMP
+nsEventListenerInfo::ToSource(nsAString& aResult)
+{
+  aResult.SetIsVoid(PR_TRUE);
+
+  nsresult rv;
+  jsval v = JSVAL_NULL;
+  nsAutoGCRoot root(&v, &rv);
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  if (GetJSVal(&v)) {
+    nsCOMPtr<nsIThreadJSContextStack> stack =
+      nsContentUtils::ThreadJSContextStack();
+    if (stack) {
+      JSContext* cx = nsnull;
+      stack->GetSafeJSContext(&cx);
+      if (cx && NS_SUCCEEDED(stack->Push(cx))) {
+        JSAutoRequest ar(cx);
+        JSString* str = JS_ValueToSource(cx, v);
+        if (str) {
+          aResult.Assign(nsDependentJSString(str));
         }
+        stack->Pop(&cx);
       }
     }
-  } else {
-    nsCOMPtr<nsIJSEventListener> jsl = do_QueryInterface(mListener);
-    if (jsl) {
-      jsl->ToString(mType, aResult);
-    }
   }
+
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+nsEventListenerInfo::GetDebugObject(nsISupports** aRetVal)
+{
+  *aRetVal = nsnull;
+
+#ifdef MOZ_JSDEBUGGER
+  nsresult rv = NS_OK;
+  jsval v = JSVAL_NULL;
+  nsAutoGCRoot root(&v, &rv);
+  NS_ENSURE_SUCCESS(rv, rv);
+  if (GetJSVal(&v)) {
+    nsCOMPtr<jsdIDebuggerService> jsd =
+      do_GetService("@mozilla.org/js/jsd/debugger-service;1", &rv);
+    NS_ENSURE_SUCCESS(rv, NS_OK);
+
+    PRBool isOn = PR_FALSE;
+    jsd->GetIsOn(&isOn);
+    NS_ENSURE_TRUE(isOn, NS_OK);
+
+    nsCOMPtr<jsdIValue> jsdValue;
+    jsd->WrapJSValue(v, getter_AddRefs(jsdValue));
+    *aRetVal = jsdValue.forget().get();
+    return NS_OK;
+  }
+#endif
+
   return NS_OK;
 }
 
 NS_IMETHODIMP
 nsEventListenerService::GetListenerInfoFor(nsIDOMEventTarget* aEventTarget,
                                            PRUint32* aCount,
                                            nsIEventListenerInfo*** aOutArray)
 {
--- a/content/events/src/nsEventListenerService.h
+++ b/content/events/src/nsEventListenerService.h
@@ -39,31 +39,35 @@
 #define nsEventListenerService_h__
 #include "nsIEventListenerService.h"
 #include "nsAutoPtr.h"
 #include "nsIDOMEventListener.h"
 #include "nsIDOMEventTarget.h"
 #include "nsString.h"
 #include "nsPIDOMEventTarget.h"
 #include "nsCycleCollectionParticipant.h"
+#include "jsapi.h"
+
 
 class nsEventListenerInfo : public nsIEventListenerInfo
 {
 public:
   nsEventListenerInfo(const nsAString& aType, nsIDOMEventListener* aListener,
                       PRBool aCapturing, PRBool aAllowsUntrusted,
                       PRBool aInSystemEventGroup)
   : mType(aType), mListener(aListener), mCapturing(aCapturing),
     mAllowsUntrusted(aAllowsUntrusted),
     mInSystemEventGroup(aInSystemEventGroup) {}
   virtual ~nsEventListenerInfo() {}
   NS_DECL_CYCLE_COLLECTING_ISUPPORTS
   NS_DECL_CYCLE_COLLECTION_CLASS(nsEventListenerInfo)
   NS_DECL_NSIEVENTLISTENERINFO
 protected:
+  PRBool GetJSVal(jsval* aJSVal);
+
   nsString                      mType;
   // nsReftPtr because that is what nsListenerStruct uses too.
   nsRefPtr<nsIDOMEventListener> mListener;
   PRPackedBool                  mCapturing;
   PRPackedBool                  mAllowsUntrusted;
   PRPackedBool                  mInSystemEventGroup;
 };
 
--- a/content/events/test/test_bug448602.html
+++ b/content/events/test/test_bug448602.html
@@ -18,16 +18,19 @@ https://bugzilla.mozilla.org/show_bug.cg
 <pre id="test">          
 <script type="application/javascript">
 
 /** Test for Bug 448602 **/
 
 
 function runTests() {
   netscape.security.PrivilegeManager.enablePrivilege("UniversalXPConnect");
+  var jsdIDebuggerService = Components.interfaces.jsdIDebuggerService;
+  var jsd = Components.classes['@mozilla.org/js/jsd/debugger-service;1']
+                      .getService(jsdIDebuggerService);
   var els = Components.classes["@mozilla.org/eventlistenerservice;1"]
                       .getService(Components.interfaces.nsIEventListenerService);
 
   // Event listener info tests
   var root = document.getElementById("testroot");
   var infos = els.getListenerInfoFor(root, {});
   is(infos.length, 0, "Element shouldn't have listeners (1)");
 
@@ -36,36 +39,53 @@ function runTests() {
   infos = els.getListenerInfoFor(root, {});
   is(infos.length, 1, "Element should have listeners (1)");
   is(infos[0].toSource(), 'function onclick(event) {' + listenerSource + '}',
      "Unexpected serialization (1)");
   is(infos[0].type, "click", "Wrong type (1)");
   is(infos[0].capturing, false, "Wrong phase (1)");
   is(infos[0].allowsUntrusted, true, "Should allow untrusted events (1)");
 
+  var jsdOn = jsd.isOn;
+  if (!jsdOn) {
+    is(infos[0].getDebugObject(), null,
+       "If JSD isn't running, getDebugObject() should return null.")
+    jsd.on();
+    ok(jsd.isOn, "JSD should be running.");
+  }
+  var jsdvalue = infos[0].getDebugObject().QueryInterface(Components.interfaces.jsdIValue);
+  is(jsdvalue.jsType, 3, "Event listener should be a function! (1)");
+
   root.removeAttribute("onclick");
   infos = els.getListenerInfoFor(root, {});
   is(infos.length, 0, "Element shouldn't have listeners (2)");
 
   var l = function (e) { alert(e); };
   root.addEventListener("foo", l, true, true);
   root.addEventListener("foo", l, false, false);
   infos = els.getListenerInfoFor(root, {});
   is(infos.length, 2, "Element should have listeners (2)");
   is(infos[0].toSource(), "(function (e) {alert(e);})",
      "Unexpected serialization (2)");
   is(infos[0].type, "foo", "Wrong type (2)");
   is(infos[0].capturing, true, "Wrong phase (2)");
   is(infos[0].allowsUntrusted, true, "Should allow untrusted events (2)");
+  jsdvalue = infos[0].getDebugObject().QueryInterface(Components.interfaces.jsdIValue);
+  is(jsdvalue.jsType, 3, "Event listener should be a function!(2)");
+  is(jsdvalue.getWrappedValue(), l, "Wrong JS value! (1)");
+
   is(infos[1].toSource(), "(function (e) {alert(e);})",
      "Unexpected serialization (3)");
   is(infos[1].type, "foo", "Wrong type (3)");
   is(infos[1].capturing, false, "Wrong phase (3)");
   is(infos[1].allowsUntrusted, false, "Shouldn't allow untrusted events (1)");
 
+  jsdvalue2 = infos[1].getDebugObject().QueryInterface(Components.interfaces.jsdIValue);
+  is(jsdvalue2.jsType, 3, "Event listener should be a function! (3)");
+  is(jsdvalue2.getWrappedValue(), l, "Wrong JS value! (2)");
   root.removeEventListener("foo", l, true);
   root.removeEventListener("foo", l, false);
   infos = els.getListenerInfoFor(root, {});
   is(infos.length, 0, "Element shouldn't have listeners (3)");
 
   root.onclick = l;
   infos = els.getListenerInfoFor(root, {});
   is(infos.length, 1, "Element should have listeners (3)");
@@ -94,16 +114,22 @@ function runTests() {
     } else if (chain[i] == window) {
       hasWindowInChain = true;
     }
   }
 
   ok(hasDocumentInChain, "Should have document in event target chain!");
   ok(hasWindowInChain, "Should have window in event target chain!");
 
+  if (!jsdOn) {
+    jsd.off();
+    ok(!jsd.isOn, "JSD shouldn't be running anymore.");
+  }
+
+  
   SimpleTest.finish();
 }
 
 
 SimpleTest.waitForExplicitFinish();
 addLoadEvent(runTests);
 </script>
 </pre>
--- a/dom/base/nsIJSEventListener.h
+++ b/dom/base/nsIJSEventListener.h
@@ -34,24 +34,25 @@
  * the terms of any one of the MPL, the GPL or the LGPL.
  *
  * ***** END LICENSE BLOCK ***** */
 
 #ifndef nsIJSEventListener_h__
 #define nsIJSEventListener_h__
 
 #include "nsIScriptContext.h"
+#include "jsapi.h"
 
 class nsIScriptObjectOwner;
 class nsIDOMEventListener;
 class nsIAtom;
 
 #define NS_IJSEVENTLISTENER_IID     \
-{ 0xe16e7146, 0x109d, 0x4f54, \
-  { 0x94, 0x78, 0xda, 0xc4, 0x3a, 0x71, 0x0b, 0x52 } }
+{ 0x8b4f3ad1, 0x1c2a, 0x43f0, \
+  { 0xac, 0x6c, 0x83, 0x33, 0xe9, 0xe1, 0xcb, 0x7e } }
 
 // Implemented by script event listeners. Used to retrieve the
 // script object corresponding to the event target.
 // (Note this interface is now used to store script objects for all
 // script languages, so is no longer JS specific)
 class nsIJSEventListener : public nsISupports
 {
 public:
@@ -76,17 +77,17 @@ public:
 
   void *GetEventScope()
   {
     return mScopeObject;
   }
 
   virtual void SetEventName(nsIAtom* aName) = 0;
 
-  virtual void ToString(const nsAString& aEventName, nsAString& aResult) = 0;
+  virtual nsresult GetJSVal(const nsAString& aEventName, jsval* aJSVal) = 0;
 
 protected:
   virtual ~nsIJSEventListener()
   {
   }
   nsCOMPtr<nsIScriptContext> mContext;
   void *mScopeObject;
   nsCOMPtr<nsISupports> mTarget;
--- a/dom/src/events/nsJSEventListener.cpp
+++ b/dom/src/events/nsJSEventListener.cpp
@@ -135,43 +135,33 @@ NS_IMPL_CYCLE_COLLECTING_RELEASE_AMBIGUO
 //static nsString onPrefix = "on";
 
 void
 nsJSEventListener::SetEventName(nsIAtom* aName)
 {
   mEventName = aName;
 }
 
-void
-nsJSEventListener::ToString(const nsAString& aEventName, nsAString& aResult)
+nsresult
+nsJSEventListener::GetJSVal(const nsAString& aEventName, jsval* aJSVal)
 {
-  aResult.Truncate();
-  nsCOMPtr<nsIThreadJSContextStack> stack = nsContentUtils::ThreadJSContextStack();
   nsCOMPtr<nsPIDOMEventTarget> target = do_QueryInterface(mTarget);
-  if (target && stack && mContext) {
-    JSContext* cx = static_cast<JSContext*>(mContext->GetNativeContext());
-    if (cx && NS_SUCCEEDED(stack->Push(cx))) {
-      JSAutoRequest ar(cx);
-      nsAutoString eventString = NS_LITERAL_STRING("on") + aEventName;
-      nsCOMPtr<nsIAtom> atomName = do_GetAtom(eventString);
-      nsScriptObjectHolder funcval(mContext);
-      mContext->GetBoundEventHandler(mTarget, mScopeObject, atomName,
-                                     funcval);
-      jsval funval =
-        OBJECT_TO_JSVAL(static_cast<JSObject*>(static_cast<void*>(funcval)));
-
-      JSString* str =
-        JS_ValueToSource(static_cast<JSContext*>(mContext->GetNativeContext()),
-                         funval);
-      if (str) {
-        aResult.Assign(nsDependentJSString(str));
-      }
-      stack->Pop(&cx);
-    }
+  if (target && mContext) {
+    nsAutoString eventString = NS_LITERAL_STRING("on") + aEventName;
+    nsCOMPtr<nsIAtom> atomName = do_GetAtom(eventString);
+    nsScriptObjectHolder funcval(mContext);
+    nsresult rv = mContext->GetBoundEventHandler(mTarget, mScopeObject,
+                                                 atomName, funcval);
+    NS_ENSURE_SUCCESS(rv, rv);
+    jsval funval =
+      OBJECT_TO_JSVAL(static_cast<JSObject*>(static_cast<void*>(funcval)));
+    *aJSVal = funval;
+    return NS_OK;
   }
+  return NS_ERROR_FAILURE;
 }
 
 nsresult
 nsJSEventListener::HandleEvent(nsIDOMEvent* aEvent)
 {
   nsresult rv;
   nsCOMPtr<nsIArray> iargv;
   nsAutoString eventString;
--- a/dom/src/events/nsJSEventListener.h
+++ b/dom/src/events/nsJSEventListener.h
@@ -60,17 +60,17 @@ public:
   NS_DECL_CYCLE_COLLECTING_ISUPPORTS
 
   // nsIDOMEventListener interface
   NS_DECL_NSIDOMEVENTLISTENER
 
   // nsIJSEventListener interface
   virtual void SetEventName(nsIAtom* aName);
 
-  virtual void ToString(const nsAString& aEventName, nsAString& aResult);
+  virtual nsresult GetJSVal(const nsAString& aEventName, jsval* aJSVal);
 
   NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS_AMBIGUOUS(nsJSEventListener,
                                                          nsIDOMEventListener)
 protected:
   nsCOMPtr<nsIAtom> mEventName;
   
   enum nsReturnResult {
      nsReturnResult_eNotSet,
--- a/js/jsd/idl/jsdIDebuggerService.idl
+++ b/js/jsd/idl/jsdIDebuggerService.idl
@@ -48,16 +48,17 @@
 [ptr] native JSDObject(JSDObject);
 [ptr] native JSDProperty(JSDProperty);
 [ptr] native JSDScript(JSDScript);
 [ptr] native JSDStackFrameInfo(JSDStackFrameInfo);
 [ptr] native JSDThreadState(JSDThreadState);
 [ptr] native JSDValue(JSDValue);
 [ptr] native JSRuntime(JSRuntime);
 [ptr] native JSContext(JSContext);
+native JSVal(jsval);
 
 /* interfaces we declare in this file */
 interface jsdIDebuggerService;
 interface jsdIFilter;
 interface jsdINestCallback;
 interface jsdIFilterEnumerator;
 interface jsdIContextEnumerator;
 interface jsdIScriptEnumerator;
@@ -72,17 +73,17 @@ interface jsdIScript;
 interface jsdIValue;
 interface jsdIObject;
 interface jsdIProperty;
 
 /**
  * Debugger service.  It's not a good idea to have more than one active client of
  * the debugger service.
  */
-[scriptable, uuid(e86c0173-49e2-48f3-b53f-b5b2691a7e45)]
+[scriptable, uuid(dc0a24db-f8ac-4889-80d0-6016545a2dda)]
 interface jsdIDebuggerService : nsISupports
 {
     /** Internal use only. */
     [noscript] readonly attribute JSDContext        JSDContext;
 
     /**
      * Called when an error or warning occurs.
      */
@@ -354,16 +355,21 @@ interface jsdIDebuggerService : nsISuppo
 
     /**
      * When called from JavaScript, this method returns the jsdIValue wrapper
      * for the given value.  If a wrapper does not exist one will be created.
      * When called from another language this method returns an xpconnect
      * defined error code.
      */
     jsdIValue wrapValue (/*in jsvalue value*/);
+
+    /**
+     * The same as above but to be called from C++.
+     */
+    [noscript] jsdIValue wrapJSValue (in JSVal value);
     
     /* XXX these two routines are candidates for refactoring.  The only problem
      * is that it is not clear where and how they should land.
      */
 
     /**
      * Push a new network queue, and enter a new UI event loop.
      * @param callback jsdINestCallback instance to be called back after the
--- a/js/jsd/jsd_xpc.cpp
+++ b/js/jsd/jsd_xpc.cpp
@@ -2933,17 +2933,23 @@ jsdService::WrapValue(jsdIValue **_rval)
     if (argc < 1)
         return NS_ERROR_INVALID_ARG;
     
     jsval    *argv;
     rv = cc->GetArgvPtr (&argv);
     if (NS_FAILED(rv))
         return rv;
 
-    JSDValue *jsdv = JSD_NewValue (mCx, argv[0]);
+    return WrapJSValue(argv[0], _rval);
+}
+
+NS_IMETHODIMP
+jsdService::WrapJSValue(jsval value, jsdIValue** _rval)
+{
+    JSDValue *jsdv = JSD_NewValue(mCx, value);
     if (!jsdv)
         return NS_ERROR_FAILURE;
     
     *_rval = jsdValue::FromPtr (mCx, jsdv);
     return NS_OK;
 }