/* 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" #include "netsurf/inttypes.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, bool clear_entry) { NSLOG(dukky, DEEPDEBUG, "ctx=%p, handle=%"PRIsizet, ctx, handle); /* Stack is ... */ duk_push_global_object(ctx); /* ..., win */ duk_get_prop_string(ctx, -1, WINDOW_CALLBACKS); /* ..., win, cbt */ duk_push_int(ctx, (duk_int_t)handle); /* ..., win, cbt, handle */ duk_get_prop(ctx, -2); /* ..., win, cbt, cbo */ dukky_log_stack_frame(ctx, "On entry to callback"); /* ..., 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... */ dukky_log_stack_frame(ctx, "Just before call"); (void) dukky_pcall(ctx, arrlen, true); /* ..., win, cbt, cbo, retval */ if (clear_entry) { NSLOG(dukky, DEEPDEBUG, "Not recurring callback, removing from cbt"); duk_pop_n(ctx, 2); /* ..., win, cbt */ duk_push_int(ctx, (duk_int_t)handle); /* ..., win, cbt, handle */ duk_del_prop(ctx, -2); /* ..., win, cbt */ duk_pop_n(ctx, 2); } else { duk_pop_n(ctx, 4); } /* ... */ dukky_log_stack_frame(ctx, "On leaving callback"); } static void window_schedule_callback(void *p) { window_schedule_t *priv = (window_schedule_t *)p; NSLOG(dukky, DEEPDEBUG, "Entered window scheduler callback: %"PRIsizet, priv->handle); window_call_callback(priv->ctx, priv->handle, priv->repeat_timeout == 0); if (priv->repeat_timeout > 0) { /* Reschedule */ NSLOG(dukky, DEEPDEBUG, "Rescheduling repeating callback %"PRIsizet, priv->handle); guit->misc->schedule(priv->repeat_timeout, window_schedule_callback, priv); } else { NSLOG(dukky, DEEPDEBUG, "Removing completed callback %"PRIsizet, priv->handle); /* Remove this from the ring */ RING_REMOVE(priv->owner->schedule_ring, priv); window_remove_callback_bits(priv->ctx, priv->handle); 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 %"PRIsizet" 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 %"PRIsizet, 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); } /* This is the dodgy compartment closedown method */ static duk_ret_t dukky_window_closedown_compartment(duk_context *ctx) { window_private_t *priv = NULL; duk_push_global_object(ctx); duk_get_prop_string(ctx, -1, dukky_magic_string_private); priv = duk_get_pointer(ctx, -1); duk_pop_2(ctx); if (priv == NULL) { return 0; } NSLOG(dukky, DEEPDEBUG, "Closing down compartment"); while (priv->schedule_ring != NULL) { window_remove_callback_by_handle(ctx, priv, priv->schedule_ring->handle); } return 0; } %}; }; 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() %{ NSLOG(dukky, DEEPDEBUG, "Shutting down Window %p", priv->win); /* 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(Number); EXPOSE(Error); EXPOSE(encodeURI); EXPOSE(encodeURIComponent); #undef EXPOSE /* Add s3kr1t method to close the compartment */ duk_dup(ctx, 0); duk_push_string(ctx, MAGIC(closedownCompartment)); duk_push_c_function(ctx, dukky_window_closedown_compartment, DUK_VARARGS); duk_def_prop(ctx, -3, DUK_DEFPROP_HAVE_VALUE | DUK_DEFPROP_HAVE_WRITABLE | DUK_DEFPROP_HAVE_ENUMERABLE | DUK_DEFPROP_ENUMERABLE | DUK_DEFPROP_HAVE_CONFIGURABLE); duk_pop(ctx); %} 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() %{ /* obtain location object for this window (if it exists) */ duk_push_this(ctx); duk_get_prop_string(ctx, -1, MAGIC(Location)); if (duk_is_undefined(ctx, -1)) { /* location object did not previously exist so create it */ 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_idx_t dukky_argc = duk_get_top(ctx); if (dukky_argc == 0) { NSLOG(netsurf, INFO, "JS ALERT"); } else { duk_size_t msg_len; const char *msg; if (!duk_is_string(ctx, 0)) { duk_to_string(ctx, 0); } 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();