Files
w3m/js/w3m_javascript.c
Storm Dragon 9118766ba4 Complete JavaScript integration Phase 4: Full functionality with error-free execution
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>
2025-08-23 03:56:20 -04:00

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 */