changeset 84:10205d88f6ff

JS-wrapped Python functions can now be passed to context.get_object_private() and context.clear_object_private(), which allows cycles to be manually broken from Python. Far from ideal, but easier than writing a cycle collector for now.
author Atul Varma <varmaa@toolness.com>
date Sun, 09 Aug 2009 15:18:33 -0700
parents ac40f4e03e91
children e9f450d30c0e
files context.c test_pymonkey.py
diffstat 2 files changed, 62 insertions(+), 4 deletions(-) [+]
line wrap: on
line diff
--- a/context.c	Sun Aug 09 14:47:35 2009 -0700
+++ b/context.c	Sun Aug 09 15:18:33 2009 -0700
@@ -118,13 +118,26 @@
   if (!PyArg_ParseTuple(args, "O!", &PYM_JSObjectType, &object))
     return NULL;
 
-  JSClass *klass = JS_GET_CLASS(self->cx, object->obj);
+  JSObject *obj = object->obj;
+
+  if (JS_ObjectIsFunction(self->cx, obj)) {
+    jsval functionHolder;
+    if (!JS_GetReservedSlot(self->cx, obj, 0, &functionHolder)) {
+      JS_ClearPendingException(self->cx);
+      Py_RETURN_NONE;
+    }
+    if (!JSVAL_IS_OBJECT(functionHolder))
+      Py_RETURN_NONE;
+    obj = JSVAL_TO_OBJECT(functionHolder);
+  }
+
+  JSClass *klass = JS_GET_CLASS(self->cx, obj);
   if (klass != &PYM_JS_ObjectClass)
     Py_RETURN_NONE;
 
   PyObject *pyObject;
 
-  if (!PYM_JS_getPrivatePyObject(self->cx, object->obj, &pyObject)) {
+  if (!PYM_JS_getPrivatePyObject(self->cx, obj, &pyObject)) {
     PYM_jsExceptionToPython(self);
     return NULL;
   }
@@ -144,11 +157,24 @@
   if (!PyArg_ParseTuple(args, "O!", &PYM_JSObjectType, &object))
     return NULL;
 
-  JSClass *klass = JS_GET_CLASS(self->cx, object->obj);
+  JSObject *obj = object->obj;
+
+  if (JS_ObjectIsFunction(self->cx, obj)) {
+    jsval functionHolder;
+    if (!JS_GetReservedSlot(self->cx, obj, 0, &functionHolder)) {
+      JS_ClearPendingException(self->cx);
+      Py_RETURN_NONE;
+    }
+    if (!JSVAL_IS_OBJECT(functionHolder))
+      Py_RETURN_NONE;
+    obj = JSVAL_TO_OBJECT(functionHolder);
+  }
+
+  JSClass *klass = JS_GET_CLASS(self->cx, obj);
   if (klass != &PYM_JS_ObjectClass)
     Py_RETURN_NONE;
 
-  if (!PYM_JS_setPrivatePyObject(self->cx, object->obj, Py_None)) {
+  if (!PYM_JS_setPrivatePyObject(self->cx, obj, Py_None)) {
     PYM_jsExceptionToPython(self);
     return NULL;
   }
--- a/test_pymonkey.py	Sun Aug 09 14:47:35 2009 -0700
+++ b/test_pymonkey.py	Sun Aug 09 15:18:33 2009 -0700
@@ -81,6 +81,14 @@
         self.assertEqual(str(pymonkey.undefined),
                          "pymonkey.undefined")
 
+    def testJsWrappedPythonFuncHasPrivate(self):
+        def foo(cx, this, args):
+            pass
+
+        cx = pymonkey.Runtime().new_context()
+        jsfunc = cx.new_function(foo, foo.__name__)
+        self.assertEqual(cx.get_object_private(jsfunc), foo)
+
     def testJsWrappedPythonFuncIsNotGCd(self):
         def define(cx, obj):
             def func(cx, this, args):
@@ -104,6 +112,30 @@
         cx.gc()
         self.assertEqual(ref(), None)
 
+    def testCircularJsWrappedPythonFuncIsGCdIfPrivateCleared(self):
+        def define(cx, obj):
+            rt = cx.get_runtime()
+            def func(cx, this, args):
+                # Oh noes, a circular reference is born!
+                rt
+            jsfunc = cx.new_function(func, func.__name__)
+            cx.define_property(obj, func.__name__, jsfunc)
+            return (jsfunc, weakref.ref(func))
+        rt = pymonkey.Runtime()
+        cx = rt.new_context()
+        obj = cx.new_object()
+        cx.init_standard_classes(obj)
+        jsfunc, ref = define(cx, obj)
+
+        # This will break the circular reference.
+        cx.clear_object_private(jsfunc)
+
+        del jsfunc
+        del rt
+        del cx
+        del obj
+        self.assertEqual(ref(), None)
+
     def testJsWrappedPythonFuncIsGCdAtRuntimeDestruction(self):
         def define(cx, obj):
             def func(cx, this, args):