summaryrefslogtreecommitdiff
path: root/javascript/jsapi/jsapi.c
diff options
context:
space:
mode:
Diffstat (limited to 'javascript/jsapi/jsapi.c')
-rw-r--r--javascript/jsapi/jsapi.c613
1 files changed, 613 insertions, 0 deletions
diff --git a/javascript/jsapi/jsapi.c b/javascript/jsapi/jsapi.c
new file mode 100644
index 000000000..8724d9be8
--- /dev/null
+++ b/javascript/jsapi/jsapi.c
@@ -0,0 +1,613 @@
+/*
+ * Copyright 2012 Vincent Sanders <vince@netsurf-browser.org>
+ *
+ * This file is part of NetSurf, http://www.netsurf-browser.org/
+ *
+ * NetSurf is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; version 2 of the License.
+ *
+ * NetSurf is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * 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"
+#include "javascript/content.h"
+#include "javascript/js.h"
+
+#include "utils/log.h"
+
+#include "window.h"
+#include "event.h"
+
+#define ENABLE_JS_HEARTBEAT 1
+
+static JSRuntime *rt; /* global runtime */
+
+void js_initialise(void)
+{
+ /* Create a JS runtime. */
+
+#if JS_VERSION >= 180
+ JS_SetCStringsAreUTF8(); /* we prefer our runtime to be utf-8 */
+#endif
+
+ rt = JS_NewRuntime(8L * 1024L * 1024L);
+ JSLOG("New runtime handle %p", rt);
+
+ if (rt != NULL) {
+ /* register script content handler */
+ javascript_init();
+ }
+}
+
+void js_finalise(void)
+{
+ if (rt != NULL) {
+ JSLOG("destroying runtime handle %p", rt);
+ JS_DestroyRuntime(rt);
+ }
+ JS_ShutDown();
+}
+
+/* The error reporter callback. */
+static void
+js_reportError(JSContext *cx, const char *message, JSErrorReport *report)
+{
+ JSLOG("%s:%u:%s",
+ report->filename ? report->filename : "<no filename>",
+ (unsigned int) report->lineno,
+ message);
+}
+
+/* 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 %ld end %ld", (long)now, (long)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 >= 180
+
+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_callback(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);
+
+ return true;
+}
+
+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
+
+nserror js_newcontext(int timeout, jscallback *cb, void *cbctx,
+ jscontext **jsctx)
+{
+ JSContext *cx;
+ *jsctx = NULL;
+
+ if (rt == NULL) {
+ return NSERROR_OK;
+ }
+
+ cx = JS_NewContext(rt, 8192);
+ if (cx == NULL) {
+ return NSERROR_NOMEM;
+ }
+
+ /* 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);
+
+ *jsctx = (jscontext *)cx;
+ return NSERROR_OK;
+}
+
+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);
+ }
+}
+
+
+/** Create new compartment to run scripts within
+ *
+ * This performs the following actions
+ * 1. constructs a new global object by initialising a window class
+ * 2. Instantiate the global a window object
+ */
+jsobject *js_newcompartment(jscontext *ctx, void *win_priv, void *doc_priv)
+{
+ JSContext *cx = (JSContext *)ctx;
+ JSObject *window_proto;
+ JSObject *window;
+
+ if (cx == NULL) {
+ return NULL;
+ }
+
+ window_proto = jsapi_InitClass_Window(cx, NULL);
+ if (window_proto == NULL) {
+ JSLOG("Unable to initialise window class");
+ return NULL;
+ }
+
+ window = jsapi_new_Window(cx, window_proto, NULL, win_priv, 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); */
+
+ if (ctx == NULL) {
+ return false;
+ }
+
+ if (txt == NULL) {
+ return false;
+ }
+
+ if (txtlen == 0) {
+ return false;
+ }
+
+ 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;
+ }
+
+ return false;
+}
+
+dom_exception _dom_event_create(dom_document *doc, dom_event **evt);
+#define dom_event_create(d, e) _dom_event_create((dom_document *)(d), (dom_event **) (e))
+
+bool js_fire_event(jscontext *ctx, const char *type, dom_document *doc, dom_node *target)
+{
+ JSContext *cx = (JSContext *)ctx;
+ dom_node *node = target;
+ JSObject *jsevent;
+ jsval rval;
+ jsval argv[1];
+ JSBool ret = JS_TRUE;
+ dom_exception exc;
+ dom_event *event;
+ dom_string *type_dom;
+ struct heartbeat *hb;
+
+ if (cx == NULL) {
+ return false;
+ }
+
+ if (node == NULL) {
+ /* deliver manufactured event to window */
+ JSLOG("Dispatching event %s at window", type);
+
+ /* create and initialise and event object */
+ exc = dom_string_create((unsigned char*)type,
+ strlen(type),
+ &type_dom);
+ if (exc != DOM_NO_ERR) {
+ return false;
+ }
+
+ exc = dom_event_create(doc, &event);
+ if (exc != DOM_NO_ERR) {
+ return false;
+ }
+
+ exc = dom_event_init(event, type_dom, false, false);
+ dom_string_unref(type_dom);
+ if (exc != DOM_NO_ERR) {
+ return false;
+ }
+
+ jsevent = jsapi_new_Event(cx, NULL, NULL, event);
+ if (jsevent == NULL) {
+ return false;
+ }
+
+ hb = enable_heartbeat(cx);
+
+ /* dispatch event at the window object */
+ argv[0] = OBJECT_TO_JSVAL(jsevent);
+
+ ret = JS_CallFunctionName(cx,
+ JS_GetGlobalObject(cx),
+ "dispatchEvent",
+ 1,
+ argv,
+ &rval);
+
+ disable_heartbeat(hb);
+
+ } else {
+ JSLOG("Dispatching event %s at %p", type, node);
+
+ /* create and initialise and event object */
+ exc = dom_string_create((unsigned char*)type,
+ strlen(type),
+ &type_dom);
+ if (exc != DOM_NO_ERR) {
+ return false;
+ }
+
+ exc = dom_event_create(doc, &event);
+ if (exc != DOM_NO_ERR) {
+ return false;
+ }
+
+ exc = dom_event_init(event, type_dom, true, true);
+ dom_string_unref(type_dom);
+ if (exc != DOM_NO_ERR) {
+ return false;
+ }
+
+ dom_event_target_dispatch_event(node, event, &ret);
+
+ }
+
+ if (ret == JS_TRUE) {
+ return true;
+ }
+ return false;
+}
+
+struct js_dom_event_private {
+ JSContext *cx; /* javascript context */
+ jsval funcval; /* javascript function to call */
+ struct dom_node *node; /* dom node event listening on */
+ dom_string *type; /* event type */
+ dom_event_listener *listener; /* the listener containing this */
+};
+
+static void
+js_dom_event_listener(struct dom_event *event, void *pw)
+{
+ struct js_dom_event_private *private = pw;
+ jsval event_argv[1];
+ jsval event_rval;
+ JSObject *jsevent;
+
+ JSLOG("WOOT dom event with %p", private);
+
+ if (!JSVAL_IS_VOID(private->funcval)) {
+ 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);
+ }
+ }
+}
+
+/* add a listener to a dom node
+ *
+ * 1. Create a dom_event_listener From a handle_event function pointer
+ * and a private word In a document context
+ *
+ * 2. Register for your events on a target (dom nodes are targets)
+ * dom_event_target_add_event_listener(node, evt_name, listener,
+ * capture_or_not)
+ *
+ */
+
+bool
+js_dom_event_add_listener(jscontext *ctx,
+ struct dom_document *document,
+ struct dom_node *node,
+ struct dom_string *event_type_dom,
+ void *js_funcval)
+{
+ JSContext *cx = (JSContext *)ctx;
+ dom_exception exc;
+ struct js_dom_event_private *private;
+
+ private = malloc(sizeof(struct js_dom_event_private));
+ if (private == NULL) {
+ return false;
+ }
+
+ exc = dom_event_listener_create(document,
+ js_dom_event_listener,
+ private,
+ &private->listener);
+ if (exc != DOM_NO_ERR) {
+ return false;
+ }
+
+ private->cx = cx;
+ private->funcval = *(jsval *)js_funcval;
+ private->node = node;
+ private->type = event_type_dom;
+
+ JSLOG("adding %p to listener", private);
+
+ JSAPI_ADD_VALUE_ROOT(cx, &private->funcval);
+ exc = dom_event_target_add_event_listener(private->node,
+ private->type,
+ private->listener,
+ true);
+ if (exc != DOM_NO_ERR) {
+ JSLOG("failed to add listener");
+ JSAPI_REMOVE_VALUE_ROOT(cx, &private->funcval);
+ }
+
+ return true;
+}