/* Event Target binding for browser using duktape and libdom * * Copyright 2016 Daniel Silverstone * * This file is part of NetSurf, http://www.netsurf-browser.org/ * * Released under the terms of the MIT License, * http://www.opensource.org/licenses/mit-license */ class EventTarget { private bool is_node; private bool capture_registered; private bool bubbling_registered; }; prologue EventTarget() %{ static event_listener_flags event_listener_pop_options(duk_context *ctx) { event_listener_flags ret = ELF_NONE; /* ... options */ duk_get_prop_string(ctx, -1, "capture"); if (duk_to_boolean(ctx, -1)) ret |= ELF_CAPTURE; duk_pop(ctx); duk_get_prop_string(ctx, -1, "passive"); if (duk_to_boolean(ctx, -1)) ret |= ELF_PASSIVE; duk_pop(ctx); duk_get_prop_string(ctx, -1, "once"); if (duk_to_boolean(ctx, -1)) ret |= ELF_ONCE; duk_pop_2(ctx); /* ... */ return ret; } static void event_target_register_listener(duk_context *ctx, event_listener_flags flags) { /* ... listeners callback */ /* If the given callback with the given flags is already present, * we do not re-add it, otherwise we need to add to listeners * a tuple of the callback and flags */ duk_uarridx_t idx = 0; while (duk_get_prop_index(ctx, -1, idx)) { /* ... listeners callback candidate */ duk_get_prop_index(ctx, -1, 0); duk_get_prop_index(ctx, -2, 1); /* ... listeners callback candidate candidatecallback candidateflags */ if (duk_strict_equals(ctx, -1, -3) && duk_get_int(ctx, -1) == (duk_int_t)flags) { /* already present, nothing to do */ duk_pop_n(ctx, 5); /* ... */ return; } /* ... listeners callback candidate candidatecallback candidateflags */ duk_pop_3(ctx); /* ... listeners callback */ idx++; } /* ... listeners callback undefined */ duk_pop(ctx); /* ... listeners callback */ duk_push_array(ctx); /* ... listeners callback newcandidate */ duk_insert(ctx, -2); /* ... listeners newcandidate callback */ duk_put_prop_index(ctx, -2, 0); /* ... listeners newcandidate */ duk_push_int(ctx, (duk_int_t)flags); /* ... listeners newcandidate flags */ duk_put_prop_index(ctx, -2, 1); /* ... listeners newcandidate */ duk_put_prop_index(ctx, -2, idx); /* ... listeners */ duk_pop(ctx); /* ... */ } static void event_target_unregister_listener(duk_context *ctx, event_listener_flags flags) { /* ... listeners callback */ /* If the given callback with the given flags is present, * we remove it and shuffle the rest up. */ duk_uarridx_t idx = 0; while (duk_get_prop_index(ctx, -1, idx)) { /* ... listeners callback candidate */ duk_get_prop_index(ctx, -1, 0); duk_get_prop_index(ctx, -2, 1); /* ... listeners callback candidate candidatecallback candidateflags */ if (duk_strict_equals(ctx, -1, -3) && duk_get_int(ctx, -1) == (duk_int_t)flags) { /* present */ duk_pop(ctx); /* ... listeners callback candidate candidatecallback */ duk_put_prop_index(ctx, -2, 2); /* ... listeners callback candidate */ duk_pop(ctx); /* ... listeners callback */ duk_push_int(ctx, idx); /* ... listeners callback found_at */ break; } /* ... listeners callback candidate candidatecallback candidateflags */ duk_pop_3(ctx); /* ... listeners callback */ idx++; } /* ... listeners callback undefined/found_at */ if (duk_is_undefined(ctx, -1)) { /* not found, clean up and come out */ duk_pop_3(ctx); return; } idx = duk_to_int(ctx, -1); duk_pop_2(ctx); /* ... listeners */ dukky_shuffle_array(ctx, idx); /* ... listeners */ duk_pop(ctx); /* ... */ } %} init EventTarget() %{ priv->is_node = false; priv->capture_registered = false; priv->bubbling_registered = false; %} method EventTarget::addEventListener() %{ dom_exception exc; event_listener_flags flags = ELF_NONE; /* Incoming stack is: type callback [options] */ if (duk_get_top(ctx) < 2) return 0; /* Bad arguments */ if (duk_get_top(ctx) > 3) return 0; /* Bad arguments */ if (duk_get_top(ctx) == 2) { duk_push_object(ctx); /* type callback options */ } if (duk_get_type(ctx, -1) != DUK_TYPE_OBJECT) { /* legacy support, if not object, it's the capture value */ duk_push_object(ctx); /* ... capture options */ duk_insert(ctx, -2); /* ... options capture */ duk_put_prop_string(ctx, -2, "capture"); /* ... options */ } /* type callback options */ flags = event_listener_pop_options(ctx); /* type callback */ duk_dup(ctx, -2); /* type callback type */ duk_push_this(ctx); /* type callback type this(=EventTarget) */ if (dukky_event_target_push_listeners(ctx, false) && priv->is_node) { /* Take a moment to register a JS callback */ duk_size_t ev_ty_l; const char *ev_ty = duk_to_lstring(ctx, -3, &ev_ty_l); dom_string *ev_ty_s; exc = dom_string_create((const uint8_t*)ev_ty, ev_ty_l, &ev_ty_s); if (exc != DOM_NO_ERR) { NSLOG(netsurf, INFO, "Oh dear, failed to create dom_string in addEventListener()"); return 0; } dukky_register_event_listener_for( ctx, (dom_element *)((node_private_t *)priv)->node, ev_ty_s, !!(flags & ELF_CAPTURE)); dom_string_unref(ev_ty_s); } /* type callback typelisteners */ duk_insert(ctx, -2); /* type typelisteners callback */ event_target_register_listener(ctx, flags); /* type */ return 0; %} method EventTarget::removeEventListener() %{ event_listener_flags flags = ELF_NONE; /* Incoming stack is: type callback [options] */ if (duk_get_top(ctx) < 2) return 0; /* Bad arguments */ if (duk_get_top(ctx) > 3) return 0; /* Bad arguments */ if (duk_get_top(ctx) == 2) { duk_push_object(ctx); /* type callback options */ } if (duk_get_type(ctx, -1) != DUK_TYPE_OBJECT) { /* legacy support, if not object, it's the capture value */ duk_push_object(ctx); /* ... capture options */ duk_insert(ctx, -2); /* ... options capture */ duk_put_prop_string(ctx, -2, "capture"); /* ... options */ } /* type callback options */ flags = event_listener_pop_options(ctx); /* type callback */ duk_dup(ctx, -2); /* type callback type */ duk_push_this(ctx); /* type callback type this(=EventTarget) */ if (dukky_event_target_push_listeners(ctx, true)) { /* nothing to do because the listener wasn't there at all */ duk_pop_3(ctx); return 0; } /* type callback typelisteners */ duk_insert(ctx, -2); /* type typelisteners callback */ event_target_unregister_listener(ctx, flags); /* type */ return 0; %} method EventTarget::dispatchEvent() %{ dom_exception exc; if (!dukky_instanceof(ctx, 0, PROTO_NAME(EVENT))) return 0; duk_get_prop_string(ctx, 0, PRIVATE_MAGIC); event_private_t *evpriv = duk_get_pointer(ctx, -1); duk_pop(ctx); dom_event *evt = evpriv->evt; /* Dispatch event logic, see: * https://dom.spec.whatwg.org/#dom-eventtarget-dispatchevent */ bool in_dispatch; if (dom_event_in_dispatch(evt, &in_dispatch) != DOM_NO_ERR) return 0; if (in_dispatch) { /** \todo Raise InvalidStateException */ return 0; } bool is_initialised; if (dom_event_is_initialised(evt, &is_initialised) != DOM_NO_ERR) return 0; if (is_initialised == false) { /** \todo Raise InvalidStateException */ return 0; } if (dom_event_set_is_trusted(evt, false) != DOM_NO_ERR) return 0; /** \todo work out how to dispatch against non-node things */ if (priv->is_node == false) return 0; bool success; /* Event prepared, dispatch against ourselves */ exc = dom_event_target_dispatch_event( ((node_private_t *)priv)->node, evt, &success); if (exc != DOM_NO_ERR) return 0; /**< \todo raise correct exception */ duk_push_boolean(ctx, success); return 1; %}