/* -*- 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 reflection of the current Navigator Document.
 *
 * Brendan Eich, 9/8/95
 */
#include "lm.h"
#include "prtypes.h"
#include "plhash.h"
#include "prmem.h"
#include "prtime.h"
#include "xp.h"
#include "lo_ele.h"
#include "shist.h"
#include "structs.h"            /* for MWContext */
#include "layout.h"             /* included via -I../layout */
#include "mkaccess.h"           /* from ../libnet */
#include "mkcache.h"            /* from ../libnet */
#include "pa_parse.h"           /* included via -I../libparse */
#include "pa_tags.h"            /* included via -I../libparse */
#include "cert.h"               /* for CERT_DupCertificate */
#include "libstyle.h"
#include "prthread.h"
#include "np.h"
#ifdef JAVA
#include "jsjava.h"
#endif

enum doc_slot {
    DOC_LENGTH          = -1,
    DOC_ELEMENTS        = -2,
    DOC_FORMS           = -3,
    DOC_LINKS           = -4,
    DOC_ANCHORS         = -5,
    DOC_APPLETS         = -6,
    DOC_EMBEDS          = -7,
    DOC_TITLE           = -8,
    DOC_URL             = -9,
    DOC_REFERRER        = -10,
    DOC_LAST_MODIFIED   = -11,
    DOC_COOKIE          = -12,
    DOC_DOMAIN          = -13,
    /* slots below this line are not secured */
    DOC_IMAGES          = -14,
    DOC_LAYERS          = -15,
    DOC_LOADED_DATE     = -16,
    DOC_BG_COLOR        = -17,
    DOC_FG_COLOR        = -18,
    DOC_LINK_COLOR      = -19,
    DOC_VLINK_COLOR     = -20,
    DOC_ALINK_COLOR     = -21,
    DOC_WIDTH           = -22,
    DOC_HEIGHT          = -23
};

#define IS_SECURE_DOC_SLOT(s) (DOC_DOMAIN <= (s) && (s) <= DOC_LENGTH) 

static JSPropertySpec doc_props[] = {
    {lm_length_str,  DOC_LENGTH,        JSPROP_READONLY},
    {"elements",     DOC_ELEMENTS,      JSPROP_READONLY},
    {lm_forms_str,   DOC_FORMS,         JSPROP_ENUMERATE|JSPROP_READONLY},
    {lm_links_str,   DOC_LINKS,         JSPROP_ENUMERATE|JSPROP_READONLY},
    {lm_anchors_str, DOC_ANCHORS,       JSPROP_ENUMERATE|JSPROP_READONLY},
    {lm_applets_str, DOC_APPLETS,       JSPROP_ENUMERATE|JSPROP_READONLY},
    {lm_embeds_str,  DOC_EMBEDS,        JSPROP_ENUMERATE|JSPROP_READONLY},
    {lm_plugins_str, DOC_EMBEDS,        JSPROP_READONLY},
    {lm_images_str,  DOC_IMAGES,        JSPROP_ENUMERATE|JSPROP_READONLY},
    {lm_layers_str,  DOC_LAYERS,        JSPROP_ENUMERATE|JSPROP_READONLY},
    {"title",        DOC_TITLE,         JSPROP_ENUMERATE|JSPROP_READONLY},
    {"URL",          DOC_URL,           JSPROP_ENUMERATE|JSPROP_READONLY},
    {"referrer",     DOC_REFERRER,      JSPROP_ENUMERATE|JSPROP_READONLY},
    {"lastModified", DOC_LAST_MODIFIED, JSPROP_ENUMERATE|JSPROP_READONLY},
    {"loadedDate",   DOC_LOADED_DATE,   JSPROP_READONLY},
    {"cookie",       DOC_COOKIE,        JSPROP_ENUMERATE},
    {"domain",       DOC_DOMAIN,        JSPROP_ENUMERATE},
    {"bgColor",      DOC_BG_COLOR,      JSPROP_ENUMERATE},
    {"fgColor",      DOC_FG_COLOR,      JSPROP_ENUMERATE},
    {"linkColor",    DOC_LINK_COLOR,    JSPROP_ENUMERATE},
    {"vlinkColor",   DOC_VLINK_COLOR,   JSPROP_ENUMERATE},
    {"alinkColor",   DOC_ALINK_COLOR,   JSPROP_ENUMERATE},
    {"width",        DOC_WIDTH,         JSPROP_ENUMERATE},
    {"height",       DOC_HEIGHT,        JSPROP_ENUMERATE},
    {0}
};

#define LO_COLOR_TYPE(slot)                             \
    (((slot) == DOC_BG_COLOR) ? LO_COLOR_BG             \
     : ((slot) == DOC_FG_COLOR) ? LO_COLOR_FG           \
     : ((slot) == DOC_LINK_COLOR) ? LO_COLOR_LINK       \
     : ((slot) == DOC_VLINK_COLOR) ? LO_COLOR_VLINK     \
     : ((slot) == DOC_ALINK_COLOR) ? LO_COLOR_ALINK     \
     : -1)


extern PRThread *mozilla_thread;
extern JSClass lm_document_class;
extern JSClass lm_form_class;
extern JSClass *lm_java_clasp;

JSContext *lm_writing_context;
static int32 writing_context_counter;

PR_STATIC_CALLBACK(JSBool)
doc_getProperty(JSContext *cx, JSObject *obj, jsval id, jsval *vp)
{
    JSDocument *doc;
    MochaDecoder *decoder;
    MWContext *context;
    History_entry *he = NULL;
    JSString *str = NULL;
    int64 prsec, scale, prusec;
    PRExplodedTime prtime;
    char *cookie, buf[100];
    const char *domain, *origin;
    uint32 type;
    LO_Color rgb, *bg_color;
    jsint slot;
    CL_Layer *layer;
    int32 active_layer_id;

    doc = JS_GetInstancePrivate(cx, obj, &lm_document_class, NULL);
    if (!doc)
        return JS_TRUE;
    decoder = doc->decoder;

    slot = JSVAL_IS_INT(id) ? JSVAL_TO_INT(id) : 0;

    if (IS_SECURE_DOC_SLOT(slot) ||
        (slot == 0 &&
         JSVAL_IS_OBJECT(*vp) && 
         JSVAL_TO_OBJECT(*vp) && 
         (JS_InstanceOf(cx, JSVAL_TO_OBJECT(*vp), &lm_form_class, NULL) ||
          (lm_java_clasp &&
	   JS_InstanceOf(cx, JSVAL_TO_OBJECT(*vp), lm_java_clasp, NULL)))))
    {
        if (!lm_CheckPermissions(cx, obj, JSTARGET_UNIVERSAL_BROWSER_READ))
            return JS_FALSE;
    }

    if (!JSVAL_IS_INT(id))
        return JS_TRUE;

    context = decoder->window_context;
    if (!context)
        return JS_TRUE;

    LO_LockLayout();
    if(decoder->doc_id != XP_DOCID(context)) {
        LO_UnlockLayout();
#ifdef DEBUG_chouck
        XP_TRACE(("DOCID: in doc_getProperty"));
#endif
        return JS_FALSE;
    }

    switch (slot) {
      case DOC_FORMS:
        *vp = OBJECT_TO_JSVAL(lm_GetFormArray(decoder, obj));
        active_layer_id = LM_GetActiveLayer(context);
        LM_SetActiveLayer(context, doc->layer_id);
        (void) LO_EnumerateForms(context, doc->layer_id);
        LM_SetActiveLayer(context, active_layer_id);
        LO_UnlockLayout();
        return JS_TRUE;

      case DOC_LINKS:
        *vp = OBJECT_TO_JSVAL(lm_GetLinkArray(decoder, obj));
        active_layer_id = LM_GetActiveLayer(context);
        LM_SetActiveLayer(context, doc->layer_id);
        (void) LO_EnumerateLinks(context, doc->layer_id);
        LM_SetActiveLayer(context, active_layer_id);
        LO_UnlockLayout();
        return JS_TRUE;

      case DOC_ANCHORS:
        *vp = OBJECT_TO_JSVAL(lm_GetNameArray(decoder, obj));
        active_layer_id = LM_GetActiveLayer(context);
        LM_SetActiveLayer(context, doc->layer_id);
        (void) LO_EnumerateNamedAnchors(context,doc->layer_id);
        LM_SetActiveLayer(context, active_layer_id);
        LO_UnlockLayout();
        return JS_TRUE;

      case DOC_APPLETS:
#ifdef JAVA
        if (LM_MOJA_OK != ET_InitMoja(context)) {
            LO_UnlockLayout();
            return JS_FALSE;
        }
        
        *vp = OBJECT_TO_JSVAL(lm_GetAppletArray(decoder, obj));
        active_layer_id = LM_GetActiveLayer(context);
        LM_SetActiveLayer(context, doc->layer_id);
        (void) LO_EnumerateApplets(context,doc->layer_id);
        LM_SetActiveLayer(context, active_layer_id);
        LO_UnlockLayout();
        return JS_TRUE;
#else
        LO_UnlockLayout();
        break;
#endif

      case DOC_EMBEDS:
#ifdef JAVA
        if (LM_MOJA_OK != ET_InitMoja(context)) {
            LO_UnlockLayout();
            return JS_FALSE;
        }

        *vp = OBJECT_TO_JSVAL(lm_GetEmbedArray(decoder, obj));
        active_layer_id = LM_GetActiveLayer(context);
        LM_SetActiveLayer(context, doc->layer_id);
        (void) LO_EnumerateEmbeds(context,doc->layer_id);
        LM_SetActiveLayer(context, active_layer_id);
        LO_UnlockLayout();
        return JS_TRUE;
#else
        LO_UnlockLayout();
        break;
#endif

      case DOC_IMAGES:
        *vp = OBJECT_TO_JSVAL(lm_GetImageArray(decoder, obj));
        active_layer_id = LM_GetActiveLayer(context);
        LM_SetActiveLayer(context, doc->layer_id);
        (void) LO_EnumerateImages(context,doc->layer_id);
        LM_SetActiveLayer(context, active_layer_id);
        LO_UnlockLayout();
        return JS_TRUE;

      case DOC_LAYERS:
        *vp = OBJECT_TO_JSVAL(lm_GetDocumentLayerArray(decoder, obj));
        LO_UnlockLayout();
        return JS_TRUE;

        /* XXX BUGBUG Need a story for some of these for a layer's document */
      case DOC_TITLE:
        he = SHIST_GetCurrent(&context->hist);
        str = lm_LocalEncodingToStr(context, he ? he->title : "");
        LO_UnlockLayout();
        break;

      case DOC_URL:
        he = SHIST_GetCurrent(&context->hist);
        str = JS_NewStringCopyZ(cx, he ? he->address : "");
        LO_UnlockLayout();
        break;

      case DOC_REFERRER:
        he = SHIST_GetCurrent(&context->hist);
        str = JS_NewStringCopyZ(cx, he ? he->referer : "");
        LO_UnlockLayout();
        break;

      case DOC_LAST_MODIFIED:
        he = SHIST_GetCurrent(&context->hist);
        if (he) {
            LL_I2L(prsec, he->last_modified);
            LL_I2L(scale, 1000000);
            LL_MUL(prusec, prsec, scale);
            PR_ExplodeTime(prusec, PR_LocalTimeParameters, &prtime);
            PR_FormatTime(buf, sizeof buf, "%c", &prtime);
        } else {
            buf[0] = '\0';
        }
        str = JS_NewStringCopyZ(cx, buf);
        LO_UnlockLayout();
        break;

      case DOC_LOADED_DATE:
        LO_UnlockLayout();
        /* XXX todo */
        str = JSVAL_TO_STRING(JS_GetEmptyStringValue(cx));
        break;

      case DOC_COOKIE:
        /* XXX overloaded return - can't distinguish "none" from "error" */
        he = SHIST_GetCurrent(&context->hist);
	cookie = NET_GetCookie(context, he ? he->address : "");
        str = JS_NewStringCopyZ(cx, cookie);
	XP_FREEIF(cookie);
        LO_UnlockLayout();
        break;

      case DOC_DOMAIN:
        LO_UnlockLayout();
        origin = lm_GetObjectOriginURL(cx, obj);
        if (!origin)
            return JS_FALSE;
        domain = NET_ParseURL(origin, GET_HOST_PART);
        if (!domain) {
            JS_ReportOutOfMemory(cx);
            return JS_FALSE;
        }
        str = JS_NewStringCopyZ(cx, domain);
        XP_FREE((char *)domain);
        break;

      case DOC_WIDTH:
        layer = LO_GetLayerFromId(context, doc->layer_id);
        *vp = INT_TO_JSVAL(LO_GetLayerScrollWidth(layer));
        LO_UnlockLayout();
        return JS_TRUE;
          
      case DOC_HEIGHT:
        layer = LO_GetLayerFromId(context, doc->layer_id);
        *vp = INT_TO_JSVAL(LO_GetLayerScrollHeight(layer));
        LO_UnlockLayout();
        return JS_TRUE;

      case DOC_BG_COLOR:
        layer = LO_GetLayerFromId(context, doc->layer_id);
        bg_color = LO_GetLayerBgColor(layer);
        LO_UnlockLayout();
        if (bg_color) {
            XP_SPRINTF(buf, "#%02x%02x%02x", bg_color->red, bg_color->green, 
                       bg_color->blue);
            str = JS_NewStringCopyZ(cx, buf);
        } 
        else {
            *vp = JSVAL_NULL;
            return JS_TRUE;
        }
        break;
         
      default:
        type = LO_COLOR_TYPE(slot);
        if (type >= LO_NCOLORS) {
            LO_UnlockLayout();
            /* Don't mess with a user-defined or method property. */
            return JS_TRUE;
        }
        /* Note: background color handled above */
        LO_GetDocumentColor(context, type, &rgb);
        XP_SPRINTF(buf, "#%02x%02x%02x", rgb.red, rgb.green, rgb.blue);
        str = JS_NewStringCopyZ(cx, buf);
        LO_UnlockLayout();
        break;
    }

    /* Common tail code for string-type properties. */
    if (!str)
        return JS_FALSE;
    *vp = STRING_TO_JSVAL(str);
    return JS_TRUE;
}


PR_STATIC_CALLBACK(JSBool)
doc_setProperty(JSContext *cx, JSObject *obj, jsval id, jsval *vp)
{
    JSObject *layer_obj;
    JSDocument *doc;
    MochaDecoder *decoder;
    MWContext *context;
    char *protocol, *pathname, *new_origin_url, *str;
    const char *domain, *new_domain, *domain_suffix, *prop_name, *origin;
    size_t domain_len, new_domain_len;
    JSBool ok;
    uint32 type;
    LO_Color *rgb;
    jsint slot;
    int32 val;
    CL_Layer *layer;
    History_entry *he = NULL;

    doc = JS_GetInstancePrivate(cx, obj, &lm_document_class, NULL);
    if (!doc)
        return JS_TRUE;
    decoder = doc->decoder;

    if (!lm_CheckPermissions(cx, obj, JSTARGET_UNIVERSAL_BROWSER_WRITE))
        return JS_FALSE;

    if (!JSVAL_IS_INT(id)) {
        /*
         * Due to the wonderful world of threads we need to know ahead of
         * time if someone is setting an onMouseMove event handler here or
         * in document so that we don't lose messages.
         */
        if (JS_TypeOfValue(cx, *vp) == JSTYPE_FUNCTION) {
            if (JSVAL_IS_STRING(id)) {
                prop_name = JS_GetStringBytes(JSVAL_TO_STRING(id));
                /* XXX use lm_onMouseMove_str etc.*/
                if (XP_STRCMP(prop_name, "onmousemove") == 0 ||
                    XP_STRCMP(prop_name, "onMouseMove") == 0) {
                    decoder->window_context->js_drag_enabled = TRUE;
                }
            }
        }
        return JS_TRUE;
    }

    slot = JSVAL_TO_INT(id);

    context = decoder->window_context;
    if (!context)
        return JS_TRUE;

    LO_LockLayout();
    if(decoder->doc_id != XP_DOCID(context)) {
        LO_UnlockLayout();
#ifdef DEBUG_chouck
        XP_TRACE(("DOCID: in doc_setProperty"));
#endif
        return JS_FALSE;
    }

    switch (slot) {
      case DOC_COOKIE:
        if (!JSVAL_IS_STRING(*vp) &&
            !JS_ConvertValue(cx, *vp, JSTYPE_STRING, vp)) {
	    LO_UnlockLayout();
            return JS_FALSE;
        }

	he = SHIST_GetCurrent(&context->hist);
	str = JS_GetStringBytes(JSVAL_TO_STRING(*vp));

        /* will make a copy that netlib can munge */
	if (str)
	    str = strdup(str);

        NET_SetCookieString(context, he ? he->address : "", str);
        LO_UnlockLayout();
        break;

      case DOC_DOMAIN:
        LO_UnlockLayout();
        origin = lm_GetObjectOriginURL(cx, obj);
        if (!origin || XP_STRCMP(origin, lm_unknown_origin_str) == 0)
            return JS_FALSE;
        domain = NET_ParseURL(origin, GET_HOST_PART);
        if (!domain) {
            JS_ReportOutOfMemory(cx);
            return JS_FALSE;
        }
        if (!JSVAL_IS_STRING(*vp) &&
            !JS_ConvertValue(cx, *vp, JSTYPE_STRING, vp)) {
            XP_FREE((char *)domain);
            return JS_FALSE;
        }
        new_domain = JS_GetStringBytes(JSVAL_TO_STRING(*vp));
        new_domain_len = JS_GetStringLength(JSVAL_TO_STRING(*vp));
        domain_len = XP_STRLEN(domain);
        domain_suffix = domain + domain_len - new_domain_len;
        ok = (JSBool)(!XP_STRCASECMP(domain, new_domain) ||
             (domain_len > new_domain_len &&
              !XP_STRCASECMP(domain_suffix, new_domain) &&
              (domain_suffix[-1] == '.' || domain_suffix[-1] == '/')));
        if (!ok) {
            JS_ReportError(cx, "illegal document.domain value %s",
                              new_domain);
        } else {
            protocol = NET_ParseURL(origin, GET_PROTOCOL_PART);
            pathname = NET_ParseURL(origin, GET_PATH_PART | GET_HASH_PART |
                                            GET_SEARCH_PART);
            new_origin_url = PR_smprintf("%s//%s%s",
                                         protocol, new_domain, pathname);
            ok = (JSBool)(protocol && pathname && new_origin_url);
            if (!ok) {
                JS_ReportOutOfMemory(cx);
            } else {
                JSPrincipals *principals;
                principals = lm_GetInnermostPrincipals(cx, obj, NULL);
                if (principals) {
                    ok = lm_SetDocumentDomain(cx, principals, new_origin_url);
                } 
            }
            PR_FREEIF(protocol);
            PR_FREEIF(pathname);
            PR_FREEIF(new_origin_url);
        }
        XP_FREE((char *)domain);
        return ok;

      case DOC_BG_COLOR:
        if (doc->layer_id == LO_DOCUMENT_LAYER_ID) {
            layer = LO_GetLayerFromId(context, LO_DOCUMENT_LAYER_ID);
            LO_UnlockLayout();
            if (!lm_jsval_to_rgb(cx, vp, &rgb))
                return JS_FALSE;
            ET_TweakLayer(decoder->window_context, layer, 0, 0,
                          rgb, 0, CL_SetBgColor, NULL, decoder->doc_id);
        } else {
            layer_obj = LO_GetLayerMochaObjectFromId(context, doc->layer_id);
            LO_UnlockLayout();
            if (layer_obj)
                layer_setBgColorProperty(cx, layer_obj, vp);
        }
        break;

      case DOC_FG_COLOR:
      case DOC_LINK_COLOR:
      case DOC_VLINK_COLOR:
      case DOC_ALINK_COLOR:
        type = LO_COLOR_TYPE(slot);
        if (type >= LO_NCOLORS) {
            LO_UnlockLayout();
            break;
        }

        if (!lm_jsval_to_rgb(cx, vp, &rgb) || !rgb) {
            LO_UnlockLayout();
            return JS_FALSE;
        }
        
        LO_SetDocumentColor(context, type, rgb);
        LO_UnlockLayout();
        if (rgb)
            XP_FREE(rgb);
        break;

        /* 
         * XXX BUGBUG These don't do the right thing for a window's document.
         * Does any of this make sense for a layer's document?
         */
      case DOC_WIDTH:
        /* Front-ends can't cope with a document dimension less than one. */
        if (!JS_ValueToInt32(cx, *vp, &val)) {
            LO_UnlockLayout();
            return JS_FALSE;
        }
        layer = LO_GetLayerFromId(context, doc->layer_id);
        LO_SetLayerScrollWidth(layer, val <= 1 ? 1 : val);
        LO_UnlockLayout();
        break;
          
      case DOC_HEIGHT:
        /* Front-ends can't cope with a document dimension less than one. */
        if (!JS_ValueToInt32(cx, *vp, &val)) {
            LO_UnlockLayout();
            return JS_FALSE;
        }
        layer = LO_GetLayerFromId(context, doc->layer_id);
        LO_SetLayerScrollHeight(layer, val <= 1 ? 1 : val);
        LO_UnlockLayout();
        break;

      default:
        LO_UnlockLayout();
        break;
    }

    return doc_getProperty(cx, obj, id, vp);
}

#ifdef JAVA
static void 
lm_reflect_stuff_eagerly(MWContext * context, int32 layer_id)
{

    lo_TopState *top_state;
    int count, index;
    LO_JavaAppStruct *applet;
    LO_EmbedStruct *embed;
    lo_DocLists *doc_lists;

    LO_LockLayout();

    top_state = lo_GetMochaTopState(context);
    if (top_state == NULL) {
        LO_UnlockLayout();
        return;
    }
    doc_lists = lo_GetDocListsById(top_state->doc_state, layer_id);

    count = 0;
    applet = doc_lists->applet_list;
    while (applet) {
        applet = applet->nextApplet;
        count++;
    }

    /* reflect all applets in reverse order */
    applet = doc_lists->applet_list;
    for (index = count-1; index >= 0; index--) {
        if (applet->mocha_object == NULL) {
            LM_ReflectApplet(context, applet, NULL, layer_id, index);
        }
        applet = applet->nextApplet;
    }

    count = 0;
    embed = doc_lists->embed_list;
    while (embed) {
        embed = embed->nextEmbed;
        count++;
    }

    /* reflect all embeds in reverse order */
    embed = doc_lists->embed_list;
    for (index = count-1; index >= 0; index--) {
        if (embed->mocha_object == NULL) {
            LM_ReflectEmbed(context, embed, NULL, layer_id, index);
        }
        embed = embed->nextEmbed;
    }

    LO_UnlockLayout();
}
#endif /* JAVA */

PR_STATIC_CALLBACK(JSBool)
doc_list_properties(JSContext *cx, JSObject *obj)
{
#ifdef JAVA

    /* reflect applets eagerly, anything else? */
    JSDocument *doc;
    MWContext *context;
    JSBool bDoInitMoja = JS_FALSE;
    lo_TopState *top_state;
    lo_DocLists *doc_lists;

    doc = JS_GetPrivate(cx, obj);
    if (!doc)
        return JS_TRUE;
    context = doc->decoder->window_context;
    if (!context)
        return JS_TRUE;

    LO_LockLayout();

    /* if no embeds or applets don't eagerly start java */
    top_state = lo_GetMochaTopState(context);
    if (top_state) {
        doc_lists = lo_GetDocListsById(top_state->doc_state, doc->layer_id);

        if(doc_lists){
			if (doc_lists->applet_list)
				bDoInitMoja = JS_TRUE;
			else if (doc_lists->embed_list){
				/* determine if any of these embeds actually require java, are liveconnected. */
				LO_EmbedStruct *embed;
				for (embed = doc_lists->embed_list; embed != NULL; embed=embed->nextEmbed){
					if (NPL_IsLiveConnected(embed)){
						bDoInitMoja = JS_TRUE;
						break; /* it only takes one  */
					}
				}
			}
		}

    }
    LO_UnlockLayout();

    if(bDoInitMoja) {
        if (LM_MOJA_OK != ET_InitMoja(context))
            return JS_FALSE;
        if (JSJ_IsEnabled())
            lm_reflect_stuff_eagerly(context, doc->layer_id);
    }

#endif
    return JS_TRUE;
}

PR_STATIC_CALLBACK(JSBool)
doc_resolve_name(JSContext *cx, JSObject *obj, jsval id)
{
    JSDocument *doc;
    const char * name;
    JSObject *layer_obj;

    if (!JSVAL_IS_STRING(id))
        return JS_TRUE;

    name = JS_GetStringBytes(JSVAL_TO_STRING(id));

    doc = JS_GetPrivate(cx, obj);
    if (!doc)
        return JS_TRUE;

    layer_obj = lm_GetNamedLayer(doc->decoder, doc->layer_id, name);
    if (layer_obj)
        return JS_DefineProperty(cx, obj, name,
                                 OBJECT_TO_JSVAL(layer_obj),
                                 NULL, NULL,
                                 JSPROP_ENUMERATE | JSPROP_READONLY);

    if (!JSS_ResolveDocName(cx, doc->decoder->window_context, obj, id))
        return JS_FALSE;

    return doc_list_properties(cx, obj);
}

PR_STATIC_CALLBACK(void)
doc_finalize(JSContext *cx, JSObject *obj)
{
    JSDocument *doc;

    doc = JS_GetPrivate(cx, obj);
    if (!doc)
        return;
    DROP_BACK_COUNT(doc->decoder);
    JS_RemoveRoot(cx, &doc->forms);
    JS_RemoveRoot(cx, &doc->links);
    JS_RemoveRoot(cx, &doc->anchors);
    JS_RemoveRoot(cx, &doc->applets);
    JS_RemoveRoot(cx, &doc->embeds);
    JS_RemoveRoot(cx, &doc->images);
    JS_RemoveRoot(cx, &doc->layers);
    JS_free(cx, doc);
}

JSClass lm_document_class = {
    "Document", JSCLASS_HAS_PRIVATE,
    JS_PropertyStub, JS_PropertyStub, doc_getProperty, doc_setProperty,
    doc_list_properties, doc_resolve_name, JS_ConvertStub, doc_finalize
};

PR_STATIC_CALLBACK(JSBool)
Document(JSContext *cx, JSObject *obj,
         uint argc, jsval *argv, jsval *rval)
{
    return JS_TRUE;
}

PR_STATIC_CALLBACK(JSBool)
doc_toString(JSContext *cx, JSObject *obj,
              uint argc, jsval *argv, jsval *rval)
{
    /* XXX make string of whole doc for trusted scripts */
    *rval = JS_GetEmptyStringValue(cx); 
    return JS_TRUE;
}

char *
LM_GetBaseHrefTag(JSContext *cx, JSPrincipals *principals)
{
    char *new_origin_url;
    const char *origin_url;
    const char *cp, *qp, *last_slash;
    char *tag;

    origin_url = principals ? principals->codebase : lm_unknown_origin_str;
    if (origin_url == NULL)
        return NULL;
    new_origin_url = 0;
    cp = origin_url;
    if ((qp = XP_STRCHR(cp, '"')) != 0) {
        do {
            new_origin_url = PR_sprintf_append(new_origin_url, "%.*s%%%x",
                                                qp - cp, cp, *qp);
            cp = qp + 1;
        } while ((qp = XP_STRCHR(cp, '"')) != 0);
        new_origin_url = PR_sprintf_append(new_origin_url, "%s", cp);
        if (!new_origin_url) {
            JS_ReportOutOfMemory(cx);
            return NULL;
        }
        origin_url = new_origin_url;
    }
    last_slash = XP_STRRCHR(origin_url, '/');
    if (last_slash) {
        tag = PR_smprintf("<BASE HREF=\"%.*s/\">\n",
                          last_slash - origin_url, origin_url);
    } else {
        tag = PR_smprintf("<BASE HREF=\"%s\">\n", origin_url);
    }
    PR_FREEIF(new_origin_url);
    if (!tag)
        JS_ReportOutOfMemory(cx);
    return tag;
}

static int
doc_write_stream(JSContext *cx, MochaDecoder *decoder,
                 NET_StreamClass *stream, char *str, size_t len)
{	
    ET_lo_DoDocWrite(cx, decoder->window_context, stream, str, len,
                     decoder->doc_id);

    return JS_TRUE;
}

static char *
context_pathname(JSContext *cx, MWContext *context, int32 layer_id)
{
    MWContext *parent;
    int count, index;
    char *name = 0;

    while ((parent = context->grid_parent) != 0) {
        if (context->name) {
            name = PR_sprintf_append(name, "%s.", context->name);
        } else {
            /* XXX silly xp_cntxt.c puts newer contexts at the front! */
            count = XP_ListCount(parent->grid_children);
            index = XP_ListGetNumFromObject(parent->grid_children, context);
            name = PR_sprintf_append(name, "%u.", count - index);
        }
        context = parent;
    }
    name = PR_sprintf_append(name, "%ld", (long)XP_DOCID(context));
    if (layer_id != LO_DOCUMENT_LAYER_ID)
        name = PR_sprintf_append(name, ".%ld", layer_id);
    if (!name)
        JS_ReportOutOfMemory(cx);
    return name;
}

/* Make a wysiwyg: URL containing the context and our origin. */
char *
lm_MakeWysiwygUrl(JSContext *cx, MochaDecoder *decoder, int32 layer_id,
                  JSPrincipals *principals)
{
    const char *origin;
    const char *pathname;
    char *url_string;
    char *slash;

    origin = principals ? principals->codebase : lm_unknown_origin_str;
    if (origin == NULL)
        return NULL;
    pathname = context_pathname(cx, decoder->window_context, layer_id);
    if (!pathname)
        return NULL;
    while ((slash = XP_STRCHR(pathname, '/')) != NULL) {
        /* Escape '/' in pathname as "%2f" */
        char *newPath;
        *slash = '\0';
        newPath = PR_smprintf("%s%%2f%s", pathname, slash+1);
        XP_FREE((char *)pathname);
        if (!newPath) {
            JS_ReportOutOfMemory(cx);
            return NULL;
        }
        pathname = newPath;
    }
    url_string = PR_smprintf("wysiwyg://%s/%s", pathname, origin);
    XP_FREE((char *)pathname);
    if (!url_string) {
        JS_ReportOutOfMemory(cx);
        return NULL;
    }
    return url_string;
}

static NET_StreamClass *
doc_cache_converter(JSContext *cx, MochaDecoder *decoder,
                    URL_Struct *url_struct, char *wysiwyg_url,
                    int32 layer_id)
{
    NET_StreamClass * rv;

    rv = ET_moz_DocCacheConverter(decoder->window_context, 
                                  url_struct, wysiwyg_url,
                                  layer_id);

    if (!rv)
        JS_ReportOutOfMemory(cx);

    return rv;

}

NET_StreamClass *
LM_WysiwygCacheConverter(MWContext *context, URL_Struct *url_struct,
                         const char * wysiwyg_url, const char * base_href)
{
    NET_StreamClass *cache_stream;

    if (!wysiwyg_url) {
        cache_stream = 0;
    } else {
        cache_stream = lm_DocCacheConverter(context, url_struct,
                                            wysiwyg_url);
        if (cache_stream) {
            /* Wysiwyg files need a base tag that removes that URL prefix. */
            if (base_href) {
                (void) cache_stream->put_block(cache_stream, 
                                               base_href,
                                               XP_STRLEN(base_href));
            }
        }
    }
    return cache_stream;
}

static char nscp_open_tag[] = "<" PT_NSCP_OPEN ">";

static NET_StreamClass *
doc_open_stream(JSContext *cx, MochaDecoder *decoder, JSObject *doc_obj,
                const char *mime_type, JSBool replace)
{
    JSDocument *doc;
    MWContext *context;
    char *wysiwyg_url, *tag;
    JSPrincipals *principals;
    URL_Struct *url_struct, *cached_url;
    MochaDecoder *running;
    History_entry *he;
    NET_StreamClass *stream;
    JSBool is_text_html;
    FO_Present_Types present_type;

    context = decoder->window_context;
    if (!context)
        return NULL;

    doc = JS_GetPrivate(cx, doc_obj);
    if (!doc)
        return NULL;
    
    /* Don't allow a script to replace a message window's contents
       with its own document.write'ed content, since that would allow
       them to spoof mail headers. */
    if ((doc->layer_id == LO_DOCUMENT_LAYER_ID) && IS_MESSAGE_WINDOW(context))
        return NULL;

    /* We must be called after any loading URL has finished. */
    XP_ASSERT(!decoder->url_struct);
    principals = lm_GetPrincipalsFromStackFrame(cx);
    wysiwyg_url = lm_MakeWysiwygUrl(cx, decoder, doc->layer_id, principals);
    if (!wysiwyg_url)
        return NULL;
    url_struct = NET_CreateURLStruct(wysiwyg_url, NET_DONT_RELOAD);
    if (!url_struct) {
        XP_FREE(wysiwyg_url);
        JS_ReportOutOfMemory(cx);
        return NULL;
    }

    /* Set content type so it can be cached. */
    StrAllocCopy(url_struct->content_type, mime_type);
    url_struct->preset_content_type = TRUE;
    url_struct->must_cache = TRUE;

    /* Set these to null so we can goto bad from here onward. */
    stream = 0;
    cached_url = 0;

    /* If the writer is secure, pass its security info into the cache. */
    running = JS_GetPrivate(cx, JS_GetGlobalObject(cx));
    he = SHIST_GetCurrent(&running->window_context->hist);
    if (he && he->security_on) {
        /* Copy security stuff (checking for malloc failure) */
        url_struct->security_on = he->security_on;
        StrAllocCopy(url_struct->key_cipher, he->key_cipher);
        if (he->key_cipher && !url_struct->key_cipher)
            goto bad;
        url_struct->key_size = he->key_size;
        url_struct->key_secret_size = he->key_secret_size;

        /* Make a url struct just to lookup a cached certificate. */
        cached_url = NET_CreateURLStruct(he->address, NET_DONT_RELOAD);
        if (!cached_url)
            goto bad;
        (void) ET_net_FindURLInCache(cached_url, running->window_context);
        if (cached_url->certificate) {
            url_struct->certificate
                = CERT_DupCertificate(cached_url->certificate);
            if (!url_struct->certificate)
                goto bad;
        }
        NET_FreeURLStruct(cached_url);
        cached_url = 0;
    }

    /* If we're opening a stream for the window's document */
    if (doc->layer_id == LO_DOCUMENT_LAYER_ID) {
        /* Free any old doc before stream building, which may discard too. */
        ET_lo_DiscardDocument(context);
        if (replace) {
            /* If replacing, flag current entry and url_struct specially. */
            lm_ReplaceURL(context, url_struct);
        }
        present_type = FO_PRESENT;
    }
    /* Otherwise, we're dealing with a layer's document */
    else {
	const char *referer = lm_GetSubjectOriginURL(cx);

	if (!referer ||
	    !ET_lo_PrepareLayerForWriting(context, doc->layer_id, referer)) {
	    goto bad;
	}
	lm_NewLayerDocument(decoder, doc->layer_id);
	LM_SetActiveLayer(context, doc->layer_id);
        present_type = FO_PRESENT_INLINE;
    }

    /* We must be called after any open stream has been closed. */
    XP_ASSERT(!decoder->stream);
    stream = ET_net_StreamBuilder(present_type, url_struct, context);
    if (!stream) {
        JS_ReportError(cx, "cannot open document for %s", mime_type);
        goto bad;
    }

    if (doc->layer_id == LO_DOCUMENT_LAYER_ID) {
        /* Layout discards documents lazily, but we are now eager. */
        if (!decoder->document && !lm_InitWindowContent(decoder))
            goto bad;

        /* 
         * Reset the doc_id since the context won't have a valid 
         * one set till we start writing out content.
         */
        decoder->doc_id = 0;
    }
    
    decoder->writing_input = JS_TRUE;
    if (!lm_SetInputStream(cx, decoder, stream, url_struct, JS_TRUE))
        goto bad;
    
    is_text_html =  (JSBool)(!XP_STRCMP(mime_type, TEXT_HTML));
    
    /* Only need to do this for a window's document */
    if (doc->layer_id == LO_DOCUMENT_LAYER_ID) {
        if (is_text_html) {
            /* Feed layout an internal tag so it will create a new top state.*/
            ET_lo_DoDocWrite(cx, context, stream, nscp_open_tag,
                             sizeof nscp_open_tag - 1, 0);
        }

        if (is_text_html || !XP_STRCMP(mime_type, TEXT_PLAIN)) {
            doc_cache_converter(cx, decoder, url_struct, 
                                wysiwyg_url, doc->layer_id);
        }

        /* Auto-generate a BASE HREF= tag for generated HTML documents. */
        if (is_text_html) {
            tag = LM_GetBaseHrefTag(cx, principals);
            if (!tag)
                goto bad;
            (void) doc_write_stream(cx, decoder, stream, tag, XP_STRLEN(tag));
            XP_FREE(tag);
        }
    }
    else {
        if (lm_SetLayerSourceURL(decoder, doc->layer_id, wysiwyg_url)) {
            if ((is_text_html || !XP_STRCMP(mime_type, TEXT_PLAIN)) &&
                !doc_cache_converter(cx, decoder, url_struct, 
                                     wysiwyg_url, doc->layer_id)) {
                lm_SetLayerSourceURL(decoder, doc->layer_id, NULL);
            }
        }
    }

    /* Drop our ref on url_struct -- decoder holds its own. */
    NET_DropURLStruct(url_struct);
    XP_FREE(wysiwyg_url);
    return stream;

bad:
    decoder->writing_input = JS_FALSE;
    decoder->free_stream_on_close = JS_FALSE;
    decoder->stream = 0;
    decoder->stream_owner = LO_DOCUMENT_LAYER_ID;
    decoder->url_struct = 0;
    if (stream) {
        (*stream->abort)(stream, MK_UNABLE_TO_CONVERT);
        XP_DELETE(stream);
    }
    NET_FreeURLStruct(url_struct);
    if (cached_url)
        NET_FreeURLStruct(cached_url);
    XP_FREE(wysiwyg_url);
    return NULL;
}

static JSBool
doc_stream_active(MochaDecoder *decoder)
{
    MWContext *context;
    lo_TopState *top_state;

    if (!decoder->writing_input)
        return JS_TRUE;

    context = decoder->window_context;
    if (context) {
        LO_LockLayout();
        top_state = lo_GetMochaTopState(context);
        if (top_state && top_state->input_write_level) {
            LO_UnlockLayout();
            return JS_TRUE;
        }
        LO_UnlockLayout();
    }
    return JS_FALSE;
}

/* XXX shared from lm_win.c for compatibility hack */
extern JSBool PR_CALLBACK
win_open(JSContext *cx, JSObject *obj, uint argc, jsval *argv, jsval *rval);

PR_STATIC_CALLBACK(JSBool)
doc_open(JSContext *cx, JSObject *obj, uint argc, jsval *argv, jsval *rval)
{
    JSDocument *doc;
    MochaDecoder *decoder;
    JSString *str, *str2;
    const char *mime_type, *options;
    JSBool replace;
    NET_StreamClass *stream;

    if (!JS_InstanceOf(cx, obj, &lm_document_class, argv))
        return JS_FALSE;
    doc = JS_GetPrivate(cx, obj);
    if (!doc)
        return JS_TRUE;
    decoder = doc->decoder;

    if (argc > 2 || argv[0] == JS_GetEmptyStringValue(cx)) {
        /* XXX hideous compatibility hack to call window.open */
        return win_open(cx, decoder->window_object, argc, argv, rval);
    }

    str = str2 = 0;
    mime_type = TEXT_HTML;
    replace = JS_FALSE;
    if (argc >= 1) {
        if (!(str = JS_ValueToString(cx, argv[0])))
            return JS_FALSE;
        mime_type = JS_GetStringBytes(str);
        if (argc >= 2) {
            if (!(str2 = JS_ValueToString(cx, argv[1])))
                return JS_FALSE;
            options = JS_GetStringBytes(str2);
            replace = (JSBool)(!XP_STRCASECMP(options, "replace"));
        }
    }
    stream = decoder->stream;
    if (stream) {
        if (doc_stream_active(decoder)) {
            /* Don't close a stream being fed by netlib. */
            *rval = JSVAL_NULL;
            return JS_TRUE;
        }
        lm_ClearDecoderStream(decoder, JS_FALSE);
    }
    stream = doc_open_stream(cx, decoder, obj, mime_type, replace);
    if (!stream)
        return JS_FALSE;
    *rval = OBJECT_TO_JSVAL(doc->object);
    return JS_TRUE;
}

PR_STATIC_CALLBACK(JSBool)
doc_close(JSContext *cx, JSObject *obj, uint argc, jsval *argv, jsval *rval)
{
    JSDocument *doc;
    MochaDecoder *decoder;

    if (!JS_InstanceOf(cx, obj, &lm_document_class, argv))
        return JS_FALSE;
    doc = JS_GetPrivate(cx, obj);
    if (!doc)
        return JS_TRUE;
    decoder = doc->decoder;
    if (!decoder->stream || doc_stream_active(decoder)) {
        /* Don't close a closed stream, or a stream being fed by netlib. */
        *rval = JSVAL_FALSE;
        return JS_TRUE;
    }
    lm_ClearDecoderStream(decoder, JS_FALSE);
    *rval = JSVAL_TRUE;
    return JS_TRUE;
}

PR_STATIC_CALLBACK(JSBool)
doc_clear(JSContext *cx, JSObject *obj, uint argc, jsval *argv, jsval *rval)
{
    JSDocument *doc;
    MochaDecoder *decoder;
    MWContext *context;

    if (!JS_InstanceOf(cx, obj, &lm_document_class, argv))
        return JS_FALSE;
    doc = JS_GetPrivate(cx, obj);
    if (!doc)
        return JS_TRUE;
    decoder = doc->decoder;
    context = decoder->window_context;
    if (!context)
        return JS_TRUE;
    if (!doc_close(cx, obj, argc, argv, rval))
        return JS_FALSE;
    /* XXX need to open and push a tag through layout? */
    ET_PostClearView(context);
    return JS_TRUE;
}

static NET_StreamClass *
doc_get_stream(JSContext *cx, MochaDecoder *decoder, JSObject *doc_obj)
{
    NET_StreamClass *stream;
    JSDocument *doc;

    doc = JS_GetPrivate(cx, doc_obj);
    if (!doc)
        return NULL;
    
    stream = decoder->stream;
    if (!stream) {
        stream = doc_open_stream(cx, decoder, doc_obj, TEXT_HTML, JS_FALSE);
    /*
     * If, at document load time, we're trying to get a stream to a layer
     * that isn't the currently loading layer, then disallow it.
     */
    } else if (doc->layer_id != decoder->active_layer_id &&
	       !decoder->load_event_sent) {
        return NULL;
    /*
     * If we're writing to the main document or before the main document
     * has completely loaded, make sure that we do all the wysiwyg
     * nonsense.
     */
    } else if (doc->layer_id == LO_DOCUMENT_LAYER_ID ||
               !decoder->load_event_sent) { 
        XP_ASSERT(decoder->url_struct);
        if (decoder->url_struct && !decoder->url_struct->wysiwyg_url)
            ET_moz_SetMochaWriteStream(decoder);
    }
    return stream;
}

static JSBool
do_doc_write(JSContext *cx, JSObject *obj,
             uint argc, jsval *argv, JSBool write_eol, 
             jsval *rval)
{
    JSDocument *doc;
    MochaDecoder *decoder;
    MochaDecoder *running;
    NET_StreamClass *stream;
    uint i;
    int status, total_len, len;
    static char eol[] = "\n";
    char * buf, * start;
    JSString * str;

    doc = JS_GetPrivate(cx, obj);
    if (!doc)
        return JS_TRUE;

    if (PR_CurrentThread() == mozilla_thread)
        return JS_FALSE;

    decoder = doc->decoder;
    stream = doc_get_stream(cx, decoder, obj);
    if (!stream)
        return JS_FALSE;

    /* see how much space we need */
    total_len = 0;
    for (i = 0; i < argc; i++) {
        if (JSVAL_IS_STRING(argv[i])) {
            str = JSVAL_TO_STRING(argv[i]);
        }
        else {
            str = JS_ValueToString(cx, argv[i]);
            if (!str)
                return JS_FALSE;
            argv[i] = STRING_TO_JSVAL(str);
        }

        total_len += JS_GetStringLength(str);

    }

    /* see if we need to allocate space for the end of line */
    if (write_eol)
        total_len += XP_STRLEN(eol);

    start = buf = XP_ALLOC((total_len + 1) * sizeof(char));
    if (!buf)
        return JS_FALSE;

    buf[0] = '\0';

    /* cache these so we don't need to look up each iteration */
    running = JS_GetPrivate(cx, JS_GetGlobalObject(cx));

    /* build the string for whatever is on the other end of the stream */
    for (i = 0; i < argc; i++) {

        str = JSVAL_TO_STRING(argv[i]);
        len = JS_GetStringLength(str);

        /* 
         * JSStrings can contain NUL, not sure how well layout will
         *   deal with that but pass them along in case they're going
         *   to the imglib.
         */
	/* XXXunicode */
        memcpy(buf, JS_GetStringBytes(str), len);
        buf += len;

    }

    /* generate a new line */
    if (write_eol)
        XP_STRCPY(buf, eol);
    else
        *buf = '\0';

    if (lm_writing_context == NULL)
        lm_writing_context = cx;
    writing_context_counter++;

    /* if we made it to here we must have a valid string */
    status = ET_lo_DoDocWrite(cx, decoder->window_context, stream, start,
                              total_len, decoder->doc_id);

    if (--writing_context_counter == 0)
        lm_writing_context = NULL;

    XP_FREE(start);

    if (status < 0) {
        *rval = JSVAL_FALSE;
        return JS_TRUE;
    }
    
    if (!decoder->document && !lm_InitWindowContent(decoder))
        return JS_FALSE;
    *rval = JSVAL_TRUE;
    return JS_TRUE;

}

PR_STATIC_CALLBACK(JSBool)
doc_write(JSContext *cx, JSObject *obj,
          uint argc, jsval *argv, jsval *rval)
{
    if (!JS_InstanceOf(cx, obj, &lm_document_class, argv))
        return JS_FALSE;

    return do_doc_write(cx, obj, argc, argv, JS_FALSE, rval);
}

PR_STATIC_CALLBACK(JSBool)
doc_writeln(JSContext *cx, JSObject *obj,
            uint argc, jsval *argv, jsval *rval)
{
    if (!JS_InstanceOf(cx, obj, &lm_document_class, argv))
        return JS_FALSE;

    return do_doc_write(cx, obj, argc, argv, JS_TRUE, rval);
}

PR_STATIC_CALLBACK(JSBool)
doc_capture_events(JSContext *cx, JSObject *obj,
            uint argc, jsval *argv, jsval *rval)
{
    JSDocument *doc;
    MochaDecoder *decoder;
    jsdouble d;

    if (!JS_InstanceOf(cx, obj, &lm_document_class, argv))
        return JS_FALSE;
    doc = JS_GetPrivate(cx, obj);
    if (!doc)
        return JS_FALSE;
    decoder=doc->decoder;

    if (!decoder->window_context)
        return JS_TRUE;

    if (argc != 1)
        return JS_TRUE;

    if (!JS_ValueToNumber(cx, argv[0], &d)) 
        return JS_FALSE;

    doc->capturer.event_bit |= (uint32)d;
    decoder->window_context->event_bit |= (uint32)d;

    return JS_TRUE;
}

PR_STATIC_CALLBACK(JSBool)
doc_release_events(JSContext *cx, JSObject *obj,
            uint argc, jsval *argv, jsval *rval)
{
    JSDocument *doc;
    JSEventCapturer *cap;
    MochaDecoder *decoder;
    jsdouble d;
    jsint layer_index;
    jsint max_layer_num;
    JSObject *cap_obj;

    if (!JS_InstanceOf(cx, obj, &lm_document_class, argv))
        return JS_FALSE;
    doc = JS_GetPrivate(cx, obj);
    if (!doc)
        return JS_FALSE;
    decoder=doc->decoder;

    if (!decoder->window_context)
        return JS_TRUE;

    if (argc != 1)
        return JS_TRUE;

    if (!JS_ValueToNumber(cx, argv[0], &d)) 
        return JS_FALSE;

    doc->capturer.event_bit &= ~(uint32)d;
    decoder->window_context->event_bit &= ~(uint32)d;

    /*Now we have to see if anyone else wanted that bit set.  Joy!*/
    /*First we check versus window */
    decoder->window_context->event_bit |= decoder->event_bit;

    /*Now we check versus layers */
    max_layer_num = LO_GetNumberOfLayers(decoder->window_context);
    
    for (layer_index=0; layer_index <= max_layer_num; layer_index++) {
        cap_obj = LO_GetLayerMochaObjectFromId(decoder->window_context, layer_index);
        if (cap_obj && (cap = JS_GetPrivate(cx, cap_obj)) != NULL)
            decoder->window_context->event_bit |= cap->event_bit;
        
        cap_obj = lm_GetDocumentFromLayerId(decoder, layer_index);
        if (cap_obj && (cap = JS_GetPrivate(cx, cap_obj)) != NULL)
            decoder->window_context->event_bit |= cap->event_bit;
    }
    
    return JS_TRUE;
}

PR_STATIC_CALLBACK(JSBool)
doc_get_selection(JSContext *cx, JSObject *obj,
            uint argc, jsval *argv, jsval *rval)
{
    JSDocument *doc;
    MWContext *context;
    JSString *str;
    char *text;
    
    if (!JS_InstanceOf(cx, obj, &lm_document_class, argv))
        return JS_FALSE;

    doc = JS_GetPrivate(cx, obj);
    if (!doc)
        return JS_FALSE;
    context = doc->decoder->window_context;

    text = ET_PostGetSelectedText(context);
    str = lm_LocalEncodingToStr(context, text);
    XP_FREE(text);
    if (!str)
        return JS_FALSE;

    *rval = STRING_TO_JSVAL(str);
    return JS_TRUE;
}

PR_STATIC_CALLBACK(JSBool)
doc_get_object_at(JSContext *cx, JSObject *obj,
            uint argc, jsval *argv, jsval *rval)
{
    JSDocument *doc;
    MochaDecoder *decoder;
    LO_Element *pElement;
    CL_Layer *layer;
    JSObject *xy_obj = 0;
    LO_AnchorData *anchor;
    int32 x, y;
    int16 type;

    if (!JS_InstanceOf(cx, obj, &lm_document_class, argv))
        return JS_FALSE;

    doc = JS_GetPrivate(cx, obj);
    if (!doc)
        return JS_FALSE;
    
    decoder = doc->decoder;

    if (argc != 2) {
        JS_ReportError(cx, lm_argc_err_str);
        return JS_FALSE;
    }

    if (!decoder->window_context)
        return JS_TRUE;

    if (!JS_ValueToInt32(cx, argv[0], &x) || !JS_ValueToInt32(cx, argv[1], &y))
        return JS_FALSE;    

    layer = LO_GetLayerFromId(decoder->window_context, doc->layer_id);
        
    LO_LockLayout();

    pElement = LO_XYToElement(decoder->window_context, x, y, layer);

    type = pElement ? pElement->type : LO_NONE;

    switch (type) {
      case LO_TEXT:
        anchor = pElement->lo_text.anchor_href;
        if (!anchor)
            *rval = JSVAL_VOID;
        else
            *rval = OBJECT_TO_JSVAL(anchor->mocha_object);
        break;
      case LO_IMAGE:
        anchor = pElement->lo_image.anchor_href;
        if (!anchor)
            *rval = OBJECT_TO_JSVAL(pElement->lo_image.mocha_object);
        else 
            *rval = OBJECT_TO_JSVAL(anchor->mocha_object);
        break;
      case LO_FORM_ELE:
        *rval = OBJECT_TO_JSVAL(pElement->lo_form.mocha_object);
        break;
      default:
        *rval = OBJECT_TO_JSVAL(obj);
        break;
    }

    LO_UnlockLayout();

    return JS_TRUE;
}

static JSFunctionSpec doc_methods[] = {
    {"clear",           doc_clear,              0},
    {"close",           doc_close,              0},
    {"open",            doc_open,               1},
    {lm_toString_str,   doc_toString,           0},
    {"write",           doc_write,              0},
    {"writeln",         doc_writeln,            0},
    {"captureEvents",   doc_capture_events,     1}, 
    {"releaseEvents",   doc_release_events,     1},
    {"getSelection",    doc_get_selection,      0},
    {"getObjectAt",     doc_get_object_at,      0},
    {0}
};

JSObject *
lm_DefineDocument(MochaDecoder *decoder, int32 layer_id)
{

    JSObject *obj;
    JSContext *cx;
    JSDocument *doc;
    JSObject *parent;

    cx = decoder->js_context;
    doc = JS_malloc(cx, sizeof *doc);
    if (!doc)
        return NULL;
    XP_BZERO(doc, sizeof *doc);

    obj = lm_GetDocumentFromLayerId(decoder, layer_id);
    if (obj)
        return obj;
    
    if (layer_id == LO_DOCUMENT_LAYER_ID) {
        parent = decoder->window_object;
    } else {
        parent = 
            (JSObject *)LO_GetLayerMochaObjectFromId(decoder->window_context, 
                                                     layer_id);    
	if (!parent)
	    parent = decoder->window_object;
    }
    obj = JS_DefineObject(cx, parent, lm_document_str, &lm_document_class, 
                          decoder->document_prototype, 
                          JSPROP_ENUMERATE|JSPROP_READONLY);

    if (!obj || !JS_SetPrivate(cx, obj, doc)) {
        JS_free(cx, doc);
        return NULL;
    }

    if (!JS_AddNamedRoot(cx, &doc->forms,   lm_forms_str) ||
        !JS_AddNamedRoot(cx, &doc->links,   lm_links_str) ||
        !JS_AddNamedRoot(cx, &doc->anchors, lm_anchors_str) ||
        !JS_AddNamedRoot(cx, &doc->applets, lm_applets_str) ||
        !JS_AddNamedRoot(cx, &doc->embeds,  lm_embeds_str) ||
        !JS_AddNamedRoot(cx, &doc->images,  lm_images_str) ||
        !JS_AddNamedRoot(cx, &doc->layers,  lm_layers_str)) {
        /* doc_finalize will clean up the rest. */
        return NULL;
    }

    doc->object = obj;
    doc->decoder = HOLD_BACK_COUNT(decoder);
    doc->layer_id = layer_id;
    if (layer_id == LO_DOCUMENT_LAYER_ID) {
        decoder->document = obj;
        XP_ASSERT(decoder->window_context);
        decoder->doc_id = XP_DOCID(decoder->window_context);
    }

    return obj;
}

JSBool
lm_InitDocumentClass(MochaDecoder *decoder)
{
    JSContext *cx;
    JSObject *prototype;

    cx = decoder->js_context;
    prototype = JS_InitClass(cx, decoder->window_object, 
                             decoder->event_capturer_prototype,
                             &lm_document_class, Document, 0,
                             doc_props, doc_methods, NULL, NULL);
    if (!prototype)
        return JS_FALSE;
    decoder->document_prototype = prototype;
    return JS_TRUE;
}

JSObject *
lm_GetDocumentFromLayerId(MochaDecoder *decoder, int32 layer_id)
{
    JSObject *layer_obj;
    JSBool ok;
    jsval val;
    JSObject *rv;

    LO_LockLayout();

    if (layer_id == LO_DOCUMENT_LAYER_ID)
        rv = decoder->document;
    else {
        layer_obj = LO_GetLayerMochaObjectFromId(decoder->window_context, 
                                                 layer_id);
        if (!layer_obj) {
            LO_UnlockLayout();
            return NULL;
        }
        
        ok = JS_LookupProperty(decoder->js_context, layer_obj, 
                               lm_document_str, &val);
        if (!ok) {
            LO_UnlockLayout();
            return NULL;
        }

        rv = JSVAL_IS_OBJECT(val) ? JSVAL_TO_OBJECT(val) : NULL;
    }

    LO_UnlockLayout();
    return rv;
}

/* Clears out object references from the doc private data */
void
lm_CleanUpDocumentRoots(MochaDecoder *decoder, JSObject *obj)
{
    JSDocument *doc;

    doc = JS_GetPrivate(decoder->js_context, obj);

    if (!doc)
        return;

    doc->forms = NULL;
    doc->links = NULL;
    doc->anchors = NULL;
    doc->applets = NULL;
    doc->embeds = NULL;
    doc->images = NULL;
    doc->layers = NULL;
}

/* 
 * Called when the content associated with a document is destroyed,
 * but the document itself may not be. Cleans out object references
 * from doc private data (so that the objects can be collected). Also
 * deals with correctly relinquishing event capture.
 */
void
lm_CleanUpDocument(MochaDecoder *decoder, JSObject *obj)
{
    JSDocument *doc;
    MWContext *context;
    jsint layer_index, max_layer_num;
    JSObject *cap_obj;
    JSEventCapturer *cap;

    lm_CleanUpDocumentRoots(decoder, obj);
    
    doc = JS_GetPrivate(decoder->js_context, obj);
    if (!doc)
        return;

    doc->capturer.event_bit = 0;
    context = decoder->window_context;
    if (!context)
        return;
    context->event_bit = 0;

    /* Now we have to see who still wants events */
    /* First we check versus window */
    context->event_bit |= decoder->event_bit;

    /*Now we check versus layers */
    max_layer_num = LO_GetNumberOfLayers(context);

    for (layer_index=0; layer_index <= max_layer_num; layer_index++) {
        if (doc->layer_id == layer_index)
            continue;

        cap_obj = LO_GetLayerMochaObjectFromId(decoder->window_context, layer_index);
        if (cap_obj && (cap = JS_GetPrivate(decoder->js_context, cap_obj)) != NULL)
            context->event_bit |= cap->event_bit;
        
        cap_obj = lm_GetDocumentFromLayerId(decoder, layer_index);
        if (cap_obj && (cap = JS_GetPrivate(decoder->js_context, cap_obj)) != NULL)
            context->event_bit |= cap->event_bit;
    }

}



/*
 * This routine needs to be callable from either the mocha or
 *   the mozilla threads.  The call to lm_FreeWindowContent()
 *   may be problematic since its going to try to post
 *   clearTimeout() messages back to the mozilla thread if any
 *   are pending.
 */
void
LM_ReleaseDocument(MWContext *context, JSBool resize_reload)
{
    MochaDecoder *decoder;
    JSContext *cx;

    if (resize_reload)
        return;

    /* Be defensive about JS-unaware contexts. */
    cx = context->mocha_context;
    if (!cx)
        return;

    /* Hold the context's decoder rather than calling LM_GetMochaDecoder. */
    decoder = JS_GetPrivate(cx, JS_GetGlobalObject(cx));
    decoder->forw_count++;

    /* Turn of the capture bits for the window and context */
    decoder->event_bit = 0;
    decoder->window_context->event_bit = 0;
    
    /* Set first language version back to unknown for the next document */
    decoder->firstVersion = JSVERSION_UNKNOWN;

    if (decoder->principals) {
        /* Drop reference to JSPrincipals object */
        JSPRINCIPALS_DROP(cx, decoder->principals);
        decoder->principals = NULL;
    }

    /* Free any anonymous images. */
    if (decoder->image_context)
        ET_PostFreeAnonImages(context, decoder->image_context);

    /* Free all the HTML-based objects and properties. */
    lm_FreeWindowContent(decoder, JS_TRUE);
    JS_GC(cx);

    LM_PutMochaDecoder(decoder);
}
