Initial experiment in adding js support to w3m.
This commit is contained in:
269
js/w3m_javascript.c
Normal file
269
js/w3m_javascript.c
Normal file
@@ -0,0 +1,269 @@
|
||||
/*
|
||||
* w3m JavaScript Integration Implementation
|
||||
*
|
||||
* Core JavaScript functionality for w3m using QuickJS engine.
|
||||
* This file implements the main JavaScript context management,
|
||||
* script execution, and buffer integration.
|
||||
*/
|
||||
|
||||
#include "fm.h"
|
||||
|
||||
#ifdef USE_JAVASCRIPT
|
||||
|
||||
#include "w3m_javascript.h"
|
||||
#include "w3m_dom.h"
|
||||
#include "w3m_events.h"
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
/* Global JavaScript configuration */
|
||||
int w3m_js_enabled = 0;
|
||||
int w3m_js_timeout = 5000; /* 5 second timeout */
|
||||
int w3m_js_memory_limit = 8388608; /* 8MB memory limit */
|
||||
int w3m_js_network_enabled = 1;
|
||||
|
||||
/* JavaScript Context Management */
|
||||
|
||||
W3MJSContext *
|
||||
w3m_js_create_context(void)
|
||||
{
|
||||
W3MJSContext *ctx = GC_MALLOC(sizeof(W3MJSContext));
|
||||
if (!ctx) return NULL;
|
||||
|
||||
ctx->runtime = JS_NewRuntime();
|
||||
if (!ctx->runtime) {
|
||||
GC_free(ctx);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* Set memory limit */
|
||||
JS_SetMemoryLimit(ctx->runtime, w3m_js_memory_limit);
|
||||
|
||||
ctx->context = JS_NewContext(ctx->runtime);
|
||||
if (!ctx->context) {
|
||||
JS_FreeRuntime(ctx->runtime);
|
||||
GC_free(ctx);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* Get global object */
|
||||
ctx->global_obj = JS_GetGlobalObject(ctx->context);
|
||||
|
||||
/* Initialize empty document and window objects */
|
||||
ctx->document_obj = JS_NULL;
|
||||
ctx->window_obj = JS_NULL;
|
||||
|
||||
ctx->memory_limit = w3m_js_memory_limit;
|
||||
ctx->execution_timeout = w3m_js_timeout;
|
||||
|
||||
return ctx;
|
||||
}
|
||||
|
||||
void
|
||||
w3m_js_destroy_context(W3MJSContext *ctx)
|
||||
{
|
||||
if (!ctx) return;
|
||||
|
||||
if (!JS_IsNull(ctx->global_obj))
|
||||
JS_FreeValue(ctx->context, ctx->global_obj);
|
||||
if (!JS_IsNull(ctx->document_obj))
|
||||
JS_FreeValue(ctx->context, ctx->document_obj);
|
||||
if (!JS_IsNull(ctx->window_obj))
|
||||
JS_FreeValue(ctx->context, ctx->window_obj);
|
||||
|
||||
if (ctx->context)
|
||||
JS_FreeContext(ctx->context);
|
||||
if (ctx->runtime)
|
||||
JS_FreeRuntime(ctx->runtime);
|
||||
|
||||
GC_free(ctx);
|
||||
}
|
||||
|
||||
int
|
||||
w3m_js_execute_script(W3MJSContext *ctx, const char *script, const char *filename)
|
||||
{
|
||||
if (!ctx || !script) return 0;
|
||||
|
||||
JSValue result = JS_Eval(ctx->context, script, strlen(script),
|
||||
filename ? filename : "<script>",
|
||||
JS_EVAL_TYPE_GLOBAL);
|
||||
|
||||
if (JS_IsException(result)) {
|
||||
/* Handle JavaScript error */
|
||||
JSValue exception = JS_GetException(ctx->context);
|
||||
const char *error_msg = JS_ToCString(ctx->context, exception);
|
||||
w3m_js_report_error(ctx, error_msg ? error_msg : "Unknown JavaScript error");
|
||||
if (error_msg) JS_FreeCString(ctx->context, error_msg);
|
||||
JS_FreeValue(ctx->context, exception);
|
||||
JS_FreeValue(ctx->context, result);
|
||||
return 0;
|
||||
}
|
||||
|
||||
JS_FreeValue(ctx->context, result);
|
||||
return 1;
|
||||
}
|
||||
|
||||
void
|
||||
w3m_js_set_timeout(W3MJSContext *ctx, int timeout_ms)
|
||||
{
|
||||
if (ctx) {
|
||||
ctx->execution_timeout = timeout_ms;
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
w3m_js_set_memory_limit(W3MJSContext *ctx, int limit_bytes)
|
||||
{
|
||||
if (ctx && ctx->runtime) {
|
||||
ctx->memory_limit = limit_bytes;
|
||||
JS_SetMemoryLimit(ctx->runtime, limit_bytes);
|
||||
}
|
||||
}
|
||||
|
||||
/* Buffer JavaScript Integration */
|
||||
|
||||
BufferJSState *
|
||||
w3m_js_init_buffer_state(Buffer *buf)
|
||||
{
|
||||
if (!buf) return NULL;
|
||||
|
||||
BufferJSState *state = GC_MALLOC(sizeof(BufferJSState));
|
||||
if (!state) return NULL;
|
||||
|
||||
state->js_ctx = w3m_js_create_context();
|
||||
if (!state->js_ctx) {
|
||||
GC_free(state);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
state->script_objects = NULL;
|
||||
state->script_count = 0;
|
||||
state->pending_scripts = NULL;
|
||||
state->pending_count = 0;
|
||||
state->scripts_enabled = w3m_js_enabled;
|
||||
|
||||
/* Initialize DOM for this buffer */
|
||||
buf->js_document = w3m_dom_create_document(buf);
|
||||
if (buf->js_document) {
|
||||
w3m_js_bind_dom_objects(state->js_ctx, buf);
|
||||
}
|
||||
|
||||
/* Initialize event system */
|
||||
buf->js_events = (struct W3MEventSystem *)w3m_events_create_system();
|
||||
if (buf->js_events) {
|
||||
w3m_events_bind_to_js(state->js_ctx, (W3MEventSystem *)buf->js_events);
|
||||
}
|
||||
|
||||
return state;
|
||||
}
|
||||
|
||||
void
|
||||
w3m_js_cleanup_buffer_state(BufferJSState *state)
|
||||
{
|
||||
if (!state) return;
|
||||
|
||||
/* Free pending scripts */
|
||||
if (state->pending_scripts) {
|
||||
for (int i = 0; i < state->pending_count; i++) {
|
||||
if (state->pending_scripts[i]) {
|
||||
GC_free(state->pending_scripts[i]);
|
||||
}
|
||||
}
|
||||
GC_free(state->pending_scripts);
|
||||
}
|
||||
|
||||
/* Free script objects */
|
||||
if (state->script_objects) {
|
||||
for (int i = 0; i < state->script_count; i++) {
|
||||
if (!JS_IsNull(state->script_objects[i])) {
|
||||
JS_FreeValue(state->js_ctx->context, state->script_objects[i]);
|
||||
}
|
||||
}
|
||||
GC_free(state->script_objects);
|
||||
}
|
||||
|
||||
/* Destroy JavaScript context */
|
||||
w3m_js_destroy_context(state->js_ctx);
|
||||
|
||||
GC_free(state);
|
||||
}
|
||||
|
||||
void
|
||||
w3m_js_add_pending_script(BufferJSState *state, const char *script)
|
||||
{
|
||||
if (!state || !script) return;
|
||||
|
||||
/* Expand array if needed */
|
||||
if (state->pending_count == 0) {
|
||||
state->pending_scripts = GC_MALLOC(sizeof(char*) * 10);
|
||||
if (!state->pending_scripts) return;
|
||||
} else if (state->pending_count % 10 == 0) {
|
||||
char **new_scripts = GC_MALLOC(sizeof(char*) * (state->pending_count + 10));
|
||||
if (!new_scripts) return;
|
||||
memcpy(new_scripts, state->pending_scripts, sizeof(char*) * state->pending_count);
|
||||
GC_free(state->pending_scripts);
|
||||
state->pending_scripts = new_scripts;
|
||||
}
|
||||
|
||||
/* Copy script content */
|
||||
int len = strlen(script);
|
||||
state->pending_scripts[state->pending_count] = GC_MALLOC(len + 1);
|
||||
if (state->pending_scripts[state->pending_count]) {
|
||||
strcpy(state->pending_scripts[state->pending_count], script);
|
||||
state->pending_count++;
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
w3m_js_execute_pending_scripts(BufferJSState *state)
|
||||
{
|
||||
if (!state || !state->scripts_enabled) return;
|
||||
|
||||
for (int i = 0; i < state->pending_count; i++) {
|
||||
if (state->pending_scripts[i]) {
|
||||
w3m_js_execute_script(state->js_ctx, state->pending_scripts[i], NULL);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* DOM Integration */
|
||||
|
||||
void
|
||||
w3m_js_bind_dom_objects(W3MJSContext *ctx, Buffer *buf)
|
||||
{
|
||||
if (!ctx || !buf || !buf->js_document) return;
|
||||
|
||||
/* This will be implemented in w3m_dom.c */
|
||||
w3m_dom_bind_to_js(ctx, buf->js_document);
|
||||
}
|
||||
|
||||
/* Utility Functions */
|
||||
|
||||
char *
|
||||
w3m_js_value_to_string(W3MJSContext *ctx, JSValue val)
|
||||
{
|
||||
if (!ctx) return NULL;
|
||||
|
||||
const char *str = JS_ToCString(ctx->context, val);
|
||||
if (!str) return NULL;
|
||||
|
||||
int len = strlen(str);
|
||||
char *result = GC_MALLOC(len + 1);
|
||||
if (result) {
|
||||
strcpy(result, str);
|
||||
}
|
||||
|
||||
JS_FreeCString(ctx->context, str);
|
||||
return result;
|
||||
}
|
||||
|
||||
void
|
||||
w3m_js_report_error(W3MJSContext *ctx, const char *msg)
|
||||
{
|
||||
/* For now, just ignore errors silently */
|
||||
/* In future, could log to status line or debug output */
|
||||
(void)ctx;
|
||||
(void)msg;
|
||||
}
|
||||
|
||||
#endif /* USE_JAVASCRIPT */
|
||||
Reference in New Issue
Block a user