netsurf: branch master updated. release/3.0-100-gaf75070

NetSurf Browser Project (Commit Mailer) no-reply at netsurf-browser.org
Wed May 22 19:15:30 BST 2013


Gitweb links:

...log http://git.netsurf-browser.org/netsurf.git/shortlog/af75070bee41816cb9ee4a53995d1f42870c1fcb
...commit http://git.netsurf-browser.org/netsurf.git/commit/af75070bee41816cb9ee4a53995d1f42870c1fcb
...tree http://git.netsurf-browser.org/netsurf.git/tree/af75070bee41816cb9ee4a53995d1f42870c1fcb

The branch, master has been updated
       via  af75070bee41816cb9ee4a53995d1f42870c1fcb (commit)
       via  7f7ff937453dc70144d1b6226fb7b34368ab271d (commit)
       via  ce0fe06349753b2ad1cfa7b0b1cfcaa77c4e47ab (commit)
       via  447c0fc2715ef690f47bda0da60ee3c3d750950d (commit)
      from  bec089e7aa1e2befcedb31a842d18de31bca4206 (commit)

Those revisions listed above that are new to this repository have
not appeared on any other notification email; so we list those
revisions in full, below.

- Log -----------------------------------------------------------------
commitdiff http://git.netsurf-browser.org/netsurf.git/commit/?id=af75070bee41816cb9ee4a53995d1f42870c1fcb
commit af75070bee41816cb9ee4a53995d1f42870c1fcb
Author: Vincent Sanders <vince at netsurf-browser.org>
Commit: Vincent Sanders <vince at netsurf-browser.org>

    add script_timeout option and use it

diff --git a/desktop/browser.c b/desktop/browser.c
index 53e3f13..ac8c699 100644
--- a/desktop/browser.c
+++ b/desktop/browser.c
@@ -683,12 +683,15 @@ 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)
+/** slow script handler
+*/
+static bool slow_script(void *ctx)
 {
 	static int count = 0;
 	LOG(("Continuing execution %d", count));
 	count++;
-	if (count >= 2) {
+	if (count > 1) {
+		count = 0;
 		return false;
 	}
 	return true;
@@ -717,7 +720,9 @@ browser_window_create(enum browser_window_nav_flags flags,
 	}
 
 	/* new javascript context for window */
-	bw->jsctx = js_newcontext(10, gui_slow_script, NULL);
+	bw->jsctx = js_newcontext(nsoption_int(script_timeout),
+				  slow_script,
+				  NULL);
 
 	/* Initialise common parts */
 	browser_window_initialise_common(bw, clone);
diff --git a/desktop/options_main.h b/desktop/options_main.h
index 7b9e731..43070ac 100644
--- a/desktop/options_main.h
+++ b/desktop/options_main.h
@@ -87,6 +87,8 @@
 	bool animate_images;					\
 	/** Whether to execute javascript */			\
 	bool enable_javascript;					\
+	/** how long to wait for a script to run */		\
+	int script_timeout;					\
 	/** How many days to retain URL data for */		\
 	int expire_url;						\
 	/** Default font family */				\
@@ -268,7 +270,8 @@
 	.suppress_curl_debug = true,			\
 	.target_blank = true,				\
 	.button_2_tab = true,				\
-	.enable_javascript = true
+	.enable_javascript = true,			\
+	.script_timeout = 10
 
 #define NSOPTION_MAIN_SYS_COLOUR_DEFAULTS		\
 	.sys_colour_ActiveBorder = 0x00000000,		\
@@ -318,7 +321,7 @@
 	{ "accept_language",	OPTION_STRING,	&nsoptions.accept_language }, \
 	{ "accept_charset",	OPTION_STRING,	&nsoptions.accept_charset }, \
 	{ "memory_cache_size",	OPTION_INTEGER,	&nsoptions.memory_cache_size },	\
-	{ "disc_cache_size",	OPTION_INTEGER,	&nsoptions.disc_cache_size },	\
+	{ "disc_cache_size",	OPTION_INTEGER,	&nsoptions.disc_cache_size }, \
 	{ "disc_cache_age",	OPTION_INTEGER,	&nsoptions.disc_cache_age }, \
 	{ "block_advertisements", OPTION_BOOL,	&nsoptions.block_ads },	\
 	{ "do_not_track", OPTION_BOOL,	&nsoptions.do_not_track },	\
@@ -327,7 +330,8 @@
 	{ "foreground_images",	OPTION_BOOL,	&nsoptions.foreground_images },	\
 	{ "background_images",	OPTION_BOOL,	&nsoptions.background_images },	\
 	{ "animate_images",	OPTION_BOOL,	&nsoptions.animate_images }, \
-	{ "enable_javascript",	OPTION_BOOL,	&nsoptions.enable_javascript},	\
+	{ "enable_javascript",	OPTION_BOOL,	&nsoptions.enable_javascript}, \
+	{ "script_timeout",	OPTION_INTEGER,	&nsoptions.script_timeout}, \
 	{ "expire_url",		OPTION_INTEGER,	&nsoptions.expire_url }, \
 	{ "font_default",	OPTION_INTEGER,	&nsoptions.font_default }, \
 	{ "ca_bundle",		OPTION_STRING,	&nsoptions.ca_bundle },	\


commitdiff http://git.netsurf-browser.org/netsurf.git/commit/?id=7f7ff937453dc70144d1b6226fb7b34368ab271d
commit 7f7ff937453dc70144d1b6226fb7b34368ab271d
Author: Vincent Sanders <vince at netsurf-browser.org>
Commit: Vincent Sanders <vince at netsurf-browser.org>

    add recusion and infinite loop tests

diff --git a/test/js/core.infinite.html b/test/js/core.infinite.html
new file mode 100644
index 0000000..7e6b8de
--- /dev/null
+++ b/test/js/core.infinite.html
@@ -0,0 +1,23 @@
+<html>
+<head>
+<title>Infinite loop</title>
+<link rel="stylesheet" type="text/css" href="tst.css">
+</head>
+<body>
+<h1>Infinite loop</h1>
+<p>Before</p>
+<script>
+
+function bar(x) { return  (x/2) + 1; }
+
+n=1;
+while (n < 3) {
+  n = bar(n);
+
+}
+
+</script>
+</script>
+<p>Afterwards</p>
+</body>
+</html>
diff --git a/test/js/core.recursion.html b/test/js/core.recursion.html
new file mode 100644
index 0000000..6c2206b
--- /dev/null
+++ b/test/js/core.recursion.html
@@ -0,0 +1,21 @@
+<html>
+<head>
+<title>Infinite recursion</title>
+<link rel="stylesheet" type="text/css" href="tst.css">
+</head>
+<body>
+<h1>Infinite recursion</h1>
+<p>Before</p>
+<script>
+
+function it_keeps_going_and_going_and_going(i) {
+
+  return it_keeps_going_and_going_and_going(i+1);
+}
+
+it_keeps_going_and_going_and_going(1)
+</script>
+</script>
+<p>Afterwards</p>
+</body>
+</html>
diff --git a/test/js/index.html b/test/js/index.html
index bb2918c..56482ec 100644
--- a/test/js/index.html
+++ b/test/js/index.html
@@ -6,6 +6,12 @@
 <body>
 <h1>JavaScript Tests</h1>
 
+<h2>Core</h2>
+<ul>
+<li><a href="core.recursion.html">Infinite Recursion</a></li>
+<li><a href="core.infinite.html">Infinite loop</a></li>
+</ul>
+
 
 <h2>Window</h2>
 <ul>


commitdiff http://git.netsurf-browser.org/netsurf.git/commit/?id=ce0fe06349753b2ad1cfa7b0b1cfcaa77c4e47ab
commit ce0fe06349753b2ad1cfa7b0b1cfcaa77c4e47ab
Author: Vincent Sanders <vince at kyllikki.org>
Commit: Vincent Sanders <vince at netsurf-browser.org>

    create javascript heartbeat and hook a script timeout to it

diff --git a/desktop/browser.c b/desktop/browser.c
index 12cc9c8..53e3f13 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 44de4fe..7102fcf 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 7b68fe9..a2bc90f 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 e38188a..8f9affd 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 3e7b39c..600a508 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;
 }


commitdiff http://git.netsurf-browser.org/netsurf.git/commit/?id=447c0fc2715ef690f47bda0da60ee3c3d750950d
commit 447c0fc2715ef690f47bda0da60ee3c3d750950d
Author: Vincent Sanders <vince at kyllikki.org>
Commit: Vincent Sanders <vince at netsurf-browser.org>

    ensure downloaded files are not interpreted

diff --git a/utils/fetch-transifex.pl b/utils/fetch-transifex.pl
index d8d5882..4d40062 100644
--- a/utils/fetch-transifex.pl
+++ b/utils/fetch-transifex.pl
@@ -43,6 +43,9 @@ use constant GETOPT_SPEC =>
       password|w=s
       help|h|? );
 
+# ensure no locale translation is applied and leave it all in UTF-8
+use bytes;
+
 # default option values:
 my %opt = qw( resource messagesany project netsurf user netsurf );
 


-----------------------------------------------------------------------

Summary of changes:
 desktop/browser.c           |   17 +++-
 desktop/options_main.h      |   10 +-
 javascript/js.h             |   10 +-
 javascript/jsapi.c          |  307 ++++++++++++++++++++++++++++++++++++++++---
 javascript/jsapi.h          |   16 ++-
 javascript/none.c           |    2 +-
 test/js/core.infinite.html  |   23 ++++
 test/js/core.recursion.html |   21 +++
 test/js/index.html          |    6 +
 utils/fetch-transifex.pl    |    3 +
 10 files changed, 386 insertions(+), 29 deletions(-)
 create mode 100644 test/js/core.infinite.html
 create mode 100644 test/js/core.recursion.html

diff --git a/desktop/browser.c b/desktop/browser.c
index 12cc9c8..ac8c699 100644
--- a/desktop/browser.c
+++ b/desktop/browser.c
@@ -683,6 +683,19 @@ void browser_window_debug_dump(struct browser_window *bw, FILE *f)
 		content_debug_dump(bw->current_content, f);
 }
 
+/** slow script handler
+*/
+static bool slow_script(void *ctx)
+{
+	static int count = 0;
+	LOG(("Continuing execution %d", count));
+	count++;
+	if (count > 1) {
+		count = 0;
+		return false;
+	}
+	return true;
+}
 
 /* exported interface, documented in desktop/browser.h */
 
@@ -707,7 +720,9 @@ browser_window_create(enum browser_window_nav_flags flags,
 	}
 
 	/* new javascript context for window */
-	bw->jsctx = js_newcontext();
+	bw->jsctx = js_newcontext(nsoption_int(script_timeout),
+				  slow_script,
+				  NULL);
 
 	/* Initialise common parts */
 	browser_window_initialise_common(bw, clone);
diff --git a/desktop/options_main.h b/desktop/options_main.h
index 7b9e731..43070ac 100644
--- a/desktop/options_main.h
+++ b/desktop/options_main.h
@@ -87,6 +87,8 @@
 	bool animate_images;					\
 	/** Whether to execute javascript */			\
 	bool enable_javascript;					\
+	/** how long to wait for a script to run */		\
+	int script_timeout;					\
 	/** How many days to retain URL data for */		\
 	int expire_url;						\
 	/** Default font family */				\
@@ -268,7 +270,8 @@
 	.suppress_curl_debug = true,			\
 	.target_blank = true,				\
 	.button_2_tab = true,				\
-	.enable_javascript = true
+	.enable_javascript = true,			\
+	.script_timeout = 10
 
 #define NSOPTION_MAIN_SYS_COLOUR_DEFAULTS		\
 	.sys_colour_ActiveBorder = 0x00000000,		\
@@ -318,7 +321,7 @@
 	{ "accept_language",	OPTION_STRING,	&nsoptions.accept_language }, \
 	{ "accept_charset",	OPTION_STRING,	&nsoptions.accept_charset }, \
 	{ "memory_cache_size",	OPTION_INTEGER,	&nsoptions.memory_cache_size },	\
-	{ "disc_cache_size",	OPTION_INTEGER,	&nsoptions.disc_cache_size },	\
+	{ "disc_cache_size",	OPTION_INTEGER,	&nsoptions.disc_cache_size }, \
 	{ "disc_cache_age",	OPTION_INTEGER,	&nsoptions.disc_cache_age }, \
 	{ "block_advertisements", OPTION_BOOL,	&nsoptions.block_ads },	\
 	{ "do_not_track", OPTION_BOOL,	&nsoptions.do_not_track },	\
@@ -327,7 +330,8 @@
 	{ "foreground_images",	OPTION_BOOL,	&nsoptions.foreground_images },	\
 	{ "background_images",	OPTION_BOOL,	&nsoptions.background_images },	\
 	{ "animate_images",	OPTION_BOOL,	&nsoptions.animate_images }, \
-	{ "enable_javascript",	OPTION_BOOL,	&nsoptions.enable_javascript},	\
+	{ "enable_javascript",	OPTION_BOOL,	&nsoptions.enable_javascript}, \
+	{ "script_timeout",	OPTION_INTEGER,	&nsoptions.script_timeout}, \
 	{ "expire_url",		OPTION_INTEGER,	&nsoptions.expire_url }, \
 	{ "font_default",	OPTION_INTEGER,	&nsoptions.font_default }, \
 	{ "ca_bundle",		OPTION_STRING,	&nsoptions.ca_bundle },	\
diff --git a/javascript/js.h b/javascript/js.h
index 44de4fe..7102fcf 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 7b68fe9..a2bc90f 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 e38188a..8f9affd 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 3e7b39c..600a508 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;
 }
diff --git a/test/js/core.infinite.html b/test/js/core.infinite.html
new file mode 100644
index 0000000..7e6b8de
--- /dev/null
+++ b/test/js/core.infinite.html
@@ -0,0 +1,23 @@
+<html>
+<head>
+<title>Infinite loop</title>
+<link rel="stylesheet" type="text/css" href="tst.css">
+</head>
+<body>
+<h1>Infinite loop</h1>
+<p>Before</p>
+<script>
+
+function bar(x) { return  (x/2) + 1; }
+
+n=1;
+while (n < 3) {
+  n = bar(n);
+
+}
+
+</script>
+</script>
+<p>Afterwards</p>
+</body>
+</html>
diff --git a/test/js/core.recursion.html b/test/js/core.recursion.html
new file mode 100644
index 0000000..6c2206b
--- /dev/null
+++ b/test/js/core.recursion.html
@@ -0,0 +1,21 @@
+<html>
+<head>
+<title>Infinite recursion</title>
+<link rel="stylesheet" type="text/css" href="tst.css">
+</head>
+<body>
+<h1>Infinite recursion</h1>
+<p>Before</p>
+<script>
+
+function it_keeps_going_and_going_and_going(i) {
+
+  return it_keeps_going_and_going_and_going(i+1);
+}
+
+it_keeps_going_and_going_and_going(1)
+</script>
+</script>
+<p>Afterwards</p>
+</body>
+</html>
diff --git a/test/js/index.html b/test/js/index.html
index bb2918c..56482ec 100644
--- a/test/js/index.html
+++ b/test/js/index.html
@@ -6,6 +6,12 @@
 <body>
 <h1>JavaScript Tests</h1>
 
+<h2>Core</h2>
+<ul>
+<li><a href="core.recursion.html">Infinite Recursion</a></li>
+<li><a href="core.infinite.html">Infinite loop</a></li>
+</ul>
+
 
 <h2>Window</h2>
 <ul>
diff --git a/utils/fetch-transifex.pl b/utils/fetch-transifex.pl
index d8d5882..4d40062 100644
--- a/utils/fetch-transifex.pl
+++ b/utils/fetch-transifex.pl
@@ -43,6 +43,9 @@ use constant GETOPT_SPEC =>
       password|w=s
       help|h|? );
 
+# ensure no locale translation is applied and leave it all in UTF-8
+use bytes;
+
 # default option values:
 my %opt = qw( resource messagesany project netsurf user netsurf );
 


-- 
NetSurf Browser



More information about the netsurf-commits mailing list