/** * PANDA 3D SOFTWARE * Copyright (c) Carnegie Mellon University. All rights reserved. * * All use of this software is subject to the terms of the revised BSD * license. You should have received a copy of this license along * with this source code in a file named "LICENSE." * * @file browsermodule.c * @author rdb * @date 2021-02-02 */ #undef _POSIX_C_SOURCE #undef _XOPEN_SOURCE #define PY_SSIZE_T_CLEAN 1 #include "Python.h" #include #include /** * emval interface, excerpted from emscripten/val.h (which is C++) */ typedef const void* TYPEID; void _emval_register_symbol(const char*); enum { _EMVAL_UNDEFINED = 1, _EMVAL_NULL = 2, _EMVAL_TRUE = 3, _EMVAL_FALSE = 4 }; typedef struct _EM_VAL* EM_VAL; typedef struct _EM_DESTRUCTORS* EM_DESTRUCTORS; typedef struct _EM_METHOD_CALLER* EM_METHOD_CALLER; typedef double EM_GENERIC_WIRE_TYPE; typedef const void* EM_VAR_ARGS; void _emval_incref(EM_VAL value); void _emval_decref(EM_VAL value); void _emval_run_destructors(EM_DESTRUCTORS handle); EM_VAL _emval_new_array(); EM_VAL _emval_new_object(); EM_VAL _emval_new_cstring(const char*); EM_VAL _emval_take_value(TYPEID type, EM_VAR_ARGS argv); EM_VAL _emval_new( EM_VAL value, unsigned argCount, const TYPEID argTypes[], EM_VAR_ARGS argv); EM_VAL _emval_get_global(const char* name); EM_VAL _emval_get_module_property(const char* name); EM_VAL _emval_get_property(EM_VAL object, EM_VAL key); void _emval_set_property(EM_VAL object, EM_VAL key, EM_VAL value); EM_GENERIC_WIRE_TYPE _emval_as(EM_VAL value, TYPEID returnType, EM_DESTRUCTORS* destructors); bool _emval_equals(EM_VAL first, EM_VAL second); bool _emval_strictly_equals(EM_VAL first, EM_VAL second); bool _emval_greater_than(EM_VAL first, EM_VAL second); bool _emval_less_than(EM_VAL first, EM_VAL second); bool _emval_not(EM_VAL object); EM_VAL _emval_call( EM_VAL value, unsigned argCount, const TYPEID argTypes[], EM_VAR_ARGS argv); // DO NOT call this more than once per signature. It will // leak generated function objects! EM_METHOD_CALLER _emval_get_method_caller( unsigned argCount, // including return value const TYPEID argTypes[]); EM_GENERIC_WIRE_TYPE _emval_call_method( EM_METHOD_CALLER caller, EM_VAL handle, const char* methodName, EM_DESTRUCTORS* destructors, EM_VAR_ARGS argv); void _emval_call_void_method( EM_METHOD_CALLER caller, EM_VAL handle, const char* methodName, EM_VAR_ARGS argv); EM_VAL _emval_typeof(EM_VAL value); bool _emval_instanceof(EM_VAL object, EM_VAL constructor); bool _emval_is_number(EM_VAL object); bool _emval_is_string(EM_VAL object); bool _emval_in(EM_VAL item, EM_VAL object); bool _emval_delete(EM_VAL object, EM_VAL property); bool _emval_throw(EM_VAL object); EM_VAL _emval_await(EM_VAL promise); /** * Forward declarations. */ static EM_VAL py_to_emval(PyObject *val); static PyObject *emval_to_py(EM_VAL val); static PyTypeObject PyBrowserObject_Type; /** * Definition of JS Object type. */ typedef struct { PyObject_HEAD EM_VAL handle; EM_VAL bound; } PyBrowserObject; /** * Constructs a new Object, or if given a single argument, simply wraps that in * an Object (like how it works in JavaScript). */ static int Object_init(PyBrowserObject *self, PyObject *args, PyObject *kwargs) { if (kwargs != NULL && PyDict_Size(kwargs) > 0) { PyErr_SetString(PyExc_TypeError, "Object() takes no keyword arguments"); return -1; } if (PyTuple_GET_SIZE(args) > 0) { EM_VAL handle = py_to_emval(PyTuple_GET_ITEM(args, 0)); if (handle == NULL) { return -1; } self->handle = handle; } else { self->handle = _emval_new_object(); } self->bound = (EM_VAL)_EMVAL_UNDEFINED; return 0; } /** * Decrements refcount of this Object. */ static void Object_dealloc(PyBrowserObject *self) { _emval_decref(self->handle); if (self->bound != (EM_VAL)_EMVAL_UNDEFINED) { _emval_decref(self->bound); } Py_TYPE(self)->tp_free((PyObject *)self); } /** * Returns a string representation of the object. */ static PyObject * Object_repr(PyBrowserObject *self) { char *str = (char *)EM_ASM_INT({ var value = emval_handle_array[$0].value; var str = value.constructor ? value.constructor.name : 'Object'; var len = lengthBytesUTF8(str) + 1; var buffer = _malloc(len); stringToUTF8(str, buffer, len); return buffer; }, self->handle); PyObject *result = PyUnicode_FromFormat("[object %s]", str); free(str); return result; } /** * Hash function. */ static Py_hash_t Object_hash(PyBrowserObject *self) { return (Py_hash_t)self->handle; } /** * Calls the object. */ static PyObject * Object_call(PyBrowserObject *self, PyObject *args, PyObject *kwargs) { if (kwargs != NULL && PyDict_Size(kwargs) > 0) { PyErr_SetString(PyExc_TypeError, "__call__() takes no keyword arguments"); return NULL; } EM_VAL result; // emval has no elegant call interface, so we add it ourselves. Make more // optimal special cases for 0 and 1 args. int num_args = PyTuple_GET_SIZE(args); switch (num_args) { case 0: result = (EM_VAL)EM_ASM_INT(return __emval_register(emval_handle_array[$0].value.call(emval_handle_array[$1].value)), self->handle, self->bound); break; case 1: { EM_VAL arg_handle = py_to_emval(PyTuple_GET_ITEM(args, 0)); if (arg_handle == NULL) { return NULL; } result = (EM_VAL)EM_ASM_INT({ var result = __emval_register(emval_handle_array[$0].value.call(emval_handle_array[$1].value, emval_handle_array[$2].value)); __emval_decref($2); return result; }, self->handle, self->bound, arg_handle); } break; default: { EM_VAL *arg_handles = (EM_VAL *)alloca(num_args * sizeof(EM_VAL)); for (int i = 0; i < num_args; ++i) { EM_VAL handle = py_to_emval(PyTuple_GET_ITEM(args, i)); if (handle == NULL) { while (--i >= 0) { _emval_decref(arg_handles[i]); } return NULL; } arg_handles[i] = handle; } result = (EM_VAL)EM_ASM_INT({ var arg_handles = []; var arg_values = []; for (var i = 0; i < $2; ++i) { var arg_handle = getValue($3+i*4, '*'); arg_handles.push(arg_handle); arg_values.push(emval_handle_array[arg_handle].value); } var result = __emval_register(emval_handle_array[$0].value.apply(emval_handle_array[$1].value, arg_values)); for (var i = 0; i < $2; ++i) { __emval_decref(arg_handles[i]); } return result; }, self->handle, self->bound, num_args, arg_handles); } break; } return emval_to_py(result); } /** * Returns a string representation of the object. */ static PyObject * Object_str(PyBrowserObject *self) { char *str = (char *)EM_ASM_INT({ var str = emval_handle_array[$0].value.toString(); var len = lengthBytesUTF8(str) + 1; var buffer = _malloc(len); stringToUTF8(str, buffer, len); return buffer; }, self->handle); PyObject *result = PyUnicode_FromString(str); free(str); return result; } /** * Implements len(). */ static Py_ssize_t Object_length(PyBrowserObject *self) { int len = EM_ASM_INT({ var val = emval_handle_array[$0].value; if (val[Symbol.iterator] && val.length !== undefined) { return val.length; } else { return -1; } }, self->handle); if (len >= 0) { return len; } else { PyErr_SetString(PyExc_TypeError, "object has no len()"); return -1; } } /** * Gets a property of this object. */ static PyObject * Object_getprop(PyBrowserObject *self, PyObject *item) { EM_VAL key_val = py_to_emval(item); if (key_val == NULL) { return NULL; } EM_VAL result = _emval_get_property(self->handle, key_val); _emval_decref(key_val); //if (result == (EM_VAL)_EMVAL_UNDEFINED) { // return NULL; //} PyObject *obj = emval_to_py(result); // Remember self, so that we can make bound method calls. if (Py_TYPE(obj) == &PyBrowserObject_Type) { _emval_incref(self->handle); ((PyBrowserObject *)obj)->bound = self->handle; } return obj; } /** * Sets a property of this object. */ static int Object_setprop(PyBrowserObject *self, PyObject *item, PyObject *value) { EM_VAL key_val = py_to_emval(item); if (key_val == NULL) { return -1; } if (value != NULL) { EM_VAL value_val = py_to_emval(value); if (value_val == NULL) { return -1; } _emval_set_property(self->handle, key_val, value_val); _emval_decref(value_val); } else { if (!_emval_delete(self->handle, key_val)) { //_emval_decref(key_val); //return -1; } } _emval_decref(key_val); return 0; } /** * Comparison. */ static PyObject * Object_richcompare(PyBrowserObject *self, PyObject *other, int op) { EM_VAL other_handle = py_to_emval(other); if (other_handle == NULL) { PyErr_Clear(); Py_INCREF(Py_NotImplemented); return Py_NotImplemented; } switch (op) { case Py_LT: return PyBool_FromLong(_emval_less_than(self->handle, other_handle)); case Py_LE: return PyBool_FromLong(!_emval_greater_than(self->handle, other_handle)); case Py_EQ: return PyBool_FromLong(self->handle == other_handle || _emval_strictly_equals(self->handle, other_handle)); case Py_NE: return PyBool_FromLong(self->handle != other_handle && !_emval_strictly_equals(self->handle, other_handle)); case Py_GT: return PyBool_FromLong(_emval_greater_than(self->handle, other_handle)); case Py_GE: return PyBool_FromLong(!_emval_less_than(self->handle, other_handle)); } return NULL; } /** * Implements iter(). */ static PyObject * Object_iter(PyBrowserObject *self) { EM_VAL val = (EM_VAL)EM_ASM_INT({ var val = emval_handle_array[$0].value; if (val[Symbol.iterator]) { return __emval_register(val[Symbol.iterator]()); } else { return 0; } }, self->handle); if (val == self->handle) { // Common case, don't create a new wrapper. Py_INCREF(self); return (PyObject *)self; } else if (val != NULL) { return emval_to_py(val); } else { PyErr_SetString(PyExc_TypeError, "object has no iter()"); return NULL; } } /** * Implements next(). */ static PyObject * Object_next(PyBrowserObject *self) { EM_VAL val = (EM_VAL)EM_ASM_INT({ var val = emval_handle_array[$0].value; if (!val.next) { return 0; } var result = val.next(); if (result && !result.done) { return __emval_register(result.value); } else { return 0; } }, self->handle); if (val != NULL) { return emval_to_py(val); } else { return NULL; } } /** * Implements dir(). */ static PyObject * Object_dir(PyBrowserObject *self, PyObject *noarg) { return emval_to_py((EM_VAL)EM_ASM_INT({ var props = []; for (var prop in emval_handle_array[$0].value) { props.push(prop); } return __emval_register(props); }, self->handle)); } static PyMappingMethods Object_mapping = { .mp_length = (lenfunc)Object_length, .mp_subscript = (binaryfunc)Object_getprop, .mp_ass_subscript = (objobjargproc)Object_setprop, }; static PyMethodDef Object_methods[] = { { "__dir__", (PyCFunction)Object_dir, METH_NOARGS }, { NULL, NULL }, }; static PyTypeObject PyBrowserObject_Type = { PyVarObject_HEAD_INIT(NULL, 0) .tp_name = "browser.Object", .tp_doc = "JavaScript object", .tp_basicsize = sizeof(PyBrowserObject), .tp_itemsize = 0, .tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE, .tp_dealloc = (destructor)Object_dealloc, .tp_repr = (reprfunc)Object_repr, .tp_as_mapping = &Object_mapping, .tp_hash = (hashfunc)Object_hash, .tp_call = (ternaryfunc)Object_call, .tp_str = (reprfunc)Object_str, .tp_getattro = (getattrofunc)Object_getprop, .tp_setattro = (setattrofunc)Object_setprop, .tp_richcompare = (richcmpfunc)Object_richcompare, .tp_iter = (getiterfunc)Object_iter, .tp_iternext = (iternextfunc)Object_next, .tp_methods = Object_methods, .tp_new = PyType_GenericNew, }; /** * Returns the given Python object as emval. */ static EM_VAL py_to_emval(PyObject *val) { if (val == Py_None) { return (EM_VAL)_EMVAL_NULL; } else if (val == Py_True) { return (EM_VAL)_EMVAL_TRUE; } else if (val == Py_False) { return (EM_VAL)_EMVAL_FALSE; } else if (Py_TYPE(val) == &PyBrowserObject_Type) { EM_VAL handle = ((PyBrowserObject *)val)->handle; _emval_incref(handle); return handle; } else if (PyFloat_Check(val)) { return (EM_VAL)EM_ASM_INT(return __emval_register($0), PyFloat_AsDouble(val)); } else if (PyLong_Check(val)) { return (EM_VAL)EM_ASM_INT(return __emval_register($0), PyLong_AsLong(val)); } else if (PyUnicode_Check(val)) { return _emval_new_cstring(PyUnicode_AsUTF8(val)); } else { PyErr_SetString(PyExc_TypeError, "type not valid for browser"); return NULL; } } /** * And the other way around. Steals a reference to val. */ static PyObject *emval_to_py(EM_VAL val) { if (val == (EM_VAL)_EMVAL_TRUE) { Py_INCREF(Py_True); return Py_True; } if (val == (EM_VAL)_EMVAL_FALSE) { Py_INCREF(Py_False); return Py_False; } if (val == (EM_VAL)_EMVAL_UNDEFINED || val == (EM_VAL)_EMVAL_NULL) { Py_INCREF(Py_None); return Py_None; } if (_emval_is_number(val)) { return PyFloat_FromDouble(EM_ASM_DOUBLE({ var value = emval_handle_array[$0].value; __emval_decref($0); return value; }, val)); } else if (_emval_is_string(val)) { char *str = (char *)EM_ASM_INT({ var str = emval_handle_array[$0].value; var len = lengthBytesUTF8(str) + 1; var buffer = _malloc(len); stringToUTF8(str, buffer, len); __emval_decref($0); return buffer; }, val); PyObject *result = PyUnicode_FromString(str); free(str); return result; } else { PyBrowserObject *obj = PyObject_New(PyBrowserObject, &PyBrowserObject_Type); obj->handle = val; obj->bound = (EM_VAL)_EMVAL_UNDEFINED; return (PyObject *)obj; } } /** * Opens an alert window to print the given message. */ static PyObject * browser_alert(PyObject *self, PyObject *arg) { const char *message = PyUnicode_AsUTF8(arg); if (message != NULL) { EM_ASM(alert(UTF8ToString($0)), message); Py_INCREF(Py_None); return Py_None; } return NULL; } /** * Opens an yes/no confirmation box, returning True or False. */ static PyObject * browser_confirm(PyObject *self, PyObject *arg) { const char *message = PyUnicode_AsUTF8(arg); if (message != NULL) { int result = EM_ASM_INT({ return confirm(UTF8ToString($0)); }, message); return PyBool_FromLong(result); } return NULL; } /** * Opens a prompt box. */ static PyObject * browser_prompt(PyObject *self, PyObject *args) { char *message; char *default_value = NULL; if (PyArg_ParseTuple(args, "s|s", &message, &default_value)) { char *str = (char *)EM_ASM_INT({ var str = prompt(UTF8ToString($0), $1 ? UTF8ToString($1) : undefined); if (str === null) { return 0; } var len = lengthBytesUTF8(str) + 1; var buffer = _malloc(len); stringToUTF8(str, buffer, len); return buffer; }, message, default_value); PyObject *result; if (str != NULL) { result = PyUnicode_FromString(str); free(str); } else { result = Py_None; Py_INCREF(result); } return result; } return NULL; } static PyMethodDef browser_functions[] = { { "alert", &browser_alert, METH_O }, { "confirm", &browser_confirm, METH_O }, { "prompt", &browser_prompt, METH_VARARGS }, { NULL, NULL } }; static struct PyModuleDef browser_module = { PyModuleDef_HEAD_INIT, "browser", NULL, -1, browser_functions, NULL, NULL, NULL, NULL }; #ifdef _WIN32 extern __declspec(dllexport) PyObject *PyInit_browser(); #elif __GNUC__ >= 4 extern __attribute__((visibility("default"))) PyObject *PyInit_browser(); #else extern PyObject *PyInit_browser(); #endif PyObject *PyInit_browser() { if (PyType_Ready(&PyBrowserObject_Type) < 0) { return NULL; } PyObject *module = PyModule_Create(&browser_module); if (module == NULL) { return NULL; } PyBrowserObject *window = PyObject_New(PyBrowserObject, &PyBrowserObject_Type); if (PyModule_AddObject(module, "window", (PyObject *)window) < 0) { Py_DECREF(&PyBrowserObject_Type); Py_DECREF(module); return NULL; } window->handle = _emval_get_global("window"); window->bound = (EM_VAL)_EMVAL_UNDEFINED; PyBrowserObject *console = PyObject_New(PyBrowserObject, &PyBrowserObject_Type); if (PyModule_AddObject(module, "console", (PyObject *)console) < 0) { Py_DECREF(&PyBrowserObject_Type); Py_DECREF(module); return NULL; } console->handle = _emval_get_global("console"); console->bound = (EM_VAL)_EMVAL_UNDEFINED; PyBrowserObject *document = PyObject_New(PyBrowserObject, &PyBrowserObject_Type); if (PyModule_AddObject(module, "document", (PyObject *)document) < 0) { Py_DECREF(&PyBrowserObject_Type); Py_DECREF(module); return NULL; } document->handle = _emval_get_global("document"); document->bound = (EM_VAL)_EMVAL_UNDEFINED; return module; }