JS_SetBranchCallback has been replaced with JS_SetOperationCallback.
This patch is to fix the FTBFS reported in #550868.  Patch based on an
upstream patch in git f31cf6f9fe805539d50ed55b62b05ce12daf7cb0 from
Miciah Dashiel Butler Masters <miciah.masters@gmail.com>

diff --git a/Makefile.config.in b/Makefile.config.in
index c463868..bcbebc0 100644
--- a/Makefile.config.in
+++ b/Makefile.config.in
@@ -117,6 +117,7 @@ CONFIG_DOM = @CONFIG_DOM@
 CONFIG_ECMASCRIPT = @CONFIG_ECMASCRIPT@
 CONFIG_ECMASCRIPT_SEE = @CONFIG_ECMASCRIPT_SEE@
 CONFIG_ECMASCRIPT_SMJS = @CONFIG_ECMASCRIPT_SMJS@
+CONFIG_ECMASCRIPT_SMJS_HEARTBEAT = @CONFIG_ECMASCRIPT_SMJS_HEARTBEAT@
 CONFIG_EXMODE = @CONFIG_EXMODE@
 CONFIG_FASTMEM = @CONFIG_FASTMEM@
 CONFIG_FINGER = @CONFIG_FINGER@
diff --git a/configure b/configure
index b8e22ce..350e587 100755
--- a/configure
+++ b/configure
@@ -709,6 +709,7 @@ SEE_CFLAGS
 CONFIG_ECMASCRIPT
 CONFIG_ECMASCRIPT_SEE
 CONFIG_ECMASCRIPT_SMJS
+CONFIG_ECMASCRIPT_SMJS_HEARTBEAT
 SPIDERMONKEY_LIBS
 SPIDERMONKEY_CFLAGS
 CONFIG_SPIDERMONKEY
@@ -13394,6 +13395,98 @@ _ACEOF
 fi
 done
 
+for ac_func in setitimer
+do
+as_ac_var=`echo "ac_cv_func_$ac_func" | $as_tr_sh`
+{ echo "$as_me:$LINENO: checking for $ac_func" >&5
+echo $ECHO_N "checking for $ac_func... $ECHO_C" >&6; }
+if { as_var=$as_ac_var; eval "test \"\${$as_var+set}\" = set"; }; then
+  echo $ECHO_N "(cached) $ECHO_C" >&6
+else
+  cat >conftest.$ac_ext <<_ACEOF
+/* confdefs.h.  */
+_ACEOF
+cat confdefs.h >>conftest.$ac_ext
+cat >>conftest.$ac_ext <<_ACEOF
+/* end confdefs.h.  */
+/* Define $ac_func to an innocuous variant, in case <limits.h> declares $ac_func.
+   For example, HP-UX 11i <limits.h> declares gettimeofday.  */
+#define $ac_func innocuous_$ac_func
+
+/* System header to define __stub macros and hopefully few prototypes,
+    which can conflict with char $ac_func (); below.
+    Prefer <limits.h> to <assert.h> if __STDC__ is defined, since
+    <limits.h> exists even on freestanding compilers.  */
+
+#ifdef __STDC__
+# include <limits.h>
+#else
+# include <assert.h>
+#endif
+
+#undef $ac_func
+
+/* Override any GCC internal prototype to avoid an error.
+   Use char because int might match the return type of a GCC
+   builtin and then its argument prototype would still apply.  */
+#ifdef __cplusplus
+extern "C"
+#endif
+char $ac_func ();
+/* The GNU C library defines this for functions which it implements
+    to always fail with ENOSYS.  Some functions are actually named
+    something starting with __ and the normal name is an alias.  */
+#if defined __stub_$ac_func || defined __stub___$ac_func
+choke me
+#endif
+
+int
+main ()
+{
+return $ac_func ();
+  ;
+  return 0;
+}
+_ACEOF
+rm -f conftest.$ac_objext conftest$ac_exeext
+if { (ac_try="$ac_link"
+case "(($ac_try" in
+  *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;;
+  *) ac_try_echo=$ac_try;;
+esac
+eval "echo \"\$as_me:$LINENO: $ac_try_echo\"") >&5
+  (eval "$ac_link") 2>conftest.er1
+  ac_status=$?
+  grep -v '^ *+' conftest.er1 >conftest.err
+  rm -f conftest.er1
+  cat conftest.err >&5
+  echo "$as_me:$LINENO: \$? = $ac_status" >&5
+  (exit $ac_status); } && {
+	 test -z "$ac_c_werror_flag" ||
+	 test ! -s conftest.err
+       } && test -s conftest$ac_exeext &&
+       $as_test_x conftest$ac_exeext; then
+  eval "$as_ac_var=yes"
+else
+  echo "$as_me: failed program was:" >&5
+sed 's/^/| /' conftest.$ac_ext >&5
+
+	eval "$as_ac_var=no"
+fi
+
+rm -f core conftest.err conftest.$ac_objext conftest_ipa8_conftest.oo \
+      conftest$ac_exeext conftest.$ac_ext
+fi
+ac_res=`eval echo '${'$as_ac_var'}'`
+	       { echo "$as_me:$LINENO: result: $ac_res" >&5
+echo "${ECHO_T}$ac_res" >&6; }
+if test `eval echo '${'$as_ac_var'}'` = yes; then
+  cat >>confdefs.h <<_ACEOF
+#define `echo "HAVE_$ac_func" | $as_tr_cpp` 1
+_ACEOF
+ HAVE_SETITIMER=yes
+fi
+done
 
 
 for ac_func in cygwin_conv_to_full_win32_path
@@ -18397,6 +18490,192 @@ _ACEOF
 fi
 done
 
+for ac_func in JS_SetBranchCallback
+do
+as_ac_var=`echo "ac_cv_func_$ac_func" | $as_tr_sh`
+{ echo "$as_me:$LINENO: checking for $ac_func" >&5
+echo $ECHO_N "checking for $ac_func... $ECHO_C" >&6; }
+if { as_var=$as_ac_var; eval "test \"\${$as_var+set}\" = set"; }; then
+  echo $ECHO_N "(cached) $ECHO_C" >&6
+else
+  cat >conftest.$ac_ext <<_ACEOF
+/* confdefs.h.  */
+_ACEOF
+cat confdefs.h >>conftest.$ac_ext
+cat >>conftest.$ac_ext <<_ACEOF
+/* end confdefs.h.  */
+/* Define $ac_func to an innocuous variant, in case <limits.h> declares $ac_func.
+   For example, HP-UX 11i <limits.h> declares gettimeofday.  */
+#define $ac_func innocuous_$ac_func
+
+/* System header to define __stub macros and hopefully few prototypes,
+    which can conflict with char $ac_func (); below.
+    Prefer <limits.h> to <assert.h> if __STDC__ is defined, since
+    <limits.h> exists even on freestanding compilers.  */
+
+#ifdef __STDC__
+# include <limits.h>
+#else
+# include <assert.h>
+#endif
+
+#undef $ac_func
+
+/* Override any GCC internal prototype to avoid an error.
+   Use char because int might match the return type of a GCC
+   builtin and then its argument prototype would still apply.  */
+#ifdef __cplusplus
+extern "C"
+#endif
+char $ac_func ();
+/* The GNU C library defines this for functions which it implements
+    to always fail with ENOSYS.  Some functions are actually named
+    something starting with __ and the normal name is an alias.  */
+#if defined __stub_$ac_func || defined __stub___$ac_func
+choke me
+#endif
+
+int
+main ()
+{
+return $ac_func ();
+  ;
+  return 0;
+}
+_ACEOF
+rm -f conftest.$ac_objext conftest$ac_exeext
+if { (ac_try="$ac_link"
+case "(($ac_try" in
+  *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;;
+  *) ac_try_echo=$ac_try;;
+esac
+eval "echo \"\$as_me:$LINENO: $ac_try_echo\"") >&5
+  (eval "$ac_link") 2>conftest.er1
+  ac_status=$?
+  grep -v '^ *+' conftest.er1 >conftest.err
+  rm -f conftest.er1
+  cat conftest.err >&5
+  echo "$as_me:$LINENO: \$? = $ac_status" >&5
+  (exit $ac_status); } && {
+	 test -z "$ac_c_werror_flag" ||
+	 test ! -s conftest.err
+       } && test -s conftest$ac_exeext &&
+       $as_test_x conftest$ac_exeext; then
+  eval "$as_ac_var=yes"
+else
+  echo "$as_me: failed program was:" >&5
+sed 's/^/| /' conftest.$ac_ext >&5
+
+	eval "$as_ac_var=no"
+fi
+
+rm -f core conftest.err conftest.$ac_objext conftest_ipa8_conftest.oo \
+      conftest$ac_exeext conftest.$ac_ext
+fi
+ac_res=`eval echo '${'$as_ac_var'}'`
+	       { echo "$as_me:$LINENO: result: $ac_res" >&5
+echo "${ECHO_T}$ac_res" >&6; }
+if test `eval echo '${'$as_ac_var'}'` = yes; then
+  cat >>confdefs.h <<_ACEOF
+#define `echo "HAVE_$ac_func" | $as_tr_cpp` 1
+_ACEOF
+
+fi
+done
+
+for ac_func in JS_TriggerOperationCallback
+do
+as_ac_var=`echo "ac_cv_func_$ac_func" | $as_tr_sh`
+{ echo "$as_me:$LINENO: checking for $ac_func" >&5
+echo $ECHO_N "checking for $ac_func... $ECHO_C" >&6; }
+if { as_var=$as_ac_var; eval "test \"\${$as_var+set}\" = set"; }; then
+  echo $ECHO_N "(cached) $ECHO_C" >&6
+else
+  cat >conftest.$ac_ext <<_ACEOF
+/* confdefs.h.  */
+_ACEOF
+cat confdefs.h >>conftest.$ac_ext
+cat >>conftest.$ac_ext <<_ACEOF
+/* end confdefs.h.  */
+/* Define $ac_func to an innocuous variant, in case <limits.h> declares $ac_func.
+   For example, HP-UX 11i <limits.h> declares gettimeofday.  */
+#define $ac_func innocuous_$ac_func
+
+/* System header to define __stub macros and hopefully few prototypes,
+    which can conflict with char $ac_func (); below.
+    Prefer <limits.h> to <assert.h> if __STDC__ is defined, since
+    <limits.h> exists even on freestanding compilers.  */
+
+#ifdef __STDC__
+# include <limits.h>
+#else
+# include <assert.h>
+#endif
+
+#undef $ac_func
+
+/* Override any GCC internal prototype to avoid an error.
+   Use char because int might match the return type of a GCC
+   builtin and then its argument prototype would still apply.  */
+#ifdef __cplusplus
+extern "C"
+#endif
+char $ac_func ();
+/* The GNU C library defines this for functions which it implements
+    to always fail with ENOSYS.  Some functions are actually named
+    something starting with __ and the normal name is an alias.  */
+#if defined __stub_$ac_func || defined __stub___$ac_func
+choke me
+#endif
+
+int
+main ()
+{
+return $ac_func ();
+  ;
+  return 0;
+}
+_ACEOF
+rm -f conftest.$ac_objext conftest$ac_exeext
+if { (ac_try="$ac_link"
+case "(($ac_try" in
+  *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;;
+  *) ac_try_echo=$ac_try;;
+esac
+eval "echo \"\$as_me:$LINENO: $ac_try_echo\"") >&5
+  (eval "$ac_link") 2>conftest.er1
+  ac_status=$?
+  grep -v '^ *+' conftest.er1 >conftest.err
+  rm -f conftest.er1
+  cat conftest.err >&5
+  echo "$as_me:$LINENO: \$? = $ac_status" >&5
+  (exit $ac_status); } && {
+	 test -z "$ac_c_werror_flag" ||
+	 test ! -s conftest.err
+       } && test -s conftest$ac_exeext &&
+       $as_test_x conftest$ac_exeext; then
+  eval "$as_ac_var=yes"
+else
+  echo "$as_me: failed program was:" >&5
+sed 's/^/| /' conftest.$ac_ext >&5
+
+	eval "$as_ac_var=no"
+fi
+
+rm -f core conftest.err conftest.$ac_objext conftest_ipa8_conftest.oo \
+      conftest$ac_exeext conftest.$ac_ext
+fi
+ac_res=`eval echo '${'$as_ac_var'}'`
+	       { echo "$as_me:$LINENO: result: $ac_res" >&5
+echo "${ECHO_T}$ac_res" >&6; }
+if test `eval echo '${'$as_ac_var'}'` = yes; then
+  cat >>confdefs.h <<_ACEOF
+#define `echo "HAVE_$ac_func" | $as_tr_cpp` 1
+_ACEOF
+ HAVE_JS_TRIGGEROPERATIONCALLBACK=yes
+fi
+done
+
 fi
 
 	CFLAGS="$CFLAGS_X";
@@ -18476,6 +18755,22 @@ _ACEOF
 
 
 
+if test "x$CONFIG_ECMASCRIPT_SMJS" = xyes &&
+   test "x$HAVE_JS_TRIGGEROPERATIONCALLBACK" = xyes &&
+   test "x$HAVE_SETITIMER" = xyes; then
+
+	  CONFIG_ECMASCRIPT_SMJS_HEARTBEAT=yes
+	  ABOUT_CONFIG_ECMASCRIPT_SMJS_HEARTBEAT="ECMAScript heartbeat support"
+
+cat >>confdefs.h <<\_ACEOF
+#define CONFIG_ECMASCRIPT_SMJS_HEARTBEAT 1
+_ACEOF
+
+else
+	CONFIG_ECMASCRIPT_SMJS_HEARTBEAT=no
+fi
+
+
 
 # ===================================================================
 # Optional Spidermonkey-based ECMAScript browser scripting
diff --git a/configure.in b/configure.in
index 4a7143d..08c4bbc 100644
--- a/configure.in
+++ b/configure.in
@@ -283,6 +283,7 @@ AC_CHECK_FUNCS(snprintf vsnprintf asprintf vasprintf)
 AC_CHECK_FUNCS(getifaddrs getpwnam inet_pton inet_ntop)
 AC_CHECK_FUNCS(fflush fsync fseeko ftello sigaction)
 AC_CHECK_FUNCS(gettimeofday clock_gettime)
+AC_CHECK_FUNCS(setitimer, HAVE_SETITIMER=yes)
 
 AC_CHECK_FUNCS([cygwin_conv_to_full_win32_path])
 
@@ -651,6 +652,8 @@ AC_MSG_RESULT($cf_result)
 CONFIG_SPIDERMONKEY="$cf_result"
 if test "$cf_result" = "yes"; then
 	AC_CHECK_FUNCS([[JS_ReportAllocationOverflow]])
+	AC_CHECK_FUNCS(JS_SetBranchCallback)
+	AC_CHECK_FUNCS(JS_TriggerOperationCallback, HAVE_JS_TRIGGEROPERATIONCALLBACK=yes)
 fi
 EL_RESTORE_FLAGS
 
@@ -665,6 +668,15 @@ EL_CONFIG_DEPENDS(CONFIG_ECMASCRIPT, [CONFIG_ECMASCRIPT_SEE CONFIG_ECMASCRIPT_SM
 AC_SUBST(CONFIG_ECMASCRIPT_SEE)
 AC_SUBST(CONFIG_ECMASCRIPT_SMJS)
 
+if test "x$CONFIG_ECMASCRIPT_SMJS" = xyes &&
+   test "x$HAVE_JS_TRIGGEROPERATIONCALLBACK" = xyes &&
+   test "x$HAVE_SETITIMER" = xyes; then
+	EL_CONFIG(CONFIG_ECMASCRIPT_SMJS_HEARTBEAT, [ECMAScript heartbeat support])
+else
+	CONFIG_ECMASCRIPT_SMJS_HEARTBEAT=no
+fi
+AC_SUBST(CONFIG_ECMASCRIPT_SMJS_HEARTBEAT)
+
 
 # ===================================================================
 # Optional Spidermonkey-based ECMAScript browser scripting
diff --git a/src/ecmascript/ecmascript.h b/src/ecmascript/ecmascript.h
index e8d84b5..53d40ae 100644
--- a/src/ecmascript/ecmascript.h
+++ b/src/ecmascript/ecmascript.h
@@ -32,7 +32,11 @@ struct ecmascript_interpreter {
 	/* The code evaluated by setTimeout() */
 	struct string code;
 
+#if defined(CONFIG_ECMASCRIPT_SMJS_HEARTBEAT)
+	struct heartbeat *heartbeat;
+#elif defined(HAVE_JS_SETBRANCHCALLBACK)
 	time_t exec_start;
+#endif
 
 	/* This is a cross-rerenderings accumulator of
 	 * @document.onload_snippets (see its description for juicy details).
diff --git a/src/ecmascript/spidermonkey.c b/src/ecmascript/spidermonkey.c
index 78c3bca..7c83cec 100644
--- a/src/ecmascript/spidermonkey.c
+++ b/src/ecmascript/spidermonkey.c
@@ -25,6 +25,7 @@
 #include "ecmascript/spidermonkey.h"
 #include "ecmascript/spidermonkey/document.h"
 #include "ecmascript/spidermonkey/form.h"
+#include "ecmascript/spidermonkey/heartbeat.h"
 #include "ecmascript/spidermonkey/location.h"
 #include "ecmascript/spidermonkey/navigator.h"
 #include "ecmascript/spidermonkey/unibar.h"
@@ -109,6 +110,7 @@ reported:
 	JS_ClearPendingException(ctx);
 }
 
+#if !defined(CONFIG_ECMASCRIPT_SMJS_HEARTBEAT) && defined(HAVE_JS_SETBRANCHCALLBACK)
 static JSBool
 safeguard(JSContext *ctx, JSScript *script)
 {
@@ -132,6 +134,7 @@ setup_safeguard(struct ecmascript_interpreter *interpreter,
 	interpreter->exec_start = time(NULL);
 	JS_SetBranchCallback(ctx, safeguard);
 }
+#endif
 
 
 static void
@@ -172,6 +175,9 @@ spidermonkey_get_interpreter(struct ecmascript_interpreter *interpreter)
 	 * some kind of bytecode cache. (If we will ever do that.) */
 	JS_SetOptions(ctx, JSOPTION_VAROBJFIX | JSOPTION_COMPILE_N_GO);
 	JS_SetErrorReporter(ctx, error_reporter);
+#if defined(CONFIG_ECMASCRIPT_SMJS_HEARTBEAT)
+	JS_SetOperationCallback(ctx, heartbeat_callback);
+#endif
 
 	window_obj = JS_NewObject(ctx, (JSClass *) &window_class, NULL, NULL);
 	if (!window_obj) goto release_and_fail;
@@ -263,10 +269,17 @@ spidermonkey_eval(struct ecmascript_interpreter *interpreter,
 	assert(interpreter);
 	if (!js_module_init_ok) return;
 	ctx = interpreter->backend_data;
+#if defined(CONFIG_ECMASCRIPT_SMJS_HEARTBEAT)
+	interpreter->heartbeat = add_heartbeat(interpreter);
+#elif defined(HAVE_JS_SETBRANCHCALLBACK)
 	setup_safeguard(interpreter, ctx);
+#endif
 	interpreter->ret = ret;
 	JS_EvaluateScript(ctx, JS_GetGlobalObject(ctx),
 	                  code->source, code->length, "", 0, &rval);
+#if defined(CONFIG_ECMASCRIPT_SMJS_HEARTBEAT)
+	done_heartbeat(interpreter->heartbeat);
+#endif
 }
 
 
@@ -274,17 +287,25 @@ unsigned char *
 spidermonkey_eval_stringback(struct ecmascript_interpreter *interpreter,
 			     struct string *code)
 {
+	JSBool ret;
 	JSContext *ctx;
 	jsval rval;
 
 	assert(interpreter);
 	if (!js_module_init_ok) return NULL;
 	ctx = interpreter->backend_data;
-	setup_safeguard(interpreter, ctx);
 	interpreter->ret = NULL;
-	if (JS_EvaluateScript(ctx, JS_GetGlobalObject(ctx),
-			      code->source, code->length, "", 0, &rval)
-	    == JS_FALSE) {
+#if defined(CONFIG_ECMASCRIPT_SMJS_HEARTBEAT)
+	interpreter->heartbeat = add_heartbeat(interpreter);
+#elif defined(HAVE_JS_SETBRANCHCALLBACK)
+	setup_safeguard(interpreter, ctx);
+#endif
+	ret = JS_EvaluateScript(ctx, JS_GetGlobalObject(ctx),
+	                        code->source, code->length, "", 0, &rval);
+#if defined(CONFIG_ECMASCRIPT_SMJS_HEARTBEAT)
+	done_heartbeat(interpreter->heartbeat);
+#endif
+	if (ret == JS_FALSE) {
 		return NULL;
 	}
 	if (JSVAL_IS_VOID(rval)) {
@@ -308,14 +329,21 @@ spidermonkey_eval_boolback(struct ecmascript_interpreter *interpreter,
 	assert(interpreter);
 	if (!js_module_init_ok) return 0;
 	ctx = interpreter->backend_data;
-	setup_safeguard(interpreter, ctx);
 	interpreter->ret = NULL;
 	fun = JS_CompileFunction(ctx, NULL, "", 0, NULL, code->source,
 				 code->length, "", 0);
 	if (!fun)
 		return -1;
 
+#if defined(CONFIG_ECMASCRIPT_SMJS_HEARTBEAT)
+	interpreter->heartbeat = add_heartbeat(interpreter);
+#elif defined(HAVE_JS_SETBRANCHCALLBACK)
+	setup_safeguard(interpreter, ctx);
+#endif
 	ret = JS_CallFunction(ctx, NULL, fun, 0, NULL, &rval);
+#if defined(CONFIG_ECMASCRIPT_SMJS_HEARTBEAT)
+	done_heartbeat(interpreter->heartbeat);
+#endif
 	if (ret == 2) { /* onClick="history.back()" */
 		return 0;
 	}
diff --git a/src/ecmascript/spidermonkey/Makefile b/src/ecmascript/spidermonkey/Makefile
index f1c0fef..fd98dc5 100644
--- a/src/ecmascript/spidermonkey/Makefile
+++ b/src/ecmascript/spidermonkey/Makefile
@@ -2,6 +2,8 @@ top_builddir=../../..
 include $(top_builddir)/Makefile.config
 INCLUDES += $(SPIDERMONKEY_CFLAGS)
 
+OBJS-$(CONFIG_ECMASCRIPT_SMJS_HEARTBEAT) += heartbeat.o
+
 OBJS = document.o form.o location.o navigator.o unibar.o window.o
 
 include $(top_srcdir)/Makefile.lib
diff --git a/src/ecmascript/spidermonkey/heartbeat.c b/src/ecmascript/spidermonkey/heartbeat.c
new file mode 100644
index 0000000..1fb87a6
--- /dev/null
+++ b/src/ecmascript/spidermonkey/heartbeat.c
@@ -0,0 +1,116 @@
+/* The SpiderMonkey ECMAScript backend heartbeat fuctionality. */
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include <sys/time.h>      /* setitimer(2) */
+
+#include "elinks.h"
+
+#include "ecmascript/spidermonkey/util.h"
+
+#include "config/options.h"
+#include "document/view.h"
+#include "ecmascript/ecmascript.h"
+#include "ecmascript/spidermonkey.h"
+#include "ecmascript/spidermonkey/heartbeat.h"
+#include "osdep/signals.h"
+#include "session/session.h"
+#include "util/lists.h"
+#include "util/math.h"   /* int_upper_bound */
+#include "util/memory.h"
+#include "viewer/text/vs.h"
+
+
+
+static INIT_LIST_OF(struct heartbeat, heartbeats);
+
+static struct itimerval heartbeat_timer = { { 1, 0 }, { 1, 0 } };
+
+
+/* This callback is installed by JS_SetOperationCallback and triggered
+ * by JS_TriggerOperationCallback in the heartbeat code below.  Returning
+ * JS_FALSE terminates script execution immediately. */
+JSBool
+heartbeat_callback(JSContext *ctx)
+{
+        return JS_FALSE;
+}
+
+/* Callback for SIGVTALRM.  Go through all heartbeats, decrease each
+ * one's TTL, and call JS_TriggerOperationCallback if a heartbeat's TTL
+ * goes to 0. */
+static void
+check_heartbeats(void *data)
+{
+        struct heartbeat *hb;
+
+        foreach (hb, heartbeats) {
+                assert(hb->interpreter);
+                --hb->ttl;
+
+                if (hb->ttl <= 0) {
+			if (hb->interpreter->vs
+			    && hb->interpreter->vs->doc_view
+			    && hb->interpreter->vs->doc_view->session
+			    && hb->interpreter->vs->doc_view->session->tab
+			    && hb->interpreter->vs->doc_view->session->tab->term) {
+				struct session *ses = hb->interpreter->vs->doc_view->session;
+				struct terminal *term = ses->tab->term;
+				int max_exec_time = get_opt_int("ecmascript.max_exec_time");
+
+				ecmascript_timeout_dialog(term, max_exec_time);
+			}
+
+                        JS_TriggerOperationCallback(hb->interpreter->backend_data);
+                }
+        }
+        install_signal_handler(SIGVTALRM, check_heartbeats, NULL, 1);
+}
+
+/* Create a new heartbeat for the given interpreter. */
+struct heartbeat *
+add_heartbeat(struct ecmascript_interpreter *interpreter)
+{
+	struct heartbeat *hb;
+	assert(interpreter);
+
+        hb = mem_alloc(sizeof(struct heartbeat));
+        if (!hb) return NULL;
+
+        hb->ttl = get_opt_int("ecmascript.max_exec_time");
+        hb->interpreter = interpreter;
+
+        add_to_list(heartbeats, hb);
+
+        /* Update the heartbeat timer. */
+        if (list_is_singleton(*hb)) {
+                heartbeat_timer.it_value.tv_sec = 1;
+                setitimer(ITIMER_VIRTUAL, &heartbeat_timer, NULL);
+        }
+
+        /* We install the handler every call to add_heartbeat instead of only on
+         * module initialisation because other code may set other handlers for
+         * the signal.  */
+        install_signal_handler(SIGVTALRM, check_heartbeats, NULL, 1);
+
+        return hb;
+}
+
+/* Destroy the given heartbeat. */
+void
+done_heartbeat(struct heartbeat *hb)
+{
+        assert(hb->interpreter);
+
+        /* Stop the heartbeat timer if this heartbeat is the only one. */
+        if (list_is_singleton(*hb)) {
+                heartbeat_timer.it_value.tv_sec = 0;
+                setitimer(ITIMER_VIRTUAL, &heartbeat_timer, NULL);
+        }
+
+        del_from_list(hb);
+        hb->interpreter->heartbeat = NULL;
+        mem_free(hb);
+}
diff --git a/src/ecmascript/spidermonkey/heartbeat.h b/src/ecmascript/spidermonkey/heartbeat.h
new file mode 100644
index 0000000..f7c8b12
--- /dev/null
+++ b/src/ecmascript/spidermonkey/heartbeat.h
@@ -0,0 +1,24 @@
+#ifndef EL__ECMASCRIPT_SPIDERMONKEY_HEARTBEAT_H
+#define EL__ECMASCRIPT_SPIDERMONKEY_HEARTBEAT_H
+
+#include "ecmascript/spidermonkey/util.h"
+
+#include "ecmascript/spidermonkey.h"
+
+struct heartbeat {
+        LIST_HEAD(struct heartbeat);
+
+        int ttl; /* Time to live.  This value is assigned when the
+                  * script begins execution and is decremented every
+                  * second.  When it reaches 0, script execution is
+                  * terminated. */
+
+        struct ecmascript_interpreter *interpreter;
+};
+
+struct heartbeat *add_heartbeat(struct ecmascript_interpreter *interpreter);
+void done_heartbeat(struct heartbeat *hb);
+
+JSBool heartbeat_callback(JSContext *ctx);
+
+#endif
