diff --git a/src/ecmascript/spidermonkey/Makefile b/src/ecmascript/spidermonkey/Makefile index 08670e7f..ebe637eb 100644 --- a/src/ecmascript/spidermonkey/Makefile +++ b/src/ecmascript/spidermonkey/Makefile @@ -3,6 +3,6 @@ include $(top_builddir)/Makefile.config INCLUDES += $(SPIDERMONKEY_CFLAGS) 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 + keyboard.obj location.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/spidermonkey/meson.build b/src/ecmascript/spidermonkey/meson.build index 2725fc2a..395fd611 100644 --- a/src/ecmascript/spidermonkey/meson.build +++ b/src/ecmascript/spidermonkey/meson.build @@ -1,4 +1,4 @@ #INCLUDES += $(SPIDERMONKEY_CFLAGS) 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', 'location.cpp', 'localstorage.cpp', 'navigator.cpp', 'nodelist.cpp', 'screen.cpp', 'unibar.cpp', 'window.cpp', 'xhr.cpp') +'keyboard.cpp', 'location.cpp', 'localstorage.cpp', 'message.cpp', 'navigator.cpp', 'nodelist.cpp', 'screen.cpp', 'unibar.cpp', 'window.cpp', 'xhr.cpp') diff --git a/src/ecmascript/spidermonkey/message.cpp b/src/ecmascript/spidermonkey/message.cpp new file mode 100644 index 00000000..45c9fc67 --- /dev/null +++ b/src/ecmascript/spidermonkey/message.cpp @@ -0,0 +1,309 @@ +/* The SpiderMonkey MessageEvent object implementation. */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include +#include +#include + +#include "elinks.h" + +#include "ecmascript/spidermonkey/util.h" +#include +#include + +#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/spidermonkey.h" +#include "ecmascript/spidermonkey/heartbeat.h" +#include "ecmascript/spidermonkey/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 + + +static bool messageEvent_get_property_data(JSContext *cx, unsigned int argc, JS::Value *vp); +static bool messageEvent_get_property_lastEventId(JSContext *cx, unsigned int argc, JS::Value *vp); +static bool messageEvent_get_property_origin(JSContext *cx, unsigned int argc, JS::Value *vp); +static bool messageEvent_get_property_source(JSContext *cx, unsigned int argc, JS::Value *vp); + +struct message_event { + char *data; + char *lastEventId; + char *origin; + char *source; +}; + +static void +messageEvent_finalize(JS::GCContext *op, JSObject *obj) +{ +#ifdef ECMASCRIPT_DEBUG + fprintf(stderr, "%s:%s\n", __FILE__, __FUNCTION__); +#endif + struct message_event *event = JS::GetMaybePtrFromReservedSlot(obj, 0); + + 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); + } +} + +JSClassOps messageEvent_ops = { + nullptr, // addProperty + nullptr, // deleteProperty + nullptr, // enumerate + nullptr, // newEnumerate + nullptr, // resolve + nullptr, // mayResolve + messageEvent_finalize, // finalize + nullptr, // call + nullptr, // construct + JS_GlobalObjectTraceHook // trace +}; + +JSClass messageEvent_class = { + "MessageEvent", + JSCLASS_HAS_RESERVED_SLOTS(1), + &messageEvent_ops +}; + +bool +messageEvent_constructor(JSContext* ctx, unsigned argc, JS::Value* vp) +{ +#ifdef ECMASCRIPT_DEBUG + fprintf(stderr, "%s:%s\n", __FILE__, __FUNCTION__); +#endif + JS::CallArgs args = JS::CallArgsFromVp(argc, vp); + JS::RootedObject newObj(ctx, JS_NewObjectForConstructor(ctx, &messageEvent_class, args)); + JS::Realm *comp = js::GetContextRealm(ctx); + + if (!comp) { +#ifdef ECMASCRIPT_DEBUG + fprintf(stderr, "%s:%s %d\n", __FILE__, __FUNCTION__, __LINE__); +#endif + return false; + } + struct ecmascript_interpreter *interpreter = (struct ecmascript_interpreter *)JS::GetRealmPrivate(comp); + + if (!newObj) { + return false; + } + struct message_event *event = (struct message_event *)mem_calloc(1, sizeof(*event)); + + if (!event) { + return false; + } + JS::SetReservedSlot(newObj, 0, JS::PrivateValue(event)); + args.rval().setObject(*newObj); + + return true; +} + +JSPropertySpec messageEvent_props[] = { + JS_PSG("data", messageEvent_get_property_data, JSPROP_ENUMERATE), + JS_PSG("lastEventId", messageEvent_get_property_lastEventId, JSPROP_ENUMERATE), + JS_PSG("origin", messageEvent_get_property_origin, JSPROP_ENUMERATE), + JS_PSG("source", messageEvent_get_property_source, JSPROP_ENUMERATE), + JS_PS_END +}; + +static bool +messageEvent_get_property_data(JSContext *ctx, unsigned int argc, JS::Value *vp) +{ +#ifdef ECMASCRIPT_DEBUG + fprintf(stderr, "%s:%s\n", __FILE__, __FUNCTION__); +#endif + JS::CallArgs args = JS::CallArgsFromVp(argc, vp); + JS::RootedObject hobj(ctx, &args.thisv().toObject()); + JS::Realm *comp = js::GetContextRealm(ctx); + + if (!comp) { +#ifdef ECMASCRIPT_DEBUG + fprintf(stderr, "%s:%s %d\n", __FILE__, __FUNCTION__, __LINE__); +#endif + return false; + } + struct ecmascript_interpreter *interpreter = (struct ecmascript_interpreter *)JS::GetRealmPrivate(comp); + struct message_event *event = JS::GetMaybePtrFromReservedSlot(hobj, 0); + + if (!event) { + return false; + } + + if (!event->data) { + args.rval().setNull(); + return true; + } + args.rval().setString(JS_NewStringCopyZ(ctx, event->data)); + + return true; +} + +static bool +messageEvent_get_property_lastEventId(JSContext *ctx, unsigned int argc, JS::Value *vp) +{ +#ifdef ECMASCRIPT_DEBUG + fprintf(stderr, "%s:%s\n", __FILE__, __FUNCTION__); +#endif + JS::CallArgs args = JS::CallArgsFromVp(argc, vp); + JS::RootedObject hobj(ctx, &args.thisv().toObject()); + JS::Realm *comp = js::GetContextRealm(ctx); + + if (!comp) { +#ifdef ECMASCRIPT_DEBUG + fprintf(stderr, "%s:%s %d\n", __FILE__, __FUNCTION__, __LINE__); +#endif + return false; + } + struct ecmascript_interpreter *interpreter = (struct ecmascript_interpreter *)JS::GetRealmPrivate(comp); + struct message_event *event = JS::GetMaybePtrFromReservedSlot(hobj, 0); + + if (!event) { + return false; + } + + if (!event->lastEventId) { + args.rval().setNull(); + return true; + } + args.rval().setString(JS_NewStringCopyZ(ctx, event->lastEventId)); + + return true; +} + +static bool +messageEvent_get_property_origin(JSContext *ctx, unsigned int argc, JS::Value *vp) +{ +#ifdef ECMASCRIPT_DEBUG + fprintf(stderr, "%s:%s\n", __FILE__, __FUNCTION__); +#endif + JS::CallArgs args = JS::CallArgsFromVp(argc, vp); + JS::RootedObject hobj(ctx, &args.thisv().toObject()); + JS::Realm *comp = js::GetContextRealm(ctx); + + if (!comp) { +#ifdef ECMASCRIPT_DEBUG + fprintf(stderr, "%s:%s %d\n", __FILE__, __FUNCTION__, __LINE__); +#endif + return false; + } + struct ecmascript_interpreter *interpreter = (struct ecmascript_interpreter *)JS::GetRealmPrivate(comp); + struct message_event *event = JS::GetMaybePtrFromReservedSlot(hobj, 0); + + if (!event) { + return false; + } + + if (!event->origin) { + args.rval().setNull(); + return true; + } + args.rval().setString(JS_NewStringCopyZ(ctx, event->origin)); + + return true; +} + +static bool +messageEvent_get_property_source(JSContext *ctx, unsigned int argc, JS::Value *vp) +{ +#ifdef ECMASCRIPT_DEBUG + fprintf(stderr, "%s:%s\n", __FILE__, __FUNCTION__); +#endif + JS::CallArgs args = JS::CallArgsFromVp(argc, vp); + JS::RootedObject hobj(ctx, &args.thisv().toObject()); + JS::Realm *comp = js::GetContextRealm(ctx); + + if (!comp) { +#ifdef ECMASCRIPT_DEBUG + fprintf(stderr, "%s:%s %d\n", __FILE__, __FUNCTION__, __LINE__); +#endif + return false; + } + struct ecmascript_interpreter *interpreter = (struct ecmascript_interpreter *)JS::GetRealmPrivate(comp); + struct message_event *event = JS::GetMaybePtrFromReservedSlot(hobj, 0); + + if (!event) { + return false; + } + +// TODO proper type + if (!event->source) { + args.rval().setNull(); + return true; + } + args.rval().setString(JS_NewStringCopyZ(ctx, event->source)); + + return true; +} + +static int lastEventId; + +JSObject * +get_messageEvent(JSContext *ctx, char *data, char *origin, char *source) +{ + JSObject *e = JS_NewObject(ctx, &messageEvent_class); + + if (!e) { + return NULL; + } + + JS::RootedObject r_event(ctx, e); + JS_DefineProperties(ctx, r_event, (JSPropertySpec *) messageEvent_props); + + struct message_event *event = (struct message_event *)mem_calloc(1, sizeof(*event)); + + if (!event) { + return 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); + JS::SetReservedSlot(e, 0, JS::PrivateValue(event)); + + return e; +} diff --git a/src/ecmascript/spidermonkey/message.h b/src/ecmascript/spidermonkey/message.h new file mode 100644 index 00000000..263fc297 --- /dev/null +++ b/src/ecmascript/spidermonkey/message.h @@ -0,0 +1,12 @@ +#ifndef EL__ECMASCRIPT_SPIDERMONKEY_MESSAGE_H +#define EL__ECMASCRIPT_SPIDERMONKEY_MESSAGE_H + +#include "ecmascript/spidermonkey/util.h" + +extern JSClass messageEvent_class; +extern JSPropertySpec messageEvent_props[]; +bool messageEvent_constructor(JSContext* ctx, unsigned argc, JS::Value* vp); + +JSObject *get_messageEvent(JSContext *ctx, char *data, char *origin, char *source); + +#endif diff --git a/src/ecmascript/spidermonkey/window.cpp b/src/ecmascript/spidermonkey/window.cpp index 84d51fa3..941b39dd 100644 --- a/src/ecmascript/spidermonkey/window.cpp +++ b/src/ecmascript/spidermonkey/window.cpp @@ -24,6 +24,8 @@ #include "document/forms.h" #include "document/view.h" #include "ecmascript/ecmascript.h" +#include "ecmascript/spidermonkey/heartbeat.h" +#include "ecmascript/spidermonkey/message.h" #include "ecmascript/spidermonkey/window.h" #include "ecmascript/timer.h" #include "intl/libintl.h" @@ -55,14 +57,38 @@ static bool window_get_property_status(JSContext *ctx, unsigned int argc, JS::Va static bool window_set_property_status(JSContext *ctx, unsigned int argc, JS::Value *vp); static bool window_get_property_top(JSContext *ctx, unsigned int argc, JS::Value *vp); +struct listener { + LIST_HEAD(struct listener); + char *typ; + JS::RootedValue fun; +}; + +struct el_window { + struct ecmascript_interpreter *interpreter; + JS::RootedObject thisval; + LIST_OF(struct listener) listeners; + JS::RootedValue onmessage; + JS::RootedObject messageObject; +}; + static void window_finalize(JS::GCContext *op, JSObject *obj) { #ifdef ECMASCRIPT_DEBUG fprintf(stderr, "%s:%s\n", __FILE__, __FUNCTION__); #endif -} + struct el_window *elwin = JS::GetMaybePtrFromReservedSlot(obj, 0); + if (elwin) { + struct listener *l; + + foreach(l, elwin->listeners) { + mem_free_set(&l->typ, NULL); + } + free_list(elwin->listeners); + mem_free(elwin); + } +} JSClassOps window_ops = { nullptr, // addProperty @@ -139,19 +165,213 @@ find_child_frame(struct document_view *doc_view, struct frame_desc *tframe) void location_goto(struct document_view *doc_view, char *url); +static bool window_addEventListener(JSContext *ctx, unsigned int argc, JS::Value *rval); static bool window_alert(JSContext *ctx, unsigned int argc, JS::Value *rval); static bool window_clearTimeout(JSContext *ctx, unsigned int argc, JS::Value *rval); static bool window_open(JSContext *ctx, unsigned int argc, JS::Value *rval); +static bool window_postMessage(JSContext *ctx, unsigned int argc, JS::Value *rval); +static bool window_removeEventListener(JSContext *ctx, unsigned int argc, JS::Value *rval); static bool window_setTimeout(JSContext *ctx, unsigned int argc, JS::Value *rval); const spidermonkeyFunctionSpec window_funcs[] = { + { "addEventListener", window_addEventListener, 3 }, { "alert", window_alert, 1 }, { "clearTimeout", window_clearTimeout, 1 }, { "open", window_open, 3 }, + { "postMessage", window_postMessage, 3 }, + { "removeEventListener", window_removeEventListener, 3 }, { "setTimeout", window_setTimeout, 2 }, { NULL } }; +static void +onmessage_run(void *data) +{ + struct el_window *elwin = (struct el_window *)data; + + if (elwin) { + struct ecmascript_interpreter *interpreter = elwin->interpreter; + JSContext *ctx = (JSContext *)interpreter->backend_data; + JS::Realm *comp = JS::EnterRealm(ctx, (JSObject *)interpreter->ac); + JS::RootedValue r_val(ctx); + interpreter->heartbeat = add_heartbeat(interpreter); + + JS::RootedValueVector argv(ctx); + if (!argv.resize(1)) { + return; + } + argv[0].setObject(*(elwin->messageObject)); + + struct listener *l; + + foreach(l, elwin->listeners) { + if (strcmp(l->typ, "message")) { + continue; + } + JS_CallFunctionValue(ctx, elwin->thisval, l->fun, argv, &r_val); + } + JS_CallFunctionValue(ctx, elwin->thisval, elwin->onmessage, argv, &r_val); + done_heartbeat(interpreter->heartbeat); + JS::LeaveRealm(ctx, comp); + check_for_rerender(interpreter, "window_onmessage"); + } +} + +static bool +window_addEventListener(JSContext *ctx, unsigned int argc, JS::Value *rval) +{ +#ifdef ECMASCRIPT_DEBUG + fprintf(stderr, "%s:%s\n", __FILE__, __FUNCTION__); +#endif + JS::CallArgs args = JS::CallArgsFromVp(argc, rval); + JS::RootedObject hobj(ctx, &args.thisv().toObject()); + JS::Realm *comp = js::GetContextRealm(ctx); + + if (!comp) { +#ifdef ECMASCRIPT_DEBUG + fprintf(stderr, "%s:%s %d\n", __FILE__, __FUNCTION__, __LINE__); +#endif + return false; + } + struct ecmascript_interpreter *interpreter = (struct ecmascript_interpreter *)JS::GetRealmPrivate(comp); + struct el_window *elwin = JS::GetMaybePtrFromReservedSlot(hobj, 0); + + if (!elwin) { + elwin = (struct el_window *)mem_calloc(1, sizeof(*elwin)); + + if (!elwin) { + return false; + } + init_list(elwin->listeners); + elwin->interpreter = interpreter; + elwin->thisval = hobj; + JS::SetReservedSlot(hobj, 0, JS::PrivateValue(elwin)); + } + + if (argc < 2) { + args.rval().setUndefined(); + return true; + } + char *method = jsval_to_string(ctx, args[0]); + JS::RootedValue fun(ctx, args[1]); + + struct listener *l; + + foreach(l, elwin->listeners) { + if (strcmp(l->typ, method)) { + continue; + } + if (l->fun == fun) { + args.rval().setUndefined(); + mem_free(method); + return true; + } + } + struct listener *n = (struct listener *)mem_calloc(1, sizeof(*n)); + + if (n) { + n->typ = method; + n->fun = fun; + add_to_list_end(elwin->listeners, n); + } + args.rval().setUndefined(); + return true; +} + +static bool +window_removeEventListener(JSContext *ctx, unsigned int argc, JS::Value *rval) +{ +#ifdef ECMASCRIPT_DEBUG + fprintf(stderr, "%s:%s\n", __FILE__, __FUNCTION__); +#endif + JS::CallArgs args = JS::CallArgsFromVp(argc, rval); + JS::RootedObject hobj(ctx, &args.thisv().toObject()); + JS::Realm *comp = js::GetContextRealm(ctx); + + if (!comp) { +#ifdef ECMASCRIPT_DEBUG + fprintf(stderr, "%s:%s %d\n", __FILE__, __FUNCTION__, __LINE__); +#endif + return false; + } + struct ecmascript_interpreter *interpreter = (struct ecmascript_interpreter *)JS::GetRealmPrivate(comp); + struct el_window *elwin = JS::GetMaybePtrFromReservedSlot(hobj, 0); + + if (argc < 2) { + args.rval().setUndefined(); + return true; + } + char *method = jsval_to_string(ctx, args[0]); + + if (!method) { + return false; + } + JS::RootedValue fun(ctx, args[1]); + + struct listener *l; + + foreach(l, elwin->listeners) { + if (strcmp(l->typ, method)) { + continue; + } + if (l->fun == fun) { + del_from_list(l); + mem_free_set(&l->typ, NULL); + mem_free(l); + mem_free(method); + args.rval().setUndefined(); + return true; + } + } + mem_free(method); + args.rval().setUndefined(); + return true; +} + +static bool +window_postMessage(JSContext *ctx, unsigned int argc, JS::Value *rval) +{ +#ifdef ECMASCRIPT_DEBUG + fprintf(stderr, "%s:%s\n", __FILE__, __FUNCTION__); +#endif + JS::CallArgs args = JS::CallArgsFromVp(argc, rval); + JS::RootedObject hobj(ctx, &args.thisv().toObject()); + JS::Realm *comp = js::GetContextRealm(ctx); + + if (!comp) { +#ifdef ECMASCRIPT_DEBUG + fprintf(stderr, "%s:%s %d\n", __FILE__, __FUNCTION__, __LINE__); +#endif + return false; + } + struct ecmascript_interpreter *interpreter = (struct ecmascript_interpreter *)JS::GetRealmPrivate(comp); + struct el_window *elwin = JS::GetMaybePtrFromReservedSlot(hobj, 0); + + if (argc < 2) { + args.rval().setUndefined(); + return true; + } + char *data = jsval_to_string(ctx, args[0]); + char *targetOrigin = jsval_to_string(ctx, args[1]); + char *source = stracpy("TODO"); + + JSObject *val = get_messageEvent(ctx, data, targetOrigin, source); + + mem_free_if(data); + mem_free_if(targetOrigin); + mem_free_if(source); + + if (!val || !elwin) { + args.rval().setUndefined(); + return true; + } + JS::RootedObject messageObject(ctx, val); + elwin->messageObject = messageObject; + register_bottom_half(onmessage_run, elwin); + args.rval().setUndefined(); + return true; +} + /* @window_funcs{"alert"} */ static bool window_alert(JSContext *ctx, unsigned int argc, JS::Value *rval)