netsurf/content/handlers/javascript/duktape/Window.bnd
Daniel Silverstone 04cf2fe588 Window.bnd: Do not remove in-train callbacks
Sometimes callbacks may be cancelled from within themselves.  In
that case we need to simply ensure that should the callback be
wanted to repeat, we instead stop that so that once the callback
is completed we do not attempt to reschedule something which had
already been deleted.

Signed-off-by: Daniel Silverstone <dsilvers@digital-scurf.org>
2019-06-09 11:04:15 +01:00

586 lines
16 KiB
Plaintext

/* Window binding for browser using duktape and libdom
*
* Copyright 2015 Vincent Sanders <vince@netsurf-browser.org>
*
* 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;
bool running;
} 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);
priv->running = true;
window_call_callback(priv->ctx, priv->handle, priv->repeat_timeout == 0);
priv->running = false;
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;
sched->running = false;
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) {
int res;
RING_ITERATE_START(window_schedule_t, window->schedule_ring, sched) {
if (sched->handle == handle) {
if (sched->running) {
NSLOG(dukky, DEEPDEBUG, "Cancelling in-train callback %"PRIsizet, sched->handle);
sched->repeat_timeout = 0;
} else {
NSLOG(dukky, DEEPDEBUG, "Cancelled callback %"PRIsizet, sched->handle);
res = guit->misc->schedule(-1,
window_schedule_callback,
sched);
assert(res == NSERROR_OK);
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);
EXPOSE(NaN);
#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();