Mercurial > pymonkey
diff src/context.cpp @ 117:ac8ca0ee7760
Moved all .cpp/.h files into 'src' dir and test suite into 'tests' dir.
author | Atul Varma <varmaa@toolness.com> |
---|---|
date | Mon, 17 Aug 2009 22:08:33 -0700 |
parents | context.cpp@00c1351b3e82 |
children | 5eda03c8e756 |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/context.cpp Mon Aug 17 22:08:33 2009 -0700 @@ -0,0 +1,553 @@ +/* ***** 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 Pymonkey. + * + * The Initial Developer of the Original Code is Mozilla. + * Portions created by the Initial Developer are Copyright (C) 2007 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * Atul Varma <atul@mozilla.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 "context.h" +#include "object.h" +#include "function.h" +#include "utils.h" + +// This is the default JSOperationCallback for pymonkey-owned JS contexts, +// when they've defined one in Python. +static JSBool +PYM_operationCallback(JSContext *cx) +{ + PYM_PyAutoEnsureGIL gil; + PYM_JSContextObject *context = (PYM_JSContextObject *) + JS_GetContextPrivate(cx); + + PyObject *callable = context->opCallback; + PyObject *args = PyTuple_Pack(1, (PyObject *) context); + if (args == NULL) { + JS_ReportOutOfMemory(cx); + return JS_FALSE; + } + PyObject *result = PyObject_Call(callable, args, NULL); + Py_DECREF(args); + if (result == NULL) { + PYM_pythonExceptionToJs(context); + return JS_FALSE; + } + + Py_DECREF(result); + return JS_TRUE; +} + +// This is the default JSErrorReporter for pymonkey-owned JS contexts. +static void +PYM_reportError(JSContext *cx, const char *message, JSErrorReport *report) +{ + PYM_PyAutoEnsureGIL gil; + + // Convert JS warnings into Python warnings. + if (JSREPORT_IS_WARNING(report->flags)) { + if (report->filename) + PyErr_WarnExplicit(NULL, message, report->filename, report->lineno, + NULL, NULL); + else + PyErr_Warn(NULL, message); + } else + // TODO: Not sure if this will ever get called, but we should know + // if it is. + PyErr_Warn(NULL, "A JS error was reported."); +} + +static void +PYM_JSContextDealloc(PYM_JSContextObject *self) +{ + if (self->opCallback) { + Py_DECREF(self->opCallback); + self->opCallback = NULL; + } + + if (self->cx) { + JS_DestroyContext(self->cx); + self->cx = NULL; + } + + Py_DECREF(self->runtime); + self->runtime = NULL; + + self->ob_type->tp_free((PyObject *) self); +} + +static PyObject * +PYM_getRuntime(PYM_JSContextObject *self, PyObject *args) +{ + Py_INCREF(self->runtime); + return (PyObject *) self->runtime; +} + +static PyObject * +PYM_getObjectPrivate(PYM_JSContextObject *self, PyObject *args) +{ + PYM_SANITY_CHECK(self->runtime); + PYM_JSObject *object; + + if (!PyArg_ParseTuple(args, "O!", &PYM_JSObjectType, &object)) + return NULL; + + PYM_ENSURE_RUNTIME_MATCH(self->runtime, object->runtime); + + 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, obj, &pyObject)) { + PYM_jsExceptionToPython(self); + return NULL; + } + + if (pyObject == NULL) + pyObject = Py_None; + + Py_INCREF(pyObject); + return pyObject; +} + +static PyObject * +PYM_clearObjectPrivate(PYM_JSContextObject *self, PyObject *args) +{ + PYM_SANITY_CHECK(self->runtime); + PYM_JSObject *object; + + if (!PyArg_ParseTuple(args, "O!", &PYM_JSObjectType, &object)) + return NULL; + + PYM_ENSURE_RUNTIME_MATCH(self->runtime, object->runtime); + + 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, obj, Py_None)) { + PYM_jsExceptionToPython(self); + return NULL; + } + + Py_RETURN_NONE; +} + +static PyObject * +PYM_newObject(PYM_JSContextObject *self, PyObject *args) +{ + PYM_SANITY_CHECK(self->runtime); + PyObject *privateObj = NULL; + + if (!PyArg_ParseTuple(args, "|O", &privateObj)) + return NULL; + + JSObject *obj = PYM_JS_newObject(self->cx, privateObj); + if (obj == NULL) { + PyErr_SetString(PYM_error, "PYM_JS_newObject() failed"); + return NULL; + } + + // If this fails, we don't need to worry about cleaning up + // obj because it'll get cleaned up at the next GC. + return (PyObject *) PYM_newJSObject(self, obj, NULL); +} + +static PyObject * +PYM_getProperty(PYM_JSContextObject *self, PyObject *args) +{ + PYM_SANITY_CHECK(self->runtime); + PYM_JSObject *object; + char *buffer = NULL; + int size; + + if (!PyArg_ParseTuple(args, "O!es#", &PYM_JSObjectType, &object, + "utf-16", &buffer, &size)) + return NULL; + + if (self->runtime != object->runtime) { + PyMem_Free(buffer); + PYM_ENSURE_RUNTIME_MATCH(self->runtime, object->runtime); + } + + jsval val; + JSBool result; + Py_BEGIN_ALLOW_THREADS; + // Note that we're manipulating buffer and size here to get rid of + // the BOM. + result = JS_GetUCProperty(self->cx, object->obj, (jschar *) (buffer + 2), + (size / 2) - 1, &val); + Py_END_ALLOW_THREADS; + + PyMem_Free(buffer); + + if (!result) { + PYM_jsExceptionToPython(self); + return NULL; + } + + return PYM_jsvalToPyObject(self, val); +} + +static PyObject * +PYM_gc(PYM_JSContextObject *self, PyObject *args) +{ + PYM_SANITY_CHECK(self->runtime); + JS_GC(self->cx); + Py_RETURN_NONE; +} + +static PyObject * +PYM_initStandardClasses(PYM_JSContextObject *self, PyObject *args) +{ + PYM_SANITY_CHECK(self->runtime); + PYM_JSObject *object; + + if (!PyArg_ParseTuple(args, "O!", &PYM_JSObjectType, &object)) + return NULL; + + PYM_ENSURE_RUNTIME_MATCH(self->runtime, object->runtime); + + if (!JS_InitStandardClasses(self->cx, object->obj)) { + PyErr_SetString(PYM_error, "JS_InitStandardClasses() failed"); + return NULL; + } + + Py_RETURN_NONE; +} + +static PyObject * +PYM_evaluateScript(PYM_JSContextObject *self, PyObject *args) +{ + PYM_SANITY_CHECK(self->runtime); + PYM_JSObject *object; + const char *source; + int sourceLen; + const char *filename; + int lineNo; + + if (!PyArg_ParseTuple(args, "O!s#si", &PYM_JSObjectType, &object, + &source, &sourceLen, &filename, &lineNo)) + return NULL; + + PYM_ENSURE_RUNTIME_MATCH(self->runtime, object->runtime); + + jsval rval; + JSBool result; + Py_BEGIN_ALLOW_THREADS; + result = JS_EvaluateScript(self->cx, object->obj, source, sourceLen, + filename, lineNo, &rval); + Py_END_ALLOW_THREADS; + + if (!result) { + PYM_jsExceptionToPython(self); + return NULL; + } + + PyObject *pyRval = PYM_jsvalToPyObject(self, rval); + return pyRval; +} + +static PyObject * +PYM_defineProperty(PYM_JSContextObject *self, PyObject *args) +{ + PYM_SANITY_CHECK(self->runtime); + PYM_JSObject *object; + PyObject *value; + char *name = NULL; + int namelen; + + if (!PyArg_ParseTuple(args, "O!es#O", &PYM_JSObjectType, &object, + "utf-16", &name, &namelen, &value)) + return NULL; + + if (self->runtime != object->runtime) { + PyMem_Free(name); + PYM_ENSURE_RUNTIME_MATCH(self->runtime, object->runtime); + } + + jsval jsValue; + + if (PYM_pyObjectToJsval(self, value, &jsValue) == -1) { + PyMem_Free(name); + return NULL; + } + + // Note that we're manipulating buffer and size here to get rid of + // the BOM. + if (!JS_DefineUCProperty(self->cx, object->obj, (jschar *) (name + 2), + (namelen / 2) - 1, jsValue, NULL, NULL, + JSPROP_ENUMERATE)) { + // TODO: There's probably an exception pending on self->cx, + // what should we do about it? + PyMem_Free(name); + PyErr_SetString(PYM_error, "JS_DefineProperty() failed"); + return NULL; + } + + PyMem_Free(name); + Py_RETURN_NONE; +} + +static PyObject * +PYM_callFunction(PYM_JSContextObject *self, PyObject *args) +{ + PYM_SANITY_CHECK(self->runtime); + PYM_JSObject *obj; + PYM_JSFunction *fun; + PyObject *funcArgs; + + if (!PyArg_ParseTuple(args, "O!O!O!", &PYM_JSObjectType, &obj, + &PYM_JSFunctionType, &fun, + &PyTuple_Type, &funcArgs)) + return NULL; + + PYM_ENSURE_RUNTIME_MATCH(self->runtime, obj->runtime); + PYM_ENSURE_RUNTIME_MATCH(self->runtime, fun->base.runtime); + + uintN argc = PyTuple_Size(funcArgs); + + jsval *argv = (jsval *) PyMem_Malloc(sizeof(jsval) * argc); + if (argv == NULL) + return PyErr_NoMemory(); + + jsval *currArg = argv; + + for (unsigned int i = 0; i < argc; i++) { + PyObject *item = PyTuple_GET_ITEM(funcArgs, i); + if (item == NULL || + PYM_pyObjectToJsval(self, item, currArg) == -1) { + PyMem_Free(argv); + return NULL; + } + currArg++; + } + + jsval rval; + + // TODO: This assumes that a JSFunction * is actually a subclass of + // a JSObject *, which may or may not be regarded as an implementation + // detail. + JSBool result; + Py_BEGIN_ALLOW_THREADS; + result = JS_CallFunction(self->cx, obj->obj, + (JSFunction *) fun->base.obj, + argc, argv, &rval); + Py_END_ALLOW_THREADS; + + PyMem_Free(argv); + + if (!result) { + PYM_jsExceptionToPython(self); + return NULL; + } + + return PYM_jsvalToPyObject(self, rval); +} + +static PyObject * +PYM_newFunction(PYM_JSContextObject *self, PyObject *args) +{ + PYM_SANITY_CHECK(self->runtime); + PyObject *callable; + const char *name; + + if (!PyArg_ParseTuple(args, "Os", &callable, &name)) + return NULL; + + return (PyObject *) PYM_newJSFunctionFromCallable(self, callable, name); +} + +static PyObject * +PYM_setOperationCallback(PYM_JSContextObject *self, PyObject *args) +{ + PYM_SANITY_CHECK(self->runtime); + PyObject *callable; + + if (!PyArg_ParseTuple(args, "O", &callable)) + return NULL; + + if (!PyCallable_Check(callable)) { + PyErr_SetString(PyExc_TypeError, "Callable must be callable"); + return NULL; + } + + JS_SetOperationCallback(self->cx, PYM_operationCallback); + + Py_INCREF(callable); + if (self->opCallback) + Py_DECREF(self->opCallback); + self->opCallback = callable; + + Py_RETURN_NONE; +} + +static PyObject * +PYM_triggerOperationCallback(PYM_JSContextObject *self, PyObject *args) +{ + JS_TriggerOperationCallback(self->cx); + Py_RETURN_NONE; +} + +static PyMethodDef PYM_JSContextMethods[] = { + {"get_runtime", (PyCFunction) PYM_getRuntime, METH_VARARGS, + "Get the JavaScript runtime associated with this context."}, + {"new_object", (PyCFunction) PYM_newObject, METH_VARARGS, + "Create a new JavaScript object."}, + {"init_standard_classes", + (PyCFunction) PYM_initStandardClasses, METH_VARARGS, + "Add standard classes and functions to the given object."}, + {"evaluate_script", + (PyCFunction) PYM_evaluateScript, METH_VARARGS, + "Evaluate the given JavaScript code in the context of the given " + "global object, using the given filename" + "and line number information."}, + {"call_function", + (PyCFunction) PYM_callFunction, METH_VARARGS, + "Calls a JS function."}, + {"new_function", + (PyCFunction) PYM_newFunction, METH_VARARGS, + "Creates a new function callable from JS."}, + {"define_property", + (PyCFunction) PYM_defineProperty, METH_VARARGS, + "Defines a property on an object."}, + {"get_property", (PyCFunction) PYM_getProperty, METH_VARARGS, + "Gets the given property for the given JavaScript object."}, + {"gc", (PyCFunction) PYM_gc, METH_VARARGS, + "Performs garbage collection on the context's runtime."}, + {"set_operation_callback", (PyCFunction) PYM_setOperationCallback, + METH_VARARGS, + "Sets the operation callback for the context."}, + {"trigger_operation_callback", (PyCFunction) PYM_triggerOperationCallback, + METH_VARARGS, + "Triggers the operation callback for the context."}, + {"get_object_private", (PyCFunction) PYM_getObjectPrivate, METH_VARARGS, + "Returns the private Python object stored in the JavaScript object."}, + {"clear_object_private", (PyCFunction) PYM_clearObjectPrivate, METH_VARARGS, + "Clears any private Python object stored in the JavaScript object."}, + {NULL, NULL, 0, NULL} +}; + +PyTypeObject PYM_JSContextType = { + PyObject_HEAD_INIT(NULL) + 0, /*ob_size*/ + "pymonkey.Context", /*tp_name*/ + sizeof(PYM_JSContextObject), /*tp_basicsize*/ + 0, /*tp_itemsize*/ + /*tp_dealloc*/ + (destructor) PYM_JSContextDealloc, + 0, /*tp_print*/ + 0, /*tp_getattr*/ + 0, /*tp_setattr*/ + 0, /*tp_compare*/ + 0, /*tp_repr*/ + 0, /*tp_as_number*/ + 0, /*tp_as_sequence*/ + 0, /*tp_as_mapping*/ + 0, /*tp_hash */ + 0, /*tp_call*/ + 0, /*tp_str*/ + 0, /*tp_getattro*/ + 0, /*tp_setattro*/ + 0, /*tp_as_buffer*/ + Py_TPFLAGS_DEFAULT, /*tp_flags*/ + /* tp_doc */ + "JavaScript Context.", + 0, /* tp_traverse */ + 0, /* tp_clear */ + 0, /* tp_richcompare */ + 0, /* tp_weaklistoffset */ + 0, /* tp_iter */ + 0, /* tp_iternext */ + PYM_JSContextMethods, /* tp_methods */ + 0, /* tp_members */ + 0, /* tp_getset */ + 0, /* tp_base */ + 0, /* tp_dict */ + 0, /* tp_descr_get */ + 0, /* tp_descr_set */ + 0, /* tp_dictoffset */ + 0, /* tp_init */ + 0, /* tp_alloc */ + 0, /* tp_new */ +}; + +extern PYM_JSContextObject * +PYM_newJSContextObject(PYM_JSRuntimeObject *runtime, JSContext *cx) +{ + PYM_JSContextObject *context = PyObject_New(PYM_JSContextObject, + &PYM_JSContextType); + if (context == NULL) + return NULL; + + context->opCallback = NULL; + context->runtime = runtime; + Py_INCREF(runtime); + + context->cx = cx; + JS_SetContextPrivate(cx, context); + JS_SetErrorReporter(cx, PYM_reportError); + +#ifdef JS_GC_ZEAL + // TODO: Consider exposing JS_SetGCZeal() to Python instead of + // hard-coding it here. + JS_SetGCZeal(cx, 2); +#endif + + return context; +}