diff options
Diffstat (limited to 'content/handlers/javascript/duktape/dukky.c')
-rw-r--r-- | content/handlers/javascript/duktape/dukky.c | 1135 |
1 files changed, 1135 insertions, 0 deletions
diff --git a/content/handlers/javascript/duktape/dukky.c b/content/handlers/javascript/duktape/dukky.c new file mode 100644 index 000000000..7dd3bd71e --- /dev/null +++ b/content/handlers/javascript/duktape/dukky.c @@ -0,0 +1,1135 @@ +/* + * Copyright 2012 Vincent Sanders <vince@netsurf-browser.org> + * Copyright 2015 Daniel Dilverstone <dsilvers@netsurf-browser.org> + * Copyright 2016 Michael Drake <tlsa@netsurf-browser.org> + * Copyright 2016 John-Mark Bell <jmb@netsurf-browser.org> + * + * This file is part of NetSurf, http://www.netsurf-browser.org/ + * + * NetSurf is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 of the License. + * + * NetSurf is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +/** \file + * Duktapeish implementation of javascript engine functions. + */ + +#include <inttypes.h> + +#include <nsutils/time.h> + +#include "content/content.h" + +#include "utils/utils.h" +#include "utils/nsoption.h" +#include "utils/log.h" +#include "utils/corestrings.h" + +#include "javascript/js.h" +#include "javascript/content.h" + +#include "duktape/binding.h" + +#include "duktape.h" +#include "dukky.h" + +#include <dom/dom.h> + +#define EVENT_MAGIC MAGIC(EVENT_MAP) +#define HANDLER_LISTENER_MAGIC MAGIC(HANDLER_LISTENER_MAP) +#define HANDLER_MAGIC MAGIC(HANDLER_MAP) + +static duk_ret_t dukky_populate_object(duk_context *ctx) +{ + /* ... obj args protoname nargs */ + int nargs = duk_get_int(ctx, -1); + duk_pop(ctx); + /* ... obj args protoname */ + duk_get_global_string(ctx, PROTO_MAGIC); + /* .. obj args protoname prototab */ + duk_insert(ctx, -2); + /* ... obj args prototab protoname */ + duk_get_prop(ctx, -2); + /* ... obj args prototab {proto/undefined} */ + if (duk_is_undefined(ctx, -1)) { + LOG("RuhRoh, couldn't find a prototype, HTMLUnknownElement it is"); + duk_pop(ctx); + duk_push_string(ctx, PROTO_NAME(HTMLUNKNOWNELEMENT)); + duk_get_prop(ctx, -2); + } + /* ... obj args prototab proto */ + duk_dup(ctx, -1); + /* ... obj args prototab proto proto */ + duk_set_prototype(ctx, -(nargs+4)); + /* ... obj[proto] args prototab proto */ + duk_get_prop_string(ctx, -1, INIT_MAGIC); + /* ... obj[proto] args prototab proto initfn */ + duk_insert(ctx, -(nargs+4)); + /* ... initfn obj[proto] args prototab proto */ + duk_pop_2(ctx); + /* ... initfn obj[proto] args */ + LOG("Call the init function"); + duk_call(ctx, nargs + 1); + return 1; /* The object */ +} + +duk_ret_t dukky_create_object(duk_context *ctx, const char *name, int args) +{ + duk_ret_t ret; + LOG("name=%s nargs=%d", name+2, args); + /* ... args */ + duk_push_object(ctx); + /* ... args obj */ + duk_push_object(ctx); + /* ... args obj handlers */ + duk_put_prop_string(ctx, -2, HANDLER_LISTENER_MAGIC); + /* ... args obj */ + duk_push_object(ctx); + /* ... args obj handlers */ + duk_put_prop_string(ctx, -2, HANDLER_MAGIC); + /* ... args obj */ + duk_insert(ctx, -(args+1)); + /* ... obj args */ + duk_push_string(ctx, name); + /* ... obj args name */ + duk_push_int(ctx, args); + /* ... obj args name nargs */ + if ((ret = duk_safe_call(ctx, dukky_populate_object, args + 3, 1)) + != DUK_EXEC_SUCCESS) + return ret; + LOG("created"); + return DUK_EXEC_SUCCESS; +} + + + +duk_bool_t +dukky_push_node_stacked(duk_context *ctx) +{ + int top_at_fail = duk_get_top(ctx) - 2; + /* ... nodeptr klass */ + duk_get_global_string(ctx, NODE_MAGIC); + /* ... nodeptr klass nodes */ + duk_dup(ctx, -3); + /* ... nodeptr klass nodes nodeptr */ + duk_get_prop(ctx, -2); + /* ... nodeptr klass nodes node/undefined */ + if (duk_is_undefined(ctx, -1)) { + /* ... nodeptr klass nodes undefined */ + duk_pop(ctx); + /* ... nodeptr klass nodes */ + duk_push_object(ctx); + /* ... nodeptr klass nodes obj */ + duk_push_object(ctx); + /* ... nodeptr klass nodes obj handlers */ + duk_put_prop_string(ctx, -2, HANDLER_LISTENER_MAGIC); + /* ... nodeptr klass nodes obj */ + duk_push_object(ctx); + /* ... nodeptr klass nodes obj handlers */ + duk_put_prop_string(ctx, -2, HANDLER_MAGIC); + /* ... nodeptr klass nodes obj */ + duk_dup(ctx, -4); + /* ... nodeptr klass nodes obj nodeptr */ + duk_dup(ctx, -4); + /* ... nodeptr klass nodes obj nodeptr klass */ + duk_push_int(ctx, 1); + /* ... nodeptr klass nodes obj nodeptr klass 1 */ + if (duk_safe_call(ctx, dukky_populate_object, 4, 1) + != DUK_EXEC_SUCCESS) { + duk_set_top(ctx, top_at_fail); + LOG("Boo and also hiss"); + return false; + } + /* ... nodeptr klass nodes node */ + duk_dup(ctx, -4); + /* ... nodeptr klass nodes node nodeptr */ + duk_dup(ctx, -2); + /* ... nodeptr klass nodes node nodeptr node */ + duk_put_prop(ctx, -4); + /* ... nodeptr klass nodes node */ + } + /* ... nodeptr klass nodes node */ + duk_insert(ctx, -4); + /* ... node nodeptr klass nodes */ + duk_pop_3(ctx); + /* ... node */ + return true; +} + +#define SET_HTML_CLASS(CLASS) \ + *html_class = PROTO_NAME(HTML##CLASS##ELEMENT); \ + *html_class_len = \ + SLEN(PROTO_NAME(HTML)) + \ + SLEN(#CLASS) + \ + SLEN("ELEMENT"); + +static void dukky_html_element_class_from_tag_type(dom_html_element_type type, + const char **html_class, size_t *html_class_len) +{ + switch(type) { + case DOM_HTML_ELEMENT_TYPE_HTML: + SET_HTML_CLASS(HTML) + break; + case DOM_HTML_ELEMENT_TYPE_HEAD: + SET_HTML_CLASS(HEAD) + break; + case DOM_HTML_ELEMENT_TYPE_META: + SET_HTML_CLASS(META) + break; + case DOM_HTML_ELEMENT_TYPE_BASE: + SET_HTML_CLASS(BASE) + break; + case DOM_HTML_ELEMENT_TYPE_TITLE: + SET_HTML_CLASS(TITLE) + break; + case DOM_HTML_ELEMENT_TYPE_BODY: + SET_HTML_CLASS(BODY) + break; + case DOM_HTML_ELEMENT_TYPE_DIV: + SET_HTML_CLASS(DIV) + break; + case DOM_HTML_ELEMENT_TYPE_FORM: + SET_HTML_CLASS(FORM) + break; + case DOM_HTML_ELEMENT_TYPE_LINK: + SET_HTML_CLASS(LINK) + break; + case DOM_HTML_ELEMENT_TYPE_BUTTON: + SET_HTML_CLASS(BUTTOM) + break; + case DOM_HTML_ELEMENT_TYPE_INPUT: + SET_HTML_CLASS(INPUT) + break; + case DOM_HTML_ELEMENT_TYPE_TEXTAREA: + SET_HTML_CLASS(TEXTAREA) + break; + case DOM_HTML_ELEMENT_TYPE_OPTGROUP: + SET_HTML_CLASS(OPTGROUP) + break; + case DOM_HTML_ELEMENT_TYPE_OPTION: + SET_HTML_CLASS(OPTION) + break; + case DOM_HTML_ELEMENT_TYPE_SELECT: + SET_HTML_CLASS(SELECT) + break; + case DOM_HTML_ELEMENT_TYPE_HR: + SET_HTML_CLASS(HR) + break; + case DOM_HTML_ELEMENT_TYPE_DL: + SET_HTML_CLASS(DLIST) + break; + case DOM_HTML_ELEMENT_TYPE_DIR: + SET_HTML_CLASS(DIRECTORY) + break; + case DOM_HTML_ELEMENT_TYPE_MENU: + SET_HTML_CLASS(MENU) + break; + case DOM_HTML_ELEMENT_TYPE_FIELDSET: + SET_HTML_CLASS(FIELDSET) + break; + case DOM_HTML_ELEMENT_TYPE_LEGEND: + SET_HTML_CLASS(LEGEND) + break; + case DOM_HTML_ELEMENT_TYPE_P: + SET_HTML_CLASS(PARAGRAPH) + break; + case DOM_HTML_ELEMENT_TYPE_H1: + case DOM_HTML_ELEMENT_TYPE_H2: + case DOM_HTML_ELEMENT_TYPE_H3: + case DOM_HTML_ELEMENT_TYPE_H4: + case DOM_HTML_ELEMENT_TYPE_H5: + case DOM_HTML_ELEMENT_TYPE_H6: + SET_HTML_CLASS(HEADING) + break; + case DOM_HTML_ELEMENT_TYPE_BLOCKQUOTE: + case DOM_HTML_ELEMENT_TYPE_Q: + SET_HTML_CLASS(QUOTE) + break; + case DOM_HTML_ELEMENT_TYPE_PRE: + SET_HTML_CLASS(PRE) + break; + case DOM_HTML_ELEMENT_TYPE_BR: + SET_HTML_CLASS(BR) + break; + case DOM_HTML_ELEMENT_TYPE_LABEL: + SET_HTML_CLASS(LABEL) + break; + case DOM_HTML_ELEMENT_TYPE_UL: + SET_HTML_CLASS(ULIST) + break; + case DOM_HTML_ELEMENT_TYPE_OL: + SET_HTML_CLASS(OLIST) + break; + case DOM_HTML_ELEMENT_TYPE_LI: + SET_HTML_CLASS(LI) + break; + case DOM_HTML_ELEMENT_TYPE_FONT: + SET_HTML_CLASS(FONT) + break; + case DOM_HTML_ELEMENT_TYPE_DEL: + case DOM_HTML_ELEMENT_TYPE_INS: + SET_HTML_CLASS(MOD) + break; + case DOM_HTML_ELEMENT_TYPE_A: + SET_HTML_CLASS(ANCHOR) + break; + case DOM_HTML_ELEMENT_TYPE_BASEFONT: + SET_HTML_CLASS(BASEFONT) + break; + case DOM_HTML_ELEMENT_TYPE_IMG: + SET_HTML_CLASS(IMAGE) + break; + case DOM_HTML_ELEMENT_TYPE_OBJECT: + SET_HTML_CLASS(OBJECT) + break; + case DOM_HTML_ELEMENT_TYPE_PARAM: + SET_HTML_CLASS(PARAM) + break; + case DOM_HTML_ELEMENT_TYPE_APPLET: + SET_HTML_CLASS(APPLET) + break; + case DOM_HTML_ELEMENT_TYPE_MAP: + SET_HTML_CLASS(MAP) + break; + case DOM_HTML_ELEMENT_TYPE_AREA: + SET_HTML_CLASS(AREA) + break; + case DOM_HTML_ELEMENT_TYPE_SCRIPT: + SET_HTML_CLASS(SCRIPT) + break; + case DOM_HTML_ELEMENT_TYPE_CAPTION: + SET_HTML_CLASS(TABLECAPTION) + break; + case DOM_HTML_ELEMENT_TYPE_TD: + case DOM_HTML_ELEMENT_TYPE_TH: + SET_HTML_CLASS(TABLECELL) + break; + case DOM_HTML_ELEMENT_TYPE_COL: + case DOM_HTML_ELEMENT_TYPE_COLGROUP: + SET_HTML_CLASS(TABLECOL) + break; + case DOM_HTML_ELEMENT_TYPE_THEAD: + case DOM_HTML_ELEMENT_TYPE_TBODY: + case DOM_HTML_ELEMENT_TYPE_TFOOT: + SET_HTML_CLASS(TABLESECTION) + break; + case DOM_HTML_ELEMENT_TYPE_TABLE: + SET_HTML_CLASS(TABLE) + break; + case DOM_HTML_ELEMENT_TYPE_TR: + SET_HTML_CLASS(TABLEROW) + break; + case DOM_HTML_ELEMENT_TYPE_STYLE: + SET_HTML_CLASS(STYLE) + break; + case DOM_HTML_ELEMENT_TYPE_FRAMESET: + SET_HTML_CLASS(FRAMESET) + break; + case DOM_HTML_ELEMENT_TYPE_FRAME: + SET_HTML_CLASS(FRAME) + break; + case DOM_HTML_ELEMENT_TYPE_IFRAME: + SET_HTML_CLASS(IFRAME) + break; + case DOM_HTML_ELEMENT_TYPE_ISINDEX: + SET_HTML_CLASS(ISINDEX) + break; + case DOM_HTML_ELEMENT_TYPE__COUNT: + assert(type != DOM_HTML_ELEMENT_TYPE__COUNT); + /* fallthrough */ + case DOM_HTML_ELEMENT_TYPE__UNKNOWN: + SET_HTML_CLASS(UNKNOWN) + break; + default: + /* Known HTML element without a specialisation */ + *html_class = PROTO_NAME(HTMLELEMENT); + *html_class_len = + SLEN(PROTO_NAME(HTML)) + + SLEN("ELEMENT"); + break; + } + return; +} + +#undef SET_HTML_CLASS + +static void +dukky_push_node_klass(duk_context *ctx, struct dom_node *node) +{ + dom_node_type nodetype; + dom_exception err; + + err = dom_node_get_node_type(node, &nodetype); + if (err != DOM_NO_ERR) { + /* Oh bum, just node then */ + duk_push_string(ctx, PROTO_NAME(NODE)); + return; + } + + switch(nodetype) { + case DOM_ELEMENT_NODE: { + dom_string *namespace; + dom_html_element_type type; + const char *html_class; + size_t html_class_len; + err = dom_node_get_namespace(node, &namespace); + if (err != DOM_NO_ERR) { + /* Feck it, element */ + LOG("dom_node_get_namespace() failed"); + duk_push_string(ctx, PROTO_NAME(ELEMENT)); + break; + } + if (namespace == NULL) { + /* No namespace, -> element */ + LOG("no namespace"); + duk_push_string(ctx, PROTO_NAME(ELEMENT)); + break; + } + + if (dom_string_isequal(namespace, corestring_dom_html_namespace) == false) { + /* definitely not an HTML element of some kind */ + duk_push_string(ctx, PROTO_NAME(ELEMENT)); + dom_string_unref(namespace); + break; + } + dom_string_unref(namespace); + + err = dom_html_element_get_tag_type(node, &type); + if (err != DOM_NO_ERR) { + type = DOM_HTML_ELEMENT_TYPE__UNKNOWN; + } + + dukky_html_element_class_from_tag_type(type, + &html_class, &html_class_len); + + duk_push_lstring(ctx, html_class, html_class_len); + break; + } + case DOM_TEXT_NODE: + duk_push_string(ctx, PROTO_NAME(TEXT)); + break; + case DOM_COMMENT_NODE: + duk_push_string(ctx, PROTO_NAME(COMMENT)); + break; + case DOM_DOCUMENT_NODE: + duk_push_string(ctx, PROTO_NAME(DOCUMENT)); + break; + case DOM_ATTRIBUTE_NODE: + case DOM_PROCESSING_INSTRUCTION_NODE: + case DOM_DOCUMENT_TYPE_NODE: + case DOM_DOCUMENT_FRAGMENT_NODE: + case DOM_NOTATION_NODE: + case DOM_ENTITY_REFERENCE_NODE: + case DOM_ENTITY_NODE: + case DOM_CDATA_SECTION_NODE: + default: + /* Oh bum, just node then */ + duk_push_string(ctx, PROTO_NAME(NODE)); + } +} + +duk_bool_t +dukky_push_node(duk_context *ctx, struct dom_node *node) +{ + JS_LOG("Pushing node %p", node); + /* First check if we can find the node */ + /* ... */ + duk_get_global_string(ctx, NODE_MAGIC); + /* ... nodes */ + duk_push_pointer(ctx, node); + /* ... nodes nodeptr */ + duk_get_prop(ctx, -2); + /* ... nodes node/undefined */ + if (!duk_is_undefined(ctx, -1)) { + /* ... nodes node */ + duk_insert(ctx, -2); + /* ... node nodes */ + duk_pop(ctx); + /* ... node */ + JS_LOG("Found it memoised"); + return true; + } + /* ... nodes undefined */ + duk_pop_2(ctx); + /* ... */ + /* We couldn't, so now we determine the node type and then + * we ask for it to be created + */ + duk_push_pointer(ctx, node); + /* ... nodeptr */ + dukky_push_node_klass(ctx, node); + /* ... nodeptr klass */ + return dukky_push_node_stacked(ctx); +} + +static duk_ret_t +dukky_bad_constructor(duk_context *ctx) +{ + duk_error(ctx, DUK_ERR_ERROR, "Bad constructor"); + return 0; +} + +void +dukky_inject_not_ctr(duk_context *ctx, int idx, const char *name) +{ + /* ... p[idx] ... proto */ + duk_push_c_function(ctx, dukky_bad_constructor, 0); + /* ... p[idx] ... proto cons */ + duk_insert(ctx, -2); + /* ... p[idx] ... cons proto */ + duk_put_prop_string(ctx, -2, "prototype"); + /* ... p[idx] ... cons[proto] */ + duk_put_prop_string(ctx, idx, name); + /* ... p ... */ + return; +} + +/* Duktape heap utility functions */ + +/* We need to override the defaults because not all platforms are fully ANSI + * compatible. E.g. RISC OS gets upset if we malloc or realloc a zero byte + * block, as do debugging tools such as Electric Fence by Bruce Perens. + */ + +static void *dukky_alloc_function(void *udata, duk_size_t size) +{ + if (size == 0) + return NULL; + + return malloc(size); +} + +static void *dukky_realloc_function(void *udata, void *ptr, duk_size_t size) +{ + if (ptr == NULL && size == 0) + return NULL; + + if (size == 0) { + free(ptr); + return NULL; + } + + return realloc(ptr, size); +} + +static void dukky_free_function(void *udata, void *ptr) +{ + if (ptr != NULL) + free(ptr); +} + + +/**************************************** js.h ******************************/ +struct jscontext { + duk_context *ctx; + duk_context *thread; + uint64_t exec_start_time; +}; + +#define CTX (ctx->thread) + +void js_initialise(void) +{ + /** TODO: Forces JS on for our testing, needs changing before a release + * lest we incur the wrath of others. + */ + /* Disabled force-on for forthcoming release */ + /* nsoption_set_bool(enable_javascript, true); + */ + javascript_init(); +} + +void js_finalise(void) +{ + /* NADA for now */ +} + +#define DUKKY_NEW_PROTOTYPE(klass, uklass, klass_name) \ + dukky_create_prototype(ctx, dukky_##klass##___proto, PROTO_NAME(uklass), klass_name) + +nserror js_newcontext(int timeout, jscallback *cb, void *cbctx, + jscontext **jsctx) +{ + duk_context *ctx; + jscontext *ret = calloc(1, sizeof(*ret)); + *jsctx = NULL; + LOG("Creating new duktape javascript context"); + if (ret == NULL) return NSERROR_NOMEM; + ctx = ret->ctx = duk_create_heap( + dukky_alloc_function, + dukky_realloc_function, + dukky_free_function, + ret, + NULL); + if (ret->ctx == NULL) { free(ret); return NSERROR_NOMEM; } + /* Create the prototype stuffs */ + duk_push_global_object(ctx); + duk_push_boolean(ctx, true); + duk_put_prop_string(ctx, -2, "protos"); + duk_put_global_string(ctx, PROTO_MAGIC); + /* Create prototypes here */ + dukky_create_prototypes(ctx); + + *jsctx = ret; + return NSERROR_OK; +} + +void js_destroycontext(jscontext *ctx) +{ + LOG("Destroying duktape javascript context"); + duk_destroy_heap(ctx->ctx); + free(ctx); +} + +jsobject *js_newcompartment(jscontext *ctx, void *win_priv, void *doc_priv) +{ + assert(ctx != NULL); + /* Pop any active thread off */ + LOG("Yay, new compartment, win_priv=%p, doc_priv=%p", win_priv, doc_priv); + duk_set_top(ctx->ctx, 0); + duk_push_thread(ctx->ctx); + ctx->thread = duk_require_context(ctx->ctx, -1); + duk_push_int(CTX, 0); + duk_push_int(CTX, 1); + duk_push_int(CTX, 2); + /* Manufacture a Window object */ + /* win_priv is a browser_window, doc_priv is an html content struct */ + duk_push_pointer(CTX, win_priv); + duk_push_pointer(CTX, doc_priv); + dukky_create_object(CTX, PROTO_NAME(WINDOW), 2); + duk_push_global_object(CTX); + duk_put_prop_string(CTX, -2, PROTO_MAGIC); + duk_set_global_object(CTX); + + /* Now we need to prepare our node mapping table */ + duk_push_object(CTX); + duk_push_pointer(CTX, NULL); + duk_push_null(CTX); + duk_put_prop(CTX, -3); + duk_put_global_string(CTX, NODE_MAGIC); + + /* And now the event mapping table */ + duk_push_object(CTX); + duk_put_global_string(CTX, EVENT_MAGIC); + + return (jsobject *)ctx; +} + +static duk_ret_t eval_top_string(duk_context *ctx) +{ + duk_eval(ctx); + return 0; +} + +duk_bool_t dukky_check_timeout(void *udata) +{ +#define JS_EXEC_TIMEOUT_MS 10000 /* 10 seconds */ + jscontext *ctx = (jscontext *) udata; + uint64_t now; + + (void) nsu_getmonotonic_ms(&now); + + /* This function may be called during duk heap construction, + * so only test for execution timeout if we've recorded a + * start time. + */ + return ctx->exec_start_time != 0 && + now > (ctx->exec_start_time + JS_EXEC_TIMEOUT_MS); +} + +bool js_exec(jscontext *ctx, const char *txt, size_t txtlen) +{ + assert(ctx); + if (txt == NULL || txtlen == 0) return false; + duk_set_top(CTX, 0); + duk_push_lstring(CTX, txt, txtlen); + + (void) nsu_getmonotonic_ms(&ctx->exec_start_time); + if (duk_safe_call(CTX, eval_top_string, 1, 1) == DUK_EXEC_ERROR) { + duk_get_prop_string(CTX, 0, "name"); + duk_get_prop_string(CTX, 0, "message"); + duk_get_prop_string(CTX, 0, "fileName"); + duk_get_prop_string(CTX, 0, "lineNumber"); + duk_get_prop_string(CTX, 0, "stack"); + LOG("Uncaught error in JS: %s: %s", duk_safe_to_string(CTX, 1), + duk_safe_to_string(CTX, 2)); + LOG(" was at: %s line %s", duk_safe_to_string(CTX, 3), + duk_safe_to_string(CTX, 4)); + LOG(" Stack trace: %s", duk_safe_to_string(CTX, 5)); + return false; + } + if (duk_get_top(CTX) == 0) duk_push_boolean(CTX, false); + LOG("Returning %s", duk_get_boolean(CTX, 0) ? "true" : "false"); + return duk_get_boolean(CTX, 0); +} + +/*** New style event handling ***/ + +static void dukky_push_event(duk_context *ctx, dom_event *evt) +{ + /* ... */ + duk_get_global_string(ctx, EVENT_MAGIC); + /* ... events */ + duk_push_pointer(ctx, evt); + /* ... events eventptr */ + duk_get_prop(ctx, -2); + /* ... events event? */ + if (duk_is_undefined(ctx, -1)) { + /* ... events undefined */ + duk_pop(ctx); + /* ... events */ + duk_push_pointer(ctx, evt); + if (dukky_create_object(ctx, PROTO_NAME(EVENT), 1) != DUK_EXEC_SUCCESS) { + /* ... events err */ + duk_pop(ctx); + /* ... events */ + duk_push_object(ctx); + /* ... events eobj[meh] */ + } + /* ... events eobj */ + duk_push_pointer(ctx, evt); + /* ... events eobj eventptr */ + duk_dup(ctx, -2); + /* ... events eobj eventptr eobj */ + duk_put_prop(ctx, -4); + /* ... events eobj */ + } + /* ... events event */ + duk_replace(ctx, -2); + /* ... event */ +} + +static void dukky_push_handler_code_(duk_context *ctx, dom_string *name, + dom_event_target *et) +{ + dom_string *onname, *val; + dom_element *ele = (dom_element *)et; + dom_exception exc; + dom_node_type ntype; + + /* Currently safe since libdom has no event targets which are not + * nodes. Reconsider this as and when we work out how to have + * window do stuff + */ + exc = dom_node_get_node_type(et, &ntype); + if (exc != DOM_NO_ERR) { + duk_push_lstring(ctx, "", 0); + return; + } + + if (ntype != DOM_ELEMENT_NODE) { + duk_push_lstring(ctx, "", 0); + return; + } + + exc = dom_string_concat(corestring_dom_on, name, &onname); + if (exc != DOM_NO_ERR) { + duk_push_lstring(ctx, "", 0); + return; + } + + exc = dom_element_get_attribute(ele, onname, &val); + if ((exc != DOM_NO_ERR) || (val == NULL)) { + dom_string_unref(onname); + duk_push_lstring(ctx, "", 0); + return; + } + + dom_string_unref(onname); + duk_push_lstring(ctx, dom_string_data(val), dom_string_length(val)); + dom_string_unref(val); +} + +bool dukky_get_current_value_of_event_handler(duk_context *ctx, + dom_string *name, + dom_event_target *et) +{ + /* Must be entered as: + * ... node(et) + */ + duk_get_prop_string(ctx, -1, HANDLER_MAGIC); + /* ... node handlers */ + duk_push_lstring(ctx, dom_string_data(name), dom_string_length(name)); + /* ... node handlers name */ + duk_get_prop(ctx, -2); + /* ... node handlers handler? */ + if (duk_is_undefined(ctx, -1)) { + /* ... node handlers undefined */ + duk_pop_2(ctx); + /* ... node */ + dukky_push_handler_code_(ctx, name, et); + /* ... node handlercode? */ + /* TODO: If this is null, clean up and propagate */ + /* ... node handlercode */ + /** @todo This is entirely wrong, but it's hard to get right */ + duk_push_string(ctx, "function (event) {"); + /* ... node handlercode prefix */ + duk_insert(ctx, -2); + /* ... node prefix handlercode */ + duk_push_string(ctx, "}"); + /* ... node prefix handlercode suffix */ + duk_concat(ctx, 3); + /* ... node fullhandlersrc */ + duk_push_string(ctx, "internal raw uncompiled handler"); + /* ... node fullhandlersrc filename */ + if (duk_pcompile(ctx, DUK_COMPILE_FUNCTION) != 0) { + /* ... node err */ + LOG("Unable to proceed with handler, could not compile"); + duk_pop_2(ctx); + return false; + } + /* ... node handler */ + duk_insert(ctx, -2); + /* ... handler node */ + } else { + /* ... node handlers handler */ + duk_insert(ctx, -3); + /* ... handler node handlers */ + duk_pop(ctx); + /* ... handler node */ + } + /* ... handler node */ + return true; +} + +static void dukky_generic_event_handler(dom_event *evt, void *pw) +{ + duk_memory_functions funcs; + duk_context *ctx = (duk_context *)pw; + jscontext *jsctx; + dom_string *name; + dom_exception exc; + dom_event_target *targ; + dom_event_flow_phase phase; + + /* Retrieve the JS context from the Duktape context */ + duk_get_memory_functions(ctx, &funcs); + jsctx = funcs.udata; + + LOG("WOOP WOOP, An event:"); + exc = dom_event_get_type(evt, &name); + if (exc != DOM_NO_ERR) { + LOG("Unable to find the event name"); + return; + } + LOG("Event's name is %*s", + dom_string_length(name), dom_string_data(name)); + exc = dom_event_get_event_phase(evt, &phase); + if (exc != DOM_NO_ERR) { + LOG("Unable to get event phase"); + return; + } + LOG("Event phase is: %s (%d)", + phase == DOM_CAPTURING_PHASE ? "capturing" : + phase == DOM_AT_TARGET ? "at-target" : + phase == DOM_BUBBLING_PHASE ? "bubbling" : + "unknown", (int)phase); + + exc = dom_event_get_current_target(evt, &targ); + if (exc != DOM_NO_ERR) { + dom_string_unref(name); + LOG("Unable to find the event target"); + return; + } + + /* ... */ + if (dukky_push_node(ctx, (dom_node *)targ) == false) { + dom_string_unref(name); + dom_node_unref(targ); + LOG("Unable to push JS node representation?!"); + return; + } + /* ... 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; + } + /** @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 */ + (void) nsu_getmonotonic_ms(&jsctx->exec_start_time); + if (duk_pcall_method(ctx, 1) != 0) { + /* Failed to run the method */ + /* ... 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, 6); + /* ... */ + return; + } + /* ... result */ + if (duk_is_boolean(ctx, -1) && + duk_to_boolean(ctx, -1) == 0) { + dom_event_prevent_default(evt); + } + duk_pop(ctx); + /* ... */ +} + +void dukky_register_event_listener_for(duk_context *ctx, + struct dom_element *ele, + dom_string *name) +{ + dom_event_listener *listen = NULL; + dom_exception exc; + + /* ... */ + if (dukky_push_node(ctx, (struct dom_node *)ele) == false) + return; + /* ... node */ + duk_get_prop_string(ctx, -1, HANDLER_LISTENER_MAGIC); + /* ... node handlers */ + duk_push_lstring(ctx, dom_string_data(name), dom_string_length(name)); + /* ... node handlers name */ + if (duk_has_prop(ctx, -2)) { + /* ... node handlers */ + duk_pop_2(ctx); + /* ... */ + return; + } + /* ... node handlers */ + duk_push_lstring(ctx, dom_string_data(name), dom_string_length(name)); + /* ... node handlers name */ + duk_push_boolean(ctx, true); + /* ... node handlers name true */ + duk_put_prop(ctx, -3); + /* ... node handlers */ + duk_pop_2(ctx); + /* ... */ + exc = dom_event_listener_create(dukky_generic_event_handler, ctx, + &listen); + if (exc != DOM_NO_ERR) return; + exc = dom_event_target_add_event_listener( + ele, name, listen, false); + if (exc != DOM_NO_ERR) { + LOG("Unable to register listener for %p.%*s", + ele, dom_string_length(name), dom_string_data(name)); + } else { + LOG("have registered listener for %p.%*s", + ele, dom_string_length(name), dom_string_data(name)); + } + dom_event_listener_unref(listen); +} + + +void js_handle_new_element(jscontext *ctx, struct dom_element *node) +{ + assert(ctx); + assert(node); + dom_namednodemap *map; + dom_exception exc; + dom_ulong idx; + dom_ulong siz; + dom_attr *attr = NULL; + dom_string *key = NULL; + dom_string *nodename; + duk_bool_t is_body = false; + + exc = dom_node_get_node_name(node, &nodename); + if (exc != DOM_NO_ERR) return; + + if (nodename == corestring_dom_BODY) + is_body = true; + + dom_string_unref(nodename); + + exc = dom_node_get_attributes(node, &map); + if (exc != DOM_NO_ERR) return; + if (map == NULL) return; + + exc = dom_namednodemap_get_length(map, &siz); + if (exc != DOM_NO_ERR) goto out; + + for (idx = 0; idx < siz; idx++) { + exc = dom_namednodemap_item(map, idx, &attr); + if (exc != DOM_NO_ERR) goto out; + exc = dom_attr_get_name(attr, &key); + if (exc != DOM_NO_ERR) goto out; + if (is_body && ( + key == corestring_dom_onblur || + key == corestring_dom_onerror || + key == corestring_dom_onfocus || + key == corestring_dom_onload || + key == corestring_dom_onresize || + key == corestring_dom_onscroll)) { + /* This is a forwarded event, it doesn't matter, + * we should skip registering for it and later + * we will register it for Window itself + */ + goto skip_register; + } + if (dom_string_length(key) > 2) { + /* Can be on* */ + const uint8_t *data = (const uint8_t *)dom_string_data(key); + if (data[0] == 'o' && data[1] == 'n') { + dom_string *sub = NULL; + exc = dom_string_substr( + key, 2, dom_string_length(key), + &sub); + if (exc == DOM_NO_ERR) { + dukky_register_event_listener_for( + CTX, node, sub); + dom_string_unref(sub); + } + } + } + skip_register: + dom_string_unref(key); key = NULL; + dom_node_unref(attr); attr = NULL; + } + +out: + if (key != NULL) + dom_string_unref(key); + + if (attr != NULL) + dom_node_unref(attr); + + dom_namednodemap_unref(map); +} + +void js_event_cleanup(jscontext *ctx, struct dom_event *evt) +{ + assert(ctx); + /* ... */ + duk_get_global_string(CTX, EVENT_MAGIC); + /* ... EVENT_MAP */ + duk_push_pointer(CTX, evt); + /* ... EVENT_MAP eventptr */ + duk_del_prop(CTX, -2); + /* ... EVENT_MAP */ + duk_pop(CTX); + /* ... */ +} + +bool js_fire_event(jscontext *ctx, const char *type, struct dom_document *doc, struct dom_node *target) +{ + dom_exception exc; + dom_event *evt; + dom_event_target *body; + + LOG("Event: %s (doc=%p, target=%p)", type, doc, target); + + /** @todo Make this more generic, this only handles load and only + * targetting the window, so that we actually stand a chance of + * getting 3.4 out. + */ + + if (target != NULL) + /* Swallow non-Window-targetted events quietly */ + return true; + + if (strcmp(type, "load") != 0) + /* Swallow non-load events quietly */ + return true; + + /* Okay, we're processing load, targetted at Window, do the single + * thing which gets us there, which is to find the appropriate event + * handler and call it. If we have no event handler on Window then + * we divert to the body, and if there's no event handler there + * we swallow the event silently + */ + + exc = dom_event_create(&evt); + if (exc != DOM_NO_ERR) return true; + exc = dom_event_init(evt, corestring_dom_load, false, false); + if (exc != DOM_NO_ERR) { + dom_event_unref(evt); + return true; + } + /* ... */ + duk_get_global_string(CTX, HANDLER_MAGIC); + /* ... handlers */ + duk_push_lstring(CTX, "load", 4); + /* ... handlers "load" */ + duk_get_prop(CTX, -2); + /* ... handlers handler? */ + if (duk_is_undefined(CTX, -1)) { + /* No handler here, *try* and retrieve a handler from + * the body + */ + duk_pop(CTX); + /* ... handlers */ + exc = dom_html_document_get_body(doc, &body); + if (exc != DOM_NO_ERR) { + dom_event_unref(evt); + return true; + } + dukky_push_node(CTX, (struct dom_node *)body); + /* ... handlers bodynode */ + if (dukky_get_current_value_of_event_handler( + CTX, corestring_dom_load, body) == false) { + /* ... handlers */ + duk_pop(CTX); + return true; + } + /* ... handlers handler bodynode */ + duk_pop(CTX); + } + /* ... handlers handler */ + duk_insert(CTX, -2); + /* ... handler handlers */ + duk_pop(CTX); + /* ... handler */ + duk_push_global_object(CTX); + /* ... handler Window */ + dukky_push_event(CTX, evt); + /* ... handler Window event */ + (void) nsu_getmonotonic_ms(&ctx->exec_start_time); + if (duk_pcall_method(CTX, 1) != 0) { + /* Failed to run the handler */ + /* ... err */ + LOG("OH NOES! An error running a handler. Meh."); + 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, 6); + /* ... */ + js_event_cleanup(ctx, evt); + dom_event_unref(evt); + return true; + } + /* ... result */ + duk_pop(CTX); + /* ... */ + js_event_cleanup(ctx, evt); + dom_event_unref(evt); + return true; +} |