From 2858aec1c2bf32d0793cbafff6849cf91625b31b Mon Sep 17 00:00:00 2001 From: Daniel Silverstone Date: Sun, 5 Feb 2017 11:27:25 +0000 Subject: [PATCH] Add EventListener support to duktape binding. This paves the way for EventTarget and its associated event listener support. In particular it ensures the generic event handling we do supports the semantics of the addEventListener() and removeEventListener() managed lists of callbacks. --- content/handlers/javascript/duktape/dukky.c | 184 +++++++++++++++++++- content/handlers/javascript/duktape/dukky.h | 10 ++ 2 files changed, 187 insertions(+), 7 deletions(-) diff --git a/content/handlers/javascript/duktape/dukky.c b/content/handlers/javascript/duktape/dukky.c index 66d1b0506..4a6b9c398 100644 --- a/content/handlers/javascript/duktape/dukky.c +++ b/content/handlers/javascript/duktape/dukky.c @@ -46,6 +46,7 @@ #define EVENT_MAGIC MAGIC(EVENT_MAP) #define HANDLER_LISTENER_MAGIC MAGIC(HANDLER_LISTENER_MAP) #define HANDLER_MAGIC MAGIC(HANDLER_MAP) +#define EVENT_LISTENER_JS_MAGIC MAGIC(EVENT_LISTENER_JS_MAP) static duk_ret_t dukky_populate_object(duk_context *ctx) { @@ -809,6 +810,8 @@ static void dukky_generic_event_handler(dom_event *evt, void *pw) dom_exception exc; dom_event_target *targ; dom_event_flow_phase phase; + duk_uarridx_t idx; + event_listener_flags flags; /* Retrieve the JS context from the Duktape context */ duk_get_memory_functions(ctx, &funcs); @@ -840,6 +843,12 @@ static void dukky_generic_event_handler(dom_event *evt, void *pw) return; } + /* If we're capturing right now, we skip the 'event handler' + * and go straight to the extras + */ + if (phase == DOM_CAPTURING_PHASE) + goto handle_extras; + /* ... */ if (dukky_push_node(ctx, (dom_node *)targ) == false) { dom_string_unref(name); @@ -850,13 +859,9 @@ static void dukky_generic_event_handler(dom_event *evt, void *pw) /* ... node */ if (dukky_get_current_value_of_event_handler( ctx, name, (dom_event_target *)targ) == false) { - dom_node_unref(targ); - dom_string_unref(name); - return; + /* ... */ + goto handle_extras; } - /** @todo handle other kinds of event than the generic case */ - dom_node_unref(targ); - dom_string_unref(name); /* ... handler node */ dukky_push_event(ctx, evt); /* ... handler node event */ @@ -882,7 +887,7 @@ static void dukky_generic_event_handler(dom_event *evt, void *pw) duk_pop_n(ctx, 6); /* ... */ - return; + goto handle_extras; } /* ... result */ if (duk_is_boolean(ctx, -1) && @@ -890,7 +895,107 @@ static void dukky_generic_event_handler(dom_event *evt, void *pw) dom_event_prevent_default(evt); } duk_pop(ctx); +handle_extras: /* ... */ + duk_push_lstring(ctx, dom_string_data(name), dom_string_length(name)); + dukky_push_node(ctx, (dom_node *)targ); + /* ... type node */ + if (dukky_event_target_push_listeners(ctx, true)) { + /* Nothing to do */ + duk_pop(ctx); + goto out; + } + /* ... sublisteners */ + duk_push_array(ctx); + /* ... sublisteners copy */ + idx = 0; + while (duk_get_prop_index(ctx, -2, idx)) { + /* ... sublisteners copy handler */ + duk_get_prop_index(ctx, -1, 1); + /* ... sublisteners copy handler flags */ + if ((event_listener_flags)duk_to_int(ctx, -1) & ELF_ONCE) { + duk_dup(ctx, -4); + /* ... subl copy handler flags subl */ + dukky_shuffle_array(ctx, idx); + duk_pop(ctx); + /* ... subl copy handler flags */ + } + duk_pop(ctx); + /* ... sublisteners copy handler */ + duk_put_prop_index(ctx, -2, idx); + /* ... sublisteners copy */ + idx++; + } + /* ... sublisteners copy undefined */ + duk_pop(ctx); + /* ... sublisteners copy */ + duk_insert(ctx, -2); + /* ... copy sublisteners */ + duk_pop(ctx); + /* ... copy */ + idx = 0; + while (duk_get_prop_index(ctx, -1, idx++)) { + /* ... copy handler */ + if (duk_get_prop_index(ctx, -1, 2)) { + /* ... copy handler meh */ + duk_pop_2(ctx); + continue; + } + duk_pop(ctx); + duk_get_prop_index(ctx, -1, 0); + duk_get_prop_index(ctx, -2, 1); + /* ... copy handler callback flags */ + flags = (event_listener_flags)duk_get_int(ctx, -1); + duk_pop(ctx); + /* ... copy handler callback */ + if (((phase == DOM_CAPTURING_PHASE) && !(flags & ELF_CAPTURE)) || + ((phase != DOM_CAPTURING_PHASE) && (flags & ELF_CAPTURE))) { + duk_pop_2(ctx); + /* ... copy */ + continue; + } + /* ... copy handler callback */ + dukky_push_node(ctx, (dom_node *)targ); + /* ... copy handler callback node */ + dukky_push_event(ctx, evt); + /* ... copy handler callback node event */ + (void) nsu_getmonotonic_ms(&jsctx->exec_start_time); + if (duk_pcall_method(ctx, 1) != 0) { + /* Failed to run the method */ + /* ... copy handler err */ + LOG("OH NOES! An error running a callback. Meh."); + exc = dom_event_stop_immediate_propagation(evt); + if (exc != DOM_NO_ERR) + LOG("WORSE! could not stop propagation"); + duk_get_prop_string(ctx, -1, "name"); + duk_get_prop_string(ctx, -2, "message"); + duk_get_prop_string(ctx, -3, "fileName"); + duk_get_prop_string(ctx, -4, "lineNumber"); + duk_get_prop_string(ctx, -5, "stack"); + /* ... err name message fileName lineNumber stack */ + LOG("Uncaught error in JS: %s: %s", duk_safe_to_string(ctx, -5), + duk_safe_to_string(ctx, -4)); + LOG(" was at: %s line %s", duk_safe_to_string(ctx, -3), + duk_safe_to_string(ctx, -2)); + LOG(" Stack trace: %s", duk_safe_to_string(ctx, -1)); + + duk_pop_n(ctx, 7); + /* ... copy */ + continue; + } + /* ... copy handler result */ + if (duk_is_boolean(ctx, -1) && + duk_to_boolean(ctx, -1) == 0) { + dom_event_prevent_default(evt); + } + duk_pop_2(ctx); + /* ... copy */ + } + duk_pop_2(ctx); +out: + /* ... */ + dom_node_unref(targ); + dom_string_unref(name); } void dukky_register_event_listener_for(duk_context *ctx, @@ -939,6 +1044,71 @@ void dukky_register_event_listener_for(duk_context *ctx, dom_event_listener_unref(listen); } +/* The sub-listeners are a list of {callback,flags} tuples */ +/* We return true if we created a new sublistener table */ +/* If we're told to not create, but we want to, we still return true */ +bool dukky_event_target_push_listeners(duk_context *ctx, bool dont_create) +{ + bool ret = false; + /* ... type this */ + duk_get_prop_string(ctx, -1, EVENT_LISTENER_JS_MAGIC); + if (duk_is_undefined(ctx, -1)) { + /* ... type this null */ + duk_pop(ctx); + duk_push_object(ctx); + duk_dup(ctx, -1); + /* ... type this listeners listeners */ + duk_put_prop_string(ctx, -3, EVENT_LISTENER_JS_MAGIC); + /* ... type this listeners */ + } + /* ... type this listeners */ + duk_insert(ctx, -3); + /* ... listeners type this */ + duk_pop(ctx); + /* ... listeners type */ + duk_dup(ctx, -1); + /* ... listeners type type */ + duk_get_prop(ctx, -3); + /* ... listeners type ??? */ + if (duk_is_undefined(ctx, -1)) { + /* ... listeners type ??? */ + if (dont_create == true) { + duk_pop_3(ctx); + duk_push_undefined(ctx); + return true; + } + duk_pop(ctx); + duk_push_array(ctx); + duk_dup(ctx, -2); + duk_dup(ctx, -2); + /* ... listeners type sublisteners type sublisteners */ + duk_put_prop(ctx, -5); + /* ... listeners type sublisteners */ + ret = true; + } + duk_insert(ctx, -3); + /* ... sublisteners listeners type */ + duk_pop_2(ctx); + /* ... sublisteners */ + return ret; +} + +/* Shuffle a duktape array "down" one. This involves iterating from + * the index provided, shuffling elements down, until we reach an + * undefined + */ +void dukky_shuffle_array(duk_context *ctx, duk_uarridx_t idx) +{ + /* ... somearr */ + while (duk_get_prop_index(ctx, -1, idx + 1)) { + duk_put_prop_index(ctx, -2, idx); + idx++; + } + /* ... somearr undefined */ + duk_del_prop_index(ctx, -2, idx + 1); + duk_pop(ctx); +} + void js_handle_new_element(jscontext *ctx, struct dom_element *node) { diff --git a/content/handlers/javascript/duktape/dukky.h b/content/handlers/javascript/duktape/dukky.h index 1d6baee55..b5809aa08 100644 --- a/content/handlers/javascript/duktape/dukky.h +++ b/content/handlers/javascript/duktape/dukky.h @@ -42,5 +42,15 @@ void dukky_register_event_listener_for(duk_context *ctx, bool dukky_get_current_value_of_event_handler(duk_context *ctx, dom_string *name, dom_event_target *et); +bool dukky_event_target_push_listeners(duk_context *ctx, bool dont_create); + +typedef enum { + ELF_CAPTURE = 1 << 0, + ELF_PASSIVE = 1 << 1, + ELF_ONCE = 1 << 2, + ELF_NONE = 0 +} event_listener_flags; + +void dukky_shuffle_array(duk_context *ctx, duk_uarridx_t idx); #endif