diff --git a/src/scripting/smjs/Makefile b/src/scripting/smjs/Makefile index 99d70c29..57adbf72 100644 --- a/src/scripting/smjs/Makefile +++ b/src/scripting/smjs/Makefile @@ -3,6 +3,6 @@ include $(top_builddir)/Makefile.config INCLUDES += $(SPIDERMONKEY_CFLAGS) -OBJS = smjs.o core.o elinks_object.o +OBJS = smjs.o core.o hooks.o elinks_object.o cache_object.o include $(top_srcdir)/Makefile.lib diff --git a/src/scripting/smjs/cache_object.c b/src/scripting/smjs/cache_object.c new file mode 100644 index 00000000..76d00a56 --- /dev/null +++ b/src/scripting/smjs/cache_object.c @@ -0,0 +1,151 @@ +/* Exports struct cache_entry to the world of ECMAScript */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "elinks.h" + +#include "cache/cache.h" +#include "ecmascript/spidermonkey/util.h" +#include "scripting/smjs/core.h" +#include "util/error.h" +#include "util/memory.h" + +enum cache_entry_prop { + CACHE_ENTRY_CONTENT, + CACHE_ENTRY_TYPE, + CACHE_ENTRY_LENGTH, + CACHE_ENTRY_HEAD, +}; + +static const JSPropertySpec cache_entry_props[] = { + { "content", CACHE_ENTRY_CONTENT, JSPROP_ENUMERATE }, + { "type", CACHE_ENTRY_TYPE, JSPROP_ENUMERATE }, + { "length", CACHE_ENTRY_LENGTH, JSPROP_ENUMERATE | JSPROP_READONLY }, + { "head", CACHE_ENTRY_HEAD, JSPROP_ENUMERATE }, +}; + +static JSBool +cache_entry_get_property(JSContext *ctx, JSObject *obj, jsval id, jsval *vp) +{ + struct cache_entry *cached = JS_GetPrivate(ctx, obj); + + if (!cached) return JS_FALSE; + + undef_to_jsval(ctx, vp); + + if (!JSVAL_IS_INT(id)) + return JS_FALSE; + + switch (JSVAL_TO_INT(id)) { + case CACHE_ENTRY_CONTENT: { + struct fragment *fragment = get_cache_fragment(cached); + + assert(fragment); + + *vp = STRING_TO_JSVAL(JS_NewStringCopyN(smjs_ctx, + fragment->data, + fragment->length)); + + return JS_TRUE; + } + case CACHE_ENTRY_TYPE: + *vp = STRING_TO_JSVAL(JS_NewStringCopyZ(smjs_ctx, + cached->content_type)); + + return JS_TRUE; + case CACHE_ENTRY_HEAD: + *vp = STRING_TO_JSVAL(JS_NewStringCopyZ(smjs_ctx, + cached->head)); + + return JS_TRUE; + case CACHE_ENTRY_LENGTH: + *vp = INT_TO_JSVAL(cached->length); + + return JS_TRUE; + default: + INTERNAL("Invalid ID %d in cache_entry_get_property().", + JSVAL_TO_INT(id)); + } + + return JS_FALSE; +} + +static JSBool +cache_entry_set_property(JSContext *ctx, JSObject *obj, jsval id, jsval *vp) +{ + struct cache_entry *cached = JS_GetPrivate(ctx, obj); + + if (!cached) return JS_FALSE; + + if (!JSVAL_IS_INT(id)) + return JS_FALSE; + + switch (JSVAL_TO_INT(id)) { + case CACHE_ENTRY_CONTENT: { + JSString *jsstr = JS_ValueToString(smjs_ctx, *vp); + unsigned char *str = JS_GetStringBytes(jsstr); + size_t len = JS_GetStringLength(jsstr); + + add_fragment(cached, 0, str, len); + normalize_cache_entry(cached, len); + + return JS_TRUE; + } + case CACHE_ENTRY_TYPE: { + JSString *jsstr = JS_ValueToString(smjs_ctx, *vp); + unsigned char *str = JS_GetStringBytes(jsstr); + + mem_free_set(&cached->content_type, stracpy(str)); + + return JS_TRUE; + } + case CACHE_ENTRY_HEAD: { + JSString *jsstr = JS_ValueToString(smjs_ctx, *vp); + unsigned char *str = JS_GetStringBytes(jsstr); + + mem_free_set(&cached->head, stracpy(str)); + + return JS_TRUE; + } + case CACHE_ENTRY_LENGTH: + + INTERNAL("attempted to assign to cache_entry's length"); + + return JS_FALSE; + default: + INTERNAL("Invalid ID %d in cache_entry_get_property().", + JSVAL_TO_INT(id)); + } + + + + return JS_FALSE; +} + +static const JSClass cache_entry_class = { + "cache_entry", + JSCLASS_HAS_PRIVATE, + JS_PropertyStub, JS_PropertyStub, + cache_entry_get_property, cache_entry_set_property, + JS_EnumerateStub, JS_ResolveStub, JS_ConvertStub, JS_FinalizeStub +}; + +JSObject * +get_cache_entry_object(struct cache_entry *cached) +{ + JSObject *cache_entry_object; + + cache_entry_object = JS_NewObject(smjs_ctx, + (JSClass *) &cache_entry_class, + NULL, NULL); + + if (!cache_entry_object) return NULL; + + JS_SetPrivate(smjs_ctx, cache_entry_object, cached); + JS_DefineProperties(smjs_ctx, cache_entry_object, + (JSPropertySpec *) cache_entry_props); + + return cache_entry_object; +} diff --git a/src/scripting/smjs/cache_object.h b/src/scripting/smjs/cache_object.h new file mode 100644 index 00000000..e81590f3 --- /dev/null +++ b/src/scripting/smjs/cache_object.h @@ -0,0 +1,9 @@ +#ifndef EL__SCRIPTING_SMJS_CACHE_H +#define EL__SCRIPTING_SMJS_CACHE_H + +struct cache_entry; + +JSObject *get_cache_entry_object(struct cache_entry *cached); + +#endif + diff --git a/src/scripting/smjs/hooks.c b/src/scripting/smjs/hooks.c new file mode 100644 index 00000000..6e7e078c --- /dev/null +++ b/src/scripting/smjs/hooks.c @@ -0,0 +1,72 @@ +/* Ruby scripting hooks */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "elinks.h" + +#include "cache/cache.h" +#include "ecmascript/spidermonkey/util.h" +#include "main/event.h" +#include "main/module.h" +#include "scripting/smjs/cache_object.h" +#include "scripting/smjs/core.h" +#include "scripting/smjs/elinks_object.h" +#include "scripting/smjs/hooks.h" +#include "session/session.h" + + +/* If elinks. is defined, call it with the given arguments, + * store the return value in rval, and return JS_TRUE. Else return JS_FALSE. */ +static JSBool +call_script_hook(unsigned char *hook, jsval argv[], int argc, jsval *rval) +{ + JSFunction *func; + + if (JS_FALSE == JS_GetProperty(smjs_ctx, smjs_elinks_object, + hook, rval)) + return JS_FALSE; + + if (JSVAL_VOID == *rval) + return JS_FALSE; + + func = JS_ValueToFunction(smjs_ctx, *rval); + assert(func); + + return JS_CallFunction(smjs_ctx, smjs_elinks_object, + func, argc, argv, rval); +} + +static enum evhook_status +script_hook_pre_format_html(va_list ap, void *data) +{ + struct session *ses = va_arg(ap, struct session *); + struct cache_entry *cached = va_arg(ap, struct cache_entry *); + enum evhook_status ret = EVENT_HOOK_STATUS_NEXT; + JSObject *cache_entry_object; + jsval args[1], rval; + + evhook_use_params(ses && cached); + + if (!smjs_ctx || !cached->length) goto end; + + smjs_ses = ses; + + cache_entry_object = get_cache_entry_object(cached); + args[0] = OBJECT_TO_JSVAL(cache_entry_object); + + if (JS_TRUE == call_script_hook("preformat_html", args, 1, &rval)) + if (JS_FALSE == JSVAL_TO_BOOLEAN(rval)) + ret = EVENT_HOOK_STATUS_LAST; + +end: + smjs_ses = NULL; + return ret; +} + +struct event_hook_info smjs_scripting_hooks[] = { + { "pre-format-html", 0, script_hook_pre_format_html, NULL }, + + NULL_EVENT_HOOK_INFO, +}; diff --git a/src/scripting/smjs/hooks.h b/src/scripting/smjs/hooks.h new file mode 100644 index 00000000..79fb43ea --- /dev/null +++ b/src/scripting/smjs/hooks.h @@ -0,0 +1,8 @@ +#ifndef EL__SCRIPTING_SMJS_HOOKS_H +#define EL__SCRIPTING_SMJS_HOOKS_H + +struct event_hook_info; + +extern struct event_hook_info smjs_scripting_hooks[]; + +#endif diff --git a/src/scripting/smjs/smjs.c b/src/scripting/smjs/smjs.c index 95eaa4ae..397636e0 100644 --- a/src/scripting/smjs/smjs.c +++ b/src/scripting/smjs/smjs.c @@ -8,12 +8,13 @@ #include "main/module.h" #include "scripting/smjs/core.h" +#include "scripting/smjs/hooks.h" struct module smjs_scripting_module = struct_module( /* name: */ "ECMAScript scripting engine", /* options: */ NULL, - /* events: */ NULL, + /* events: */ smjs_scripting_hooks, /* submodules: */ NULL, /* data: */ NULL, /* init: */ init_smjs,