/* * w3m DOM Implementation * * Basic DOM implementation for JavaScript integration. * This provides minimal DOM functionality for Phase 1. */ #include "fm.h" #ifdef USE_JAVASCRIPT #include "w3m_dom.h" #include "w3m_javascript.h" #include #include /* Forward declarations */ static W3MElement *w3m_dom_find_anchor_element_recursive(W3MElement *elem, Anchor *anchor); /* DOM Document Management */ W3MDocument * w3m_dom_create_document(Buffer *buf) { if (!buf) return NULL; W3MDocument *doc = GC_MALLOC(sizeof(W3MDocument)); if (!doc) return NULL; doc->buffer = buf; doc->documentElement = NULL; doc->body = NULL; doc->head = NULL; /* Set document properties */ Str url_str = parsedURL2Str(&buf->currentURL); if (url_str && url_str->ptr) { int len = strlen(url_str->ptr); doc->URL = GC_MALLOC(len + 1); if (doc->URL) { strcpy(doc->URL, url_str->ptr); } } else { doc->URL = NULL; } doc->title = NULL; doc->domain = NULL; /* Initialize element collections */ doc->all_elements = NULL; doc->element_count = 0; doc->element_capacity = 0; doc->js_document = JS_NULL; return doc; } void w3m_dom_destroy_document(W3MDocument *doc) { if (!doc) return; /* Free document properties */ if (doc->URL) GC_free(doc->URL); if (doc->title) GC_free(doc->title); if (doc->domain) GC_free(doc->domain); /* Free element collections */ if (doc->all_elements) { for (int i = 0; i < doc->element_count; i++) { w3m_dom_destroy_element(doc->all_elements[i]); } GC_free(doc->all_elements); } /* Free JavaScript object reference */ if (doc->buffer && doc->buffer->js_state) { /* JavaScript object cleanup will be handled elsewhere */ /* For Phase 1, we'll keep this simple */ } GC_free(doc); } /* Element Management */ W3MElement * w3m_dom_create_element(const char *tagName) { if (!tagName) return NULL; W3MElement *elem = GC_MALLOC(sizeof(W3MElement)); if (!elem) return NULL; /* Copy tag name to uppercase */ int len = strlen(tagName); elem->tagName = GC_MALLOC(len + 1); if (elem->tagName) { for (int i = 0; i <= len; i++) { elem->tagName[i] = (tagName[i] >= 'a' && tagName[i] <= 'z') ? tagName[i] - 'a' + 'A' : tagName[i]; } } elem->id = NULL; elem->className = NULL; /* Initialize tree structure */ elem->parent = NULL; elem->firstChild = NULL; elem->lastChild = NULL; elem->nextSibling = NULL; elem->previousSibling = NULL; /* Initialize w3m mappings */ elem->line = NULL; elem->line_pos = 0; elem->anchor = NULL; elem->form_item = NULL; /* Initialize attributes */ elem->attributes.names = NULL; elem->attributes.values = NULL; elem->attributes.count = 0; elem->attributes.capacity = 0; elem->textContent = NULL; elem->innerHTML = NULL; /* Initialize JavaScript object */ elem->js_object = JS_NULL; elem->js_object_valid = 0; return elem; } void w3m_dom_destroy_element(W3MElement *elem) { if (!elem) return; /* Free string properties */ if (elem->tagName) GC_free(elem->tagName); if (elem->id) GC_free(elem->id); if (elem->className) GC_free(elem->className); if (elem->textContent) GC_free(elem->textContent); if (elem->innerHTML) GC_free(elem->innerHTML); /* Free attributes */ if (elem->attributes.names) { for (int i = 0; i < elem->attributes.count; i++) { if (elem->attributes.names[i]) GC_free(elem->attributes.names[i]); if (elem->attributes.values[i]) GC_free(elem->attributes.values[i]); } GC_free(elem->attributes.names); GC_free(elem->attributes.values); } GC_free(elem); } /* Attribute Management */ const char * w3m_dom_get_attribute(W3MElement *elem, const char *name) { if (!elem || !name) return NULL; for (int i = 0; i < elem->attributes.count; i++) { if (elem->attributes.names[i] && strcasecmp(elem->attributes.names[i], name) == 0) { return elem->attributes.values[i]; } } return NULL; } void w3m_dom_set_attribute(W3MElement *elem, const char *name, const char *value) { if (!elem || !name) return; /* Check if attribute already exists */ for (int i = 0; i < elem->attributes.count; i++) { if (elem->attributes.names[i] && strcasecmp(elem->attributes.names[i], name) == 0) { /* Update existing attribute */ if (elem->attributes.values[i]) GC_free(elem->attributes.values[i]); if (value) { int len = strlen(value); elem->attributes.values[i] = GC_MALLOC(len + 1); if (elem->attributes.values[i]) { strcpy(elem->attributes.values[i], value); } } else { elem->attributes.values[i] = NULL; } return; } } /* Add new attribute */ if (elem->attributes.count >= elem->attributes.capacity) { /* Expand arrays */ int new_capacity = elem->attributes.capacity + 10; char **new_names = GC_MALLOC(sizeof(char*) * new_capacity); char **new_values = GC_MALLOC(sizeof(char*) * new_capacity); if (!new_names || !new_values) return; if (elem->attributes.names) { memcpy(new_names, elem->attributes.names, sizeof(char*) * elem->attributes.count); memcpy(new_values, elem->attributes.values, sizeof(char*) * elem->attributes.count); GC_free(elem->attributes.names); GC_free(elem->attributes.values); } elem->attributes.names = new_names; elem->attributes.values = new_values; elem->attributes.capacity = new_capacity; } /* Copy name */ int name_len = strlen(name); elem->attributes.names[elem->attributes.count] = GC_MALLOC(name_len + 1); if (elem->attributes.names[elem->attributes.count]) { strcpy(elem->attributes.names[elem->attributes.count], name); } /* Copy value */ if (value) { int value_len = strlen(value); elem->attributes.values[elem->attributes.count] = GC_MALLOC(value_len + 1); if (elem->attributes.values[elem->attributes.count]) { strcpy(elem->attributes.values[elem->attributes.count], value); } } else { elem->attributes.values[elem->attributes.count] = NULL; } elem->attributes.count++; } void w3m_dom_remove_attribute(W3MElement *elem, const char *name) { if (!elem || !name) return; for (int i = 0; i < elem->attributes.count; i++) { if (elem->attributes.names[i] && strcasecmp(elem->attributes.names[i], name) == 0) { /* Free the attribute */ if (elem->attributes.names[i]) GC_free(elem->attributes.names[i]); if (elem->attributes.values[i]) GC_free(elem->attributes.values[i]); /* Shift remaining attributes */ for (int j = i; j < elem->attributes.count - 1; j++) { elem->attributes.names[j] = elem->attributes.names[j + 1]; elem->attributes.values[j] = elem->attributes.values[j + 1]; } elem->attributes.count--; return; } } } int w3m_dom_has_attribute(W3MElement *elem, const char *name) { if (!elem || !name) return 0; return (w3m_dom_get_attribute(elem, name) != NULL); } /* JavaScript Binding - Phase 2 Implementation */ void w3m_dom_bind_to_js(W3MJSContext *ctx, W3MDocument *doc) { if (!ctx || !doc) return; /* Create document object */ JSValue document = JS_NewObject(ctx->context); /* Set basic properties */ if (doc->URL) { JS_SetPropertyStr(ctx->context, document, "URL", JS_NewString(ctx->context, doc->URL)); } if (doc->title) { JS_SetPropertyStr(ctx->context, document, "title", JS_NewString(ctx->context, doc->title)); } /* Bind DOM methods to document object */ JS_SetPropertyStr(ctx->context, document, "getElementById", JS_NewCFunction(ctx->context, js_getElementById, "getElementById", 1)); JS_SetPropertyStr(ctx->context, document, "getElementsByTagName", JS_NewCFunction(ctx->context, js_getElementsByTagName, "getElementsByTagName", 1)); JS_SetPropertyStr(ctx->context, document, "createElement", JS_NewCFunction(ctx->context, js_createElement, "createElement", 1)); JS_SetPropertyStr(ctx->context, document, "write", JS_NewCFunction(ctx->context, js_document_write, "write", 1)); /* Store document reference in context for function access */ JS_SetPropertyStr(ctx->context, ctx->global_obj, "_w3m_document_ptr", JS_NewInt64(ctx->context, (int64_t)(uintptr_t)doc)); /* Bind to global object */ JS_SetPropertyStr(ctx->context, ctx->global_obj, "document", document); ctx->document_obj = document; doc->js_document = document; } /* DOM Tree Operations */ void w3m_dom_append_child(W3MElement *parent, W3MElement *child) { if (!parent || !child) return; child->parent = parent; if (!parent->firstChild) { parent->firstChild = child; parent->lastChild = child; child->previousSibling = NULL; child->nextSibling = NULL; } else { child->previousSibling = parent->lastChild; child->nextSibling = NULL; parent->lastChild->nextSibling = child; parent->lastChild = child; } } void w3m_dom_remove_child(W3MElement *parent, W3MElement *child) { if (!parent || !child || child->parent != parent) return; if (child->previousSibling) { child->previousSibling->nextSibling = child->nextSibling; } else { parent->firstChild = child->nextSibling; } if (child->nextSibling) { child->nextSibling->previousSibling = child->previousSibling; } else { parent->lastChild = child->previousSibling; } child->parent = NULL; child->previousSibling = NULL; child->nextSibling = NULL; } void w3m_dom_insert_before(W3MElement *parent, W3MElement *newChild, W3MElement *referenceChild) { if (!parent || !newChild) return; if (!referenceChild) { w3m_dom_append_child(parent, newChild); return; } if (referenceChild->parent != parent) return; newChild->parent = parent; newChild->nextSibling = referenceChild; newChild->previousSibling = referenceChild->previousSibling; if (referenceChild->previousSibling) { referenceChild->previousSibling->nextSibling = newChild; } else { parent->firstChild = newChild; } referenceChild->previousSibling = newChild; } /* Element Search and Access */ W3MElement * w3m_dom_get_element_by_id(W3MDocument *doc, const char *id) { if (!doc || !id) return NULL; for (int i = 0; i < doc->element_count; i++) { W3MElement *elem = doc->all_elements[i]; if (elem && elem->id && strcmp(elem->id, id) == 0) { return elem; } } return NULL; } W3MElement ** w3m_dom_get_elements_by_tag_name(W3MDocument *doc, const char *tagName, int *count) { if (!doc || !tagName || !count) { if (count) *count = 0; return NULL; } /* First pass: count matching elements */ int match_count = 0; for (int i = 0; i < doc->element_count; i++) { W3MElement *elem = doc->all_elements[i]; if (elem && elem->tagName && (strcasecmp(tagName, "*") == 0 || strcasecmp(elem->tagName, tagName) == 0)) { match_count++; } } if (match_count == 0) { *count = 0; return NULL; } /* Second pass: collect matching elements */ W3MElement **result = GC_MALLOC(sizeof(W3MElement*) * match_count); if (!result) { *count = 0; return NULL; } int result_index = 0; for (int i = 0; i < doc->element_count; i++) { W3MElement *elem = doc->all_elements[i]; if (elem && elem->tagName && (strcasecmp(tagName, "*") == 0 || strcasecmp(elem->tagName, tagName) == 0)) { result[result_index++] = elem; } } *count = match_count; return result; } W3MElement ** w3m_dom_get_elements_by_class_name(W3MDocument *doc, const char *className, int *count) { if (!doc || !className || !count) { if (count) *count = 0; return NULL; } /* Simplified implementation - exact class name match only */ int match_count = 0; for (int i = 0; i < doc->element_count; i++) { W3MElement *elem = doc->all_elements[i]; if (elem && elem->className && strcmp(elem->className, className) == 0) { match_count++; } } if (match_count == 0) { *count = 0; return NULL; } W3MElement **result = GC_MALLOC(sizeof(W3MElement*) * match_count); if (!result) { *count = 0; return NULL; } int result_index = 0; for (int i = 0; i < doc->element_count; i++) { W3MElement *elem = doc->all_elements[i]; if (elem && elem->className && strcmp(elem->className, className) == 0) { result[result_index++] = elem; } } *count = match_count; return result; } /* Content Access and Modification */ char * w3m_dom_get_text_content(W3MElement *elem) { if (!elem) return NULL; if (elem->textContent) { int len = strlen(elem->textContent); char *result = GC_MALLOC(len + 1); if (result) { strcpy(result, elem->textContent); } return result; } return NULL; } void w3m_dom_set_text_content(W3MElement *elem, const char *text) { if (!elem) return; if (elem->textContent) { GC_free(elem->textContent); } if (text) { int len = strlen(text); elem->textContent = GC_MALLOC(len + 1); if (elem->textContent) { strcpy(elem->textContent, text); } } else { elem->textContent = NULL; } } char * w3m_dom_get_inner_html(W3MElement *elem) { if (!elem) return NULL; /* Basic implementation - just return innerHTML if set */ if (elem->innerHTML) { int len = strlen(elem->innerHTML); char *result = GC_MALLOC(len + 1); if (result) { strcpy(result, elem->innerHTML); } return result; } return NULL; } void w3m_dom_set_inner_html(W3MElement *elem, const char *html) { if (!elem) return; if (elem->innerHTML) { GC_free(elem->innerHTML); } if (html) { int len = strlen(html); elem->innerHTML = GC_MALLOC(len + 1); if (elem->innerHTML) { strcpy(elem->innerHTML, html); } } else { elem->innerHTML = NULL; } } /* Buffer Integration - Phase 2 Implementation */ void set_element_buffer_position(W3MElement *elem, Buffer *buf) { if (!elem || !buf) return; /* Set current line reference if buffer has content */ if (buf->currentLine) { elem->line = (struct Line *)buf->currentLine; elem->line_pos = 0; /* TODO: implement proper column tracking */ } } void add_element_to_document(W3MDocument *doc, W3MElement *elem) { if (!doc || !elem) return; /* Expand array if needed */ if (doc->element_count >= doc->element_capacity) { int new_capacity = doc->element_capacity + 50; W3MElement **new_array = GC_MALLOC(sizeof(W3MElement*) * new_capacity); if (!new_array) return; if (doc->all_elements) { memcpy(new_array, doc->all_elements, sizeof(W3MElement*) * doc->element_count); GC_free(doc->all_elements); } doc->all_elements = new_array; doc->element_capacity = new_capacity; } doc->all_elements[doc->element_count++] = elem; } void w3m_dom_build_from_buffer(W3MDocument *doc, Buffer *buf) { if (!doc || !buf) return; /* Create basic document structure */ W3MElement *html_elem = w3m_dom_create_element("HTML"); W3MElement *head_elem = w3m_dom_create_element("HEAD"); W3MElement *body_elem = w3m_dom_create_element("BODY"); if (!html_elem || !head_elem || !body_elem) return; doc->documentElement = html_elem; doc->head = head_elem; doc->body = body_elem; add_element_to_document(doc, html_elem); add_element_to_document(doc, head_elem); add_element_to_document(doc, body_elem); w3m_dom_append_child(html_elem, head_elem); w3m_dom_append_child(html_elem, body_elem); /* Set document title from buffer */ if (buf->buffername) { int len = strlen(buf->buffername); doc->title = GC_MALLOC(len + 1); if (doc->title) { strcpy(doc->title, buf->buffername); } } /* TODO Phase 2: Parse buffer lines and create DOM elements * This is a complex process that will require: * 1. Walking through buffer lines * 2. Identifying HTML structure from anchors and form items * 3. Creating appropriate DOM elements * 4. Building proper parent-child relationships */ } void w3m_dom_update_buffer(W3MDocument *doc) { if (!doc || !doc->buffer) return; /* TODO Phase 2: Update buffer content from DOM changes * This is complex and will require: * 1. Regenerating buffer lines from DOM structure * 2. Updating anchors and form items * 3. Maintaining scroll position and cursor */ } W3MElement * w3m_dom_find_element_at_position(W3MDocument *doc, int line_num, int col) { if (!doc) return NULL; /* Search for element at specific buffer position */ for (int i = 0; i < doc->element_count; i++) { W3MElement *elem = doc->all_elements[i]; if (elem && elem->line) { /* TODO: Implement proper position matching */ /* This requires understanding buffer line structure */ } } return NULL; } /* Forward declarations */ static JSValue create_js_element_object(JSContext *ctx, W3MElement *elem); /* Helper function to get document from JavaScript context */ static W3MDocument * get_document_from_context(JSContext *ctx) { JSValue doc_ptr = JS_GetPropertyStr(ctx, JS_GetGlobalObject(ctx), "_w3m_document_ptr"); if (JS_IsNumber(doc_ptr)) { int64_t ptr_val; if (JS_ToInt64(ctx, &ptr_val, doc_ptr) == 0) { JS_FreeValue(ctx, doc_ptr); return (W3MDocument*)(uintptr_t)ptr_val; } } JS_FreeValue(ctx, doc_ptr); return NULL; } /* JavaScript API Implementation - Phase 2 */ JSValue js_getElementById(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) { if (argc < 1) return JS_NULL; const char *id = JS_ToCString(ctx, argv[0]); if (!id) return JS_NULL; /* Get document from context */ W3MDocument *doc = get_document_from_context(ctx); if (!doc) { JS_FreeCString(ctx, id); return JS_NULL; } /* Find element by ID */ W3MElement *elem = w3m_dom_get_element_by_id(doc, id); JS_FreeCString(ctx, id); if (!elem) return JS_NULL; /* Create complete JavaScript element object */ return create_js_element_object(ctx, elem); } JSValue js_getElementsByTagName(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) { if (argc < 1) return JS_NewArray(ctx); const char *tagName = JS_ToCString(ctx, argv[0]); if (!tagName) return JS_NewArray(ctx); /* Get document from context */ W3MDocument *doc = get_document_from_context(ctx); if (!doc) { JS_FreeCString(ctx, tagName); return JS_NewArray(ctx); } /* Find elements by tag name */ int count = 0; W3MElement **elements = w3m_dom_get_elements_by_tag_name(doc, tagName, &count); JS_FreeCString(ctx, tagName); /* Create JavaScript array */ JSValue js_array = JS_NewArray(ctx); if (elements && count > 0) { for (int i = 0; i < count; i++) { W3MElement *elem = elements[i]; if (elem) { /* Create complete JavaScript element object */ JSValue js_elem = create_js_element_object(ctx, elem); /* Add to array */ JS_SetPropertyUint32(ctx, js_array, i, js_elem); } } /* Set array length */ JS_SetPropertyStr(ctx, js_array, "length", JS_NewInt32(ctx, count)); /* Free elements array (elements themselves are managed by GC) */ GC_free(elements); } return js_array; } JSValue js_createElement(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) { if (argc < 1) return JS_NewObject(ctx); const char *tagName = JS_ToCString(ctx, argv[0]); if (!tagName) return JS_NewObject(ctx); /* Create actual DOM element */ W3MElement *elem = w3m_dom_create_element(tagName); if (!elem) { JS_FreeCString(ctx, tagName); return JS_NULL; } /* Get document to add element to collection */ W3MDocument *doc = get_document_from_context(ctx); if (doc) { add_element_to_document(doc, elem); } /* Create complete JavaScript element object */ JS_FreeCString(ctx, tagName); return create_js_element_object(ctx, elem); } /* Helper function to create a complete JavaScript element object */ static JSValue create_js_element_object(JSContext *ctx, W3MElement *elem) { if (!elem) return JS_NULL; JSValue js_elem = JS_NewObject(ctx); /* Set basic properties */ if (elem->tagName) { JS_SetPropertyStr(ctx, js_elem, "tagName", JS_NewString(ctx, elem->tagName)); } if (elem->id) { JS_SetPropertyStr(ctx, js_elem, "id", JS_NewString(ctx, elem->id)); } if (elem->className) { JS_SetPropertyStr(ctx, js_elem, "className", JS_NewString(ctx, elem->className)); } /* Store element reference for method calls */ JS_SetPropertyStr(ctx, js_elem, "_w3m_element_ptr", JS_NewInt64(ctx, (int64_t)(uintptr_t)elem)); /* Add DOM methods */ JS_SetPropertyStr(ctx, js_elem, "getAttribute", JS_NewCFunction(ctx, js_element_getAttribute, "getAttribute", 1)); JS_SetPropertyStr(ctx, js_elem, "setAttribute", JS_NewCFunction(ctx, js_element_setAttribute, "setAttribute", 2)); /* Add property accessors */ JSValue textContent = elem->textContent ? JS_NewString(ctx, elem->textContent) : JS_NewString(ctx, ""); JS_SetPropertyStr(ctx, js_elem, "textContent", textContent); JSValue innerHTML = elem->innerHTML ? JS_NewString(ctx, elem->innerHTML) : JS_NewString(ctx, ""); JS_SetPropertyStr(ctx, js_elem, "innerHTML", innerHTML); return js_elem; } /* Element-level JavaScript API functions */ static W3MElement * get_element_from_js_value(JSContext *ctx, JSValueConst this_val) { JSValue elem_ptr = JS_GetPropertyStr(ctx, this_val, "_w3m_element_ptr"); if (JS_IsNumber(elem_ptr)) { int64_t ptr_val; if (JS_ToInt64(ctx, &ptr_val, elem_ptr) == 0) { JS_FreeValue(ctx, elem_ptr); return (W3MElement*)(uintptr_t)ptr_val; } } JS_FreeValue(ctx, elem_ptr); return NULL; } JSValue js_element_getAttribute(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) { if (argc < 1) return JS_NULL; W3MElement *elem = get_element_from_js_value(ctx, this_val); if (!elem) return JS_NULL; const char *attr_name = JS_ToCString(ctx, argv[0]); if (!attr_name) return JS_NULL; const char *attr_value = w3m_dom_get_attribute(elem, attr_name); JS_FreeCString(ctx, attr_name); if (attr_value) { return JS_NewString(ctx, attr_value); } else { return JS_NULL; } } JSValue js_element_setAttribute(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) { if (argc < 2) return JS_UNDEFINED; W3MElement *elem = get_element_from_js_value(ctx, this_val); if (!elem) return JS_UNDEFINED; const char *attr_name = JS_ToCString(ctx, argv[0]); const char *attr_value = JS_ToCString(ctx, argv[1]); if (attr_name && attr_value) { w3m_dom_set_attribute(elem, attr_name, attr_value); /* Update special attributes */ if (strcasecmp(attr_name, "id") == 0) { if (elem->id) GC_free(elem->id); int len = strlen(attr_value); elem->id = GC_MALLOC(len + 1); if (elem->id) strcpy(elem->id, attr_value); } else if (strcasecmp(attr_name, "class") == 0) { if (elem->className) GC_free(elem->className); int len = strlen(attr_value); elem->className = GC_MALLOC(len + 1); if (elem->className) strcpy(elem->className, attr_value); } } if (attr_name) JS_FreeCString(ctx, attr_name); if (attr_value) JS_FreeCString(ctx, attr_value); return JS_UNDEFINED; } JSValue js_element_get_textContent(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) { W3MElement *elem = get_element_from_js_value(ctx, this_val); if (!elem) return JS_NULL; char *text = w3m_dom_get_text_content(elem); if (text) { JSValue result = JS_NewString(ctx, text); GC_free(text); return result; } return JS_NewString(ctx, ""); } JSValue js_element_set_textContent(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) { if (argc < 1) return JS_UNDEFINED; W3MElement *elem = get_element_from_js_value(ctx, this_val); if (!elem) return JS_UNDEFINED; const char *text = JS_ToCString(ctx, argv[0]); if (text) { w3m_dom_set_text_content(elem, text); JS_FreeCString(ctx, text); } return JS_UNDEFINED; } /* Document.write() implementation - Enhanced for midpoint review * This creates a basic DOM element and appends it to the body */ JSValue js_document_write(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) { if (argc < 1) return JS_UNDEFINED; /* Get the content to write */ const char *content = JS_ToCString(ctx, argv[0]); if (!content) return JS_UNDEFINED; /* Get the document from the context */ JSValue doc_ptr_val = JS_GetPropertyStr(ctx, JS_GetGlobalObject(ctx), "_w3m_document_ptr"); if (JS_IsNumber(doc_ptr_val)) { int64_t ptr_val; JS_ToInt64(ctx, &ptr_val, doc_ptr_val); W3MDocument *doc = (W3MDocument *)(uintptr_t)ptr_val; if (doc && doc->body) { /* Create a new text/content element */ W3MElement *content_elem = w3m_dom_create_element("SPAN"); if (content_elem) { /* Set the content as text */ w3m_dom_set_text_content(content_elem, content); /* Append to body */ w3m_dom_append_child(doc->body, content_elem); /* Add to document's element list */ add_element_to_document(doc, content_elem); } } } JS_FreeValue(ctx, doc_ptr_val); JS_FreeCString(ctx, content); return JS_UNDEFINED; } /* Element-JavaScript conversion functions (Phase 3 stubs) */ JSValue w3m_dom_element_to_js(W3MJSContext *ctx, W3MElement *elem) { if (!ctx || !elem) return JS_NULL; /* Phase 3 stub: Return a basic JavaScript object * Full implementation will create proper Element objects with methods */ JSValue obj = JS_NewObject(ctx->context); JS_SetPropertyStr(ctx->context, obj, "tagName", JS_NewString(ctx->context, elem->tagName ? elem->tagName : "unknown")); return obj; } W3MElement * w3m_dom_js_to_element(W3MJSContext *ctx, JSValue val) { if (!ctx) return NULL; /* Extract W3MElement pointer from JavaScript object */ JSValue elem_ptr = JS_GetPropertyStr(ctx->context, val, "_w3m_element_ptr"); if (JS_IsNumber(elem_ptr)) { int64_t ptr_val; if (JS_ToInt64(ctx->context, &ptr_val, elem_ptr) == 0) { JS_FreeValue(ctx->context, elem_ptr); return (W3MElement*)(uintptr_t)ptr_val; } } JS_FreeValue(ctx->context, elem_ptr); return NULL; } W3MElement * w3m_dom_find_anchor_element(W3MDocument *doc, Anchor *anchor) { if (!doc || !anchor) return NULL; /* Search for anchor element that matches the w3m Anchor */ W3MElement *elem = doc->body; /* Start from body */ while (elem) { if (elem->tagName && strcasecmp(elem->tagName, "A") == 0) { /* Check if this element could match the anchor */ /* For a more sophisticated match, we could compare href attributes */ if (!elem->anchor) { /* This anchor element hasn't been linked yet */ return elem; } } /* Search children */ if (elem->firstChild) { W3MElement *found = w3m_dom_find_anchor_element_recursive(elem->firstChild, anchor); if (found) return found; } elem = elem->nextSibling; } return NULL; } static W3MElement * w3m_dom_find_anchor_element_recursive(W3MElement *elem, Anchor *anchor) { while (elem) { if (elem->tagName && strcasecmp(elem->tagName, "A") == 0) { if (!elem->anchor) { return elem; } } /* Search children */ if (elem->firstChild) { W3MElement *found = w3m_dom_find_anchor_element_recursive(elem->firstChild, anchor); if (found) return found; } elem = elem->nextSibling; } return NULL; } #endif /* USE_JAVASCRIPT */