/* -*- 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. */ /* * Image reflection and event notification * * Scott Furman, 3/30/96 */ #include "lm.h" #include "lo_ele.h" #include "prtypes.h" #include "pa_tags.h" #include "layout.h" #define IL_CLIENT #include "libimg.h" /* Image Library public API. */ enum image_array_slot { IMAGE_ARRAY_LENGTH = -1 }; static JSPropertySpec image_array_props[] = { {lm_length_str, IMAGE_ARRAY_LENGTH, JSPROP_ENUMERATE | JSPROP_READONLY | JSPROP_PERMANENT}, {0} }; extern JSClass lm_image_array_class; PR_STATIC_CALLBACK(JSBool) image_array_getProperty(JSContext *cx, JSObject *obj, jsval id, jsval *vp) { JSObjectArray *array; MochaDecoder *decoder; MWContext *context; jsint count, slot; LO_ImageStruct *image; int32 active_layer_id; if (!JSVAL_IS_INT(id)) return JS_TRUE; slot = JSVAL_TO_INT(id); array = JS_GetInstancePrivate(cx, obj, &lm_image_array_class, NULL); if (!array) return JS_TRUE; decoder = array->decoder; context = decoder->window_context; if (!context) return JS_TRUE; LO_LockLayout(); switch (slot) { case IMAGE_ARRAY_LENGTH: active_layer_id = LM_GetActiveLayer(context); LM_SetActiveLayer(context, array->layer_id); count = LO_EnumerateImages(context, array->layer_id); LM_SetActiveLayer(context, active_layer_id); if (count > array->length) array->length = count; *vp = INT_TO_JSVAL(count); break; default: if (slot < 0) { /* Don't mess with user-defined or method properties. */ LO_UnlockLayout(); return JS_TRUE; } if (slot >= array->length) array->length = slot + 1; image = LO_GetImageByIndex(context, array->layer_id, (uint)slot); if (image) { *vp = OBJECT_TO_JSVAL(LM_ReflectImage(context, image, NULL, array->layer_id, (uint)slot)); } break; } LO_UnlockLayout(); return JS_TRUE; } PR_STATIC_CALLBACK(void) image_array_finalize(JSContext *cx, JSObject *obj) { JSObjectArray *array; array = JS_GetPrivate(cx, obj); if (!array) return; DROP_BACK_COUNT(array->decoder); JS_free(cx, array); } JSClass lm_image_array_class = { "ImageArray", JSCLASS_HAS_PRIVATE, JS_PropertyStub, JS_PropertyStub, image_array_getProperty, image_array_getProperty, JS_EnumerateStub, JS_ResolveStub, JS_ConvertStub, image_array_finalize }; enum image_slot { IMAGE_NAME = -2, IMAGE_SRC = -3, IMAGE_LOWSRC = -4, IMAGE_X = -5, IMAGE_Y = -6, IMAGE_HEIGHT = -7, IMAGE_WIDTH = -8, IMAGE_BORDER = -9, IMAGE_VSPACE = -10, IMAGE_HSPACE = -11, IMAGE_COMPLETE = -12 }; static JSPropertySpec image_props[] = { {"name", IMAGE_NAME, JSPROP_ENUMERATE | JSPROP_READONLY}, {"src", IMAGE_SRC, JSPROP_ENUMERATE}, {"lowsrc", IMAGE_LOWSRC, JSPROP_ENUMERATE}, {"x", IMAGE_X, JSPROP_ENUMERATE | JSPROP_READONLY}, {"y", IMAGE_Y, JSPROP_ENUMERATE | JSPROP_READONLY}, {"height", IMAGE_HEIGHT, JSPROP_ENUMERATE | JSPROP_READONLY}, {"width", IMAGE_WIDTH, JSPROP_ENUMERATE | JSPROP_READONLY}, {"border", IMAGE_BORDER, JSPROP_ENUMERATE | JSPROP_READONLY}, {"vspace", IMAGE_VSPACE, JSPROP_ENUMERATE | JSPROP_READONLY}, {"hspace", IMAGE_HSPACE, JSPROP_ENUMERATE | JSPROP_READONLY}, {"complete", IMAGE_COMPLETE, JSPROP_ENUMERATE | JSPROP_READONLY}, {0} }; /* * Base image element type. */ typedef struct JSImage { JSEventReceiver receiver; MochaDecoder *decoder; LO_ImageStruct *image_data; /* 0 unless made by new Image() */ uint8 pending_events; int32 layer_id; uint index; JSBool complete; /* Finished loading or aborted */ JSString *name; } JSImage; #define GET_IMAGE_DATA(context, image) \ ((image)->image_data ? (image)->image_data \ : LO_GetImageByIndex((context), (image)->layer_id, (image)->index)) extern JSClass lm_image_class; /* * Force the mozilla event queue to flush to make sure any image-set-src * events have been processed */ PR_STATIC_CALLBACK(void) lm_img_src_sync(void * data) { } PR_STATIC_CALLBACK(JSBool) image_getProperty(JSContext *cx, JSObject *obj, jsval id, jsval *vp) { JSImage *image; LO_ImageStruct *image_data; enum image_slot image_slot; JSString *str; jsint slot; if (!JSVAL_IS_INT(id)) return JS_TRUE; slot = JSVAL_TO_INT(id); image = JS_GetInstancePrivate(cx, obj, &lm_image_class, NULL); if (!image) return JS_TRUE; image_data = GET_IMAGE_DATA(image->decoder->window_context, image); if (!image_data) return JS_TRUE; /* Try to handle this case gracefully. */ image_slot = slot; if (image_slot == IMAGE_SRC || image_slot == IMAGE_LOWSRC) { if (!lm_CheckPermissions(cx, obj, JSTARGET_UNIVERSAL_BROWSER_READ)) return JS_FALSE; } switch (image_slot) { case IMAGE_NAME: if (image->name) *vp = STRING_TO_JSVAL(image->name); else *vp = JSVAL_NULL; break; case IMAGE_SRC: if (image_data->pending_mocha_event) { ET_moz_CallFunction(lm_img_src_sync, NULL); image_data->pending_mocha_event = PR_FALSE; } str = JS_NewStringCopyZ(cx, (char*)image_data->image_url); if (!str) return JS_FALSE; *vp = STRING_TO_JSVAL(str); break; case IMAGE_LOWSRC: if (image_data->pending_mocha_event) { ET_moz_CallFunction(lm_img_src_sync, NULL); image_data->pending_mocha_event = PR_FALSE; } str = JS_NewStringCopyZ(cx, (char*)image_data->lowres_image_url); if (!str) return JS_FALSE; *vp = STRING_TO_JSVAL(str); break; case IMAGE_X: *vp = INT_TO_JSVAL(image_data->x + image_data->x_offset); break; case IMAGE_Y: *vp = INT_TO_JSVAL(image_data->y + image_data->y_offset); break; case IMAGE_HEIGHT: *vp = INT_TO_JSVAL(image_data->height); break; case IMAGE_WIDTH: *vp = INT_TO_JSVAL(image_data->width); break; case IMAGE_BORDER: *vp = INT_TO_JSVAL(image_data->border_width); break; case IMAGE_HSPACE: *vp = INT_TO_JSVAL(image_data->border_horiz_space); break; case IMAGE_VSPACE: *vp = INT_TO_JSVAL(image_data->border_vert_space); break; case IMAGE_COMPLETE: *vp = BOOLEAN_TO_JSVAL(image->complete); break; default: /* Don't mess with a user-defined or method property. */ return JS_TRUE; } return JS_TRUE; } static JSBool image_set_src(JSImage *image, const char *str, LO_ImageStruct *image_data) { MochaDecoder *decoder; MWContext *context; IL_GroupContext *img_cx; decoder = image->decoder; context = decoder->window_context; img_cx = decoder->image_context; if (!context) return JS_TRUE; image_data->pending_mocha_event = PR_TRUE; image_data->image_attr->attrmask |= LO_ATTR_MOCHA_IMAGE; ET_il_GetImage(str, context, img_cx, image_data, NET_DONT_RELOAD); return JS_TRUE; } PR_STATIC_CALLBACK(JSBool) image_setProperty(JSContext *cx, JSObject *obj, jsval id, jsval *vp) { JSBool ok=JS_FALSE; JSImage *image; MochaDecoder *decoder; MWContext *context; LO_ImageStruct *image_data; enum image_slot image_slot; const char *url; jsint slot; image = JS_GetInstancePrivate(cx, obj, &lm_image_class, NULL); if (!image) return JS_TRUE; decoder = image->decoder; context = decoder->window_context; if (!context) return JS_TRUE; if (!JSVAL_IS_INT(id)) return JS_TRUE; slot = JSVAL_TO_INT(id); image_data = GET_IMAGE_DATA(context, image); if (!image_data) return JS_TRUE; /* Try to handle this case gracefully. */ image_slot = slot; switch (image_slot) { case IMAGE_SRC: case IMAGE_LOWSRC: if (!lm_CheckPermissions(cx, obj, JSTARGET_UNIVERSAL_BROWSER_WRITE)) return JS_FALSE; if (JSVAL_IS_NULL(*vp)) { url = NULL; } else { if (!JSVAL_IS_STRING(*vp) && !JS_ConvertValue(cx, *vp, JSTYPE_STRING, vp)) return JS_FALSE; url = JS_GetStringBytes(JSVAL_TO_STRING(*vp)); url = lm_CheckURL(cx, url, JS_TRUE); /* will allocate new string */ if (!url) return JS_FALSE; } if (image_slot == IMAGE_SRC) { ok = image_set_src(image, url, image_data); } else if (url) { ok = lm_SaveParamString(cx, &image_data->lowres_image_url, url); } if (url) XP_FREE((void *) url); if (!ok) return JS_FALSE; /* * don't call image_getProperty so that we don't immediately * turn around and block */ return JS_TRUE; break; case IMAGE_NAME: case IMAGE_X: case IMAGE_Y: case IMAGE_HEIGHT: case IMAGE_WIDTH: case IMAGE_BORDER: case IMAGE_VSPACE: case IMAGE_HSPACE: case IMAGE_COMPLETE: /* These are immutable. */ break; } return image_getProperty(cx, obj, id, vp); } PR_STATIC_CALLBACK(void) image_finalize(JSContext *cx, JSObject *obj) { JSImage *image; LO_ImageStruct *image_data; MochaDecoder *decoder; MWContext *context; image = JS_GetPrivate(cx, obj); if (!image) return; image_data = image->image_data; decoder = image->decoder; context = decoder->window_context; if (image_data) { /* If this is a layer background image or a reflection of an existing layout image, then layout will take care of destroying the image. For anonymous images, however, we need to handle destruction here. */ if (!image_data->layer) { ET_PostFreeImageElement(context, image_data); XP_FREE(image_data->image_attr); XP_FREE(image_data); } } else { if (context) { LO_LockLayout(); image_data = LO_GetImageByIndex(context, image->layer_id, image->index); if (image_data && image_data->mocha_object == obj) image_data->mocha_object = NULL; LO_UnlockLayout(); } } DROP_BACK_COUNT(decoder); JS_UnlockGCThing(cx, image->name); JS_free(cx, image); } JSClass lm_image_class = { "Image", JSCLASS_HAS_PRIVATE, JS_PropertyStub, JS_PropertyStub, image_getProperty, image_setProperty, JS_EnumerateStub, JS_ResolveStub, JS_ConvertStub, image_finalize }; /* Fill in native, private part of JS image */ static JSImage * init_image_object(JSContext *cx, JSObject *obj, LO_ImageStruct *image_data) { JSImage *image; MochaDecoder *decoder; image = JS_malloc(cx, sizeof *image); if (!image) return NULL; XP_BZERO(image, sizeof *image); image->image_data = image_data; decoder = JS_GetPrivate(cx, JS_GetGlobalObject(cx)); image->decoder = HOLD_BACK_COUNT(decoder); image_data->mocha_object = obj; /* Events are never blocked for anonymous images since there is no associated layout. */ image->pending_events = PR_BIT(LM_IMGUNBLOCK); if (!JS_SetPrivate(cx, obj, image)) return NULL; return image; } JSObject * lm_NewImage(JSContext *cx, LO_ImageStruct *image_data) { JSObject *obj, *outer_obj; MochaDecoder *decoder; decoder = JS_GetPrivate(cx, JS_GetGlobalObject(cx)); outer_obj = lm_GetOuterObject(decoder); obj = JS_NewObject(cx, &lm_image_class, decoder->image_prototype, outer_obj); if (!init_image_object(cx, obj, image_data)) return NULL; return obj; } PR_STATIC_CALLBACK(JSBool) Image(JSContext *cx, JSObject *obj, uint argc, jsval *argv, jsval *rval) { jsint width, height; LO_ImageStruct *image_data; XP_ASSERT(JS_InstanceOf(cx, obj, &lm_image_class, NULL)); height = width = 0; if (argc > 0) { if (argc != 2) { JS_ReportError(cx, lm_argc_err_str); return JS_FALSE; } if (!JSVAL_IS_INT(argv[0]) || !JSVAL_IS_INT(argv[1])) { return JS_FALSE; } width = JSVAL_TO_INT(argv[0]); height = JSVAL_TO_INT(argv[1]); } /* Allocate dummy layout structure. This is not really used by layout, but the front-ends and the imagelib need it as a handle on an image instance. */ image_data = XP_NEW_ZAP(LO_ImageStruct); if (!image_data) { JS_ReportOutOfMemory(cx); return JS_FALSE; } image_data->image_attr = XP_NEW_ZAP(LO_ImageAttr); if (!image_data->image_attr) { XP_FREE(image_data); JS_ReportOutOfMemory(cx); return JS_FALSE; } image_data->type = LO_IMAGE; /* Fake layout ID, guaranteed not to match any real layout element */ image_data->ele_id = -1; if (!init_image_object(cx, obj, image_data)) { XP_FREE(image_data->image_attr); XP_FREE(image_data); return JS_FALSE; } /* Process arguments */ /* Width & Height */ if (argc == 2) { image_data->width = (int)width; image_data->height = (int)height; } return JS_TRUE; } static JSObject * reflect_image_array(MochaDecoder *decoder, JSObject *document) { JSContext *cx; JSObjectArray *array; JSObject *obj; JSDocument *doc; cx = decoder->js_context; doc = JS_GetPrivate(cx, document); if (!doc) return NULL; array = JS_malloc(cx, sizeof *array); if (!array) return NULL; XP_BZERO(array, sizeof *array); obj = JS_NewObject(cx, &lm_image_array_class, NULL, document); if (!obj || !JS_SetPrivate(cx, obj, array)) { LM_PutMochaDecoder(decoder); return NULL; } if (!JS_DefineProperties(cx, obj, image_array_props)) return NULL; array->decoder = HOLD_BACK_COUNT(decoder); array->layer_id = doc->layer_id; return obj; } JSObject * lm_GetImageArray(MochaDecoder *decoder, JSObject *document) { JSObject *obj; JSDocument *doc; doc = JS_GetPrivate(decoder->js_context, document); if (!doc) return NULL; obj = doc->images; if (obj) return obj; obj = reflect_image_array(decoder, document); doc->images = obj; return obj; } JSObject * LM_ReflectImage(MWContext *context, LO_ImageStruct *image_data, PA_Tag * tag, int32 layer_id, uint index) { JSObject *obj, *array_obj, *outer_obj, *document; MochaDecoder *decoder; JSContext *cx; JSImage *image; PA_Block name = NULL; lo_TopState *top_state; PRHashTable *map; image_data = LO_GetImageByIndex(context, layer_id, index); XP_ASSERT(image_data); if (! image_data) return NULL; obj = image_data->mocha_object; if (obj) return obj; decoder = LM_GetMochaDecoder(context); if (!decoder) return NULL; cx = decoder->js_context; top_state = lo_GetMochaTopState(context); if (top_state->resize_reload) { map = lm_GetIdToObjectMap(decoder); if (map) obj = (JSObject *)PR_HashTableLookup(map, LM_GET_MAPPING_KEY(LM_IMAGES, layer_id, index)); if (obj) { image_data->mocha_object = obj; goto done; } } /* Get the document object that will hold this link */ document = lm_GetDocumentFromLayerId(decoder, layer_id); if (!document) goto done; array_obj = lm_GetImageArray(decoder, document); if (!array_obj) goto done; image = JS_malloc(cx, sizeof *image); if (!image) goto done; XP_BZERO(image, sizeof *image); /* if we got a tag passed in try to get the name out of there */ name = lo_FetchParamValue(context, tag, PARAM_NAME); outer_obj = lm_GetOuterObject(decoder); obj = JS_NewObject(cx, &lm_image_class, decoder->image_prototype, outer_obj); if (!obj || !JS_SetPrivate(cx, obj, image)) { JS_free(cx, image); goto done; } if (name) { JSObject *doc_obj; extern JSClass lm_form_class; if (!JS_DefineProperty(cx, outer_obj, (const char *) name, OBJECT_TO_JSVAL(obj), NULL, NULL, JSPROP_ENUMERATE|JSPROP_READONLY)) { obj = NULL; goto done; } /* XXX backward compatibility with 3.0 bug: lo_BlockedImageLayout would eagerly reflect images outside of any active form, so they'd end up in document scope. */ if (JS_GetClass(cx, outer_obj) == &lm_form_class && (doc_obj = JS_GetParent(cx, outer_obj)) != NULL && !JS_DefineProperty(cx, doc_obj, (const char *) name, OBJECT_TO_JSVAL(obj), NULL, NULL, JSPROP_ENUMERATE|JSPROP_READONLY)) { obj = NULL; goto done; } } image->decoder = HOLD_BACK_COUNT(decoder); image->index = index; image->layer_id = layer_id; image->name = JS_NewStringCopyZ(cx, (const char *) name); if (!JS_LockGCThing(cx, image->name)) { obj = NULL; goto done; } image_data->mocha_object = obj; if (!lm_AddObjectToArray(cx, array_obj, (const char *) name, index, obj)) { obj = NULL; goto done; } /* Put it in the index to object hash table */ map = lm_GetIdToObjectMap(decoder); if (map) PR_HashTableAdd(map, LM_GET_MAPPING_KEY(LM_IMAGES, layer_id, index), obj); /* OK, we've got our image, see if there are any event handlers * defined with it */ if(tag) { PA_Block onload = lo_FetchParamValue(context, tag, PARAM_ONLOAD); PA_Block onabort = lo_FetchParamValue(context, tag, PARAM_ONABORT); PA_Block onerror = lo_FetchParamValue(context, tag, PARAM_ONERROR); PA_Block onmousedown = lo_FetchParamValue(context, tag, PARAM_ONMOUSEDOWN); PA_Block onmouseup = lo_FetchParamValue(context, tag, PARAM_ONMOUSEUP); PA_Block id = lo_FetchParamValue(context, tag, PARAM_ID); /* don't hold the layout lock across compiles */ LO_UnlockLayout(); if (onload != NULL) { (void) lm_CompileEventHandler(decoder, id, tag->data, tag->newline_count, obj, PARAM_ONLOAD, onload); PA_FREE(onload); } if (onabort != NULL) { (void) lm_CompileEventHandler(decoder, id, tag->data, tag->newline_count, obj, PARAM_ONABORT, onabort); PA_FREE(onabort); } if (onerror != NULL) { (void) lm_CompileEventHandler(decoder, id, tag->data, tag->newline_count, obj, PARAM_ONERROR, onerror); PA_FREE(onerror); } if (onmousedown != NULL) { (void) lm_CompileEventHandler(decoder, id, tag->data, tag->newline_count, obj, PARAM_ONMOUSEDOWN, onmousedown); PA_FREE(onmousedown); } if (onmouseup != NULL) { (void) lm_CompileEventHandler(decoder, id, tag->data, tag->newline_count, obj, PARAM_ONMOUSEUP, onmouseup); PA_FREE(onmouseup); } if (id) PA_FREE(id); LO_LockLayout(); } done: if(name) PA_FREE(name); LM_PutMochaDecoder(decoder); return obj; } void lm_ProcessImageEvent(MWContext *context, JSObject *obj, LM_ImageEvent event) { uint event_mask; jsval result; JSImage *image; image = JS_GetPrivate(context->mocha_context, obj); if (!image) return; image->pending_events |= PR_BIT(event); /* Special event used to trigger deferred events */ if (! (image->pending_events & PR_BIT(LM_IMGUNBLOCK))) return; for (event = LM_IMGLOAD; event <= LM_LASTEVENT; event++) { event_mask = PR_BIT(event); if (image->pending_events & event_mask) { JSEvent *pEvent; pEvent=XP_NEW_ZAP(JSEvent); image->pending_events &= ~event_mask; switch (event) { case LM_IMGLOAD: pEvent->type = EVENT_LOAD; image->complete = JS_TRUE; break; case LM_IMGABORT: pEvent->type = EVENT_ABORT; image->complete = JS_TRUE; break; case LM_IMGERROR: pEvent->type = EVENT_ERROR; image->complete = JS_TRUE; break; default: XP_ABORT(("Unknown image event")); } lm_SendEvent(context, obj, pEvent, &result); } } } JSBool lm_InitImageClass(MochaDecoder *decoder) { JSContext *cx; JSObject *prototype; cx = decoder->js_context; prototype = JS_InitClass(cx, decoder->window_object, decoder->event_receiver_prototype, &lm_image_class, Image, 0, image_props, NULL, NULL, NULL); if (!prototype) return JS_FALSE; decoder->image_prototype = prototype; return JS_TRUE; } /* Create an image context for anonymous images and attach it to the specified mocha decoder. */ JSBool lm_NewImageContext(MWContext *context, MochaDecoder *decoder) { IL_GroupContext *img_cx; IMGCB* img_cb; JMCException *exc = NULL; if (!decoder->image_context) { img_cb = IMGCBFactory_Create(&exc); /* JMC Module */ if (exc) { JMC_DELETE_EXCEPTION(&exc); /* XXX Should really return exception */ JS_ReportOutOfMemory(decoder->js_context); return JS_FALSE; } /* Create an Image Group Context. IL_NewGroupContext augments the reference count for the JMC callback interface. The opaque argument to IL_NewGroupContext is the Front End's display context, which will be passed back to all the Image Library's FE callbacks. */ img_cx = IL_NewGroupContext((void*)context, (IMGCBIF *)img_cb); if (!img_cx) { JS_ReportOutOfMemory(decoder->js_context); return JS_FALSE; } /* Attach the IL_GroupContext to the mocha decoder. */ decoder->image_context = img_cx; /* Allow the context to observe the decoder's image context. */ ET_il_SetGroupObserver(context, decoder->image_context, context, JS_TRUE); } return JS_TRUE; }