summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorVincent Sanders <vince@kyllikki.org>2013-05-19 23:48:55 +0100
committerVincent Sanders <vince@netsurf-browser.org>2013-05-22 19:12:26 +0100
commitce0fe06349753b2ad1cfa7b0b1cfcaa77c4e47ab (patch)
tree695b26567b3277cc799ee5d925cf9a46454fb806
parent447c0fc2715ef690f47bda0da60ee3c3d750950d (diff)
downloadnetsurf-ce0fe06349753b2ad1cfa7b0b1cfcaa77c4e47ab.tar.gz
netsurf-ce0fe06349753b2ad1cfa7b0b1cfcaa77c4e47ab.tar.bz2
create javascript heartbeat and hook a script timeout to it
-rw-r--r--desktop/browser.c12
-rw-r--r--javascript/js.h10
-rw-r--r--javascript/jsapi.c307
-rw-r--r--javascript/jsapi.h16
-rw-r--r--javascript/none.c2
5 files changed, 321 insertions, 26 deletions
diff --git a/desktop/browser.c b/desktop/browser.c
index 12cc9c830..53e3f13bf 100644
--- a/desktop/browser.c
+++ b/desktop/browser.c
@@ -683,6 +683,16 @@ void browser_window_debug_dump(struct browser_window *bw, FILE *f)
content_debug_dump(bw->current_content, f);
}
+static bool gui_slow_script(void *ctx)
+{
+ static int count = 0;
+ LOG(("Continuing execution %d", count));
+ count++;
+ if (count >= 2) {
+ return false;
+ }
+ return true;
+}
/* exported interface, documented in desktop/browser.h */
@@ -707,7 +717,7 @@ browser_window_create(enum browser_window_nav_flags flags,
}
/* new javascript context for window */
- bw->jsctx = js_newcontext();
+ bw->jsctx = js_newcontext(10, gui_slow_script, NULL);
/* Initialise common parts */
browser_window_initialise_common(bw, clone);
diff --git a/javascript/js.h b/javascript/js.h
index 44de4fe3d..7102fcf0e 100644
--- a/javascript/js.h
+++ b/javascript/js.h
@@ -26,6 +26,8 @@
typedef struct jscontext jscontext;
typedef struct jsobject jsobject;
+typedef bool(jscallback)(void *ctx);
+
struct dom_document;
struct dom_node;
struct dom_string;
@@ -38,9 +40,13 @@ void js_finalise(void);
/** Create a new javascript context.
*
- * There aare usually one context per browser context
+ * There is usually one context per browser context
+ *
+ * \param timeout elapsed wallclock time (in seconds) before \a callback is called
+ * \param cb the callback when the runtime exceeds the timeout
+ * \param cbctx The context to pass to the callback
*/
-jscontext *js_newcontext(void);
+jscontext *js_newcontext(int timeout, jscallback *cb, void *cbctx);
/** Destroy a previously created context */
void js_destroycontext(jscontext *ctx);
diff --git a/javascript/jsapi.c b/javascript/jsapi.c
index 7b68fe975..a2bc90fc7 100644
--- a/javascript/jsapi.c
+++ b/javascript/jsapi.c
@@ -16,6 +16,9 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
+#include <unistd.h>
+#include <signal.h>
+
#include "javascript/jsapi.h"
#include "render/html_internal.h"
#include "content/content.h"
@@ -27,6 +30,8 @@
#include "window.h"
#include "event.h"
+#define ENABLE_JS_HEARTBEAT 1
+
static JSRuntime *rt; /* global runtime */
void js_initialise(void)
@@ -56,7 +61,8 @@ void js_finalise(void)
}
/* The error reporter callback. */
-static void js_reportError(JSContext *cx, const char *message, JSErrorReport *report)
+static void
+js_reportError(JSContext *cx, const char *message, JSErrorReport *report)
{
JSLOG("%s:%u:%s",
report->filename ? report->filename : "<no filename>",
@@ -64,7 +70,248 @@ static void js_reportError(JSContext *cx, const char *message, JSErrorReport *re
message);
}
-jscontext *js_newcontext(void)
+/* heartbeat routines */
+#ifndef ENABLE_JS_HEARTBEAT
+
+struct heartbeat;
+
+/* prepares a context with a heartbeat handler */
+static bool
+setup_heartbeat(JSContext *cx, int timeout, jscallback *cb, void *cbctx)
+{
+ return true;
+}
+
+/* enables the heartbeat on a context */
+static struct heartbeat *enable_heartbeat(JSContext *cx)
+{
+ return NULL;
+}
+
+/* disables heartbeat on a context */
+static bool
+disable_heartbeat(struct heartbeat *hb)
+{
+ return true;
+}
+
+#else
+
+/* private context for heartbeats */
+struct jscontext_priv {
+ int timeout;
+ jscallback *cb;
+ void *cbctx;
+
+ unsigned int branch_reset; /**< reset value for branch counter */
+ unsigned int branch_count; /**< counter for branch callback */
+ time_t last; /**< last time heartbeat happened */
+ time_t end; /**< end time for the current script execution */
+};
+
+/** execution heartbeat */
+static JSBool heartbeat_callback(JSContext *cx)
+{
+ struct jscontext_priv *priv = JS_GetContextPrivate(cx);
+ JSBool ret = JS_TRUE;
+ time_t now = time(NULL);
+
+ /* dynamically update the branch times to ensure we do not get
+ * called back more than once a second
+ */
+ if (now == priv->last) {
+ priv->branch_reset = priv->branch_reset * 2;
+ }
+ priv->last = now;
+
+ JSLOG("Running heatbeat at %d end %d", now , priv->end);
+
+ if ((priv->cb != NULL) &&
+ (now > priv->end)) {
+ if (priv->cb(priv->cbctx) == false) {
+ ret = JS_FALSE; /* abort */
+ } else {
+ priv->end = time(NULL) + priv->timeout;
+ }
+ }
+
+ return ret;
+}
+
+#if JS_VERSION >= 185
+
+struct heartbeat {
+ JSContext *cx;
+ struct sigaction sact; /* signal handler action to restore */
+ int alm; /* alarm value to restore */
+};
+
+static struct heartbeat *cur_hb;
+
+static bool
+setup_heartbeat(JSContext *cx, int timeout, jscallback *cb, void *cbctx)
+{
+ struct jscontext_priv *priv;
+
+ if (timeout == 0) {
+ return true;
+ }
+
+ priv = calloc(1, sizeof(*priv));
+ if (priv == NULL) {
+ return false;
+ }
+
+ priv->timeout = timeout;
+ priv->cb = cb;
+ priv->cbctx = cbctx;
+
+ JS_SetContextPrivate(cx, priv);
+
+ /* if heartbeat is enabled disable JIT or callbacks do not happen */
+ JS_SetOptions(cx, JS_GetOptions(cx) & ~JSOPTION_JIT);
+
+ JS_SetOperationCallback(cx, heartbeat_callback);
+
+ return true;
+}
+
+static void sig_alm_handler(int signum)
+{
+ JS_TriggerOperationCallback(cur_hb->cx);
+ alarm(1);
+ JSDBG("alarm signal handler for context %p", cur_hb->cx);
+}
+
+static struct heartbeat *enable_heartbeat(JSContext *cx)
+{
+ struct jscontext_priv *priv = JS_GetContextPrivate(cx);
+ struct sigaction sact;
+ struct heartbeat *hb;
+
+ if (priv == NULL) {
+ return NULL;
+ }
+
+ priv->last = time(NULL);
+ priv->end = priv->last + priv->timeout;
+
+ hb = malloc(sizeof(*hb));
+ if (hb != NULL) {
+ sigemptyset(&sact.sa_mask);
+ sact.sa_flags = 0;
+ sact.sa_handler = sig_alm_handler;
+ if (sigaction(SIGALRM, &sact, &hb->sact) == 0) {
+ cur_hb = hb;
+ hb->cx = cx;
+ hb->alm = alarm(1);
+ } else {
+ free(hb);
+ hb = NULL;
+ LOG(("Unable to set heartbeat"));
+ }
+ }
+ return hb;
+}
+
+/** disable heartbeat
+ *
+ * /param hb heartbeat to disable may be NULL
+ * /return true on success.
+ */
+static bool
+disable_heartbeat(struct heartbeat *hb)
+{
+ if (hb != NULL) {
+ sigaction(SIGALRM, &hb->sact, NULL); /* restore old handler */
+ alarm(hb->alm); /* restore alarm signal */
+ }
+ return true;
+}
+
+#else
+
+/* need to setup callback to prevent long running scripts infinite
+ * hanging.
+ *
+ * old method is to use:
+ * JSBranchCallback JS_SetBranchCallback(JSContext *cx, JSBranchCallback cb);
+ * which gets called a *lot* and should only do something every 5k calls
+ * The callback function
+ * JSBool (*JSBranchCallback)(JSContext *cx, JSScript *script);
+ * returns JS_TRUE to carry on and JS_FALSE to abort execution
+ * single thread of execution on the context
+ * documented in
+ * https://developer.mozilla.org/en-US/docs/SpiderMonkey/JSAPI_Reference/JS_SetBranchCallback
+ *
+ */
+
+#define INITIAL_BRANCH_RESET 5000
+
+struct heartbeat;
+
+static JSBool branch_callback(JSContext *cx, JSScript *script)
+{
+ struct jscontext_priv *priv = JS_GetContextPrivate(cx);
+ JSBool ret = JS_TRUE;
+
+ priv->branch_count--;
+ if (priv->branch_count == 0) {
+ priv->branch_count = priv->branch_reset; /* reset branch count */
+
+ ret = heartbeat(cx);
+ }
+ return ret;
+}
+
+static bool
+setup_heartbeat(JSContext *cx, int timeout, jscallback *cb, void *cbctx)
+{
+ struct jscontext_priv *priv;
+
+ if (timeout == 0) {
+ return true;
+ }
+
+ priv = calloc(1, sizeof(*priv));
+ if (priv == NULL) {
+ return false;
+ }
+
+ priv->timeout = timeout;
+ priv->cb = cb;
+ priv->cbctx = cbctx;
+
+ priv->branch_reset = INITIAL_BRANCH_RESET;
+ priv->branch_count = priv->branch_reset;
+
+ JS_SetContextPrivate(cx, priv);
+
+ JS_SetBranchCallback(cx, branch_callback);
+}
+
+static struct heartbeat *enable_heartbeat(JSContext *cx)
+{
+ struct jscontext_priv *priv = JS_GetContextPrivate(cx);
+
+ if (priv != NULL) {
+ priv->last = time(NULL);
+ priv->end = priv->last + priv->timeout;
+ }
+ return NULL;
+}
+
+static bool
+disable_heartbeat(struct heartbeat *hb)
+{
+ return true;
+}
+
+#endif
+
+#endif
+
+jscontext *js_newcontext(int timeout, jscallback *cb, void *cbctx)
{
JSContext *cx;
@@ -76,10 +323,16 @@ jscontext *js_newcontext(void)
if (cx == NULL) {
return NULL;
}
- JS_SetOptions(cx, JSOPTION_VAROBJFIX | JSOPTION_JIT );
+
+ /* set options on context */
+ JS_SetOptions(cx, JS_GetOptions(cx) | JSOPTION_VAROBJFIX | JSOPTION_JIT);
+
JS_SetVersion(cx, JSVERSION_LATEST);
JS_SetErrorReporter(cx, js_reportError);
+ /* run a heartbeat */
+ setup_heartbeat(cx, timeout, cb, cbctx);
+
/*JS_SetGCZeal(cx, 2); */
JSLOG("New Context %p", cx);
@@ -90,9 +343,15 @@ jscontext *js_newcontext(void)
void js_destroycontext(jscontext *ctx)
{
JSContext *cx = (JSContext *)ctx;
+ struct jscontext_priv *priv;
+
if (cx != NULL) {
JSLOG("Destroying Context %p", cx);
+ priv = JS_GetContextPrivate(cx);
+
JS_DestroyContext(cx);
+
+ free(priv);
}
}
@@ -124,10 +383,14 @@ jsobject *js_newcompartment(jscontext *ctx, void *win_priv, void *doc_priv)
return (jsobject *)window;
}
+
+
bool js_exec(jscontext *ctx, const char *txt, size_t txtlen)
{
JSContext *cx = (JSContext *)ctx;
jsval rval;
+ JSBool eval_res;
+ struct heartbeat *hb;
/* JSLOG("%p \"%s\"",cx ,txt); */
@@ -143,10 +406,16 @@ bool js_exec(jscontext *ctx, const char *txt, size_t txtlen)
return false;
}
- if (JS_EvaluateScript(cx,
- JS_GetGlobalObject(cx),
- txt, txtlen,
- "<head>", 0, &rval) == JS_TRUE) {
+ hb = enable_heartbeat(cx);
+
+ eval_res = JS_EvaluateScript(cx,
+ JS_GetGlobalObject(cx),
+ txt, txtlen,
+ "<head>", 0, &rval);
+
+ disable_heartbeat(hb);
+
+ if (eval_res == JS_TRUE) {
return true;
}
@@ -168,6 +437,7 @@ bool js_fire_event(jscontext *ctx, const char *type, dom_document *doc, dom_node
dom_exception exc;
dom_event *event;
dom_string *type_dom;
+ struct heartbeat *hb;
if (cx == NULL) {
return false;
@@ -201,6 +471,8 @@ bool js_fire_event(jscontext *ctx, const char *type, dom_document *doc, dom_node
return false;
}
+ hb = enable_heartbeat(cx);
+
/* dispatch event at the window object */
argv[0] = OBJECT_TO_JSVAL(jsevent);
@@ -210,6 +482,9 @@ bool js_fire_event(jscontext *ctx, const char *type, dom_document *doc, dom_node
1,
argv,
&rval);
+
+ disable_heartbeat(hb);
+
} else {
JSLOG("Dispatching event %s at %p", type, node);
@@ -264,15 +539,15 @@ js_dom_event_listener(struct dom_event *event, void *pw)
jsevent = jsapi_new_Event(private->cx, NULL, NULL, event);
if (jsevent != NULL) {
- /* dispatch event at the window object */
- event_argv[0] = OBJECT_TO_JSVAL(jsevent);
-
- JS_CallFunctionValue(private->cx,
- NULL,
- private->funcval,
- 1,
- event_argv,
- &event_rval);
+ /* dispatch event at the window object */
+ event_argv[0] = OBJECT_TO_JSVAL(jsevent);
+
+ JS_CallFunctionValue(private->cx,
+ NULL,
+ private->funcval,
+ 1,
+ event_argv,
+ &event_rval);
}
}
}
diff --git a/javascript/jsapi.h b/javascript/jsapi.h
index e38188ab4..8f9affd02 100644
--- a/javascript/jsapi.h
+++ b/javascript/jsapi.h
@@ -23,12 +23,21 @@
#ifndef _NETSURF_JAVASCRIPT_JSAPI_H_
#define _NETSURF_JAVASCRIPT_JSAPI_H_
+/* include teh correct header */
#ifdef WITH_MOZJS
#include "js/jsapi.h"
#else
#include "mozjs/jsapi.h"
#endif
+/* logging macros */
+#define JSLOG(args...) LOG((args))
+#ifdef ENABLE_VERBOSE_JS_DEBUG
+#define JSDBG(args...) LOG((args))
+#else
+#define JSDBG(args...)
+#endif
+
#if JS_VERSION < 180
/************************** Spidermonkey 1.7.0 **************************/
@@ -375,11 +384,6 @@ JS_NewCompartmentAndGlobalObject(JSContext *cx,
#endif
-#define JSLOG(args...) LOG((args))
-#ifdef ENABLE_VERBOSE_JS_DEBUG
-#define JSDBG(args...) LOG((args))
-#else
-#define JSDBG(args...)
-#endif
+/************************** **************************/
#endif
diff --git a/javascript/none.c b/javascript/none.c
index 3e7b39cb3..600a50860 100644
--- a/javascript/none.c
+++ b/javascript/none.c
@@ -35,7 +35,7 @@ void js_finalise(void)
{
}
-jscontext *js_newcontext(void)
+jscontext *js_newcontext(int timeout, jscallback *cb, void *cbctx)
{
return NULL;
}