/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- * * The contents of this file are subject to the Netscape Public License * Version 1.0 (the "NPL"); you may not use this file except in * compliance with the NPL. You may obtain a copy of the NPL at * http://www.mozilla.org/NPL/ * * Software distributed under the NPL is distributed on an "AS IS" basis, * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the NPL * for the specific language governing rights and limitations under the * NPL. * * The Initial Developer of this code under the NPL is Netscape * Communications Corporation. Portions created by Netscape are * Copyright (C) 1998 Netscape Communications Corporation. All Rights * Reserved. */ /* * JS input focus and event notifiers. * * Brendan Eich, 9/27/95 * * XXX SIZE, MAXLENGTH attributes */ #include "lm.h" #include "xp.h" #include "lo_ele.h" #include "pa_tags.h" #include "layout.h" #include "prmem.h" enum input_slot { INPUT_TYPE = -1, INPUT_NAME = -2, INPUT_FORM = -3, INPUT_VALUE = -4, INPUT_DEFAULT_VALUE = -5, INPUT_LENGTH = -6, INPUT_OPTIONS = -7, INPUT_SELECTED_INDEX = -8, INPUT_STATUS = -9, INPUT_DEFAULT_STATUS = -10 #if DISABLED_READONLY_SUPPORT INPUT_DISABLED = -11, INPUT_READONLY = -12 #endif }; static char lm_options_str[] = "options"; static JSPropertySpec input_props[] = { {"type", INPUT_TYPE, JSPROP_ENUMERATE|JSPROP_READONLY}, {"name", INPUT_NAME, JSPROP_ENUMERATE}, {"form", INPUT_FORM, JSPROP_ENUMERATE|JSPROP_READONLY}, {"value", INPUT_VALUE, JSPROP_ENUMERATE}, {"defaultValue", INPUT_DEFAULT_VALUE, JSPROP_ENUMERATE}, {lm_length_str, INPUT_LENGTH, JSPROP_ENUMERATE}, {lm_options_str, INPUT_OPTIONS, JSPROP_ENUMERATE|JSPROP_READONLY}, {"selectedIndex", INPUT_SELECTED_INDEX, JSPROP_ENUMERATE}, {"status", INPUT_STATUS, 0}, {"defaultStatus", INPUT_DEFAULT_STATUS, 0}, {PARAM_CHECKED, INPUT_STATUS, JSPROP_ENUMERATE}, {"defaultChecked", INPUT_DEFAULT_STATUS, JSPROP_ENUMERATE}, #if DISABLED_READONLY_SUPPORT {"disabled", INPUT_DISABLED, JSPROP_ENUMERATE}, {"readonly", INPUT_READONLY, JSPROP_ENUMERATE}, #endif {0} }; /* * Base input element type. */ typedef struct JSInput { JSInputHandler handler; int32 index; } JSInput; #define input_decoder handler.base_decoder #define input_type handler.base_type #define input_object handler.object #define input_event_mask handler.event_mask /* * Text and textarea input type. */ typedef struct JSTextInput { JSInput input; } JSTextInput; /* * Select option tag reflected type. */ enum option_slot { OPTION_INDEX = -1, OPTION_TEXT = -2, OPTION_VALUE = -3, OPTION_DEFAULT_SELECTED = -4, OPTION_SELECTED = -5 }; static JSPropertySpec option_props[] = { {"index", OPTION_INDEX, JSPROP_ENUMERATE|JSPROP_READONLY}, {"text", OPTION_TEXT, JSPROP_ENUMERATE}, {"value", OPTION_VALUE, JSPROP_ENUMERATE}, {"defaultSelected", OPTION_DEFAULT_SELECTED, JSPROP_ENUMERATE}, {"selected", OPTION_SELECTED, JSPROP_ENUMERATE}, {0} }; typedef struct JSSelectOption { MochaDecoder *decoder; JSObject *object; uint32 index; int32 indexInForm; lo_FormElementOptionData *data; } JSSelectOption; extern JSClass lm_option_class; PR_STATIC_CALLBACK(JSBool) option_getProperty(JSContext *cx, JSObject *obj, jsval id, jsval *vp) { JSSelectOption *option; lo_FormElementOptionData *optionData; lo_FormElementSelectData *selectData; LO_FormElementStruct *form_element; enum option_slot option_slot; JSString *str; char *value; jsint slot; if (!JSVAL_IS_INT(id)) return JS_TRUE; slot = JSVAL_TO_INT(id); option = JS_GetInstancePrivate(cx, obj, &lm_option_class, NULL); if (!option) return JS_TRUE; LO_LockLayout(); optionData = option->data; if (optionData) { selectData = 0; form_element = 0; } else { JSObject * parent = JS_GetParent(cx, obj); if (!parent) goto good; form_element = lm_GetFormElementByIndex(cx, JS_GetParent(cx, parent), option->indexInForm); if (!form_element) goto good; selectData = &form_element->element_data->ele_select; } option_slot = slot; switch (option_slot) { case OPTION_INDEX: *vp = INT_TO_JSVAL(option->index); break; case OPTION_TEXT: case OPTION_VALUE: if (selectData) optionData = (lo_FormElementOptionData *) selectData->options; if (slot == OPTION_TEXT) value = (char *)optionData[option->index].text_value; else value = (char *)optionData[option->index].value; str = lm_LocalEncodingToStr(option->decoder->window_context, value); if (!str) goto bad; *vp = STRING_TO_JSVAL(str); break; case OPTION_DEFAULT_SELECTED: case OPTION_SELECTED: if (selectData) optionData = (lo_FormElementOptionData *) selectData->options; *vp = BOOLEAN_TO_JSVAL((option_slot == OPTION_DEFAULT_SELECTED) ? optionData[option->index].def_selected : optionData[option->index].selected); break; default: /* Don't mess with a user-defined or method property. */ break; } good: LO_UnlockLayout(); return JS_TRUE; bad: LO_UnlockLayout(); return JS_FALSE; } PR_STATIC_CALLBACK(JSBool) option_setProperty(JSContext *cx, JSObject *obj, jsval id, jsval *vp) { JSSelectOption *option; lo_FormElementOptionData *optionData; lo_FormElementSelectData *selectData; LO_FormElementStruct *form_element; enum option_slot option_slot; JSBool showChange; int32 i; jsint slot; char * value = NULL; MWContext * context; if (!JSVAL_IS_INT(id)) return JS_TRUE; slot = JSVAL_TO_INT(id); option = JS_GetInstancePrivate(cx, obj, &lm_option_class, NULL); if (!option) return JS_TRUE; context = option->decoder->window_context; optionData = option->data; LO_LockLayout(); if (optionData) { selectData = 0; form_element = 0; } else { JSObject * parent = JS_GetParent(cx, obj); if (!parent) goto good; form_element = lm_GetFormElementByIndex(cx, JS_GetParent(cx, parent), option->indexInForm); if (!form_element) goto good; selectData = &form_element->element_data->ele_select; } if (selectData && option->index >= (uint32) selectData->option_cnt) goto good; option_slot = slot; showChange = JS_FALSE; switch (option_slot) { case OPTION_TEXT: case OPTION_VALUE: if (!JSVAL_IS_STRING(*vp) && !JS_ConvertValue(cx, *vp, JSTYPE_STRING, vp)) { goto bad; } if (selectData) optionData = (lo_FormElementOptionData *) selectData->options; value = lm_StrToLocalEncoding(context, JSVAL_TO_STRING(*vp)); if (!value) goto bad; if (option_slot == OPTION_TEXT) { if (!lm_SaveParamString(cx, &optionData[option->index].text_value, value)) { goto bad; } showChange = JS_TRUE; } else { if (!lm_SaveParamString(cx, &optionData[option->index].value, value)) { goto bad; } } XP_FREE(value); break; case OPTION_DEFAULT_SELECTED: case OPTION_SELECTED: if (!JSVAL_IS_BOOLEAN(*vp) && !JS_ConvertValue(cx, *vp, JSTYPE_BOOLEAN, vp)) { goto bad; } if (selectData) optionData = (lo_FormElementOptionData *) selectData->options; if (option_slot == OPTION_DEFAULT_SELECTED) optionData[option->index].def_selected = JSVAL_TO_BOOLEAN(*vp); else optionData[option->index].selected = JSVAL_TO_BOOLEAN(*vp); if (selectData) { if (JSVAL_TO_BOOLEAN(*vp) && !selectData->multiple) { /* Clear all the others. */ for (i = 0; i < selectData->option_cnt; i++) { if ((uint32)i == option->index) continue; if (option_slot == OPTION_DEFAULT_SELECTED) optionData[i].def_selected = FALSE; else optionData[i].selected = FALSE; } } } if (option_slot == OPTION_SELECTED) showChange = JS_TRUE; break; default: /* Don't mess with a user-defined property. */ goto good; } if (showChange && context && form_element) { ET_PostManipulateForm(context, (LO_Element *)form_element, EVENT_CHANGE); } good: LO_UnlockLayout(); return JS_TRUE; bad: XP_FREEIF(value); LO_UnlockLayout(); return JS_FALSE; } PR_STATIC_CALLBACK(void) option_finalize(JSContext *cx, JSObject *obj) { JSSelectOption *option; lo_FormElementOptionData *optionData; option = JS_GetPrivate(cx, obj); if (!option) return; optionData = option->data; if (optionData) { if (optionData->text_value) JS_free(cx, optionData->text_value); if (optionData->value) JS_free(cx, optionData->value); JS_free(cx, optionData); } DROP_BACK_COUNT(option->decoder); JS_free(cx, option); } JSClass lm_option_class = { "Option", JSCLASS_HAS_PRIVATE, JS_PropertyStub, JS_PropertyStub, option_getProperty, option_setProperty, JS_EnumerateStub, JS_ResolveStub, JS_ConvertStub, option_finalize }; /* * Select option constructor, can be called any of these ways: * opt = new Option() * opt = new Option(text) * opt = new Option(text, value) * opt = new Option(text, value, defaultSelected) * opt = new Option(text, value, defaultSelected, selected) * Where opt can be selectData.options[i] for any nonnegative integer i. */ PR_STATIC_CALLBACK(JSBool) Option(JSContext *cx, JSObject *obj, uint argc, jsval *argv, jsval *rval) { MochaDecoder *decoder; JSSelectOption *option; lo_FormElementOptionData *optionData; JSString *str; JSBool bval; MWContext *context; XP_ASSERT(JS_InstanceOf(cx, obj, &lm_option_class, NULL)); decoder = JS_GetPrivate(cx, JS_GetGlobalObject(cx)); context = decoder->window_context; option = JS_malloc(cx, sizeof *option); if (!option) return JS_TRUE; XP_BZERO(option, sizeof *option); if (!JS_SetPrivate(cx, obj, option)) { JS_free(cx, option); return JS_FALSE; } optionData = JS_malloc(cx, sizeof *optionData); if (!optionData) return JS_FALSE; XP_BZERO(optionData, sizeof *optionData); option->data = optionData; if (argc >= 4) { if (!JSVAL_IS_BOOLEAN(argv[3]) && !JS_ValueToBoolean(cx, argv[3], &bval)) { return JS_FALSE; } optionData->selected = bval; } if (argc >= 3) { if (!JSVAL_IS_BOOLEAN(argv[2]) && !JS_ValueToBoolean(cx, argv[2], &bval)) { return JS_FALSE; } optionData->def_selected = bval; } if (argc >= 2) { if (JSVAL_IS_STRING(argv[1])) str = JSVAL_TO_STRING(argv[1]); else if (!(str = JS_ValueToString(cx, argv[1]))) return JS_FALSE; optionData->value = (PA_Block)lm_StrToLocalEncoding(context, str); if (!optionData->value) return JS_FALSE; } if (argc >= 1) { if (JSVAL_IS_STRING(argv[0])) str = JSVAL_TO_STRING(argv[0]); else if (!(str = JS_ValueToString(cx, argv[0]))) return JS_FALSE; optionData->text_value = (PA_Block)lm_StrToLocalEncoding(context, str); if (!optionData->text_value) return JS_FALSE; } option->decoder = HOLD_BACK_COUNT(decoder); option->object = obj; option->index = 0; /* so option->data[option->index] works */ option->indexInForm = -1; return JS_TRUE; } static char *typenames[] = { "none", S_FORM_TYPE_TEXT, S_FORM_TYPE_RADIO, S_FORM_TYPE_CHECKBOX, S_FORM_TYPE_HIDDEN, S_FORM_TYPE_SUBMIT, S_FORM_TYPE_RESET, S_FORM_TYPE_PASSWORD, S_FORM_TYPE_BUTTON, S_FORM_TYPE_JOT, "select-one", "select-multiple", "textarea", "isindex", S_FORM_TYPE_IMAGE, S_FORM_TYPE_FILE, "keygen", S_FORM_TYPE_READONLY }; extern JSClass lm_input_class; /* * Note early returns below, to avoid common string-valued property code at * the bottom of the function. */ PR_STATIC_CALLBACK(JSBool) input_getProperty(JSContext *cx, JSObject *obj, jsval id, jsval *vp) { JSInput *input; MWContext *context; enum input_slot input_slot; LO_FormElementStruct *form_element; JSObject *option_obj; JSString *str; jsint slot; if (!JSVAL_IS_INT(id)) return JS_TRUE; slot = JSVAL_TO_INT(id); input = JS_GetInstancePrivate(cx, obj, &lm_input_class, NULL); if (!input) return JS_TRUE; input_slot = slot; if (input_slot == INPUT_FORM) { /* Each input in a form has a back-pointer to its form. */ *vp = OBJECT_TO_JSVAL(JS_GetParent(cx, obj)); return JS_TRUE; } LO_LockLayout(); form_element = lm_GetFormElementByIndex(cx, JS_GetParent(cx, obj), input->index); if (!form_element) goto good; if (input_slot == INPUT_TYPE) { uint type_index; type_index = form_element->element_data->type; if (type_index >= sizeof typenames / sizeof typenames[0]) { JS_ReportError(cx, "unknown form element type %u", type_index); goto bad; } str = JS_NewStringCopyZ(cx, typenames[type_index]); if (!str) goto bad; *vp = STRING_TO_JSVAL(str); goto good; } context = input->input_decoder->window_context; switch (form_element->element_data->type) { case FORM_TYPE_TEXT: case FORM_TYPE_TEXTAREA: /* XXX we ASSUME common struct prefixes */ case FORM_TYPE_FILE: /* XXX as above, also get-only without signing */ case FORM_TYPE_PASSWORD: #ifdef ENDER case FORM_TYPE_HTMLAREA : #endif /*ENDER*/ { lo_FormElementTextData *text; text = &form_element->element_data->ele_text; switch (input_slot) { case INPUT_NAME: str = lm_LocalEncodingToStr(context, (char *)text->name); break; case INPUT_VALUE: str = lm_LocalEncodingToStr(context, (char *)text->current_text); break; case INPUT_DEFAULT_VALUE: str = lm_LocalEncodingToStr(context, (char *)text->default_text); break; #if DISABLED_READONLY_SUPPORT case INPUT_DISABLED: *vp = BOOLEAN_TO_JSVAL(text->disabled); goto good; case INPUT_READONLY: *vp = BOOLEAN_TO_JSVAL(text->read_only); goto good; #endif default: /* Don't mess with a user-defined property. */ goto good; } } break; case FORM_TYPE_SELECT_ONE: case FORM_TYPE_SELECT_MULT: { lo_FormElementSelectData *selectData; lo_FormElementOptionData *optionData; int32 i; JSSelectOption *option; selectData = &form_element->element_data->ele_select; switch (input_slot) { case INPUT_NAME: str = lm_LocalEncodingToStr(context, (char *)selectData->name); break; case INPUT_LENGTH: *vp = INT_TO_JSVAL(selectData->option_cnt); goto good; case INPUT_OPTIONS: *vp = OBJECT_TO_JSVAL(input->input_object); goto good; case INPUT_SELECTED_INDEX: *vp = INT_TO_JSVAL(-1); optionData = (lo_FormElementOptionData *) selectData->options; for (i = 0; i < selectData->option_cnt; i++) { if (optionData[i].selected) { *vp = INT_TO_JSVAL(i); break; } } goto good; #if DISABLED_READONLY_SUPPORT case INPUT_DISABLED: *vp = BOOLEAN_TO_JSVAL(selectData->disabled); goto good; case INPUT_READONLY: *vp = BOOLEAN_TO_JSVAL(FALSE); goto good; #endif default: if ((uint32)slot >= (uint32)selectData->option_cnt) { *vp = JSVAL_NULL; goto good; } if (JSVAL_IS_OBJECT(*vp) && JSVAL_TO_OBJECT(*vp)) { XP_ASSERT(JS_InstanceOf(cx, JSVAL_TO_OBJECT(*vp), &lm_option_class, NULL)); goto good; } option = JS_malloc(cx, sizeof *option); if (!option) goto bad; option_obj = JS_NewObject(cx, &lm_option_class, input->input_decoder->option_prototype, obj); if (!option_obj || !JS_SetPrivate(cx, option_obj, option)) { JS_free(cx, option); goto bad; } option->decoder = HOLD_BACK_COUNT(input->input_decoder); option->object = option_obj; option->index = (uint32)slot; option->indexInForm = form_element->element_index; option->data = NULL; *vp = OBJECT_TO_JSVAL(option_obj); goto good; } } break; case FORM_TYPE_RADIO: case FORM_TYPE_CHECKBOX: { lo_FormElementToggleData *toggle; toggle = &form_element->element_data->ele_toggle; switch (input_slot) { case INPUT_NAME: str = lm_LocalEncodingToStr(context, (char *)toggle->name); break; case INPUT_VALUE: str = lm_LocalEncodingToStr(context, (char *)toggle->value); break; case INPUT_STATUS: *vp = BOOLEAN_TO_JSVAL(toggle->toggled); goto good; case INPUT_DEFAULT_STATUS: *vp = BOOLEAN_TO_JSVAL(toggle->default_toggle); goto good; #if DISABLED_READONLY_SUPPORT case INPUT_DISABLED: *vp = BOOLEAN_TO_JSVAL(toggle->disabled); goto good; case INPUT_READONLY: *vp = BOOLEAN_TO_JSVAL(FALSE); goto good; #endif default: /* Don't mess with a user-defined property. */ goto good; } } break; default: { lo_FormElementMinimalData *minimal; minimal = &form_element->element_data->ele_minimal; switch (input_slot) { case INPUT_NAME: str = lm_LocalEncodingToStr(context, (char *)minimal->name); break; case INPUT_VALUE: str = lm_LocalEncodingToStr(context, (char *)minimal->value); break; #if DISABLED_READONLY_SUPPORT case INPUT_DISABLED: *vp = BOOLEAN_TO_JSVAL(minimal->disabled); goto good; case INPUT_READONLY: *vp = BOOLEAN_TO_JSVAL(FALSE); /* minimal elements don't have the readonly attribute. */ goto good; #endif default: /* Don't mess with a user-defined property. */ goto good; } } break; } if (!str) goto bad; *vp = STRING_TO_JSVAL(str); good: LO_UnlockLayout(); return JS_TRUE; bad: LO_UnlockLayout(); return JS_FALSE; } char * lm_FixNewlines(JSContext *cx, const char *value, JSBool formElement) { size_t size; const char *cp; char *tp, *new_value; #if defined XP_PC size = 1; for (cp = value; *cp != '\0'; cp++) { switch (*cp) { case '\r': if (cp[1] != '\n') size++; break; case '\n': if (cp > value && cp[-1] != '\r') size++; break; } } size += cp - value; #else size = XP_STRLEN(value) + 1; #endif new_value = JS_malloc(cx, size); if (!new_value) return NULL; for (cp = value, tp = new_value; *cp != '\0'; cp++) { #if defined XP_MAC if (*cp == '\n') { if (cp > value && cp[-1] != '\r') *tp++ = '\r'; } else { *tp++ = *cp; } #elif defined XP_PC switch (*cp) { case '\r': *tp++ = '\r'; if (cp[1] != '\n' && formElement) *tp++ = '\n'; break; case '\n': if (cp > value && cp[-1] != '\r' && formElement) *tp++ = '\r'; *tp++ = '\n'; break; default: *tp++ = *cp; break; } #else /* XP_UNIX */ if (*cp == '\r') { if (cp[1] != '\n') *tp++ = '\n'; } else { *tp++ = *cp; } #endif } *tp = '\0'; return new_value; } PR_STATIC_CALLBACK(JSBool) input_setProperty(JSContext *cx, JSObject *obj, jsval id, jsval *vp) { JSInput *input; enum input_slot input_slot; const char *prop_name; char *value = NULL; LO_FormElementStruct *form_element; MochaDecoder *decoder; MWContext *context; int32 intval; jsint slot; input = JS_GetInstancePrivate(cx, obj, &lm_input_class, NULL); if (!input) return JS_TRUE; /* If the property is seting a key handler we find out now so * that we can tell the front end to send the event. */ if (JSVAL_IS_STRING(id)) { prop_name = JS_GetStringBytes(JSVAL_TO_STRING(id)); /* XXX use lm_onKeyDown_str etc. initialized by PARAM_ONKEYDOWN */ if (XP_STRCASECMP(prop_name, "onkeydown") == 0 || XP_STRCASECMP(prop_name, "onkeyup") == 0 || XP_STRCASECMP(prop_name, "onkeypress") == 0) { form_element = lm_GetFormElementByIndex(cx, JS_GetParent(cx, obj), input->index); form_element->event_handler_present = TRUE; } return JS_TRUE; } XP_ASSERT(JSVAL_IS_INT(id)); slot = JSVAL_TO_INT(id); decoder = input->input_decoder; context = decoder->window_context; input_slot = slot; switch (input_slot) { case INPUT_TYPE: case INPUT_FORM: case INPUT_OPTIONS: /* These are immutable. */ break; case INPUT_NAME: case INPUT_VALUE: case INPUT_DEFAULT_VALUE: /* These are string-valued. */ if (!JSVAL_IS_STRING(*vp) && !JS_ConvertValue(cx, *vp, JSTYPE_STRING, vp)) { return JS_FALSE; } value = lm_StrToLocalEncoding(context, JSVAL_TO_STRING(*vp)); break; case INPUT_STATUS: case INPUT_DEFAULT_STATUS: #if DISABLED_READONLY_SUPPORT case INPUT_READONLY: case INPUT_DISABLED: #endif /* These must be Booleans. */ if (!JSVAL_IS_BOOLEAN(*vp) && !JS_ConvertValue(cx, *vp, JSTYPE_BOOLEAN, vp)) { return JS_FALSE; } break; case INPUT_LENGTH: case INPUT_SELECTED_INDEX: /* These should be integers. */ if (JSVAL_IS_INT(*vp)) intval = JSVAL_TO_INT(*vp); else if (!JS_ValueToInt32(cx, *vp, &intval)) { return JS_FALSE; } break; } LO_LockLayout(); form_element = lm_GetFormElementByIndex(cx, JS_GetParent(cx, obj), input->index); if (!form_element) goto good; switch (form_element->element_data->type) { case FORM_TYPE_FILE: /* if we try to set a file upload widget we better be a signed script */ if (!lm_CanAccessTarget(cx, JSTARGET_UNIVERSAL_FILE_READ)) break; /* else fall through... */ case FORM_TYPE_TEXT: case FORM_TYPE_TEXTAREA: /* XXX we ASSUME common struct prefixes */ case FORM_TYPE_PASSWORD: #ifdef ENDER case FORM_TYPE_HTMLAREA : #endif /*ENDER*/ { lo_FormElementTextData *text; JSBool ok; char * fixed_string; text = &form_element->element_data->ele_text; switch (input_slot) { case INPUT_NAME: if (!lm_SaveParamString(cx, &text->name, value)) goto bad; break; case INPUT_VALUE: case INPUT_DEFAULT_VALUE: fixed_string = lm_FixNewlines(cx, value, JS_TRUE); if (!fixed_string) goto bad; ok = (input_slot == INPUT_VALUE) ? lm_SaveParamString(cx, &text->current_text, fixed_string) : lm_SaveParamString(cx, &text->default_text, fixed_string); JS_free(cx, (char *)fixed_string); if (!ok) goto bad; if (input_slot == INPUT_VALUE && context) { ET_PostManipulateForm(context, (LO_Element *)form_element, EVENT_CHANGE); } break; #if DISABLED_READONLY_SUPPORT case INPUT_DISABLED: text->disabled = JSVAL_TO_BOOLEAN(*vp); if (context) { ET_PostManipulateForm(context, (LO_Element *)form_element, EVENT_CHANGE); } break; case INPUT_READONLY: if (form_element->element_data->type == FORM_TYPE_FILE) break; text->read_only = JSVAL_TO_BOOLEAN(*vp); if (context) { ET_PostManipulateForm(context, (LO_Element *)form_element, EVENT_CHANGE); } break; #endif default: /* Don't mess with option or user-defined property. */ goto good; } } break; case FORM_TYPE_SELECT_ONE: case FORM_TYPE_SELECT_MULT: { lo_FormElementSelectData *selectData; lo_FormElementOptionData *optionData; JSSelectOption *option; int32 i, new_option_cnt, old_option_cnt; selectData = &form_element->element_data->ele_select; switch (slot) { case INPUT_NAME: if (!lm_SaveParamString(cx, &selectData->name, value)) goto bad; break; case INPUT_LENGTH: new_option_cnt = intval; old_option_cnt = selectData->option_cnt; optionData = (lo_FormElementOptionData *) selectData->options; /* Remove truncated slots, or clear extended element data. */ if (new_option_cnt < old_option_cnt) { /* * Make truncated options stand alone in case someone else * in case someone else has a reference to one. */ for (i = new_option_cnt; i < old_option_cnt; i++) { jsval oval; JSObject * option_obj; if (!JS_LookupElement(cx, obj, i, &oval)) goto bad; if (JSVAL_IS_OBJECT(oval) && (option_obj = JSVAL_TO_OBJECT(oval))) { lo_FormElementOptionData *myData; myData = JS_malloc(cx, sizeof(lo_FormElementOptionData)); if (!myData) goto bad; XP_MEMCPY(myData, &optionData[i], sizeof(lo_FormElementOptionData)); option = JS_GetPrivate(cx, option_obj); option->data = myData; } JS_DeleteElement(cx, obj, i); } } /* Get layout to reallocate the options array. */ selectData->option_cnt = new_option_cnt; if (!LO_ResizeSelectOptions(selectData)) { selectData->option_cnt = old_option_cnt; JS_ReportOutOfMemory(cx); goto bad; } /* Handle the grow case by clearing the new options. */ if (new_option_cnt > old_option_cnt) { XP_BZERO(&optionData[old_option_cnt], (new_option_cnt - old_option_cnt) * sizeof *optionData); } /* Tell the FE about it. */ if (context) { ET_PostManipulateForm(context, (LO_Element *)form_element, EVENT_CHANGE); } break; case INPUT_OPTIONS: break; case INPUT_SELECTED_INDEX: optionData = (lo_FormElementOptionData *) selectData->options; for (i = 0; i < selectData->option_cnt; i++) optionData[i].selected = (i == intval); /* Tell the FE about it. */ if (context) ET_PostManipulateForm(context, (LO_Element *)form_element, EVENT_CHANGE); break; #if DISABLED_READONLY_SUPPORT case INPUT_DISABLED: selectData->disabled = JSVAL_TO_BOOLEAN(*vp); if (context) { ET_PostManipulateForm(context, (LO_Element *)form_element, EVENT_CHANGE); } break; case INPUT_READONLY: /* silenty ignore updates to the READONLY attribute. */ break; #endif default: if (slot < 0) { /* Don't mess with a user-defined, named property. */ goto good; } /* The vp arg must refer to an object of the right class. */ if (!JSVAL_IS_OBJECT(*vp) && !JS_ConvertValue(cx, *vp, JSTYPE_OBJECT, vp)) { goto bad; } if (JSVAL_IS_NULL(*vp)) { int32 count, limit; JSBool ok = JS_TRUE; if (slot >= selectData->option_cnt) goto good; /* Clear the option and compress the options array. */ optionData = (lo_FormElementOptionData *) selectData->options; count = selectData->option_cnt - (slot + 1); if (count > 0) { /* * Move down the options that were after the option * we are deleting. Note, the JS_GetElement() * and SetElement() calls will make sure the * layout-based data gets copied too. */ for (limit = slot + count; slot < limit; slot++) { jsval v; ok = JS_GetElement(cx, obj, slot + 1, &v); if (!ok) break; JS_SetElement(cx, obj, slot, &v); /* Fix each option's index-in-select property. */ XP_ASSERT(JSVAL_IS_OBJECT(v)); option = JS_GetPrivate(cx, JSVAL_TO_OBJECT(v)); option->index = slot; } if (ok) JS_DeleteElement(cx, obj, slot); } /* Shrink the select element data's options array. */ if (ok) { selectData->option_cnt--; ok = (JSBool)LO_ResizeSelectOptions(selectData); if (!ok) { JS_ReportOutOfMemory(cx); } else if (context) { ET_PostManipulateForm(context, (LO_Element *)form_element, EVENT_CHANGE); } } LO_UnlockLayout(); return ok; } if (!JS_InstanceOf(cx, JSVAL_TO_OBJECT(*vp), &lm_option_class, NULL)) { JS_ReportError(cx, "cannot set %s.%s to incompatible %s", JS_GetClass(cx, obj)->name, lm_options_str, JS_GetClass(cx, JSVAL_TO_OBJECT(*vp))->name); goto bad; } option = JS_GetPrivate(cx, JSVAL_TO_OBJECT(*vp)); if (!option) goto good; if (!option->data && JS_GetParent(cx, option->object) != obj) { JS_ReportError(cx, "can't share options between select elements"); goto bad; } /* Grow the option array if necessary. */ old_option_cnt = selectData->option_cnt; if (slot >= old_option_cnt) { selectData->option_cnt = slot + 1; if (!LO_ResizeSelectOptions(selectData)) { selectData->option_cnt = old_option_cnt; JS_ReportOutOfMemory(cx); goto bad; } } /* Clear any option structs in the gap, then set slot. */ optionData = (lo_FormElementOptionData *) selectData->options; if (slot > old_option_cnt) { XP_BZERO(&optionData[old_option_cnt], (slot - old_option_cnt) * sizeof *optionData); } if (option->data) { XP_MEMCPY(&optionData[slot], option->data, sizeof(lo_FormElementOptionData)); } else if ((uint32)slot != option->index) { XP_MEMCPY(&optionData[slot], &optionData[option->index], sizeof(lo_FormElementOptionData)); } /* Update the option to point at its form and form element. */ JS_SetParent(cx, JSVAL_TO_OBJECT(*vp), obj); option->index = (uint32)slot; option->indexInForm = form_element->element_index; if (option->data) { JS_free(cx, option->data); option->data = NULL; } /* Tell the FE about it. */ if (context) ET_PostManipulateForm(context, (LO_Element *)form_element, EVENT_CHANGE); break; } } break; case FORM_TYPE_RADIO: case FORM_TYPE_CHECKBOX: { lo_FormElementToggleData *toggle; toggle = &form_element->element_data->ele_toggle; switch (input_slot) { case INPUT_NAME: if (!lm_SaveParamString(cx, &toggle->name, value)) goto bad; break; case INPUT_VALUE: if (!lm_SaveParamString(cx, &toggle->value, value)) goto bad; break; case INPUT_STATUS: if (JSVAL_IS_BOOLEAN(*vp)) toggle->toggled = JSVAL_TO_BOOLEAN(*vp); /* Tell the FE about it (the FE keeps radio-sets consistent). */ if (context) ET_PostManipulateForm(context, (LO_Element *)form_element, EVENT_CHANGE); break; case INPUT_DEFAULT_STATUS: if (JSVAL_IS_BOOLEAN(*vp)) toggle->default_toggle = JSVAL_TO_BOOLEAN(*vp); break; #if DISABLED_READONLY_SUPPORT case INPUT_DISABLED: toggle->disabled = JSVAL_TO_BOOLEAN(*vp); if (context) { ET_PostManipulateForm(context, (LO_Element *)form_element, EVENT_CHANGE); } break; case INPUT_READONLY: /* silenty ignore updates to the READONLY attribute. */ break; #endif default: /* Don't mess with a user-defined property. */ goto good; } } break; case FORM_TYPE_READONLY: /* Don't allow modification of readonly fields. */ break; default: { lo_FormElementMinimalData *minimal; minimal = &form_element->element_data->ele_minimal; switch (input_slot) { case INPUT_NAME: if (!lm_SaveParamString(cx, &minimal->name, value)) goto bad; break; case INPUT_VALUE: if (!lm_SaveParamString(cx, &minimal->value, value)) goto bad; if (context) { ET_PostManipulateForm(context, (LO_Element *)form_element, EVENT_CHANGE); } break; #if DISABLED_READONLY_SUPPORT case INPUT_DISABLED: minimal->disabled = JSVAL_TO_BOOLEAN(*vp); if (context) { ET_PostManipulateForm(context, (LO_Element *)form_element, EVENT_CHANGE); } break; case INPUT_READONLY: /* silenty ignore updates to the READONLY attribute. */ break; #endif default: /* Don't mess with a user-defined property. */ goto good; } } break; } good: XP_FREEIF(value); LO_UnlockLayout(); return JS_TRUE; bad: XP_FREEIF(value); LO_UnlockLayout(); return JS_FALSE; } PR_STATIC_CALLBACK(void) input_finalize(JSContext *cx, JSObject *obj) { JSInput *input; LO_FormElementStruct *form_element; input = JS_GetPrivate(cx, obj); if (!input) return; LO_LockLayout(); form_element = lm_GetFormElementByIndex(cx, JS_GetParent(cx, obj), input->index); if (form_element && form_element->mocha_object == obj) form_element->mocha_object = NULL; LO_UnlockLayout(); DROP_BACK_COUNT(input->input_decoder); JS_free(cx, input); } JSClass lm_input_class = { "Input", JSCLASS_HAS_PRIVATE, JS_PropertyStub, JS_PropertyStub, input_getProperty, input_setProperty, JS_EnumerateStub, JS_ResolveStub, JS_ConvertStub, input_finalize }; PR_STATIC_CALLBACK(JSBool) Input(JSContext *cx, JSObject *obj, uint argc, jsval *argv, jsval *rval) { return JS_TRUE; } PR_STATIC_CALLBACK(JSBool) input_toString(JSContext *cx, JSObject *obj, uint argc, jsval *argv, jsval *rval) { JSInput *input; LO_FormElementStruct *form_element; uint type; char *typename, *string, *value; size_t length; long truelong; jsval result; JSString *str; if (!JS_InstanceOf(cx, obj, &lm_input_class, argv)) return JS_FALSE; input = JS_GetPrivate(cx, obj); if (!input) return JS_TRUE; LO_LockLayout(); form_element = lm_GetFormElementByIndex(cx, JS_GetParent(cx, obj), input->index); if (!form_element) { *rval = JS_GetEmptyStringValue(cx); goto bad; } type = form_element->element_data->type; if (type >= sizeof typenames / sizeof typenames[0]) { JS_ReportError(cx, "unknown form element type %u", type); goto bad; } typename = typenames[type]; string = PR_sprintf_append(0, "<"); switch (type) { case FORM_TYPE_TEXT: { lo_FormElementTextData *text; text = &form_element->element_data->ele_text; string = PR_sprintf_append(string, "%s %s=\"%s\"", PT_INPUT, PARAM_TYPE, typename); if (text->name) { string = PR_sprintf_append(string, " %s=\"%s\"", PARAM_NAME, (char *)text->name); } if (text->default_text) { string = PR_sprintf_append(string, " %s=\"%s\"", PARAM_VALUE, (char *)text->default_text); } if (text->size) { truelong = text->size; string = PR_sprintf_append(string, " %s=%ld\"", PARAM_SIZE, truelong); } if (text->max_size) { truelong = text->max_size; string = PR_sprintf_append(string, " %s=%ld\"", PARAM_MAXLENGTH, truelong); } } break; case FORM_TYPE_TEXTAREA: /* XXX we ASSUME common struct prefixes */ #ifdef ENDER case FORM_TYPE_HTMLAREA : #endif /*ENDER*/ { lo_FormElementTextareaData *textarea; textarea = &form_element->element_data->ele_textarea; string = PR_sprintf_append(string, PT_TEXTAREA); if (textarea->name) { string = PR_sprintf_append(string, " %s=\"%s\"", PARAM_NAME, (char *)textarea->name); } if (textarea->default_text) { string = PR_sprintf_append(string, " %s=\"%s\"", PARAM_VALUE, (char *)textarea->default_text); } if (textarea->rows) { truelong = textarea->rows; string = PR_sprintf_append(string, " %s=%ld\"", PARAM_SIZE, truelong); } if (textarea->cols) { truelong = textarea->cols; string = PR_sprintf_append(string, " %s=%ld\"", PARAM_SIZE, truelong); } if (textarea->auto_wrap) { switch (textarea->auto_wrap) { case TEXTAREA_WRAP_OFF: value = "off"; break; case TEXTAREA_WRAP_HARD: value = "hard"; break; case TEXTAREA_WRAP_SOFT: value = "soft"; break; default: value = "unknown"; break; } string = PR_sprintf_append(string, " %s=\"%s\"", PARAM_WRAP, value); } } break; case FORM_TYPE_SELECT_ONE: case FORM_TYPE_SELECT_MULT: { lo_FormElementSelectData *selectData; lo_FormElementOptionData *optionData; int32 i; selectData = &form_element->element_data->ele_select; string = PR_sprintf_append(string, PT_SELECT); if (selectData->name) { string = PR_sprintf_append(string, " %s=\"%s\"", PARAM_NAME, (char *)selectData->name); } if (selectData->size) { truelong = selectData->size; string = PR_sprintf_append(string, " %s=%ld\"", PARAM_SIZE, truelong); } if (selectData->multiple) { string = PR_sprintf_append(string, " %s", PARAM_MULTIPLE); } string = PR_sprintf_append(string, ">\n"); PA_LOCK(optionData, lo_FormElementOptionData *, selectData->options); for (i = 0; i < selectData->option_cnt; i++) { string = PR_sprintf_append(string, "<%s", PT_OPTION); if (optionData[i].value) { string = PR_sprintf_append(string, " %s=\"%s\"", PARAM_VALUE, optionData[i].value); } if (optionData[i].def_selected) string = PR_sprintf_append(string, " %s", PARAM_SELECTED); string = PR_sprintf_append(string, ">"); if (optionData[i].text_value) { string = PR_sprintf_append(string, "%s", optionData[i].text_value); } string = PR_sprintf_append(string, "\n"); } PA_UNLOCK(selectData->options); string = PR_sprintf_append(string, "element_data->ele_toggle; string = PR_sprintf_append(string, "%s %s=\"%s\"", PT_INPUT, PARAM_TYPE, typename); if (toggle->name) { string = PR_sprintf_append(string, " %s=\"%s\"", PARAM_NAME, (char *)toggle->name); } if (toggle->value) { string = PR_sprintf_append(string, " %s=\"%s\"", PARAM_VALUE, (char *)toggle->value); } if (toggle->default_toggle) string = PR_sprintf_append(string, " %s", PARAM_CHECKED); } break; default: { lo_FormElementMinimalData *minimal; minimal = &form_element->element_data->ele_minimal; string = PR_sprintf_append(string, "%s %s=\"%s\"", PT_INPUT, PARAM_TYPE, typename); if (minimal->name) { string = PR_sprintf_append(string, " %s=\"%s\"", PARAM_NAME, (char *)minimal->name); } if (minimal->value) { string = PR_sprintf_append(string, " %s=\"%s\"", PARAM_VALUE, (char *)minimal->value); } } break; } #define FROB(param) { \ if (!JS_LookupProperty(cx, input->input_object, param, &result)) { \ PR_FREEIF(string); \ return JS_FALSE; \ } \ if (JS_TypeOfValue(cx, result) == JSTYPE_FUNCTION) { \ JSFunction *fun = JS_ValueToFunction(cx, result); \ if (!fun) { \ PR_FREEIF(string); \ return JS_FALSE; \ } \ str = JS_DecompileFunctionBody(cx, fun, 0); \ value = JS_GetStringBytes(str); \ length = strlen(value); \ if (length && value[length-1] == '\n') length--; \ string = PR_sprintf_append(string," %s='%.*s'", param, length, value);\ } \ } FROB(lm_onFocus_str); FROB(lm_onBlur_str); FROB(lm_onSelect_str); FROB(lm_onChange_str); FROB(lm_onClick_str); FROB(lm_onScroll_str); #undef FROB LO_UnlockLayout(); string = PR_sprintf_append(string, ">"); if (!string) { JS_ReportOutOfMemory(cx); return JS_FALSE; } str = lm_LocalEncodingToStr(input->input_decoder->window_context, string); XP_FREE(string); if (!str) return JS_FALSE; *rval = STRING_TO_JSVAL(str); return JS_TRUE; bad: LO_UnlockLayout(); return JS_FALSE; } static JSBool input_method(JSContext *cx, JSObject *obj, jsval *argv, uint32 event) { JSInput *input; MWContext *context; LO_FormElementStruct *form_element; if (!JS_InstanceOf(cx, obj, &lm_input_class, argv)) return JS_FALSE; input = JS_GetPrivate(cx, obj); if (!input) return JS_TRUE; context = input->input_decoder->window_context; if (!context) return JS_TRUE; LO_LockLayout(); form_element = lm_GetFormElementByIndex(cx, JS_GetParent(cx, obj), input->index); if (!form_element) { LO_UnlockLayout(); return JS_TRUE; } input->input_event_mask |= event; ET_PostManipulateForm(context, (LO_Element *)form_element, event); input->input_event_mask &= ~event; LO_UnlockLayout(); return JS_TRUE; } PR_STATIC_CALLBACK(JSBool) input_focus(JSContext *cx, JSObject *obj, uint argc, jsval *argv, jsval *rval) { return input_method(cx, obj, argv, EVENT_FOCUS); } PR_STATIC_CALLBACK(JSBool) input_blur(JSContext *cx, JSObject *obj, uint argc, jsval *argv, jsval *rval) { return input_method(cx, obj, argv, EVENT_BLUR); } PR_STATIC_CALLBACK(JSBool) input_select(JSContext *cx, JSObject *obj, uint argc, jsval *argv, jsval *rval) { return input_method(cx, obj, argv, EVENT_SELECT); } PR_STATIC_CALLBACK(JSBool) input_click(JSContext *cx, JSObject *obj, uint argc, jsval *argv, jsval *rval) { return input_method(cx, obj, argv, EVENT_CLICK); } #ifdef NOTYET PR_STATIC_CALLBACK(JSBool) input_enable(JSContext *cx, JSObject *obj, uint argc, jsval *argv, jsval *rval) { return input_method(cx, obj, argv, EVENT_ENABLE); } PR_STATIC_CALLBACK(JSBool) input_disable(JSContext *cx, JSObject *obj, uint argc, jsval *argv, jsval *rval) { return input_method(cx, obj, argv, EVENT_DISABLE); } #endif /* NOTYET */ static JSFunctionSpec input_methods[] = { {lm_toString_str, input_toString, 0}, {"focus", input_focus, 0}, {"blur", input_blur, 0}, {"select", input_select, 0}, {"click", input_click, 0}, #ifdef NOTYET {"enable", input_enable, 0}, {"disable", input_disable, 0}, #endif /* NOTYET */ {0} }; /* * XXX move me somewhere else... */ enum input_array_slot { INPUT_ARRAY_LENGTH = -1 }; static JSPropertySpec input_array_props[] = { {lm_length_str, INPUT_ARRAY_LENGTH, JSPROP_ENUMERATE | JSPROP_READONLY | JSPROP_PERMANENT}, {0} }; typedef struct JSInputArray { JSInputBase base; uint length; } JSInputArray; extern JSClass lm_input_array_class; PR_STATIC_CALLBACK(JSBool) input_array_getProperty(JSContext *cx, JSObject *obj, jsval id, jsval *vp) { JSInputArray *array; jsint slot; if (!JSVAL_IS_INT(id)) return JS_TRUE; slot = JSVAL_TO_INT(id); array = JS_GetInstancePrivate(cx, obj, &lm_input_array_class, NULL); if (!array) return JS_TRUE; switch (slot) { case INPUT_ARRAY_LENGTH: *vp = INT_TO_JSVAL(array->length); break; } return JS_TRUE; } PR_STATIC_CALLBACK(void) input_array_finalize(JSContext *cx, JSObject *obj) { JSInputArray *array; array = JS_GetPrivate(cx, obj); if (!array) return; DROP_BACK_COUNT(array->base_decoder); JS_free(cx, array); } JSClass lm_input_array_class = { "InputArray", JSCLASS_HAS_PRIVATE, JS_PropertyStub, JS_PropertyStub, input_array_getProperty, input_array_getProperty, JS_EnumerateStub, JS_ResolveStub, JS_ConvertStub, input_array_finalize }; #ifdef NOTYET PR_STATIC_CALLBACK(JSBool) InputArray(JSContext *cx, JSObject *obj, uint argc, jsval *argv, jsval *rval) { return JS_TRUE; } #endif static void lm_compile_event_handlers(MochaDecoder * decoder, LO_FormElementStruct * form_element, JSObject *obj, PA_Tag *tag) { MWContext * context = decoder->window_context; PA_Block method, id, keydown, keypress, keyup; JSInputBase *base; JSContext *cx; cx = decoder->js_context; base = JS_GetPrivate(cx, obj); keydown = lo_FetchParamValue(context, tag, PARAM_ONKEYDOWN); keypress = lo_FetchParamValue(context, tag, PARAM_ONKEYPRESS); keyup = lo_FetchParamValue(context, tag, PARAM_ONKEYUP); /* Text fields need this info. */ if (keydown || keypress || keyup) form_element->event_handler_present = TRUE; LO_UnlockLayout(); id = lo_FetchParamValue(context, tag, PARAM_ID); method = lo_FetchParamValue(context, tag, PARAM_ONCLICK); if (method) { (void) lm_CompileEventHandler(decoder, id, tag->data, tag->newline_count, obj, PARAM_ONCLICK, method); base->handlers |= HANDLER_ONCLICK; PA_FREE(method); } method = lo_FetchParamValue(context, tag, PARAM_ONFOCUS); if (method) { (void) lm_CompileEventHandler(decoder, id, tag->data, tag->newline_count, obj, PARAM_ONFOCUS, method); base->handlers |= HANDLER_ONFOCUS; PA_FREE(method); } method = lo_FetchParamValue(context, tag, PARAM_ONBLUR); if (method) { (void) lm_CompileEventHandler(decoder, id, tag->data, tag->newline_count, obj, PARAM_ONBLUR, method); base->handlers |= HANDLER_ONBLUR; PA_FREE(method); } method = lo_FetchParamValue(context, tag, PARAM_ONCHANGE); if (method) { (void) lm_CompileEventHandler(decoder, id, tag->data, tag->newline_count, obj, PARAM_ONCHANGE, method); base->handlers |= HANDLER_ONCHANGE; PA_FREE(method); } method = lo_FetchParamValue(context, tag, PARAM_ONSELECT); if (method) { (void) lm_CompileEventHandler(decoder, id, tag->data, tag->newline_count, obj, PARAM_ONSELECT, method); base->handlers |= HANDLER_ONSELECT; PA_FREE(method); } method = lo_FetchParamValue(context, tag, PARAM_ONSCROLL); if (method) { (void) lm_CompileEventHandler(decoder, id, tag->data, tag->newline_count, obj, PARAM_ONSCROLL, method); base->handlers |= HANDLER_ONSCROLL; PA_FREE(method); } method = lo_FetchParamValue(context, tag, PARAM_ONMOUSEDOWN); if (method) { (void) lm_CompileEventHandler(decoder, id, tag->data, tag->newline_count, obj, PARAM_ONMOUSEDOWN, method); base->handlers |= HANDLER_ONMOUSEDOWN; PA_FREE(method); } method = lo_FetchParamValue(context, tag, PARAM_ONMOUSEUP); if (method) { (void) lm_CompileEventHandler(decoder, id, tag->data, tag->newline_count, obj, PARAM_ONMOUSEUP, method); base->handlers |= HANDLER_ONMOUSEUP; PA_FREE(method); } if (keydown) { (void) lm_CompileEventHandler(decoder, id, tag->data, tag->newline_count, obj, PARAM_ONKEYDOWN, keydown); base->handlers |= HANDLER_ONKEYDOWN; PA_FREE(keydown); } if (keyup) { (void) lm_CompileEventHandler(decoder, id, tag->data, tag->newline_count, obj, PARAM_ONKEYUP, keyup); base->handlers |= HANDLER_ONKEYUP; PA_FREE(keyup); } if (keypress) { (void) lm_CompileEventHandler(decoder, id, tag->data, tag->newline_count, obj, PARAM_ONKEYPRESS, keypress); base->handlers |= HANDLER_ONKEYPRESS; PA_FREE(keypress); } method = lo_FetchParamValue(context, tag, PARAM_ONDBLCLICK); if (method) { (void) lm_CompileEventHandler(decoder, id, tag->data, tag->newline_count, obj, PARAM_ONDBLCLICK, method); base->handlers |= HANDLER_ONDBLCLICK; PA_FREE(method); } if (id) PA_FREE(id); LO_LockLayout(); } #define ANTI_RECURSIVE_KLUDGE ((JSObject *)1) /* * Reflect a bunch of different types of form elements into JS. */ JSObject * LM_ReflectFormElement(MWContext *context, int32 layer_id, int32 form_id, int32 element_id, PA_Tag * tag) { JSObject *obj, *form_obj, *prototype, *old_obj, *array_obj; LO_FormElementData *data; LO_FormElementStruct *form_element; MochaDecoder *decoder; JSContext *cx; int32 type; char *name = NULL; JSBool ok; size_t size; JSInput *input; JSClass *clasp; JSInputBase *base; JSInputArray *array; jsval val; lo_FormData * form_data; lo_TopState *top_state; int32 element_index; LMWindowGroup *grp; /* reflect the form */ if (!LM_ReflectForm(context, NULL, NULL, layer_id, form_id)) return NULL; /* make the form the active form */ decoder = LM_GetMochaDecoder(context); if (!decoder) return NULL; LM_PutMochaDecoder(decoder); /* if this is a radio button we're gonna need to get this later */ if (tag) ((PA_Tag *)tag)->lo_data = (void*)element_id; form_data = LO_GetFormDataByID(context, layer_id, form_id); if (!form_data || !form_data->mocha_object) return NULL; form_obj = form_data->mocha_object; form_element = LO_GetFormElementByIndex(form_data, element_id); if (!form_element || !form_element->element_data) return NULL; data = form_element->element_data; /* see if we've already reflected it (or are reflecting it) */ obj = form_element->mocha_object; if (obj) { if (obj == ANTI_RECURSIVE_KLUDGE) return NULL; /* * This object might have already gotten reflected but it might * not have had its tag (and thus event handlers) at the time * it was reflected */ if (tag) lm_compile_event_handlers(decoder, form_element, obj, tag); return obj; } decoder = LM_GetMochaDecoder(context); if (!decoder) return NULL; cx = decoder->js_context; top_state = lo_GetMochaTopState(context); if (top_state->resize_reload) { obj = lm_GetFormElementFromMapping(cx, form_obj, element_id); if (obj) { form_element->mocha_object = obj; LM_PutMochaDecoder(decoder); return obj; } } prototype = decoder->input_prototype; type = data->type; if ((char *)data->ele_minimal.name) name = XP_STRDUP((char *)data->ele_minimal.name); grp = lm_MWContextToGroup(context); if(!grp) { grp = LM_GetDefaultWindowGroup(context); } switch (type) { case FORM_TYPE_TEXT: case FORM_TYPE_TEXTAREA: #ifdef ENDER case FORM_TYPE_HTMLAREA: #endif /*ENDER*/ size = sizeof(JSTextInput); break; case FORM_TYPE_RADIO: if (!grp->inputRecurring) { grp->inputRecurring++; ok = lm_ReflectRadioButtonArray(context, layer_id, form_element->form_id, name, tag); grp->inputRecurring--; obj = form_element->mocha_object; if (obj) { LM_PutMochaDecoder(decoder); return obj; } } /* FALL THROUGH */ default: size = sizeof(JSInput); break; } input = JS_malloc(cx, size); if (!input) goto fail; XP_BZERO(input, size); obj = JS_NewObject(cx, &lm_input_class, prototype, form_obj); if (!obj || !JS_SetPrivate(cx, obj, input)) { JS_free(cx, input); goto fail; } /* * get val before we lose the form_element since * lm_compile_event_handlers() is going to lose the layout lock */ if (name) { form_element->mocha_object = ANTI_RECURSIVE_KLUDGE; ok = JS_LookupProperty(cx, form_obj, name, &val); form_element->mocha_object = NULL; if (!ok) { LM_PutMochaDecoder(decoder); return NULL; } } element_index = form_element->element_index; /* see if there are any event handlers we need to compile */ if (tag) lm_compile_event_handlers(decoder, form_element, obj, tag); /* * In 3.0 we would reflect hidden elements only if they had event * handlers, not just a name attribute. */ if (type == FORM_TYPE_HIDDEN && JS_GetVersion(cx) < JSVERSION_1_2) { base = JS_GetPrivate(cx, obj); if (!base || (!name && !base->handlers)) goto fail; } array_obj = NULL; if (name) { old_obj = JSVAL_IS_OBJECT(val) ? JSVAL_TO_OBJECT(val) : NULL; if (old_obj) { clasp = JS_GetClass(cx, old_obj); if (clasp != &lm_input_class && clasp != &lm_input_array_class) old_obj = NULL; } if (old_obj) { base = JS_GetPrivate(cx, old_obj); if (!base) goto fail; if (JS_GetVersion(cx) < JSVERSION_1_2 && base->type == FORM_TYPE_HIDDEN) { /* * We have two or more elements of the form with the same name. * For JavaScript1.1 or earlier some peculiarities apply to a * set of form elements with the same name. If any elements in * the set had handlers, then only those elements with handlers * would be reflected. Otherwise, all form elements in the set * are reflected. */ JSObject *temp_obj; jsval result; JSBool currentHasHandler; JSBool accumulatedHasHandlers; JSInputBase *currentBase; currentBase = JS_GetPrivate(cx, obj); if (!currentBase) goto fail; currentHasHandler = (JSBool)(currentBase->handlers != 0); temp_obj = old_obj; if (clasp == &lm_input_array_class) { JS_GetElement(cx, old_obj, 0, &result); temp_obj = JSVAL_TO_OBJECT(result); } accumulatedHasHandlers = (JSBool)( (JS_LookupProperty(cx, temp_obj, lm_onClick_str, &result) && JS_TypeOfValue(cx, result) == JSTYPE_FUNCTION) || (JS_LookupProperty(cx, temp_obj, lm_onFocus_str, &result) && JS_TypeOfValue(cx, result) == JSTYPE_FUNCTION) || (JS_LookupProperty(cx, temp_obj, lm_onBlur_str, &result) && JS_TypeOfValue(cx, result) == JSTYPE_FUNCTION) || (JS_LookupProperty(cx, temp_obj, lm_onChange_str, &result) && JS_TypeOfValue(cx, result) == JSTYPE_FUNCTION) || (JS_LookupProperty(cx, temp_obj, lm_onSelect_str, &result) && JS_TypeOfValue(cx, result) == JSTYPE_FUNCTION) || (JS_LookupProperty(cx, temp_obj, lm_onScroll_str, &result) && JS_TypeOfValue(cx, result) == JSTYPE_FUNCTION)); if (currentHasHandler && !accumulatedHasHandlers) { /* * Replace the accumulated form elements with this one. * That way, we will create an array for form elements with * the same name, adding only those elements that have * handlers, unless no elements have handlers, in which * case all are reflected. */ JS_DeleteProperty(cx, form_obj, name); old_obj = NULL; } else if (!currentHasHandler && accumulatedHasHandlers) { /* Don't add the current form element to the array. */ goto fail; } } } if (old_obj) { if (clasp == &lm_input_class) { /* Make an array out of the previous element and this one. */ array = JS_malloc(cx, sizeof *array); if (!array) goto fail; XP_BZERO(array, sizeof *array); /* * Lock old_obj temporarily until we remove it from form_obj * and add it as a property of the radio button array. */ JS_LockGCThing(cx, old_obj); JS_DeleteProperty(cx, form_obj, name); /* XXXbe use JS_InitClass instead of this! */ array_obj = JS_DefineObject(cx, form_obj, name, &lm_input_array_class, NULL, JSPROP_ENUMERATE|JSPROP_READONLY); if (array_obj && !JS_SetPrivate(cx, array_obj, array)) array_obj = NULL; if (array_obj && !JS_DefineProperties(cx, array_obj, input_array_props)) { array_obj = NULL; } if (!array_obj) { JS_UnlockGCThing(cx, old_obj); JS_free(cx, array); goto fail; } array->base_decoder = HOLD_BACK_COUNT(decoder); array->base_type = base->type; /* Insert old_obj (referred to by val) into the array. */ if (!JS_DefineElement(cx, array_obj, (jsint) array->length, val, NULL, NULL, JSPROP_ENUMERATE|JSPROP_READONLY)) { JS_UnlockGCThing(cx, old_obj); goto fail; } array->length++; JS_UnlockGCThing(cx, old_obj); } else { array_obj = old_obj; array = (JSInputArray *)base; } /* ugly hack to prevent rebinding in lm_AddFormElement */ name = NULL; if (!JS_DefineElement(cx, array_obj, (jsint) array->length, OBJECT_TO_JSVAL(obj), NULL, NULL, JSPROP_ENUMERATE|JSPROP_READONLY)) { goto fail; } array->length++; } } input->input_decoder = HOLD_BACK_COUNT(decoder); input->input_type = type; input->input_object = obj; input->index = element_index; /* * get the form_element again incase it changed when we release the * layout lock */ form_element = LO_GetFormElementByIndex(form_data, element_id); if (form_element) form_element->mocha_object = obj; if (!lm_AddFormElement(cx, form_obj, obj, name, input->index)) { /* XXX undefine name if it's non-null? */ } LM_PutMochaDecoder(decoder); XP_FREEIF(name); return obj; fail: LM_PutMochaDecoder(decoder); XP_FREEIF(name); return NULL; } JSBool lm_InitInputClasses(MochaDecoder *decoder) { JSContext *cx; JSObject *prototype; cx = decoder->js_context; prototype = JS_InitClass(cx, decoder->window_object, decoder->event_receiver_prototype, &lm_input_class, Input, 0, input_props, input_methods, NULL, NULL); if (!prototype) return JS_FALSE; decoder->input_prototype = prototype; prototype = JS_InitClass(cx, decoder->window_object, NULL, &lm_option_class, Option, 0, option_props, NULL, NULL, NULL); if (!prototype) return JS_FALSE; decoder->option_prototype = prototype; return JS_TRUE; } #define MAX_KEY_NUM 256 #define KEY_STATE_DOWN 0x00000001 #define KEY_STATE_UP 0x00000002 #define KEY_STATE_PRESS 0x00000004 /* user is mousing over a link */ #define KEY_STATE_CANCEL 0x00000008 /* user is mousing out of a link */ static uint8 key_state[MAX_KEY_NUM]; /* We need to look here to see if any KEYPRESS events coming in were cancelled * at the KEYDOWN phase and should be blocked. We also need to use the KEYDOWN * and KEYUP messages to update this state. After this we can normally process * through lm_InputEvent. */ JSBool lm_KeyInputEvent(MWContext *context, LO_Element *element, JSEvent *pEvent, jsval *rval) { JSBool ok = JS_TRUE; if (pEvent->which > 255) return lm_InputEvent(context, element, pEvent, rval); switch (pEvent->type) { case EVENT_KEYDOWN: key_state[pEvent->which] = (uint8)KEY_STATE_DOWN; break; case EVENT_KEYPRESS: if (key_state[pEvent->which] == KEY_STATE_CANCEL) { *rval = BOOLEAN_TO_JSVAL(JS_FALSE); LO_UnlockLayout(); return JS_TRUE; } key_state[pEvent->which] = (uint8)KEY_STATE_PRESS; break; case EVENT_KEYUP: key_state[pEvent->which] = (uint8)KEY_STATE_UP; break; default: break; } ok = lm_InputEvent(context, element, pEvent, rval); if (pEvent->type == EVENT_KEYDOWN && *rval == JSVAL_FALSE) key_state[pEvent->which] = (uint8)KEY_STATE_CANCEL; return ok; } #define MAX_MOUSE_NUM 4 #define MOUSE_STATE_DOWN 0x00000001 #define MOUSE_STATE_UP 0x00000002 #define MOUSE_STATE_CANCEL 0x00000004 #define MOUSE_STATE_DBLCLICK 0x00000008 static uint8 mouse_state[MAX_MOUSE_NUM]; /* If a mousedown is cancelled we do not allow mouseups to do anything. This is * because all Navigator mouse responses are based on a down followed by an up. * This has the net effect of returning false from all mouseups if the previous * mousedown was cancelled. */ JSBool lm_MouseInputEvent(MWContext *context, LO_Element *element, JSEvent *pEvent, jsval *rval) { JSBool ok = JS_TRUE; JSEvent *dcEvent; switch (pEvent->type) { case EVENT_MOUSEDOWN: mouse_state[pEvent->which] = (uint8)MOUSE_STATE_DOWN; break; case EVENT_DBLCLICK: mouse_state[pEvent->which] = (uint8)MOUSE_STATE_DBLCLICK; pEvent->type = EVENT_MOUSEDOWN; break; default: break; } ok = lm_InputEvent(context, element, pEvent, rval); switch (pEvent->type) { case EVENT_MOUSEDOWN: if (*rval == JSVAL_FALSE) mouse_state[pEvent->which] = (uint8)MOUSE_STATE_CANCEL; break; case EVENT_MOUSEUP: if (mouse_state[pEvent->which] == MOUSE_STATE_CANCEL) { *rval = BOOLEAN_TO_JSVAL(JS_FALSE); } else if (*rval != JSVAL_FALSE && mouse_state[pEvent->which] == MOUSE_STATE_DBLCLICK) { dcEvent = XP_NEW_ZAP(JSEvent); dcEvent->type = EVENT_DBLCLICK; dcEvent->x = pEvent->x; dcEvent->y = pEvent->y; dcEvent->docx = pEvent->docx; dcEvent->docy = pEvent->docy; dcEvent->screenx = pEvent->screenx; dcEvent->screeny = pEvent->screeny; dcEvent->which = pEvent->which; dcEvent->modifiers = pEvent->modifiers; dcEvent->layer_id = pEvent->layer_id; LO_LockLayout(); ok = lm_InputEvent(context, element, dcEvent, rval); if (!dcEvent->saved) XP_FREE(dcEvent); } mouse_state[pEvent->which] = (uint8)MOUSE_STATE_UP; break; default: break; } return ok; } /* * OK, we assume our caller has locked layout so that we can hold * on to the element pointer. As soon as we are done with the * element pointer it is up to us to make sure we unlock layout. * Unlock layout before we call lm_SendEvent() so that we don't go * re-entrant into the mozilla thread (and also so we hold the * lock for as little time as possible) */ JSBool lm_InputEvent(MWContext *context, LO_Element *element, JSEvent *pEvent, jsval *rval) { JSContext *cx; MochaDecoder *decoder = NULL; JSBool ok; LO_AnchorData *anchor; JSObject *obj; JSDocument *doc; JSEventCapturer *cap; JSEventReceiver *rec=NULL; JSInputHandler *handler=NULL; LO_FormElementData *data; lo_FormData *form_data; JSString *str; char *re_input_bytes = NULL; JSBool multiline = JS_FALSE; int16 type; JSBool event_receiver_type = JS_FALSE; int32 layer_id, active_layer_id; *rval = JSVAL_VOID; cx = context->mocha_context; if (!cx) { LO_UnlockLayout(); return JS_TRUE; } /* * If the event is has no element and is one of the event types listed * in the if statement it is being sent to the layer or window. Handle * these first. */ if (!element && (pEvent->type == EVENT_FOCUS || pEvent->type == EVENT_BLUR || pEvent->type == EVENT_MOUSEOVER || pEvent->type == EVENT_MOUSEOUT)) { if (pEvent->layer_id == LO_DOCUMENT_LAYER_ID) { decoder = LM_GetMochaDecoder(context); if (!decoder) { LO_UnlockLayout(); return JS_FALSE; } /* Send event to the window. */ obj = decoder->window_object; LO_UnlockLayout(); if (decoder->event_mask & pEvent->type) { ok = JS_TRUE; } else { decoder->event_mask |= pEvent->type; ok = lm_SendEvent(context, obj, pEvent, rval); decoder->event_mask &= ~pEvent->type; } LM_PutMochaDecoder(decoder); } else { /* Send event to the layer matching the layer_id. */ obj = LO_GetLayerMochaObjectFromId(context, pEvent->layer_id); LO_UnlockLayout(); if (!obj) return JS_FALSE; cap = JS_GetPrivate(cx, obj); if (!cap) return JS_FALSE; if (cap->base.event_mask & pEvent->type){ ok = JS_TRUE; } else { cap->base.event_mask |= pEvent->type; ok = lm_SendEvent(context, obj, pEvent, rval); cap->base.event_mask &= ~pEvent->type; } } return ok; } type = element ? element->type : LO_NONE; /* If we're over plain text its easier to do this now than in the switch */ if (type == LO_TEXT && !element->lo_text.anchor_href && LM_EventCaptureCheck(context, pEvent->type)) { type = LO_NONE; } switch (type) { case LO_TEXT: anchor = element->lo_text.text ? element->lo_text.anchor_href : 0; obj = anchor ? anchor->mocha_object : 0; if (!obj) { if (!LM_EventCaptureCheck(context, pEvent->type) || !anchor) { LO_UnlockLayout(); return JS_TRUE; } /* Reflect the anchor now because someone is capturing */ layer_id = LO_GetIdFromLayer(context, anchor->layer); active_layer_id = LM_GetActiveLayer(context); LM_SetActiveLayer(context, pEvent->layer_id); LO_EnumerateLinks(context, pEvent->layer_id); LM_SetActiveLayer(context, active_layer_id); obj = anchor->mocha_object; } re_input_bytes = (char *)element->lo_text.text; multiline = JS_TRUE; break; case LO_IMAGE: anchor = element->lo_image.image_attr ? element->lo_image.anchor_href : 0; if (anchor) { obj = anchor->mocha_object; } else { obj = element->lo_image.image_attr ? element->lo_image.mocha_object : 0; event_receiver_type = JS_TRUE; } if (!obj) { if (!LM_EventCaptureCheck(context, pEvent->type) || !element->lo_image.image_attr) { LO_UnlockLayout(); return JS_TRUE; } /* Reflect the object now because someone is capturing */ if (anchor) { layer_id = LO_GetIdFromLayer(context, anchor->layer); active_layer_id = LM_GetActiveLayer(context); LM_SetActiveLayer(context, layer_id); LO_EnumerateLinks(context, layer_id); LM_SetActiveLayer(context, active_layer_id); obj = anchor->mocha_object; } else { active_layer_id = LM_GetActiveLayer(context); LM_SetActiveLayer(context, element->lo_image.layer_id); LO_EnumerateImages(context, element->lo_image.layer_id); LM_SetActiveLayer(context, active_layer_id); obj = element->lo_image.mocha_object; event_receiver_type = JS_TRUE; } } break; case LO_FORM_ELE: obj = element->lo_form.element_data ? element->lo_form.mocha_object:0; if (!obj) { if (!LM_EventCaptureCheck(context, pEvent->type) || !element->lo_form.element_data) { LO_UnlockLayout(); return JS_TRUE; } /* Reflect the object now because someone is capturing */ active_layer_id = LM_GetActiveLayer(context); LM_SetActiveLayer(context, element->lo_form.layer_id); LO_EnumerateForms(context, element->lo_form.layer_id); form_data = LO_GetFormDataByID(context, element->lo_form.layer_id, element->lo_form.form_id); if (!form_data) { LM_SetActiveLayer(context, active_layer_id); LO_UnlockLayout(); return JS_TRUE; } LO_EnumerateFormElements(context, form_data); LM_SetActiveLayer(context, active_layer_id); obj = element->lo_form.mocha_object; } data = element->lo_form.element_data; switch (data->type) { case FORM_TYPE_TEXT: re_input_bytes = (char *)data->ele_text.current_text; break; case FORM_TYPE_TEXTAREA: #ifdef ENDER case FORM_TYPE_HTMLAREA: #endif /*ENDER*/ re_input_bytes = (char *)data->ele_textarea.current_text; multiline = JS_TRUE; break; case FORM_TYPE_SELECT_ONE: case FORM_TYPE_SELECT_MULT: { lo_FormElementSelectData *selectData; lo_FormElementOptionData *optionData; int32 i; selectData = &data->ele_select; optionData = (lo_FormElementOptionData *) selectData->options; for (i = 0; i < selectData->option_cnt; i++) { if (optionData[i].selected) { re_input_bytes = (char *)optionData[i].text_value; break; } } } break; } break; default: /* Any event over nothing or a non-reflectable layout element (linefeeds, * horizontal rules, etc) goes to the main document or layer document. */ decoder = LM_GetMochaDecoder(context); if (!decoder) { LO_UnlockLayout(); return JS_FALSE; } obj = lm_GetDocumentFromLayerId(decoder, pEvent->layer_id); LO_UnlockLayout(); LM_PutMochaDecoder(decoder); if (!obj) return JS_FALSE; doc = JS_GetPrivate(cx, obj); if (!doc) return JS_FALSE; if (doc->capturer.base.event_mask & pEvent->type) { ok = JS_TRUE; } else { doc->capturer.base.event_mask |= pEvent->type; ok = lm_SendEvent(context, obj, pEvent, rval); doc->capturer.base.event_mask &= ~pEvent->type; } return ok; } /* whether we got an object or not we are done with the element ptr */ LO_UnlockLayout(); if (!obj) { XP_ASSERT(0); return JS_FALSE; } /* Images do not have the same base private data structure as the * other input elements do so we must use a different private data * structs. Eventually these should be unified for all event receivers. */ if (event_receiver_type) { rec = JS_GetPrivate(cx, obj); if (!rec || rec->event_mask & pEvent->type) return JS_FALSE; } else { handler = JS_GetPrivate(cx, obj); if (!handler || handler->event_mask & pEvent->type) return JS_FALSE; } decoder = LM_GetMochaDecoder(context); if (!decoder) return JS_FALSE; decoder->event_receiver = obj; LM_PutMochaDecoder(decoder); if (re_input_bytes) { str = lm_LocalEncodingToStr(context, re_input_bytes); if (!str) return JS_FALSE; JS_SetRegExpInput(cx, str, multiline); } if (event_receiver_type) rec->event_mask |= pEvent->type; else handler->event_mask |= pEvent->type; ok = lm_SendEvent(context, obj, pEvent, rval); if (event_receiver_type) rec->event_mask &= ~pEvent->type; else handler->event_mask &= ~pEvent->type; if (re_input_bytes) JS_ClearRegExpStatics(cx); return ok; }