Major improvements to JavaScript engine integration: - Fixed script concatenation: JavaScript now executes as complete scripts instead of line-by-line, resolving syntax errors with multi-line constructs - Enhanced document.write() integration: Content now injects directly into HTML parsing buffer and displays in browser instead of stderr only - Added comprehensive DOM element methods: All elements now have addEventListener, removeEventListener, and appendChild methods - Implemented document.body fallback: Created functional document.body object when DOM body element not available - Fixed HTML body element creation: Added proper DOM body element creation during HTML_BODY tag processing - Improved HTML environment context passing: JavaScript execution now receives HTML parsing context for proper content injection - Resolved all major JavaScript errors: Fixed "unexpected end of string", "expecting semicolon", "not a function", and "appendChild undefined" errors JavaScript integration is now fully functional with error-free execution of complex scripts including DOM manipulation, event listeners, form handling, and dynamic content generation. 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
331 lines
9.1 KiB
C
331 lines
9.1 KiB
C
/*
|
|
* 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 = 1; /* Enable JavaScript by default for Phase 2 testing */
|
|
int w3m_js_timeout = 5000; /* 5 second timeout */
|
|
int w3m_js_memory_limit = 8388608; /* 8MB memory limit */
|
|
int w3m_js_network_enabled = 1;
|
|
|
|
/* Debug console.log implementation */
|
|
static JSValue
|
|
w3m_js_console_log(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv)
|
|
{
|
|
if (argc > 0) {
|
|
const char *str = JS_ToCString(ctx, argv[0]);
|
|
if (str) {
|
|
/* For now, just add to debug - later we might display this somewhere */
|
|
fprintf(stderr, "JS: %s\n", str);
|
|
JS_FreeCString(ctx, str);
|
|
}
|
|
}
|
|
return JS_UNDEFINED;
|
|
}
|
|
|
|
/* 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);
|
|
|
|
/* Add basic console.log support for debugging */
|
|
JSValue console = JS_NewObject(ctx->context);
|
|
JS_SetPropertyStr(ctx->context, console, "log",
|
|
JS_NewCFunction(ctx->context, w3m_js_console_log, "log", 1));
|
|
JS_SetPropertyStr(ctx->context, ctx->global_obj, "console", console);
|
|
|
|
/* 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, void *html_env)
|
|
{
|
|
if (!state || !state->scripts_enabled || state->pending_count == 0) return;
|
|
|
|
/* Concatenate all script lines into one complete script */
|
|
int total_len = 0;
|
|
for (int i = 0; i < state->pending_count; i++) {
|
|
if (state->pending_scripts[i]) {
|
|
total_len += strlen(state->pending_scripts[i]) + 1; /* +1 for newline */
|
|
}
|
|
}
|
|
|
|
if (total_len == 0) return;
|
|
|
|
char *complete_script = GC_MALLOC(total_len + 1);
|
|
if (!complete_script) return;
|
|
|
|
complete_script[0] = '\0';
|
|
for (int i = 0; i < state->pending_count; i++) {
|
|
if (state->pending_scripts[i]) {
|
|
strcat(complete_script, state->pending_scripts[i]);
|
|
strcat(complete_script, "\n");
|
|
}
|
|
}
|
|
|
|
/* Store HTML environment for document.write() access */
|
|
if (html_env) {
|
|
JSValue html_env_val = JS_NewBigUint64(state->js_ctx->context, (uintptr_t)html_env);
|
|
JS_SetPropertyStr(state->js_ctx->context, state->js_ctx->global_obj, "_w3m_html_env", html_env_val);
|
|
}
|
|
|
|
/* Execute the complete script once */
|
|
w3m_js_execute_script(state->js_ctx, complete_script, NULL);
|
|
|
|
/* Clean up HTML environment reference */
|
|
if (html_env) {
|
|
JS_SetPropertyStr(state->js_ctx->context, state->js_ctx->global_obj, "_w3m_html_env", JS_NULL);
|
|
}
|
|
|
|
/* Clean up pending scripts */
|
|
for (int i = 0; i < state->pending_count; i++) {
|
|
if (state->pending_scripts[i]) {
|
|
GC_free(state->pending_scripts[i]);
|
|
state->pending_scripts[i] = NULL;
|
|
}
|
|
}
|
|
state->pending_count = 0;
|
|
|
|
GC_free(complete_script);
|
|
}
|
|
|
|
/* 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)
|
|
{
|
|
/* Report JavaScript errors to stderr for debugging */
|
|
if (msg) {
|
|
fprintf(stderr, "JavaScript Error: %s\n", msg);
|
|
}
|
|
(void)ctx;
|
|
}
|
|
|
|
#endif /* USE_JAVASCRIPT */ |