netsurf/content/handlers/javascript/duktape/Window.bnd
Daniel Silverstone 24ec30359b
Window: Do not get stuck if callbacks are in-train during compartment close
When we close the JS compartment we try and cancel all callbacks so that
they do not fire after the compartment is closed.  However if we have
in-train callbacks, they can gum up the closedown and so we need to check
and if we've done all we can, we break out of the callback removal loop.

Signed-off-by: Daniel Silverstone <dsilvers@digital-scurf.org>
2020-04-25 14:04:54 +01:00

721 lines
19 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;
private bool closed_down;
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 thread closedown method */
static duk_ret_t dukky_window_closedown_thread(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;
}
priv->closed_down = true;
NSLOG(dukky, DEEPDEBUG, "Closing down thread");
while (priv->schedule_ring != NULL) {
window_schedule_t *to_remove = NULL;
// Find a schedule item to remove
RING_ITERATE_START(window_schedule_t, priv->schedule_ring, sched) {
if (sched->running == false) {
// This one is not running, we can remove it
to_remove = sched;
RING_ITERATE_STOP(window->schedule_ring, sched);
} else if (sched->repeat_timeout != 0) {
// This one is running and has yet to be
// cancelled, so prevent it rescheduling itself
NSLOG(dukky, DEEPDEBUG,
"Cancelling in-train callback %"PRIsizet,
sched->handle);
sched->repeat_timeout = 0;
}
} RING_ITERATE_END(priv->schedule_ring, sched);
if (to_remove == NULL) {
// We didn't find any non-running callbacks
// so let's log that and break out of the closedown
// loop so we can continue and hopefully close down
NSLOG(dukky, DEEPDEBUG,
"Leaving in-train callbacks to unwind");
break;
}
// Remove the handle we found, this will reduce the callback
// scheduler ring by one and perhaps leave it empty so we can
// finish the closedown.
window_remove_callback_by_handle(ctx,
priv,
to_remove->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;
priv->closed_down = false;
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)
/* https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects */
/* ** Value properties */
EXPOSE(Infinity);
EXPOSE(NaN);
EXPOSE(undefined);
EXPOSE(null);
EXPOSE(globalThis);
/* ** Function properties */
EXPOSE(eval);
/* EXPOSE(uneval); */ /* Not standard, maybe not available */
EXPOSE(isFinite);
EXPOSE(isNaN);
EXPOSE(parseFloat);
EXPOSE(parseInt);
EXPOSE(decodeURI);
EXPOSE(decodeURIComponent);
EXPOSE(encodeURI);
EXPOSE(encodeURIComponent);
EXPOSE(escape);
EXPOSE(unescape);
/* ** Fundamental Objects */
EXPOSE(Object);
EXPOSE(Function);
EXPOSE(Boolean);
EXPOSE(Symbol);
EXPOSE(Error);
EXPOSE(EvalError);
EXPOSE(InternalError);
EXPOSE(RangeError);
EXPOSE(ReferenceError);
EXPOSE(SyntaxError);
EXPOSE(TypeError);
EXPOSE(URIError);
/* ** Numbers and Dates */
EXPOSE(Number);
EXPOSE(BigInt);
EXPOSE(Math);
EXPOSE(Date);
/* ** Text Processing */
EXPOSE(String);
EXPOSE(RegExp);
/* ** Indexed Collections */
EXPOSE(Array);
EXPOSE(Int8Array);
EXPOSE(Uint8Array);
EXPOSE(Uint8ClampedArray);
EXPOSE(Int16Array);
EXPOSE(Uint16Array);
EXPOSE(Int32Array);
EXPOSE(Uint32Array);
EXPOSE(Float32Array);
EXPOSE(Float64Array);
/* EXPOSE(BigInt64Array); */ /* Duktape lacks this - nonstandard API */
/* EXPOSE(BigUint64Array); */ /* Duktape lacks this - nonstandard API */
/* ** Keyed Collections */
/* EXPOSE(Map); */ /* Duktape lacks this - ES6 */
/* EXPOSE(Set); */ /* Duktape lacks this - ES6 */
/* EXPOSE(WeakMap); */ /* Duktape lacks this - ES6 */
/* EXPOSE(WeakSet); */ /* Duktape lacks this - ES6 */
/* Structured Data */
EXPOSE(ArrayBuffer);
/* EXPOSE(SharedArrayBuffer); */ /* Duktape lacks this - experimental API */
/* EXPOSE(Atomics); */ /* Duktape lacks this - experimental API */
EXPOSE(DataView);
EXPOSE(JSON);
/* ** Control abstraction properties */
/* EXPOSE(Promise); */ /* Probably ought to be one of ours? Also ES6 */
/* EXPOSE(Generator); */ /* Duktape and async? ES6 */
/* EXPOSE(GeneratorFunction); */ /* Duktape and async? ES6 */
/* EXPOSE(AsyncFunction); */ /* Duktape lacks this - experimental API */
/* Reflection */
EXPOSE(Reflect);
EXPOSE(Proxy);
/* ** Internationalisation */
/* Duktape lacks Intl - Maybe polyfill it? */
/* There is suggestion that cdn.polyfill.io exists for it */
/* ** WebAssembly */
/* As yet, Duktape lacks WA */
#undef EXPOSE
/* Add s3kr1t method to close the JS thread (browsing context) */
duk_dup(ctx, 0);
duk_push_string(ctx, MAGIC(closedownThread));
duk_push_c_function(ctx, dukky_window_closedown_thread, 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);
duk_int_t timeout = 10;
if (priv->closed_down == true) {
return 0; /* coerced to undefined */
}
if (argc >= 2) {
timeout = duk_get_int(ctx, 1);
}
/* func, [timeout, args...] */
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);
duk_int_t timeout = 10;
if (priv->closed_down == true) {
return 0; /* coerced to undefined */
}
if (argc >= 2) {
timeout = duk_get_int(ctx, 1);
}
/* func, [timeout, args...] */
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();