/* * w3m Event System Implementation * * Basic event system for JavaScript integration. * This provides minimal event functionality for Phase 1. */ #include "fm.h" #ifdef USE_JAVASCRIPT #include "w3m_events.h" #include "w3m_javascript.h" #include "w3m_dom.h" #include #include /* Event System Management */ W3MEventSystem * w3m_events_create_system(void) { W3MEventSystem *system = GC_MALLOC(sizeof(W3MEventSystem)); if (!system) return NULL; /* Initialize listener arrays */ for (int i = 0; i < W3M_EVENT_TYPE_COUNT; i++) { system->listeners[i] = NULL; } /* Initialize event queue */ system->event_queue = NULL; system->queue_size = 0; system->queue_capacity = 0; system->processing_events = 0; return system; } void w3m_events_destroy_system(W3MEventSystem *system) { if (!system) return; /* Free event listeners */ for (int i = 0; i < W3M_EVENT_TYPE_COUNT; i++) { W3MEventListener *listener = system->listeners[i]; while (listener) { W3MEventListener *next = listener->next; if (!JS_IsNull(listener->callback)) { /* Note: We can't free the callback here as we don't have context */ } GC_free(listener); listener = next; } } /* Free event queue */ if (system->event_queue) { for (int i = 0; i < system->queue_size; i++) { w3m_events_destroy_event(system->event_queue[i]); } GC_free(system->event_queue); } GC_free(system); } /* Event Type Utilities */ W3MEventType w3m_events_string_to_type(const char *type_string) { if (!type_string) return W3M_EVENT_TYPE_COUNT; if (strcasecmp(type_string, "click") == 0) return W3M_EVENT_CLICK; if (strcasecmp(type_string, "submit") == 0) return W3M_EVENT_SUBMIT; if (strcasecmp(type_string, "load") == 0) return W3M_EVENT_LOAD; if (strcasecmp(type_string, "unload") == 0) return W3M_EVENT_UNLOAD; if (strcasecmp(type_string, "focus") == 0) return W3M_EVENT_FOCUS; if (strcasecmp(type_string, "blur") == 0) return W3M_EVENT_BLUR; if (strcasecmp(type_string, "change") == 0) return W3M_EVENT_CHANGE; if (strcasecmp(type_string, "keypress") == 0) return W3M_EVENT_KEYPRESS; if (strcasecmp(type_string, "keydown") == 0) return W3M_EVENT_KEYDOWN; if (strcasecmp(type_string, "keyup") == 0) return W3M_EVENT_KEYUP; if (strcasecmp(type_string, "mouseover") == 0) return W3M_EVENT_MOUSEOVER; if (strcasecmp(type_string, "mouseout") == 0) return W3M_EVENT_MOUSEOUT; return W3M_EVENT_TYPE_COUNT; } const char * w3m_events_type_to_string(W3MEventType type) { switch (type) { case W3M_EVENT_CLICK: return "click"; case W3M_EVENT_SUBMIT: return "submit"; case W3M_EVENT_LOAD: return "load"; case W3M_EVENT_UNLOAD: return "unload"; case W3M_EVENT_FOCUS: return "focus"; case W3M_EVENT_BLUR: return "blur"; case W3M_EVENT_CHANGE: return "change"; case W3M_EVENT_KEYPRESS: return "keypress"; case W3M_EVENT_KEYDOWN: return "keydown"; case W3M_EVENT_KEYUP: return "keyup"; case W3M_EVENT_MOUSEOVER: return "mouseover"; case W3M_EVENT_MOUSEOUT: return "mouseout"; default: return "unknown"; } } /* Event Management - Functions moved to after Event Creation and Dispatch section */ /* Event Listener Management */ void w3m_events_add_listener(W3MEventSystem *system, W3MElement *target, const char *type, JSValue callback, int use_capture) { if (!system || !target || !type) return; W3MEventType event_type = w3m_events_string_to_type(type); if (event_type >= W3M_EVENT_TYPE_COUNT) return; W3MEventListener *listener = GC_MALLOC(sizeof(W3MEventListener)); if (!listener) return; listener->type = event_type; listener->callback = callback; listener->target = target; listener->use_capture = use_capture; /* Add to linked list */ listener->next = system->listeners[event_type]; system->listeners[event_type] = listener; } int w3m_events_has_listener(W3MEventSystem *system, W3MElement *target, const char *type) { if (!system || !target || !type) return 0; W3MEventType event_type = w3m_events_string_to_type(type); if (event_type >= W3M_EVENT_TYPE_COUNT) return 0; W3MEventListener *listener = system->listeners[event_type]; while (listener) { if (listener->target == target) { return 1; } listener = listener->next; } return 0; } /* Event Creation and Dispatch */ W3MEvent * w3m_events_create_event(W3MEventType type, W3MElement *target) { W3MEvent *event = GC_MALLOC(sizeof(W3MEvent)); if (!event) return NULL; event->type = type; event->type_string = w3m_events_type_to_string(type); event->target = target; event->currentTarget = target; /* Set default properties */ event->bubbles = 1; event->cancelable = 1; event->defaultPrevented = 0; event->propagationStopped = 0; /* Initialize data union */ memset(&event->data, 0, sizeof(event->data)); /* JavaScript object will be created when needed */ event->js_event = JS_NULL; return event; } void w3m_events_destroy_event(W3MEvent *event) { if (!event) return; /* Free JavaScript object if it exists */ if (!JS_IsNull(event->js_event)) { /* Note: Cannot free JSValue without context */ } GC_free(event); } int w3m_events_dispatch_event(W3MEventSystem *system, W3MJSContext *ctx, W3MEvent *event) { if (!system || !ctx || !event || !event->target) return 0; W3MEventType event_type = event->type; if (event_type >= W3M_EVENT_TYPE_COUNT) return 0; int handled = 0; /* Find listeners for this event type and target */ W3MEventListener *listener = system->listeners[event_type]; while (listener) { if (listener->target == event->target) { /* Call the JavaScript callback */ if (!JS_IsNull(listener->callback) && JS_IsFunction(ctx->context, listener->callback)) { /* Create JavaScript event object if needed */ if (JS_IsNull(event->js_event)) { event->js_event = w3m_events_event_to_js(ctx, event); } /* Call the callback */ JSValue result = JS_Call(ctx->context, listener->callback, JS_UNDEFINED, 1, &event->js_event); /* Check for exceptions */ if (JS_IsException(result)) { /* Log error but continue */ JS_FreeValue(ctx->context, result); } else { JS_FreeValue(ctx->context, result); handled = 1; } /* Check if propagation was stopped */ if (event->propagationStopped) break; } } listener = listener->next; } return handled; } /* Integration with w3m Input System */ int w3m_events_handle_click(Buffer *buf, Anchor *anchor) { if (!buf || !anchor) return 0; #ifdef USE_JAVASCRIPT /* Check if anchor has an associated DOM element */ if (!anchor->element) return 0; /* Get JavaScript context and event system */ BufferJSState *js_state = (BufferJSState *)buf->js_state; if (!js_state || !js_state->ctx || !js_state->event_system) return 0; /* Check if there are click listeners for this element */ if (!w3m_events_has_listener(js_state->event_system, anchor->element, "click")) { return 0; /* No listeners, continue normal processing */ } /* Create click event */ W3MEvent *event = w3m_events_create_event(W3M_EVENT_CLICK, anchor->element); if (!event) return 0; /* Set mouse event data */ event->data.mouse.button = 1; /* Left button */ event->data.mouse.clientX = 0; /* TODO: Get from mouse position */ event->data.mouse.clientY = 0; /* Dispatch the event */ int handled = w3m_events_dispatch_event(js_state->event_system, js_state->ctx, event); /* Check if default action was prevented */ int prevent_default = event->defaultPrevented; /* Cleanup */ w3m_events_destroy_event(event); /* Return 1 to prevent default action, 0 to continue */ return prevent_default; #else return 0; #endif } int w3m_events_handle_form_submit(Buffer *buf, FormList *form) { if (!buf || !form) return 0; /* Get JavaScript context and event system */ BufferJSState *js_state = (BufferJSState *)buf->js_state; if (!js_state || !js_state->ctx || !js_state->event_system) return 0; /* Find the form element in our DOM */ if (js_state->dom_document && js_state->dom_document->documentElement) { /* TODO: Search for form element that matches this FormList */ /* For now, we'll skip form event handling until we have better DOM integration */ return 0; } return 0; } int w3m_events_handle_key_press(Buffer *buf, int key) { /* Phase 1: Always return 0 to continue normal processing */ return 0; } void w3m_events_handle_page_load(Buffer *buf) { /* Phase 1: Execute pending scripts */ if (buf && buf->js_state) { w3m_js_execute_pending_scripts((BufferJSState *)buf->js_state); } } void w3m_events_handle_page_unload(Buffer *buf) { /* Phase 1: No action needed */ } /* JavaScript Event API Implementation */ void w3m_events_bind_to_js(W3MJSContext *ctx, W3MEventSystem *system) { if (!ctx || !system) return; JSContext *js_ctx = ctx->context; /* Get the Element prototype */ JSValue global_obj = JS_GetGlobalObject(js_ctx); JSValue element_proto = JS_GetPropertyStr(js_ctx, global_obj, "Element"); if (JS_IsUndefined(element_proto)) { JS_FreeValue(js_ctx, element_proto); element_proto = JS_GetPropertyStr(js_ctx, global_obj, "HTMLElement"); } if (!JS_IsUndefined(element_proto)) { JSValue proto = JS_GetPropertyStr(js_ctx, element_proto, "prototype"); if (!JS_IsUndefined(proto)) { /* Bind addEventListener method */ JS_SetPropertyStr(js_ctx, proto, "addEventListener", JS_NewCFunction(js_ctx, js_addEventListener, "addEventListener", 3)); /* Bind removeEventListener method */ JS_SetPropertyStr(js_ctx, proto, "removeEventListener", JS_NewCFunction(js_ctx, js_removeEventListener, "removeEventListener", 3)); /* Bind dispatchEvent method */ JS_SetPropertyStr(js_ctx, proto, "dispatchEvent", JS_NewCFunction(js_ctx, js_dispatchEvent, "dispatchEvent", 1)); } JS_FreeValue(js_ctx, proto); } JS_FreeValue(js_ctx, element_proto); /* Store event system reference in context for use by functions */ ctx->event_system = system; /* Also store in global object for JavaScript functions to access */ JSValue system_ref = JS_NewObjectClass(js_ctx, 0); JS_SetOpaque(system_ref, system); JS_SetPropertyStr(js_ctx, global_obj, "__w3m_event_system__", system_ref); JS_FreeValue(js_ctx, global_obj); } JSValue js_addEventListener(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) { if (argc < 2) return JS_UNDEFINED; /* Get event type */ const char *event_type = JS_ToCString(ctx, argv[0]); if (!event_type) return JS_UNDEFINED; /* Get callback function */ JSValue callback = argv[1]; if (!JS_IsFunction(ctx, callback)) { JS_FreeCString(ctx, event_type); return JS_UNDEFINED; } /* Get use_capture flag (optional) */ int use_capture = 0; if (argc >= 3) { use_capture = JS_ToBool(ctx, argv[2]); } /* Get the W3MElement from this_val using same pattern as DOM functions */ JSValue elem_ptr = JS_GetPropertyStr(ctx, this_val, "_w3m_element_ptr"); W3MElement *element = NULL; if (JS_IsNumber(elem_ptr)) { int64_t ptr_val; if (JS_ToInt64(ctx, &ptr_val, elem_ptr) == 0) { element = (W3MElement*)(uintptr_t)ptr_val; } } JS_FreeValue(ctx, elem_ptr); if (!element) { JS_FreeCString(ctx, event_type); /* No valid element found */ return JS_UNDEFINED; } /* Get event system from context */ W3MEventSystem *event_system = NULL; /* Find the W3MJSContext from the JSContext */ /* This is a bit of a hack - we should store this mapping better */ JSValue global_obj = JS_GetGlobalObject(ctx); JSValue w3m_internal = JS_GetPropertyStr(ctx, global_obj, "__w3m_event_system__"); if (!JS_IsUndefined(w3m_internal)) { event_system = (W3MEventSystem *)JS_GetOpaque(w3m_internal, 0); } JS_FreeValue(ctx, w3m_internal); JS_FreeValue(ctx, global_obj); if (event_system) { /* Duplicate the callback to prevent garbage collection */ JSValue callback_dup = JS_DupValue(ctx, callback); /* Add the event listener */ w3m_events_add_listener(event_system, element, event_type, callback_dup, use_capture); } JS_FreeCString(ctx, event_type); return JS_UNDEFINED; } JSValue js_removeEventListener(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) { /* TODO: Implement removeEventListener */ return JS_UNDEFINED; } JSValue js_dispatchEvent(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) { /* TODO: Implement dispatchEvent */ return JS_UNDEFINED; } /* Event Object JavaScript API */ JSValue js_event_preventDefault(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) { W3MEvent *event = (W3MEvent *)JS_GetOpaque(this_val, 0); if (event && event->cancelable) { event->defaultPrevented = 1; } return JS_UNDEFINED; } JSValue js_event_stopPropagation(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) { W3MEvent *event = (W3MEvent *)JS_GetOpaque(this_val, 0); if (event) { event->propagationStopped = 1; } return JS_UNDEFINED; } JSValue js_event_get_target(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) { W3MEvent *event = (W3MEvent *)JS_GetOpaque(this_val, 0); if (event && event->target) { /* Create a basic JavaScript object representing the target element */ JSValue target_obj = JS_NewObject(ctx); JS_SetPropertyStr(ctx, target_obj, "tagName", JS_NewString(ctx, event->target->tagName ? event->target->tagName : "unknown")); if (event->target->id) { JS_SetPropertyStr(ctx, target_obj, "id", JS_NewString(ctx, event->target->id)); } return target_obj; } return JS_NULL; } JSValue js_event_get_type(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) { W3MEvent *event = (W3MEvent *)JS_GetOpaque(this_val, 0); if (event && event->type_string) { return JS_NewString(ctx, event->type_string); } return JS_NULL; } /* Helper Functions */ JSValue w3m_events_event_to_js(W3MJSContext *ctx, W3MEvent *event) { if (!ctx || !event) return JS_NULL; JSContext *js_ctx = ctx->context; /* Create JavaScript event object */ JSValue event_obj = JS_NewObject(js_ctx); /* Set basic properties */ JS_SetPropertyStr(js_ctx, event_obj, "type", JS_NewString(js_ctx, event->type_string)); JS_SetPropertyStr(js_ctx, event_obj, "bubbles", JS_NewBool(js_ctx, event->bubbles)); JS_SetPropertyStr(js_ctx, event_obj, "cancelable", JS_NewBool(js_ctx, event->cancelable)); JS_SetPropertyStr(js_ctx, event_obj, "defaultPrevented", JS_NewBool(js_ctx, event->defaultPrevented)); /* Set target (convert W3MElement to JavaScript) */ if (event->target) { JSValue target = w3m_dom_element_to_js(ctx, event->target); JS_SetPropertyStr(js_ctx, event_obj, "target", target); JS_SetPropertyStr(js_ctx, event_obj, "currentTarget", target); /* Same for now */ } /* Set event-specific data */ switch (event->type) { case W3M_EVENT_CLICK: JS_SetPropertyStr(js_ctx, event_obj, "button", JS_NewInt32(js_ctx, event->data.mouse.button)); JS_SetPropertyStr(js_ctx, event_obj, "clientX", JS_NewInt32(js_ctx, event->data.mouse.clientX)); JS_SetPropertyStr(js_ctx, event_obj, "clientY", JS_NewInt32(js_ctx, event->data.mouse.clientY)); break; case W3M_EVENT_KEYPRESS: case W3M_EVENT_KEYDOWN: case W3M_EVENT_KEYUP: JS_SetPropertyStr(js_ctx, event_obj, "keyCode", JS_NewInt32(js_ctx, event->data.keyboard.keyCode)); JS_SetPropertyStr(js_ctx, event_obj, "charCode", JS_NewInt32(js_ctx, event->data.keyboard.charCode)); JS_SetPropertyStr(js_ctx, event_obj, "ctrlKey", JS_NewBool(js_ctx, event->data.keyboard.ctrlKey)); JS_SetPropertyStr(js_ctx, event_obj, "altKey", JS_NewBool(js_ctx, event->data.keyboard.altKey)); JS_SetPropertyStr(js_ctx, event_obj, "shiftKey", JS_NewBool(js_ctx, event->data.keyboard.shiftKey)); break; default: break; } /* Bind event methods */ JS_SetPropertyStr(js_ctx, event_obj, "preventDefault", JS_NewCFunction(js_ctx, js_event_preventDefault, "preventDefault", 0)); JS_SetPropertyStr(js_ctx, event_obj, "stopPropagation", JS_NewCFunction(js_ctx, js_event_stopPropagation, "stopPropagation", 0)); /* Store a back-reference to the W3MEvent for method calls */ JS_SetOpaque(event_obj, event); return event_obj; } W3MEvent * w3m_events_js_to_event(W3MJSContext *ctx, JSValue val) { /* Get the W3MEvent from the JavaScript object's opaque data */ return (W3MEvent *)JS_GetOpaque(val, 0); } #endif /* USE_JAVASCRIPT */