/* Window binding for browser using duktape and libdom * * Copyright 2015 Vincent Sanders * * 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 Window { private struct browser_window * win; private struct html_content * htmlc; private struct window_schedule_s * schedule_ring; prologue %{ #include "utils/corestrings.h" #include "utils/nsurl.h" #include "netsurf/browser_window.h" #include "content/hlcache.h" #include "html/html.h" #include "html/html_internal.h" #include "desktop/gui_internal.h" #include "netsurf/misc.h" #include "utils/ring.h" #define WINDOW_CALLBACKS MAGIC(WindowCallbacks) #define HANDLER_MAGIC MAGIC(HANDLER_MAP) static size_t next_handle = 0; typedef struct window_schedule_s { window_private_t *owner; duk_context *ctx; struct window_schedule_s *r_next; struct window_schedule_s *r_prev; size_t handle; int repeat_timeout; } window_schedule_t; static void window_remove_callback_bits(duk_context *ctx, size_t handle) { /* stack is ... */ duk_push_global_object(ctx); duk_get_prop_string(ctx, -1, WINDOW_CALLBACKS); /* stack is ..., win, cbt */ duk_push_int(ctx, (duk_int_t)handle); /* ..., win, cbt, handle */ duk_del_prop(ctx, -2); /* ..., win, cbt */ duk_pop_2(ctx); /* ... */ } static void window_call_callback(duk_context *ctx, size_t handle) { NSLOG(dukky, DEEPDEBUG, "ctx=%p, handle=%zd", ctx, handle); /* Stack is ... */ duk_push_context_dump(ctx); NSLOG(dukky, DEEPDEBUG, "On entry to callback, stack is: %s", duk_get_string(ctx, -1)); duk_pop(ctx); duk_push_global_object(ctx); duk_get_prop_string(ctx, -1, WINDOW_CALLBACKS); duk_push_int(ctx, (duk_int_t)handle); duk_get_prop(ctx, -2); /* ..., win, cbt, cbo */ /* What we want to do is call cbo.func passing all of cbo.args */ duk_get_prop_string(ctx, -1, "func"); duk_get_prop_string(ctx, -2, "args"); /* ..., win, cbt, cbo, func, argarr */ duk_size_t arrlen = duk_get_length(ctx, -1); for (duk_size_t i = 0; i < arrlen; ++i) { duk_push_int(ctx, (duk_int_t)i); duk_get_prop(ctx, -(2+i)); } /* ..., win, cbt, cbo, func, argarr, args... */ duk_remove(ctx, -(arrlen+1)); /* ..., win, cbt, cbo, func, args... */ duk_push_context_dump(ctx); NSLOG(dukky, DEEPDEBUG, "Just before call with %d args: %s", (int)arrlen, duk_get_string(ctx, -1)); duk_pop(ctx); (void) dukky_pcall(ctx, arrlen, true); /* ..., win, cbt, cbo, retval */ duk_pop_n(ctx, 4); /* ... */ duk_push_context_dump(ctx); NSLOG(dukky, DEEPDEBUG, "On leaving callback, stack is: %s", duk_get_string(ctx, -1)); duk_pop(ctx); } static void window_schedule_callback(void *p) { window_schedule_t *priv = (window_schedule_t *)p; NSLOG(dukky, DEEPDEBUG, "Entered window scheduler callback: %zd", priv->handle); window_call_callback(priv->ctx, priv->handle); if (priv->repeat_timeout > 0) { /* Reschedule */ NSLOG(dukky, DEEPDEBUG, "Rescheduling repeating callback %zd", priv->handle); guit->misc->schedule(priv->repeat_timeout, window_schedule_callback, priv); } else { NSLOG(dukky, DEEPDEBUG, "Removing completed callback %zd", priv->handle); /* Remove this from the ring */ RING_REMOVE(priv->owner->schedule_ring, priv); window_remove_callback_bits(priv->ctx, priv->handle); /* TODO: Remove the entry from the JS part */ free(priv); } } static size_t window_alloc_new_callback(duk_context *ctx, window_private_t *window, bool repeating, int timeout) { size_t new_handle = next_handle++; window_schedule_t *sched = calloc(sizeof *sched, 1); if (sched == NULL) { return new_handle; } sched->owner = window; sched->ctx = ctx; sched->handle = new_handle; sched->repeat_timeout = repeating ? timeout : 0; RING_INSERT(window->schedule_ring, sched); /* Next, the duktape stack looks like: func, timeout, ... * In order to proceed, we want to put into the WINDOW_CALLBACKS * keyed by the handle, an object containing the call to make and * the array of arguments to call the function with */ duk_idx_t nargs = duk_get_top(ctx) - 2; duk_push_global_object(ctx); duk_get_prop_string(ctx, -1, WINDOW_CALLBACKS); duk_push_int(ctx, (duk_int_t)new_handle); duk_push_object(ctx); /* stack is: func, timeout, ..., win, cbt, handle, cbo */ /* put the function into the cbo */ duk_dup(ctx, 0); duk_put_prop_string(ctx, -2, "func"); /* Now the arguments */ duk_push_array(ctx); for (duk_idx_t i = 0; i < nargs; ++i) { duk_dup(ctx, 2 + i); /* Dup the arg */ duk_put_prop_index(ctx, -2, i); /* arr[i] = arg[i] */ } duk_put_prop_string(ctx, -2, "args"); /* stack is: func, timeout, ..., win, cbt, handle, cbo */ duk_put_prop(ctx, -3); /* stack is: func, timeout, ..., win, cbt */ duk_pop_2(ctx); /* And we're back to func, timeout, ... */ guit->misc->schedule(timeout, window_schedule_callback, sched); NSLOG(dukky, DEEPDEBUG, "Scheduled callback %zd for %d ms from now", new_handle, timeout); return new_handle; } static void window_remove_callback_by_handle(duk_context *ctx, window_private_t *window, size_t handle) { RING_ITERATE_START(window_schedule_t, window->schedule_ring, sched) { if (sched->handle == handle) { NSLOG(dukky, DEEPDEBUG, "Cancelled callback %zd", sched->handle); guit->misc->schedule(-1, window_schedule_callback, sched); RING_REMOVE(window->schedule_ring, sched); window_remove_callback_bits(ctx, sched->handle); free(sched); RING_ITERATE_STOP(window->schedule_ring, sched); } } RING_ITERATE_END(window->schedule_ring, sched); } %}; }; init Window(struct browser_window *win, struct html_content *htmlc) %{ /* element window */ priv->win = win; priv->htmlc = htmlc; priv->schedule_ring = NULL; NSLOG(netsurf, DEEPDEBUG, "win=%p htmlc=%p", priv->win, priv->htmlc); NSLOG(netsurf, DEEPDEBUG, "URL is %s", nsurl_access(browser_window_access_url(priv->win))); duk_push_object(ctx); duk_put_prop_string(ctx, 0, WINDOW_CALLBACKS); %} fini Window() %{ /* Cheaply iterate the schedule ring, cancelling any pending callbacks */ while (priv->schedule_ring != NULL) { window_remove_callback_by_handle(ctx, priv, priv->schedule_ring->handle); } %} prototype Window() %{ #define EXPOSE(v) \ duk_get_global_string(ctx, #v); \ duk_put_prop_string(ctx, 0, #v) /* steal undefined */ EXPOSE(undefined); EXPOSE(eval); EXPOSE(Object); EXPOSE(parseInt); EXPOSE(parseFloat); EXPOSE(Array); EXPOSE(Date); EXPOSE(RegExp); EXPOSE(Math); EXPOSE(Function); EXPOSE(Proxy); EXPOSE(String); EXPOSE(encodeURI); EXPOSE(encodeURIComponent); #undef EXPOSE %} getter Window::document() %{ NSLOG(netsurf, DEBUG, "priv=%p", priv); dom_document *doc = priv->htmlc->document; dukky_push_node(ctx, (struct dom_node *)doc); return 1; %} getter Window::window() %{ duk_push_this(ctx); return 1; %} getter Window::console() %{ duk_push_this(ctx); duk_get_prop_string(ctx, -1, MAGIC(Console)); if (duk_is_undefined(ctx, -1)) { duk_pop(ctx); if (dukky_create_object(ctx, PROTO_NAME(CONSOLE), 0) != DUK_EXEC_SUCCESS) { return duk_error(ctx, DUK_ERR_ERROR, "Unable to create console object"); } duk_dup(ctx, -1); duk_put_prop_string(ctx, -3, MAGIC(Console)); } return 1; %} getter Window::location() %{ duk_push_this(ctx); duk_get_prop_string(ctx, -1, MAGIC(Location)); if (duk_is_undefined(ctx, -1)) { duk_pop(ctx); duk_push_pointer(ctx, llcache_handle_get_url(priv->htmlc->base.llcache)); if (dukky_create_object(ctx, PROTO_NAME(LOCATION), 1) != DUK_EXEC_SUCCESS) { return duk_error(ctx, DUK_ERR_ERROR, "Unable to create location object"); } duk_dup(ctx, -1); duk_put_prop_string(ctx, -3, MAGIC(Location)); } return 1; %} getter Window::navigator() %{ duk_push_this(ctx); duk_get_prop_string(ctx, -1, MAGIC(Navigator)); if (duk_is_undefined(ctx, -1)) { duk_pop(ctx); if (dukky_create_object(ctx, PROTO_NAME(NAVIGATOR), 0) != DUK_EXEC_SUCCESS) { return duk_error(ctx, DUK_ERR_ERROR, "Unable to create navigator object"); } duk_dup(ctx, -1); duk_put_prop_string(ctx, -3, MAGIC(Navigator)); } return 1; %} getter Window::name() %{ const char *name; browser_window_get_name(priv->win, &name); duk_push_string(ctx, name); return 1; %} setter Window::name() %{ const char *name; name = duk_to_string(ctx, -1); browser_window_set_name(priv->win, name); return 0; %} method Window::alert() %{ duk_size_t msg_len; const char *msg = duk_safe_to_lstring(ctx, 0, &msg_len); NSLOG(netsurf, INFO, "JS ALERT: %*s", (int)msg_len, msg); return 0; %} method Window::setTimeout() %{ duk_idx_t argc = duk_get_top(ctx); if (argc < 2) { /* not enough arguments */ return duk_error(ctx, DUK_RET_TYPE_ERROR, dukky_error_fmt_argument, 2, argc); } /* func, timeout, args... */ duk_int_t timeout = duk_get_int(ctx, 1); if (timeout < 10) { timeout = 10; } size_t handle = window_alloc_new_callback(ctx, priv, false, (int)timeout); duk_push_int(ctx, (duk_int_t)handle); return 1; %} method Window::setInterval() %{ duk_idx_t argc = duk_get_top(ctx); if (argc < 2) { /* not enough arguments */ return duk_error(ctx, DUK_RET_TYPE_ERROR, dukky_error_fmt_argument, 2, argc); } /* func, timeout, args... */ duk_int_t timeout = duk_get_int(ctx, 1); if (timeout < 10) { timeout = 10; } size_t handle = window_alloc_new_callback(ctx, priv, true, (int)timeout); duk_push_int(ctx, (duk_int_t)handle); return 1; %} method Window::clearTimeout() %{ duk_int_t handle = duk_get_int(ctx, 0); window_remove_callback_by_handle(ctx, priv, (size_t) handle); return 0; %} method Window::clearInterval() %{ duk_int_t handle = duk_get_int(ctx, 0); window_remove_callback_by_handle(ctx, priv, (size_t) handle); return 0; %} getter Window::onabort(); setter Window::onabort(); getter Window::onafterprint(); setter Window::onafterprint(); getter Window::onautocompleteerror(); setter Window::onautocompleteerror(); getter Window::onautocomplete(); setter Window::onautocomplete(); getter Window::onbeforeprint(); setter Window::onbeforeprint(); getter Window::onbeforeunload(); setter Window::onbeforeunload(); getter Window::onblur(); setter Window::onblur(); getter Window::oncancel(); setter Window::oncancel(); getter Window::oncanplaythrough(); setter Window::oncanplaythrough(); getter Window::oncanplay(); setter Window::oncanplay(); getter Window::onchange(); setter Window::onchange(); getter Window::onclick(); setter Window::onclick(); getter Window::onclose(); setter Window::onclose(); getter Window::oncontextmenu(); setter Window::oncontextmenu(); getter Window::oncuechange(); setter Window::oncuechange(); getter Window::ondblclick(); setter Window::ondblclick(); getter Window::ondragend(); setter Window::ondragend(); getter Window::ondragenter(); setter Window::ondragenter(); getter Window::ondragexit(); setter Window::ondragexit(); getter Window::ondragleave(); setter Window::ondragleave(); getter Window::ondragover(); setter Window::ondragover(); getter Window::ondragstart(); setter Window::ondragstart(); getter Window::ondrag(); setter Window::ondrag(); getter Window::ondrop(); setter Window::ondrop(); getter Window::ondurationchange(); setter Window::ondurationchange(); getter Window::onemptied(); setter Window::onemptied(); getter Window::onended(); setter Window::onended(); getter Window::onerror(); setter Window::onerror(); getter Window::onfocus(); setter Window::onfocus(); getter Window::onhashchange(); setter Window::onhashchange(); getter Window::oninput(); setter Window::oninput(); getter Window::oninvalid(); setter Window::oninvalid(); getter Window::onkeydown(); setter Window::onkeydown(); getter Window::onkeypress(); setter Window::onkeypress(); getter Window::onkeyup(); setter Window::onkeyup(); getter Window::onlanguagechange(); setter Window::onlanguagechange(); getter Window::onloadeddata(); setter Window::onloadeddata(); getter Window::onloadedmetadata(); setter Window::onloadedmetadata(); getter Window::onloadstart(); setter Window::onloadstart(); getter Window::onload(); setter Window::onload(); getter Window::onmessage(); setter Window::onmessage(); getter Window::onmousedown(); setter Window::onmousedown(); getter Window::onmouseenter(); setter Window::onmouseenter(); getter Window::onmouseleave(); setter Window::onmouseleave(); getter Window::onmousemove(); setter Window::onmousemove(); getter Window::onmouseout(); setter Window::onmouseout(); getter Window::onmouseover(); setter Window::onmouseover(); getter Window::onmouseup(); setter Window::onmouseup(); getter Window::onoffline(); setter Window::onoffline(); getter Window::ononline(); setter Window::ononline(); getter Window::onpagehide(); setter Window::onpagehide(); getter Window::onpageshow(); setter Window::onpageshow(); getter Window::onpause(); setter Window::onpause(); getter Window::onplaying(); setter Window::onplaying(); getter Window::onplay(); setter Window::onplay(); getter Window::onpopstate(); setter Window::onpopstate(); getter Window::onprogress(); setter Window::onprogress(); getter Window::onratechange(); setter Window::onratechange(); getter Window::onreset(); setter Window::onreset(); getter Window::onresize(); setter Window::onresize(); getter Window::onscroll(); setter Window::onscroll(); getter Window::onseeked(); setter Window::onseeked(); getter Window::onseeking(); setter Window::onseeking(); getter Window::onselect(); setter Window::onselect(); getter Window::onshow(); setter Window::onshow(); getter Window::onsort(); setter Window::onsort(); getter Window::onstalled(); setter Window::onstalled(); getter Window::onstorage(); setter Window::onstorage(); getter Window::onsubmit(); setter Window::onsubmit(); getter Window::onsuspend(); setter Window::onsuspend(); getter Window::ontimeupdate(); setter Window::ontimeupdate(); getter Window::ontoggle(); setter Window::ontoggle(); getter Window::onunload(); setter Window::onunload(); getter Window::onvolumechange(); setter Window::onvolumechange(); getter Window::onwaiting(); setter Window::onwaiting(); getter Window::onwheel(); setter Window::onwheel();