Milán Major

netscape-revival

1 branch
Code

lib/libmocha/lm_form.c

/*
** Mocha reflection of the HTML FORM elements.
**
** Brendan Eich, 9/27/95
*/
#include "lm.h"
#include "xp.h"
#include "fe_proto.h"
#include "layout.h"	/* XXX for lo_FormData only */
#include "lo_ele.h"
#include "mkutils.h"	/* XXX for NET_URL_Type() etc. in form_submit() */

enum form_array_slot {
    FORM_ARRAY_LENGTH = -1
};

static MochaPropertySpec form_array_props[] = {
    {lm_length_str,     FORM_ARRAY_LENGTH,      MDF_ENUMERATE | MDF_READONLY},
    {0}
};

typedef struct MochaFormArray {
    MochaDecoder        *decoder;
    uint                length;
} MochaFormArray;

static MochaBoolean
form_array_get_property(MochaContext *mc, MochaObject *obj, MochaSlot slot,
			MochaDatum *dp)
{
    MochaFormArray *array;
    MochaDecoder *decoder;
    MWContext *context;
    uint count;
    lo_FormData *form_data;

    array = obj->data;
    decoder = array->decoder;
    context = decoder->window_context;
    if (!context) return MOCHA_TRUE;

    switch (slot) {
      case FORM_ARRAY_LENGTH:
	count = LO_EnumerateForms(context);
	if (count > array->length)
	    array->length = count;
	MOCHA_INIT_DATUM(mc, dp, MOCHA_NUMBER, u.fval, array->length);
	break;

      default:
	if (slot < 0) {
	    /* Don't mess with user-defined or method properties. */
	    return MOCHA_TRUE;
	}
	if ((uint)slot >= array->length)
	    array->length = slot + 1;
	/* NB: form IDs start at 1, not 0. */
	form_data = LO_GetFormDataByID(context, slot + 1);
	if (form_data) {
	    MOCHA_INIT_DATUM(mc, dp, MOCHA_OBJECT, u.obj,
			     LM_ReflectForm(context, form_data));
	}
	break;
    }
    return MOCHA_TRUE;
}

static void
form_array_finalize(MochaContext *mc, MochaObject *obj)
{
    MochaFormArray *array = obj->data;

    if (!array) return;
    LM_UNLINK_OBJECT(array->decoder, obj);
    MOCHA_free(mc, array);
}

static MochaClass form_array_class = {
    "FormArray",
    form_array_get_property, form_array_get_property, MOCHA_ListPropStub,
    MOCHA_ResolveStub, MOCHA_ConvertStub, form_array_finalize
};

static MochaBoolean
FormArray(MochaContext *mc, MochaObject *obj,
	  uint argc, MochaDatum *argv, MochaDatum *rval)
{
    return MOCHA_TRUE;
}

MochaObject *
lm_GetFormArray(MochaDecoder *decoder)
{
    MochaObject *obj, *prototype;
    MochaContext *mc;
    MochaFormArray *array;

    obj = decoder->forms;
    if (obj)
	return obj;
    mc = decoder->mocha_context;
    prototype = MOCHA_InitClass(mc, decoder->window_object, &form_array_class,
				0, FormArray, 0, form_array_props, 0, 0, 0);
    if (!prototype)
	return 0;

    array = MOCHA_malloc(mc, sizeof *array);
    if (!array)
	return 0;
    XP_BZERO(array, sizeof *array);
    array->decoder = decoder;

    obj = MOCHA_NewObject(mc, &form_array_class, array, prototype,
			  decoder->document, 0, 0);
    if (!obj) {
	MOCHA_free(mc, array);
	return 0;
    }
    LM_LINK_OBJECT(decoder, obj, "forms");
    decoder->forms = MOCHA_HoldObject(mc, obj);
    return obj;
}

/*
** Forms can be treated as arrays of their elements, so all named properties
** have negative slot numbers < -1.
*/
enum form_slot {
    FORM_LENGTH         = -1,
    FORM_NAME           = -2,
    FORM_ELEMENTS       = -3,
    FORM_METHOD         = -4,
    FORM_ACTION         = -5,
    FORM_ENCODING       = -6,
    FORM_TARGET         = -7
};

static char form_action_str[] = "action";

static MochaPropertySpec form_props[] = {
    {"length",          FORM_LENGTH,    MDF_ENUMERATE | MDF_READONLY},
    {"name",            FORM_NAME,      MDF_ENUMERATE},
    {"elements",        FORM_ELEMENTS,  MDF_BACKEDGE | MDF_ENUMERATE |
					MDF_READONLY},
    {"method",          FORM_METHOD,    MDF_ENUMERATE},
    {form_action_str,   FORM_ACTION,    MDF_ENUMERATE|MDF_TAINTED},
    {"encoding",        FORM_ENCODING,  MDF_ENUMERATE},
    {"target",          FORM_TARGET,    MDF_ENUMERATE},
    {0}
};

typedef struct MochaForm {
    MochaInputHandler   handler;
    intn                form_id;
    uint                length;
    MochaAtom           *name;
} MochaForm;

#define form_decoder	handler.base_decoder
#define form_type       handler.base_type
#define form_object	handler.object
#define form_event_mask	handler.event_mask

typedef struct FormMethodMap {
    char                *name;
    uint                code;
} FormMethodMap;

static FormMethodMap form_method_map[] = {
    {"get",             FORM_METHOD_GET},
    {"post",            FORM_METHOD_POST},
    {0}
};

static char *
form_method_name(uint code)
{
    FormMethodMap *mm;

    for (mm = form_method_map; mm->name; mm++)
	if (mm->code == code)
	    return mm->name;
    return "unknown";
}

static int
form_method_code(const char *name)
{
    FormMethodMap *mm;

    for (mm = form_method_map; mm->name; mm++)
	if (XP_STRCASECMP(mm->name, name) == 0)
	    return mm->code;
    return -1;
}

static MochaBoolean
form_get_property(MochaContext *mc, MochaObject *obj, MochaSlot slot,
		  MochaDatum *dp)
{
    MochaForm *form;
    MWContext *context;
    lo_FormData *form_data;
    uint count;
    MochaAtom *atom;
    LO_Element **ele_list;
    LO_FormElementStruct *form_ele;

    form = obj->data;
    context = form->form_decoder->window_context;
    if (!context) return MOCHA_TRUE;
    form_data = LO_GetFormDataByID(context, form->form_id);
    if (!form_data)
	return MOCHA_TRUE;

    switch (slot) {
      case FORM_LENGTH:
	count = LO_EnumerateFormElements(context, form_data);
	if (count > form->length)
	    form->length = count;
	MOCHA_INIT_DATUM(mc, dp, MOCHA_NUMBER, u.fval, form->length);
	return MOCHA_TRUE;

      case FORM_NAME:
	atom = MOCHA_Atomize(mc, (char *)form_data->name);
	break;

      case FORM_ELEMENTS:
	MOCHA_INIT_DATUM(mc, dp, MOCHA_OBJECT, u.obj,
			 form->form_object);
	return MOCHA_TRUE;

      case FORM_METHOD:
	atom = MOCHA_Atomize(mc, form_method_name(form_data->method));
	break;

      case FORM_ACTION:
	atom = MOCHA_Atomize(mc, (char *)form_data->action);
	break;

      case FORM_ENCODING:
	atom = MOCHA_Atomize(mc, (char *)form_data->encoding);
	break;

      case FORM_TARGET:
	atom = MOCHA_Atomize(mc, (char *)form_data->window_target);
	break;

      default:
	if ((uint)slot >= (uint)form_data->form_ele_cnt) {
	    /* Don't mess with a user-defined or method property. */
	    return MOCHA_TRUE;
	}

	PA_LOCK(ele_list, LO_Element **, form_data->form_elements);
	form_ele = (LO_FormElementStruct *)ele_list[slot];
	if (form_ele) {
	    MOCHA_INIT_DATUM(mc, dp, MOCHA_OBJECT, u.obj,
			     LM_ReflectFormElement(context, form_ele));
	} else {
	    *dp = MOCHA_null;
	}
	PA_UNLOCK(form_data->form_elements);
	return MOCHA_TRUE;
    }

    /* Common tail code for string-type properties. */
    if (!atom)
	return MOCHA_FALSE;
    MOCHA_INIT_DATUM(mc, dp, MOCHA_STRING, u.atom, atom);
    if ((dp->flags & MDF_TAINTED) && dp->taint == MOCHA_TAINT_IDENTITY)
	return lm_GetTaint(mc, form->form_decoder, &dp->taint);
    return MOCHA_TRUE;
}

static MochaBoolean
form_set_property(MochaContext *mc, MochaObject *obj, MochaSlot slot,
		  MochaDatum *dp)
{
    MochaForm *form;
    MWContext *context;
    lo_FormData *form_data;
    const char *value;

    if (slot < FORM_TARGET || 0 <= slot) {
	/* Don't mess with a user-defined property. */
	return MOCHA_TRUE;
    }

    form = obj->data;
    context = form->form_decoder->window_context;
    if (!context) return MOCHA_TRUE;
    form_data = LO_GetFormDataByID(context, form->form_id);
    if (!form_data)
	return MOCHA_TRUE;

    if (dp->tag != MOCHA_STRING &&
	!MOCHA_ConvertDatum(mc, *dp, MOCHA_STRING, dp)) {
	return MOCHA_FALSE;
    }

    value = MOCHA_GetAtomName(mc, dp->u.atom);
    switch (slot) {
      case FORM_METHOD:
	form_data->method = form_method_code(value);
	break;

      case FORM_ACTION:
	value = lm_CheckURL(mc, value);
	if (!value)
	    return MOCHA_FALSE;
	if (!lm_SaveParamString(mc, &form_data->action, value))
	    return MOCHA_FALSE;
	XP_FREE((char *)value);
	break;

      case FORM_ENCODING:
	if (!lm_SaveParamString(mc, &form_data->encoding, value))
	    return MOCHA_FALSE;
	break;

      case FORM_TARGET:
	if (!lm_CheckWindowName(mc, value))
	    return MOCHA_FALSE;
	if (!lm_SaveParamString(mc, &form_data->window_target, value))
	    return MOCHA_FALSE;
	break;
    }

    return form_get_property(mc, obj, slot, dp);
}

static MochaBoolean
form_list_properties(MochaContext *mc, MochaObject *obj)
{
    MochaForm *form;
    MWContext *context;
    lo_FormData *form_data;

    form = obj->data;
    context = form->form_decoder->window_context;
    if (!context)
	return MOCHA_TRUE;
    form_data = LO_GetFormDataByID(context, form->form_id);
    if (!form_data)
	return MOCHA_TRUE;
    /* XXX should return -1 on reflection error */
    (void) LO_EnumerateFormElements(context, form_data);
    return MOCHA_TRUE;
}

static MochaBoolean
form_resolve_name(MochaContext *mc, MochaObject *obj, const char *name)
{
    MochaForm *form;
    MWContext *context;
    lo_FormData *form_data;
    LO_Element **ele_list;
    LO_FormElementStruct *form_ele;
    lo_FormElementMinimalData *min_data;
    int32 i;

    form = obj->data;
    context = form->form_decoder->window_context;
    if (!context)
	return MOCHA_TRUE;
    form_data = LO_GetFormDataByID(context, form->form_id);
    if (!form_data)
	return MOCHA_TRUE;

    PA_LOCK(ele_list, LO_Element **, form_data->form_elements);
    for (i = 0; i < form_data->form_ele_cnt; i++) {
	if (ele_list[i]->type != LO_FORM_ELE)
	    continue;
	form_ele = (LO_FormElementStruct *)ele_list[i];
	if (!form_ele->element_data)
	    continue;
	min_data = &form_ele->element_data->ele_minimal;
	if (min_data->name && XP_STRCMP((char *)min_data->name, name) == 0)
	    (void) LM_ReflectFormElement(context, form_ele);
    }
    PA_UNLOCK(form_data->form_elements);
    return MOCHA_TRUE;
}

static void
form_finalize(MochaContext *mc, MochaObject *obj)
{
    MochaForm *form;

    form = obj->data;
    if (!form) return;
    LM_UNLINK_OBJECT(form->form_decoder, obj);
    MOCHA_free(mc, form);
}

static MochaClass form_class = {
    "Form",
    form_get_property, form_set_property, form_list_properties,
    form_resolve_name, MOCHA_ConvertStub, form_finalize
};

static MochaBoolean
Form(MochaContext *mc, MochaObject *obj,
     uint argc, MochaDatum *argv, MochaDatum *rval)
{
    return MOCHA_TRUE;
}

static MochaBoolean
form_native_prolog(MochaContext *mc, MochaObject *obj, MochaDatum *argv,
		   MochaForm **formp, lo_FormData **form_datap)
{
    MochaForm *form;
    MochaDecoder *decoder;
    lo_FormData *form_data;

    if (!MOCHA_InstanceOf(mc, obj, &form_class, argv[-1].u.fun))
        return MOCHA_FALSE;
    form = obj->data;
    decoder = form->form_decoder;
    form_data = decoder->window_context
		? LO_GetFormDataByID(decoder->window_context, form->form_id)
		: 0;
    *formp = form;
    *form_datap = form_data;
    return MOCHA_TRUE;
}

static MochaBoolean
form_reset(MochaContext *mc, MochaObject *obj,
	   uint argc, MochaDatum *argv, MochaDatum *rval)
{
    MochaForm *form;
    lo_FormData *form_data;
    LO_Element **ele_list;

    if (!form_native_prolog(mc, obj, argv, &form, &form_data))
        return MOCHA_FALSE;
    if (form_data && form_data->form_ele_cnt > 0) {
	/* There is no form LO_Element; use the first thing in the form. */
	PA_LOCK(ele_list, LO_Element **, form_data->form_elements);
	LO_ResetForm(form->form_decoder->window_context,
		     (LO_FormElementStruct *)ele_list[0]);
	PA_UNLOCK(form_data->form_elements);
    }
    return MOCHA_TRUE;
}

static MochaBoolean
form_check_taint(MochaContext *mc, lo_FormData *form_data)
{
    MochaObject *obj;
    MochaDatum datum;
    const char *action;
    MochaDecoder *decoder;
    uint16 accum, taint;
    LO_Element **ele_list;
    int32 i;
    MochaBoolean ok;

    obj = form_data->mocha_object;
    if (obj) {
	/* Check the action property only if it was set by Mocha. */
	if (!MOCHA_LookupName(mc, obj, form_action_str, &datum))
	    return MOCHA_FALSE;
	XP_ASSERT(datum.tag == MOCHA_STRING);
	action = MOCHA_GetAtomName(mc, datum.u.atom);
	if (!lm_CheckTaint(mc, datum.taint, "form.action property", action))
	    return MOCHA_FALSE;
    } else {
	PA_LOCK(action, const char *, form_data->action);
    }

    decoder = MOCHA_GetGlobalObject(mc)->data;
    accum = decoder->write_taint_accum;
    PA_LOCK(ele_list, LO_Element **, form_data->form_elements);
    for (i = 0; i < form_data->form_ele_cnt; i++) {
	taint = lm_GetFormElementTaint(mc, (LO_FormElementStruct *)ele_list[i]);
	MOCHA_MIX_TAINT(mc, accum, taint);
    }
    PA_UNLOCK(form_data->form_elements);
    ok = action ? lm_CheckTaint(mc, accum, "form submission to", action)
		: MOCHA_TRUE;
    if (!obj)
	PA_UNLOCK(form_data->action);
    return ok;
}

static MochaBoolean
form_submit(MochaContext *mc, MochaObject *obj,
	    uint argc, MochaDatum *argv, MochaDatum *rval)
{
    MochaForm *form;
    lo_FormData *form_data;
    const char *action;
    MochaBoolean ok;
    MochaDecoder *decoder;
    MWContext *context;
    LO_Element **ele_list, *element;
    LO_FormSubmitData *submit;
    URL_Struct *url_struct;
    History_entry *he;

    if (!form_native_prolog(mc, obj, argv, &form, &form_data))
        return MOCHA_FALSE;
    if (!form_data)
        return MOCHA_TRUE;

    PA_LOCK(action, const char *, form_data->action);
    switch (NET_URL_Type(action)) {
      case MAILTO_TYPE_URL:
      case NEWS_TYPE_URL:
	ok = MOCHA_FALSE;
	break;
      default:
	ok = MOCHA_TRUE;
	break;
    }
    PA_UNLOCK(form_data->action);
    if (!ok) {
	/* XXX silently fail this mailto: or news: form post. */
	return MOCHA_TRUE;
    }

    if (!form_check_taint(mc, form_data))
	return MOCHA_FALSE;

    if (form_data->form_ele_cnt > 0) {
	decoder = form->form_decoder;
	context = decoder->window_context;
	PA_LOCK(ele_list, LO_Element **, form_data->form_elements);
	element = ele_list[0];
	PA_UNLOCK(form_data->form_elements);

/* XXX begin FE_SubmitInputElement, but use lm_GetURL to avoid reentrancy */
	submit = LO_SubmitForm(context, (LO_FormElementStruct *) element);
	if (!submit) {
	    MOCHA_ReportOutOfMemory(mc);
	    return MOCHA_FALSE;
	}

	/* Create the URL to load. */
	url_struct = NET_CreateURLStruct((char *)submit->action,
					 NET_DONT_RELOAD);
	if (!url_struct) {
	    MOCHA_ReportOutOfMemory(mc);
	    ok = MOCHA_FALSE;
	} else {
	    /* Add the form data. */
	    NET_AddLOSubmitDataToURLStruct(submit, url_struct);

	    /* Set the referrer field if there is one. */
	    he = SHIST_GetCurrent(&context->hist);
	    if (he && he->address) {
		url_struct->referer = MOCHA_strdup(mc, he->address);
		if (!url_struct->referer)
		    ok = MOCHA_FALSE;
	    }

	    /* Get the URL to submit the form. */
	    if (ok) {
		ok = lm_GetURL(mc, decoder, url_struct,
			       MOCHA_TAINT_IDENTITY);
	    }
	}
	LO_FreeSubmitData(submit);
/* XXX end FE_SubmitInputElement */
    }
    return ok;
}

static MochaFunctionSpec form_methods[] = {
    {"reset",	form_reset,	0},
    {"submit",	form_submit,	0},
    {0}
};

static MochaBoolean
form_event(MWContext *context, LO_Element *element, uint32 event, char *method,
	   lo_FormData **form_datap)
{
    lo_FormData *form_data;
    MochaObject *obj;
    MochaForm *form;
    MochaContext *mc;
    MochaBoolean ok;
    MochaDatum rval;

    if (element->type != LO_FORM_ELE &&
	(element->type != LO_IMAGE || !element->lo_image.image_attr)) {
	*form_datap = 0;
	return MOCHA_TRUE;
    }
    form_data = LO_GetFormDataByID(context,
				   (element->type == LO_IMAGE)
				   ? element->lo_image.image_attr->form_id
				   : element->lo_form.form_id);
    *form_datap = form_data;
    if (!form_data || !(obj = form_data->mocha_object))
	return MOCHA_TRUE;
    form = obj->data;
    if (form->form_event_mask & event)
	return MOCHA_TRUE;

    mc = form->form_decoder->mocha_context;
    MOCHA_HoldObject(mc, obj);
    form->form_event_mask |= event;
    ok = lm_SendEvent(context, obj, method, &rval);
    form->form_event_mask &= ~event;
    MOCHA_DropObject(mc, obj);
    if (ok && rval.tag == MOCHA_BOOLEAN && rval.u.bval == MOCHA_FALSE)
	return MOCHA_FALSE;
    return MOCHA_TRUE;
}

MochaBoolean
LM_SendOnReset(MWContext *context, LO_Element *element)
{
    lo_FormData *form_data;
    MochaDecoder *decoder;
    LO_Element **ele_list;
    int32 i;

    if (!context->mocha_context)
	return MOCHA_TRUE;
    if (!form_event(context, element, EVENT_RESET, lm_onReset_str, &form_data))
	return MOCHA_FALSE;
    if (form_data) {
	decoder = LM_GetMochaDecoder(context);
	if (!decoder)
	    return MOCHA_FALSE;
	PA_LOCK(ele_list, LO_Element **, form_data->form_elements);
	for (i = 0; i < form_data->form_ele_cnt; i++) {
	    lm_ResetFormElementTaint(decoder->mocha_context,
				     (LO_FormElementStruct *)ele_list[i]);
	}
	PA_UNLOCK(form_data->form_elements);
	LM_PutMochaDecoder(decoder);
    }
    return MOCHA_TRUE;
}

MochaBoolean
LM_SendOnSubmit(MWContext *context, LO_Element *element)
{
    lo_FormData *form_data;
    MochaContext *mc;

    if (!form_event(context, element, EVENT_SUBMIT, lm_onSubmit_str,
		    &form_data)) {
	return MOCHA_FALSE;
    }
    if (!form_data)
	return MOCHA_TRUE;

    mc = lm_running_context;
    if (!mc) {
	mc = context->mocha_context;
	if (!mc)
	    return MOCHA_TRUE;
    }
    return form_check_taint(mc, form_data);
}

#ifdef DEBUG_brendan
static char *
form_name(lo_FormData *form_data)
{
    static char buf[20];

    if (form_data->name)
	return (char *)form_data->name;
    PR_snprintf(buf, sizeof buf, "$form%d", form_data->id - 1);
    return buf;
}
#endif

MochaObject *
LM_ReflectForm(MWContext *context, lo_FormData *form_data)
{
    MochaObject *obj, *array_obj, *prototype;
    MochaDecoder *decoder;
    MochaContext *mc;
    MochaFormArray *array;
    MochaForm *form;
    char *name;
    MochaBoolean ok;
    MochaDatum datum;

    obj = form_data->mocha_object;
    if (obj)
	return obj;

    decoder = LM_GetMochaDecoder(context);
    if (!decoder)
	return 0;
    array_obj = lm_GetFormArray(decoder);
    if (!array_obj) {
	LM_PutMochaDecoder(decoder);
	return 0;
    }
    array = array_obj->data;

    mc = decoder->mocha_context;
    prototype = decoder->form_prototype;
    if (!prototype) {
	prototype =  MOCHA_InitClass(mc, decoder->window_object, &form_class,
				     0, Form, 0, form_props, form_methods,
				     0, 0);
	if (!prototype) {
	    LM_PutMochaDecoder(decoder);
	    return 0;
	}
	decoder->form_prototype = MOCHA_HoldObject(mc, prototype);
    }

    form = MOCHA_malloc(mc, sizeof *form);
    if (!form) {
	LM_PutMochaDecoder(decoder);
	return 0;
    }
    XP_BZERO(form, sizeof *form);
    form->form_decoder = decoder;
    form->form_type = FORM_TYPE_NONE;

    obj = MOCHA_NewObject(mc, &form_class, form, prototype,
			  decoder->document, 0, 0);
    if (!obj) {
	MOCHA_free(mc, form);
	LM_PutMochaDecoder(decoder);
	return 0;
    }
    if (form_data->name) {
	PA_LOCK(name, char *, form_data->name);
	ok = MOCHA_DefineObject(mc, decoder->document, name, obj,
				MDF_ENUMERATE | MDF_READONLY);
	PA_UNLOCK(form_data->name);
	if (!ok) {
	    MOCHA_DestroyObject(mc, obj);
	    LM_PutMochaDecoder(decoder);
	    return 0;
	}
    }

    if (form_data->name) {
	PA_LOCK(name, char *, form_data->name);
    } else {
	name = 0;
    }
    MOCHA_INIT_FULL_DATUM(mc, &datum, MOCHA_OBJECT, MDF_ENUMERATE|MDF_READONLY,
			  MOCHA_TAINT_IDENTITY, u.obj, obj);
    MOCHA_SetProperty(mc, decoder->forms, name, form_data->id - 1, datum);
    if (form_data->name)
	PA_UNLOCK(form_data->name);
    if ((uint)form_data->id > array->length)
	array->length = form_data->id;

    form->form_object = obj;
    form->form_id = form_data->id;
    form_data->mocha_object = obj;

    LM_LINK_OBJECT(decoder, obj, form_name(form_data));
    LM_PutMochaDecoder(decoder);
    return obj;
}

MochaObject *
lm_GetFormObjectByID(MochaDecoder *decoder, uint form_id)
{
    MWContext *context;
    lo_FormData *form_data;

    context = decoder->window_context;
    if (!context)
	return 0;
    form_data = LO_GetFormDataByID(context, form_id);
    if (!form_data)
	return 0;
    return form_data->mocha_object;
}

LO_FormElementStruct *
lm_GetFormElementByIndex(MochaObject *form_obj, int32 index)
{
    MochaForm *form;
    MWContext *context;
    lo_FormData *form_data;

    if (!form_obj)
	return 0;
    form = form_obj->data;
    context = form->form_decoder->window_context;
    if (!context)
	return 0;
    form_data = LO_GetFormDataByID(context, form->form_id);
    if (!form_data)
	return 0;
    return LO_GetFormElementByIndex(form_data, index);
}

MochaBoolean
lm_AddFormElement(MochaContext *mc, MochaObject *form_obj, MochaObject *ele_obj,
		  char *name, uint index)
{
    MochaForm *form;
    MWContext *context;
    lo_FormData *form_data;
    LO_Element **ele_list;
    LO_FormElementStruct *first_ele;
    uint first_index;
    MochaDatum datum;

    form = form_obj->data;
    context = form->form_decoder->window_context;
    if (!context)
	return MOCHA_FALSE;
    form_data = LO_GetFormDataByID(context, form->form_id);
    if (!form_data)
	return MOCHA_FALSE;

    /* XXX confine this to laymocha.c where LO_GetFormByElementIndex() lives */
    if (form_data->form_ele_cnt == 0) {
	index = 0;
    } else {
	PA_LOCK(ele_list, LO_Element **, form_data->form_elements);
	first_ele = (LO_FormElementStruct *)ele_list[0];
	first_index = (uint)first_ele->element_index;
	PA_UNLOCK(form_data->form_elements);
	XP_ASSERT(index >= first_index);
	if (index < first_index)
	    return MOCHA_FALSE;
	index -= first_index;
    }

    MOCHA_INIT_FULL_DATUM(mc, &datum, MOCHA_OBJECT, MDF_ENUMERATE|MDF_READONLY,
			  MOCHA_TAINT_IDENTITY, u.obj, ele_obj);
    if (!MOCHA_SetProperty(mc, form_obj, name, index, datum))
	return MOCHA_FALSE;

    if (index >= form->length)
	form->length = index + 1;
    return MOCHA_TRUE;
}

MochaBoolean
lm_ReflectRadioButtonArray(MWContext *context, intn form_id, const char *name)
{
    lo_FormData *form_data;
    MochaBoolean ok;
    LO_Element **ele_list;
    int32 i;
    LO_FormElementStruct *form_ele;
    LO_FormElementData *data;

    form_data = LO_GetFormDataByID(context, form_id);
    if (!form_data)
	return MOCHA_FALSE;
    ok = MOCHA_TRUE;
    PA_LOCK(ele_list, LO_Element **, form_data->form_elements);
    for (i = 0; i < form_data->form_ele_cnt; i++) {
	form_ele = (LO_FormElementStruct *)ele_list[i];
	if ((data = form_ele->element_data) &&
	    data->ele_minimal.name &&
	    !XP_STRCMP((char *)data->ele_minimal.name, name) &&
	    form_ele->mocha_object == NULL) {
	    ok = (LM_ReflectFormElement(context, form_ele) != 0);
	    if (!ok)
		break;
	}
    }
    PA_UNLOCK(form_data->form_elements);
    return ok;
}