Javascript phase 2 finished.

This commit is contained in:
Storm Dragon
2025-08-17 09:34:38 -04:00
parent 6cf0975fe1
commit 5738cf9132
6 changed files with 842 additions and 20 deletions

View File

@@ -243,14 +243,46 @@ w3m_dom_set_attribute(W3MElement *elem, const char *name, const char *value)
elem->attributes.count++;
}
/* JavaScript Binding (Stubs for Phase 1) */
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 basic document object */
/* Create document object */
JSValue document = JS_NewObject(ctx->context);
/* Set basic properties */
@@ -258,6 +290,24 @@ w3m_dom_bind_to_js(W3MJSContext *ctx, W3MDocument *doc)
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);
@@ -266,27 +316,645 @@ w3m_dom_bind_to_js(W3MJSContext *ctx, W3MDocument *doc)
doc->js_document = document;
}
/* JavaScript API Stubs */
/* 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)
{
/* Phase 1: Return null for now */
return JS_NULL;
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)
{
/* Phase 1: Return empty array */
return JS_NewArray(ctx);
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)
{
/* Phase 1: Return empty object */
return JS_NewObject(ctx);
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() stub - Phase 2 implementation
* This prevents JavaScript errors when pages call document.write()
* The actual implementation will be in Phase 3 */
JSValue
js_document_write(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv)
{
if (argc < 1) return JS_UNDEFINED;
/* For Phase 2, we silently ignore document.write() calls
* This prevents JavaScript errors while maintaining compatibility
* Phase 3 will implement actual DOM insertion */
/* Optional: Log what would have been written for debugging */
const char *content = JS_ToCString(ctx, argv[0]);
if (content) {
/* Phase 2: Stub implementation - content is discarded
* Phase 3 will insert this content into the DOM */
JS_FreeCString(ctx, content);
}
return JS_UNDEFINED;
}
#endif /* USE_JAVASCRIPT */