Javascript: Support setTimeout and friends
Signed-off-by: Daniel Silverstone <dsilvers@digital-scurf.org>
This commit is contained in:
parent
35e9b5de6d
commit
c17e588b66
|
@ -11,12 +11,163 @@
|
|||
class Window {
|
||||
private struct browser_window * win;
|
||||
private struct html_content * htmlc;
|
||||
private struct window_schedule_s * schedule_ring;
|
||||
prologue %{
|
||||
#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)
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
%};
|
||||
};
|
||||
|
||||
|
@ -25,10 +176,21 @@ init Window(struct browser_window *win, struct html_content *htmlc)
|
|||
/* element window */
|
||||
priv->win = win;
|
||||
priv->htmlc = htmlc;
|
||||
NSLOG(netsurf, INFO, "win=%p htmlc=%p", priv->win, priv->htmlc);
|
||||
priv->schedule_ring = NULL;
|
||||
NSLOG(netsurf, DEEPDEBUG, "win=%p htmlc=%p", priv->win, priv->htmlc);
|
||||
|
||||
NSLOG(netsurf, INFO,
|
||||
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()
|
||||
|
@ -142,3 +304,53 @@ method Window::alert()
|
|||
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;
|
||||
%}
|
||||
|
|
|
@ -649,6 +649,62 @@ static duk_ret_t dukky_safe_get(duk_context *ctx, void *udata)
|
|||
return 1;
|
||||
}
|
||||
|
||||
static void dukky_dump_error(duk_context *ctx)
|
||||
{
|
||||
/* stack is ..., errobj */
|
||||
duk_idx_t stacktop = duk_get_top(ctx);
|
||||
if (!duk_is_error(ctx, stacktop - 1)) {
|
||||
NSLOG(dukky, INFO, "Uncaught non-Error derived error in JS: %s", duk_safe_to_string(ctx, stacktop - 1));
|
||||
} else {
|
||||
#define GETTER(what) \
|
||||
if (duk_has_prop_string(ctx, stacktop - 1, what)) { \
|
||||
NSLOG(dukky, DEEPDEBUG, "Fetching " what); \
|
||||
duk_dup(ctx, stacktop - 1); \
|
||||
if (duk_safe_call(ctx, dukky_safe_get, (void *)what, 1, 1) != DUK_EXEC_SUCCESS) { \
|
||||
NSLOG(dukky, DEBUG, "Error fetching " what ": %s", duk_safe_to_string(ctx, -1)); \
|
||||
} else { \
|
||||
NSLOG(dukky, DEEPDEBUG, "Success fetching " what); \
|
||||
} \
|
||||
} else { \
|
||||
NSLOG(dukky, DEBUG, "Faking " what); \
|
||||
duk_push_string(ctx, "?" what "?"); \
|
||||
}
|
||||
GETTER("name");
|
||||
GETTER("message");
|
||||
GETTER("fileName");
|
||||
GETTER("lineNumber");
|
||||
GETTER("stack");
|
||||
NSLOG(dukky, DEBUG, "Uncaught error in JS: %s: %s",
|
||||
duk_safe_to_string(ctx, -5), duk_safe_to_string(ctx, -4));
|
||||
NSLOG(dukky, DEBUG, " was at: %s line %s",
|
||||
duk_safe_to_string(ctx, -3), duk_safe_to_string(ctx, -2));
|
||||
NSLOG(dukky, DEBUG, " Stack trace: %s",
|
||||
duk_safe_to_string(ctx, -1));
|
||||
#undef GETTER
|
||||
}
|
||||
duk_set_top(ctx, stacktop);
|
||||
}
|
||||
|
||||
duk_int_t dukky_pcall(duk_context *ctx, duk_size_t argc, bool reset_timeout)
|
||||
{
|
||||
if (reset_timeout) {
|
||||
duk_memory_functions funcs;
|
||||
jscontext *jsctx;
|
||||
duk_get_memory_functions(ctx, &funcs);
|
||||
jsctx = funcs.udata;
|
||||
(void) nsu_getmonotonic_ms(&jsctx->exec_start_time);
|
||||
}
|
||||
|
||||
duk_int_t ret = duk_pcall(ctx, argc);
|
||||
if (ret) {
|
||||
/* Something went wrong calling this... */
|
||||
dukky_dump_error(ctx);
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
||||
bool js_exec(jscontext *ctx, const char *txt, size_t txtlen)
|
||||
{
|
||||
assert(ctx);
|
||||
|
@ -674,35 +730,7 @@ bool js_exec(jscontext *ctx, const char *txt, size_t txtlen)
|
|||
return duk_get_boolean(CTX, 0);
|
||||
|
||||
handle_error:
|
||||
if (!duk_is_error(CTX, 0)) {
|
||||
NSLOG(dukky, INFO, "Uncaught non-Error derived error in JS: %s", duk_safe_to_string(CTX, 0));
|
||||
} else {
|
||||
#define GETTER(what) \
|
||||
if (duk_has_prop_string(CTX, 0, what)) { \
|
||||
NSLOG(dukky, DEEPDEBUG, "Fetching " what); \
|
||||
duk_dup(CTX, 0); \
|
||||
if (duk_safe_call(CTX, dukky_safe_get, (void *)what, 1, 1) != DUK_EXEC_SUCCESS) { \
|
||||
NSLOG(dukky, DEBUG, "Error fetching " what ": %s", duk_safe_to_string(CTX, -1)); \
|
||||
} else { \
|
||||
NSLOG(dukky, DEEPDEBUG, "Success fetching " what); \
|
||||
} \
|
||||
} else { \
|
||||
NSLOG(dukky, DEBUG, "Faking " what); \
|
||||
duk_push_string(CTX, "?" what "?"); \
|
||||
}
|
||||
GETTER("name");
|
||||
GETTER("message");
|
||||
GETTER("fileName");
|
||||
GETTER("lineNumber");
|
||||
GETTER("stack");
|
||||
NSLOG(dukky, DEBUG, "Uncaught error in JS: %s: %s",
|
||||
duk_safe_to_string(CTX, 1), duk_safe_to_string(CTX, 2));
|
||||
NSLOG(dukky, DEBUG, " was at: %s line %s",
|
||||
duk_safe_to_string(CTX, 3), duk_safe_to_string(CTX, 4));
|
||||
NSLOG(dukky, DEBUG, " Stack trace: %s",
|
||||
duk_safe_to_string(CTX, 5));
|
||||
#undef GETTER
|
||||
}
|
||||
dukky_dump_error(CTX);
|
||||
return false;
|
||||
}
|
||||
|
||||
|
|
|
@ -47,4 +47,7 @@ typedef enum {
|
|||
|
||||
void dukky_shuffle_array(duk_context *ctx, duk_uarridx_t idx);
|
||||
|
||||
/* pcall something, and if it errored, also dump the error to the log */
|
||||
duk_int_t dukky_pcall(duk_context *ctx, duk_size_t argc, bool reset_timeout);
|
||||
|
||||
#endif
|
||||
|
|
|
@ -0,0 +1,17 @@
|
|||
<html>
|
||||
<head>
|
||||
<title>setTimeout and setInterval</title>
|
||||
<script>
|
||||
var counter = 0;
|
||||
var interval_handle = setInterval(function() {
|
||||
console.log("Called back ", counter, " times");
|
||||
counter = counter + 1;
|
||||
}, 100);
|
||||
setTimeout(function() {clearInterval(interval_handle);}, 10000);
|
||||
</script>
|
||||
</head>
|
||||
<body>
|
||||
Check the log, it should be printing a callback indicator for ten
|
||||
seconds and then stop.
|
||||
</body>
|
||||
</html>
|
Loading…
Reference in New Issue