diff --git a/src/ecmascript/quickjs/Makefile b/src/ecmascript/quickjs/Makefile index 215ea443..4a590213 100644 --- a/src/ecmascript/quickjs/Makefile +++ b/src/ecmascript/quickjs/Makefile @@ -3,6 +3,6 @@ include $(top_builddir)/Makefile.config OBJS = attr.obj attributes.obj collection.obj console.obj document.obj element.obj form.obj \ forms.obj heartbeat.obj history.obj implementation.obj input.obj keyboard.obj location.obj \ - localstorage.obj navigator.obj nodelist.obj screen.obj unibar.obj window.obj xhr.obj + localstorage.obj message.obj navigator.obj nodelist.obj screen.obj unibar.obj window.obj xhr.obj include $(top_srcdir)/Makefile.lib diff --git a/src/ecmascript/quickjs/meson.build b/src/ecmascript/quickjs/meson.build index d7352bd6..c82de35d 100644 --- a/src/ecmascript/quickjs/meson.build +++ b/src/ecmascript/quickjs/meson.build @@ -1,2 +1,2 @@ srcs += files('attr.cpp', 'attributes.cpp', 'collection.cpp', 'console.cpp', 'document.cpp', 'element.cpp', 'form.cpp', 'forms.cpp', 'heartbeat.cpp', 'history.cpp', 'implementation.cpp', -'input.cpp', 'keyboard.cpp', 'localstorage.cpp', 'location.cpp', 'navigator.cpp', 'nodelist.cpp', 'screen.cpp', 'unibar.cpp', 'window.cpp', 'xhr.cpp') +'input.cpp', 'keyboard.cpp', 'localstorage.cpp', 'location.cpp', 'message.cpp', 'navigator.cpp', 'nodelist.cpp', 'screen.cpp', 'unibar.cpp', 'window.cpp', 'xhr.cpp') diff --git a/src/ecmascript/quickjs/message.cpp b/src/ecmascript/quickjs/message.cpp new file mode 100644 index 00000000..a52233c4 --- /dev/null +++ b/src/ecmascript/quickjs/message.cpp @@ -0,0 +1,240 @@ +/* The QuickJS MessageEvent object implementation. */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include +#include +#include + +#include "elinks.h" + +#include "bfu/dialog.h" +#include "cache/cache.h" +#include "cookies/cookies.h" +#include "dialogs/menu.h" +#include "dialogs/status.h" +#include "document/html/frames.h" +#include "document/document.h" +#include "document/forms.h" +#include "document/view.h" +#include "ecmascript/ecmascript.h" +#include "ecmascript/quickjs.h" +#include "ecmascript/quickjs/heartbeat.h" +#include "ecmascript/quickjs/message.h" +#include "ecmascript/timer.h" +#include "intl/libintl.h" +#include "main/select.h" +#include "main/timer.h" +#include "network/connection.h" +#include "osdep/newwin.h" +#include "osdep/sysname.h" +#include "protocol/http/http.h" +#include "protocol/uri.h" +#include "session/download.h" +#include "session/history.h" +#include "session/location.h" +#include "session/session.h" +#include "session/task.h" +#include "terminal/tab.h" +#include "terminal/terminal.h" +#include "util/conv.h" +#include "util/memory.h" +#include "util/string.h" +#include "viewer/text/draw.h" +#include "viewer/text/form.h" +#include "viewer/text/link.h" +#include "viewer/text/vs.h" + +#include +#include +#include +#include +#include +#include + +#define countof(x) (sizeof(x) / sizeof((x)[0])) + +static JSClassID js_messageEvent_class_id; + +static JSValue js_messageEvent_get_property_data(JSContext *cx, JSValueConst this_val); +static JSValue js_messageEvent_get_property_lastEventId(JSContext *cx, JSValueConst this_val); +static JSValue js_messageEvent_get_property_origin(JSContext *cx, JSValueConst this_val); +static JSValue js_messageEvent_get_property_source(JSContext *cx, JSValueConst this_val); + +struct message_event { + char *data; + char *lastEventId; + char *origin; + char *source; +}; + +static +void js_messageEvent_finalizer(JSRuntime *rt, JSValue val) +{ +#ifdef ECMASCRIPT_DEBUG + fprintf(stderr, "%s:%s\n", __FILE__, __FUNCTION__); +#endif + struct message_event *event = (struct message_event *)JS_GetOpaque(val, js_messageEvent_class_id); + + if (event) { + mem_free_if(event->data); + mem_free_if(event->lastEventId); + mem_free_if(event->origin); + mem_free_if(event->source); + mem_free(event); + } +} + +static JSClassDef js_messageEvent_class = { + "MessageEvent", + js_messageEvent_finalizer +}; + +static JSValue +js_messageEvent_ctor(JSContext *ctx, JSValueConst new_target, int argc, JSValueConst *argv) +{ +#ifdef ECMASCRIPT_DEBUG + fprintf(stderr, "%s:%s\n", __FILE__, __FUNCTION__); +#endif + JSValue obj = JS_UNDEFINED; + JSValue proto; + + struct message_event *event = (struct message_event *)mem_calloc(1, sizeof(*event)); + + if (!event) { + return JS_EXCEPTION; + } + + /* using new_target to get the prototype is necessary when the + class is extended. */ + proto = JS_GetPropertyStr(ctx, new_target, "prototype"); + + if (JS_IsException(proto)) { + goto fail; + } + obj = JS_NewObjectProtoClass(ctx, proto, js_messageEvent_class_id); + JS_FreeValue(ctx, proto); + + if (JS_IsException(obj)) { + goto fail; + } + JS_SetOpaque(obj, event); + + RETURN_JS(obj); + +fail: + JS_FreeValue(ctx, obj); + mem_free(event); + return JS_EXCEPTION; +} + +static const JSCFunctionListEntry js_messageEvent_proto_funcs[] = { + JS_CGETSET_DEF("data", js_messageEvent_get_property_data, nullptr), + JS_CGETSET_DEF("lastEventId", js_messageEvent_get_property_lastEventId, nullptr), + JS_CGETSET_DEF("origin", js_messageEvent_get_property_origin, nullptr), + JS_CGETSET_DEF("source", js_messageEvent_get_property_source, nullptr) +}; + +static JSValue +js_messageEvent_get_property_data(JSContext *ctx, JSValueConst this_val) +{ +#ifdef ECMASCRIPT_DEBUG + fprintf(stderr, "%s:%s\n", __FILE__, __FUNCTION__); +#endif + struct message_event *event = static_cast(JS_GetOpaque(this_val, js_messageEvent_class_id)); + + if (!event || !event->data) { + return JS_NULL; + } + JSValue r = JS_NewString(ctx, event->data); + + RETURN_JS(r); +} + +static JSValue +js_messageEvent_get_property_lastEventId(JSContext *ctx, JSValueConst this_val) +{ +#ifdef ECMASCRIPT_DEBUG + fprintf(stderr, "%s:%s\n", __FILE__, __FUNCTION__); +#endif + struct message_event *event = static_cast(JS_GetOpaque(this_val, js_messageEvent_class_id)); + + if (!event || !event->lastEventId) { + return JS_NULL; + } + JSValue r = JS_NewString(ctx, event->lastEventId); + + RETURN_JS(r); +} + +static JSValue +js_messageEvent_get_property_origin(JSContext *ctx, JSValueConst this_val) +{ +#ifdef ECMASCRIPT_DEBUG + fprintf(stderr, "%s:%s\n", __FILE__, __FUNCTION__); +#endif + struct message_event *event = static_cast(JS_GetOpaque(this_val, js_messageEvent_class_id)); + + if (!event || !event->origin) { + return JS_NULL; + } + JSValue r = JS_NewString(ctx, event->origin); + + RETURN_JS(r); +} + +static JSValue +js_messageEvent_get_property_source(JSContext *ctx, JSValueConst this_val) +{ +#ifdef ECMASCRIPT_DEBUG + fprintf(stderr, "%s:%s\n", __FILE__, __FUNCTION__); +#endif + struct message_event *event = static_cast(JS_GetOpaque(this_val, js_messageEvent_class_id)); + + if (!event || !event->source) { + return JS_NULL; + } + JSValue r = JS_NewString(ctx, event->source); + + RETURN_JS(r); +} + +static int lastEventId; + +JSValue +get_messageEvent(JSContext *ctx, char *data, char *origin, char *source) +{ +#ifdef ECMASCRIPT_DEBUG + fprintf(stderr, "%s:%s\n", __FILE__, __FUNCTION__); +#endif + static int initialized; + /* create the element class */ + if (!initialized) { + JS_NewClassID(&js_messageEvent_class_id); + JS_NewClass(JS_GetRuntime(ctx), js_messageEvent_class_id, &js_messageEvent_class); + initialized = 1; + } + struct message_event *event = (struct message_event *)mem_calloc(1, sizeof(*event)); + + if (!event) { + return JS_NULL; + } + event->data = null_or_stracpy(data); + event->origin = null_or_stracpy(origin); + event->source = null_or_stracpy(source); + + char id[32]; + + snprintf(id, "%d", 31, ++lastEventId); + event->lastEventId = stracpy(id); + + JSValue event_obj = JS_NewObjectClass(ctx, js_messageEvent_class_id); + JS_SetPropertyFunctionList(ctx, event_obj, js_messageEvent_proto_funcs, countof(js_messageEvent_proto_funcs)); + JS_SetClassProto(ctx, js_messageEvent_class_id, event_obj); + JS_SetOpaque(event_obj, event); + + JSValue rr = JS_DupValue(ctx, event_obj); + RETURN_JS(rr); +} diff --git a/src/ecmascript/quickjs/message.h b/src/ecmascript/quickjs/message.h new file mode 100644 index 00000000..a46ed50a --- /dev/null +++ b/src/ecmascript/quickjs/message.h @@ -0,0 +1,8 @@ +#ifndef EL__ECMASCRIPT_QUICKJS_MESSAGE_H +#define EL__ECMASCRIPT_QUICKJS_MESSAGE_H + +#include + +JSValue get_messageEvent(JSContext *ctx, char *data, char *origin, char *source); + +#endif diff --git a/src/ecmascript/quickjs/window.cpp b/src/ecmascript/quickjs/window.cpp index e1ea34f7..5c72fbf3 100644 --- a/src/ecmascript/quickjs/window.cpp +++ b/src/ecmascript/quickjs/window.cpp @@ -21,6 +21,8 @@ #include "document/view.h" #include "ecmascript/ecmascript.h" #include "ecmascript/quickjs.h" +#include "ecmascript/quickjs/heartbeat.h" +#include "ecmascript/quickjs/message.h" #include "ecmascript/quickjs/window.h" #include "ecmascript/timer.h" #include "intl/libintl.h" @@ -48,6 +50,43 @@ static JSClassID js_window_class_id; +struct listener { + LIST_HEAD(struct listener); + char *typ; + JSValue fun; +}; + +struct el_window { + struct ecmascript_interpreter *interpreter; + JSValue thisval; + LIST_OF(struct listener) listeners; + JSValue onmessage; +}; + +struct el_message { + JSValue messageObject; + struct el_window *elwin; +}; + +static void +js_window_finalize(JSRuntime *rt, JSValue val) +{ +#ifdef ECMASCRIPT_DEBUG + fprintf(stderr, "%s:%s\n", __FILE__, __FUNCTION__); +#endif + struct el_window *elwin = (struct el_window *)JS_GetOpaque(val, js_window_class_id); + + if (elwin) { + struct listener *l; + + foreach(l, elwin->listeners) { + mem_free_set(&l->typ, NULL); + } + free_list(elwin->listeners); + mem_free(elwin); + } +} + /* @window_funcs{"open"} */ JSValue js_window_open(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) @@ -422,6 +461,208 @@ js_window_toString(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst return JS_NewString(ctx, "[window object]"); } +static JSValue +js_window_addEventListener(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) +{ +#ifdef ECMASCRIPT_DEBUG + fprintf(stderr, "%s:%s\n", __FILE__, __FUNCTION__); +#endif + struct ecmascript_interpreter *interpreter = (struct ecmascript_interpreter *)JS_GetContextOpaque(ctx); + struct el_window *elwin = (struct el_window *)(JS_GetOpaque(this_val, js_window_class_id)); + + if (!elwin) { + elwin = (struct el_window *)mem_calloc(1, sizeof(*elwin)); + + if (!elwin) { + return JS_EXCEPTION; + } + init_list(elwin->listeners); + elwin->interpreter = interpreter; + elwin->thisval = JS_DupValue(ctx, this_val); + JS_SetOpaque(this_val, elwin); + } + + if (argc < 2) { + return JS_UNDEFINED; + } + const char *str; + size_t len; + str = JS_ToCStringLen(ctx, &len, argv[0]); + + if (!str) { + return JS_EXCEPTION; + } + char *method = stracpy(str); + JS_FreeCString(ctx, str); + + if (!method) { + return JS_EXCEPTION; + } + + JSValue fun = argv[1]; + struct listener *l; + + foreach(l, elwin->listeners) { + if (strcmp(l->typ, method)) { + continue; + } + if (JS_VALUE_GET_PTR(l->fun) == JS_VALUE_GET_PTR(fun)) { + mem_free(method); + return JS_UNDEFINED; + } + } + struct listener *n = (struct listener *)mem_calloc(1, sizeof(*n)); + + if (n) { + n->typ = method; + n->fun = JS_DupValue(ctx, argv[1]); + add_to_list_end(elwin->listeners, n); + } + return JS_UNDEFINED; +} + +static JSValue +js_window_removeEventListener(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) +{ +#ifdef ECMASCRIPT_DEBUG + fprintf(stderr, "%s:%s\n", __FILE__, __FUNCTION__); +#endif + struct ecmascript_interpreter *interpreter = (struct ecmascript_interpreter *)JS_GetContextOpaque(ctx); + struct el_window *elwin = (struct el_window *)(JS_GetOpaque(this_val, js_window_class_id)); + + if (!elwin) { + return JS_NULL; + } + + if (argc < 2) { + return JS_UNDEFINED; + } + const char *str; + size_t len; + str = JS_ToCStringLen(ctx, &len, argv[0]); + + if (!str) { + return JS_EXCEPTION; + } + char *method = stracpy(str); + JS_FreeCString(ctx, str); + + if (!method) { + return JS_EXCEPTION; + } + JSValue fun = argv[1]; + struct listener *l; + + foreach(l, elwin->listeners) { + if (strcmp(l->typ, method)) { + continue; + } + if (JS_VALUE_GET_PTR(l->fun) == JS_VALUE_GET_PTR(fun)) { + del_from_list(l); + mem_free_set(&l->typ, NULL); + mem_free(l); + mem_free(method); + return JS_UNDEFINED; + } + } + mem_free(method); + return JS_UNDEFINED; +} + +static void +onmessage_run(void *data) +{ + struct el_message *mess = (struct el_message *)data; + + if (mess) { + struct el_window *elwin = mess->elwin; + + if (!elwin) { + mem_free(mess); + return; + } + + struct ecmascript_interpreter *interpreter = elwin->interpreter; + JSContext *ctx = (JSContext *)interpreter->backend_data; + interpreter->heartbeat = add_heartbeat(interpreter); + + struct listener *l; + + foreach(l, elwin->listeners) { + if (strcmp(l->typ, "message")) { + continue; + } + JSValue func = JS_DupValue(ctx, l->fun); + JSValue arg = JS_DupValue(ctx, mess->messageObject); + JSValue ret = JS_Call(ctx, func, elwin->thisval, 1, (JSValueConst *) &arg); + JS_FreeValue(ctx, ret); + JS_FreeValue(ctx, func); + JS_FreeValue(ctx, arg); + } + if (JS_IsFunction(ctx, elwin->onmessage)) { + JSValue func = JS_DupValue(ctx, elwin->onmessage); + JSValue arg = JS_DupValue(ctx, mess->messageObject); + JSValue ret = JS_Call(ctx, func, elwin->thisval, 1, (JSValueConst *) &arg); + JS_FreeValue(ctx, ret); + JS_FreeValue(ctx, func); + JS_FreeValue(ctx, arg); + } + done_heartbeat(interpreter->heartbeat); + check_for_rerender(interpreter, "window_message"); + } +} + +static JSValue +js_window_postMessage(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) +{ +#ifdef ECMASCRIPT_DEBUG + fprintf(stderr, "%s:%s\n", __FILE__, __FUNCTION__); +#endif + struct ecmascript_interpreter *interpreter = (struct ecmascript_interpreter *)JS_GetContextOpaque(ctx); + struct el_window *elwin = (struct el_window *)(JS_GetOpaque(this_val, js_window_class_id)); + + if (argc < 2) { + return JS_UNDEFINED; + } + const char *str; + size_t len; + str = JS_ToCStringLen(ctx, &len, argv[0]); + + if (!str) { + return JS_EXCEPTION; + } + char *data = stracpy(str); + JS_FreeCString(ctx, str); + + str = JS_ToCStringLen(ctx, &len, argv[1]); + if (!str) { + mem_free_if(data); + return JS_EXCEPTION; + } + char *targetOrigin = stracpy(str); + JS_FreeCString(ctx, str); + char *source = stracpy("TODO"); + + JSValue val = get_messageEvent(ctx, data, targetOrigin, source); + + mem_free_if(data); + mem_free_if(targetOrigin); + mem_free_if(source); + + if (JS_IsNull(val) || !elwin) { + return JS_UNDEFINED; + } + struct el_message *mess = (struct el_message *)mem_calloc(1, sizeof(*mess)); + if (!mess) { + return JS_EXCEPTION; + } + mess->messageObject = JS_DupValue(ctx, val); + mess->elwin = elwin; + register_bottom_half(onmessage_run, mess); + + return JS_UNDEFINED; +} + static const JSCFunctionListEntry js_window_proto_funcs[] = { JS_CGETSET_DEF("closed", js_window_get_property_closed, nullptr), JS_CGETSET_DEF("parent", js_window_get_property_parent, nullptr), @@ -429,15 +670,19 @@ static const JSCFunctionListEntry js_window_proto_funcs[] = { JS_CGETSET_DEF("status", js_window_get_property_status, js_window_set_property_status), JS_CGETSET_DEF("top", js_window_get_property_top, nullptr), JS_CGETSET_DEF("window", js_window_get_property_self, nullptr), + JS_CFUNC_DEF("addEventListener", js_window_addEventListener, 3), JS_CFUNC_DEF("alert", 1, js_window_alert), JS_CFUNC_DEF("clearTimeout", 1, js_window_clearTimeout), JS_CFUNC_DEF("open", 3, js_window_open), + JS_CFUNC_DEF("postMessage", js_window_postMessage, 3), + JS_CFUNC_DEF("removeEventListener", js_window_removeEventListener, 3), JS_CFUNC_DEF("setTimeout", 2, js_window_setTimeout), JS_CFUNC_DEF("toString", 0, js_window_toString) }; static JSClassDef js_window_class = { "window", + js_window_finalize }; int