netscape-revival
lib/libmocha/lm_win.c
/*
** Mocha reflection of the current Navigator Window.
**
** Brendan Eich, 9/8/95
*/
#include "lm.h"
#include "xp.h"
#include "fe_proto.h"
#include "structs.h"
#include "layout.h" /* included via -I../layout */
#include "prprf.h"
#include "prtime.h"
#include "libi18n.h"
#undef FREE_AND_CLEAR /* XXX over-including Mac compiled headers */
enum window_slot {
WIN_LENGTH = -1,
WIN_FRAMES = -2,
WIN_PARENT = -3,
WIN_TOP = -4,
WIN_SELF = -5,
WIN_NAME = -6,
WIN_STATUS = -7,
WIN_DEFAULT_STATUS = -8,
WIN_OPENER = -9,
WIN_CLOSED = -10
};
static MochaPropertySpec window_props[] = {
{"length", WIN_LENGTH, MDF_ENUMERATE|MDF_READONLY},
{"frames", WIN_FRAMES, MDF_BACKEDGE|MDF_ENUMERATE|MDF_READONLY},
{"parent", WIN_PARENT, MDF_BACKEDGE|MDF_ENUMERATE|MDF_READONLY},
{"top", WIN_TOP, MDF_BACKEDGE|MDF_ENUMERATE|MDF_READONLY},
{"self", WIN_SELF, MDF_BACKEDGE|MDF_ENUMERATE|MDF_READONLY},
{"window", WIN_SELF, MDF_READONLY},
{"name", WIN_NAME, MDF_ENUMERATE},
{"status", WIN_STATUS, MDF_ENUMERATE|MDF_TAINTED},
{"defaultStatus", WIN_DEFAULT_STATUS, MDF_ENUMERATE|MDF_TAINTED},
{lm_opener_str, WIN_OPENER, MDF_ENUMERATE},
{lm_closed_str, WIN_CLOSED, MDF_ENUMERATE|MDF_READONLY},
{0}
};
static MochaBoolean
win_get_property(MochaContext *mc, MochaObject *obj, MochaSlot slot,
MochaDatum *dp)
{
MochaDecoder *decoder;
MWContext *context;
enum window_slot window_slot;
uint count;
decoder = obj->data;
context = decoder->window_context;
if (!context) {
if (slot == WIN_CLOSED)
MOCHA_INIT_DATUM(mc, dp, MOCHA_BOOLEAN, u.bval, MOCHA_TRUE);
return MOCHA_TRUE;
}
if (mc != decoder->mocha_context &&
!lm_MergeTaint(mc, decoder->mocha_context)) {
return MOCHA_FALSE;
}
window_slot = slot;
switch (window_slot) {
case WIN_LENGTH:
MOCHA_INIT_DATUM(mc, dp, MOCHA_NUMBER, u.fval,
XP_ListCount(context->grid_children));
break;
case WIN_FRAMES:
case WIN_SELF:
MOCHA_INIT_DATUM(mc, dp, MOCHA_OBJECT, u.obj, decoder->window_object);
break;
case WIN_PARENT:
MOCHA_INIT_DATUM(mc, dp, MOCHA_OBJECT, u.obj, decoder->window_object);
if (context->grid_parent) {
decoder = LM_GetMochaDecoder(context->grid_parent);
if (decoder) {
dp->u.obj = decoder->window_object;
MOCHA_WeakenRef(mc, dp);
}
}
break;
case WIN_TOP:
while (context->grid_parent)
context = context->grid_parent;
decoder = LM_GetMochaDecoder(context);
MOCHA_INIT_DATUM(mc, dp, MOCHA_OBJECT, u.obj,
decoder ? decoder->window_object : 0);
if (decoder)
MOCHA_WeakenRef(mc, dp);
break;
case WIN_NAME:
MOCHA_INIT_DATUM(mc, dp, MOCHA_STRING, u.atom,
MOCHA_Atomize(mc, context->name));
break;
case WIN_STATUS:
return MOCHA_TRUE; /* XXX can't get yet, return last known */
case WIN_DEFAULT_STATUS:
MOCHA_INIT_DATUM(mc, dp, MOCHA_STRING, u.atom,
MOCHA_Atomize(mc, context->defaultStatus));
break;
case WIN_CLOSED:
MOCHA_INIT_DATUM(mc, dp, MOCHA_BOOLEAN, u.bval, MOCHA_FALSE);
break;
default:
if (slot < 0) {
/* Don't mess with user-defined or method properties. */
return MOCHA_TRUE;
}
/* XXX silly xp_cntxt.c puts newer contexts at the front! fix. */
count = XP_ListCount(context->grid_children);
context = XP_ListGetObjectNum(context->grid_children, count - slot);
if (context) {
decoder = LM_GetMochaDecoder(context);
if (decoder) {
MOCHA_INIT_DATUM(mc, dp, MOCHA_OBJECT, u.obj,
decoder->window_object);
LM_PutMochaDecoder(decoder);
} else {
*dp = MOCHA_null;
}
}
break;
}
if ((dp->flags & MDF_TAINTED) && dp->taint == MOCHA_TAINT_IDENTITY)
return lm_GetTaint(mc, decoder, &dp->taint);
return MOCHA_TRUE;
}
static MochaBoolean
win_set_property(MochaContext *mc, MochaObject *obj, MochaSlot slot,
MochaDatum *dp)
{
MochaDecoder *decoder, *parent_decoder;
MWContext *context;
enum window_slot window_slot;
const char *atom_name;
char *str, *name, *old_name;
decoder = obj->data;
context = decoder->window_context;
if (!context) return MOCHA_TRUE;
if (mc != decoder->mocha_context &&
!lm_MergeTaint(mc, decoder->mocha_context)) {
return MOCHA_FALSE;
}
window_slot = slot;
switch (window_slot) {
case WIN_NAME:
case WIN_STATUS:
case WIN_DEFAULT_STATUS:
if (dp->tag != MOCHA_STRING &&
!MOCHA_ConvertDatum(mc, *dp, MOCHA_STRING, dp)) {
return MOCHA_FALSE;
}
break;
case WIN_OPENER:
if (dp->tag != MOCHA_OBJECT &&
!MOCHA_ConvertDatum(mc, *dp, MOCHA_OBJECT, dp)) {
return MOCHA_FALSE;
}
break;
default:;
}
switch (window_slot) {
case WIN_NAME:
/* Don't let rogue JS name a mail or news window and then close it. */
if (context->type != MWContextBrowser)
return MOCHA_TRUE;
atom_name = MOCHA_GetAtomName(mc, dp->u.atom);
if (!lm_CheckWindowName(mc, atom_name))
return MOCHA_FALSE;
name = MOCHA_strdup(mc, atom_name);
if (!name)
return MOCHA_FALSE;
old_name = context->name;
if (old_name) {
/* If context is a frame, change its name in its parent's scope. */
if (context->grid_parent) {
parent_decoder = LM_GetMochaDecoder(context->grid_parent);
if (parent_decoder) {
MOCHA_RemoveProperty(mc, parent_decoder->window_object,
old_name);
LM_PutMochaDecoder(parent_decoder);
}
}
XP_FREE(old_name);
}
context->name = name;
break;
case WIN_STATUS:
FE_Progress(context, MOCHA_GetAtomName(mc, dp->u.atom));
break;
case WIN_DEFAULT_STATUS:
str = MOCHA_strdup(mc, MOCHA_GetAtomName(mc, dp->u.atom));
if (!str)
return MOCHA_FALSE;
if (context->defaultStatus)
XP_FREE(context->defaultStatus);
context->defaultStatus = str;
FE_Progress(context, 0);
break;
case WIN_OPENER:
if (decoder->opener && !dp->u.obj) {
MOCHA_DropObject(mc, decoder->opener);
decoder->opener = 0;
}
break;
default:;
}
return MOCHA_TRUE;
}
static MochaBoolean
win_list_properties(MochaContext *mc, MochaObject *obj)
{
MochaDecoder *decoder;
MWContext *context, *kid;
XP_List *list;
uint slot;
MochaDatum d;
decoder = obj->data;
context = decoder->window_context;
if (!context)
return MOCHA_TRUE;
/* XXX silly xp_cntxt.c puts newer contexts at the front! fix. */
list = context->grid_children;
slot = XP_ListCount(list);
while ((kid = XP_ListNextObject(list)) != 0) {
slot--;
d = MOCHA_null;
d.flags |= MDF_ENUMERATE;
if (!MOCHA_SetProperty(mc, decoder->window_object, kid->name, slot, d))
return MOCHA_FALSE;
break;
}
return MOCHA_TRUE;
}
static MochaBoolean
win_resolve_name(MochaContext *mc, MochaObject *obj, const char *name)
{
MochaDecoder *decoder;
MWContext *context, *kid;
XP_List *list;
uint slot;
MochaDatum d;
decoder = obj->data;
context = decoder->window_context;
if (!context)
return MOCHA_TRUE;
/* XXX silly xp_cntxt.c puts newer contexts at the front! fix. */
list = context->grid_children;
slot = XP_ListCount(list);
while ((kid = XP_ListNextObject(list)) != 0) {
slot--;
if (kid->name && XP_STRCMP(kid->name, name) == 0) {
d = MOCHA_null;
d.flags |= MDF_ENUMERATE;
if (!MOCHA_SetProperty(mc, decoder->window_object, name, slot, d))
return MOCHA_FALSE;
break;
}
}
return MOCHA_TRUE;
}
static void
win_finalize(MochaContext *mc, MochaObject *obj)
{
MochaDecoder *decoder = obj->data;
LM_UNLINK_OBJECT(decoder, obj);
decoder->window_object = 0;
lm_DestroyWindow(decoder);
}
MochaClass lm_window_class = {
"Window",
win_get_property, win_set_property, win_list_properties,
win_resolve_name, MOCHA_ConvertStub, win_finalize
};
/*
** Alert and some simple dialogs.
*/
static MochaBoolean
win_alert(MochaContext *mc, MochaObject *obj,
uint argc, MochaDatum *argv, MochaDatum *rval)
{
MochaDecoder *decoder;
MochaAtom *atom;
char *message;
if (!MOCHA_InstanceOf(mc, obj, &lm_window_class, argv[-1].u.fun))
return MOCHA_FALSE;
decoder = obj->data;
if (!MOCHA_DatumToString(mc, argv[0], &atom))
return MOCHA_FALSE;
message = PR_smprintf("%s Alert:\n%s",
mocha_language_name, MOCHA_GetAtomName(mc, atom));
MOCHA_DropAtom(mc, atom);
if (message) {
if (decoder->window_context)
FE_Alert(decoder->window_context, message);
XP_FREE(message);
}
return MOCHA_TRUE;
}
static MochaBoolean
win_confirm(MochaContext *mc, MochaObject *obj,
uint argc, MochaDatum *argv, MochaDatum *rval)
{
MochaDecoder *decoder;
MochaAtom *atom;
char *message;
MochaBoolean ok;
if (!MOCHA_InstanceOf(mc, obj, &lm_window_class, argv[-1].u.fun))
return MOCHA_FALSE;
decoder = obj->data;
if (!MOCHA_DatumToString(mc, argv[0], &atom))
return MOCHA_FALSE;
message = PR_smprintf("%s Confirm:\n%s",
mocha_language_name, MOCHA_GetAtomName(mc, atom));
MOCHA_DropAtom(mc, atom);
if (!message) {
*rval = MOCHA_false;
return MOCHA_TRUE;
}
if (decoder->window_context)
ok = FE_Confirm(decoder->window_context, message);
else
ok = MOCHA_FALSE;
XP_FREE(message);
MOCHA_INIT_DATUM(mc, rval, MOCHA_BOOLEAN, u.bval, ok);
return MOCHA_TRUE;
}
static MochaBoolean
win_prompt(MochaContext *mc, MochaObject *obj,
uint argc, MochaDatum *argv, MochaDatum *rval)
{
MochaDecoder *decoder;
MochaAtom *arg1, *arg2, *atom;
char *message, *retval;
if (!MOCHA_InstanceOf(mc, obj, &lm_window_class, argv[-1].u.fun))
return MOCHA_FALSE;
decoder = obj->data;
if (!MOCHA_DatumToString(mc, argv[0], &arg1))
return MOCHA_FALSE;
message = PR_smprintf("%s Prompt:\n%s",
mocha_language_name, MOCHA_GetAtomName(mc, arg1));
MOCHA_DropAtom(mc, arg1);
if (!message) {
MOCHA_ReportOutOfMemory(mc);
return MOCHA_FALSE;
}
if (decoder->window_context) {
if (!MOCHA_DatumToString(mc, argv[1], &arg2)) {
XP_FREE(message);
return MOCHA_FALSE;
}
retval = FE_Prompt(decoder->window_context, message,
MOCHA_GetAtomName(mc, arg2));
MOCHA_DropAtom(mc, arg2);
} else {
retval = 0;
}
XP_FREE(message);
if (!retval) {
*rval = MOCHA_null;
return MOCHA_TRUE;
}
atom = MOCHA_Atomize(mc, retval);
XP_FREE(retval);
if (!atom) return MOCHA_FALSE;
MOCHA_INIT_DATUM(mc, rval, MOCHA_STRING, u.atom, atom);
return MOCHA_TRUE;
}
/*
** Open and close of a named window.
*/
MochaBoolean
lm_CheckWindowName(MochaContext *mc, const char *window_name)
{
const char *cp;
for (cp = window_name; *cp != '\0'; cp++) {
if (!XP_IS_ALPHA(*cp) && !XP_IS_DIGIT(*cp) && *cp != '_') {
MOCHA_ReportError(mc,
"illegal character '%c' ('\\%o') in window name %s",
*cp, *cp, window_name);
return MOCHA_FALSE;
}
}
return MOCHA_TRUE;
}
static int32
win_has_option(char *options, char *name)
{
char *comma, *equal;
int32 found = 0;
for (;;) {
comma = XP_STRCHR(options, ',');
if (comma) *comma = '\0';
equal = XP_STRCHR(options, '=');
if (equal) *equal = '\0';
if (XP_STRCASECMP(options, name) == 0) {
if (!equal || XP_STRCASECMP(equal + 1, "yes") == 0)
found = 1;
else
found = XP_ATOI(equal + 1);
}
if (equal) *equal = '=';
if (comma) *comma = ',';
if (found || !comma)
break;
options = comma + 1;
}
return found;
}
static void
delete_window(void *closure)
{
FE_DestroyWindow((MWContext *)closure);
}
/* NB: These apply only to top-level windows, not to frames. */
static uint lm_window_count = 0;
static uint lm_window_limit = 100;
/* static XXXsee doc_open */
MochaBoolean
win_open(MochaContext *mc, MochaObject *obj,
uint argc, MochaDatum *argv, MochaDatum *rval)
{
MochaDecoder *decoder, *new_decoder;
URL_Struct *url_struct;
uint16 url_taint;
MochaAtom *atom, *window_name_atom;
const char *url_string, *window_name;
char *options;
Chrome chrome;
MWContext *old_context, *context;
if (!MOCHA_InstanceOf(mc, obj, &lm_window_class, argv[-1].u.fun))
return MOCHA_FALSE;
decoder = obj->data;
/* Make url_string absolute based on current document's base URL. */
url_struct = 0;
url_taint = MOCHA_TAINT_IDENTITY;
if (argc > 0) {
if (!MOCHA_DatumToString(mc, argv[0], &atom))
return MOCHA_FALSE;
url_string = (char *) MOCHA_GetAtomName(mc, atom);
if (*url_string != '\0') {
url_string = lm_CheckURL(mc, url_string);
if (url_string) {
url_struct = NET_CreateURLStruct(url_string, NET_DONT_RELOAD);
XP_FREE((char *)url_string);
if (!url_struct) {
MOCHA_ReportOutOfMemory(mc);
MOCHA_DropAtom(mc, atom);
return MOCHA_FALSE;
}
}
}
MOCHA_DropAtom(mc, atom);
if (!url_string)
return MOCHA_FALSE;
url_taint = argv[0].taint;
}
/* Set these to null so we can goto fail from here onward. */
new_decoder = 0;
window_name_atom = 0;
/* Sanity-check the optional window_name argument. */
if (argc > 1) {
if (!MOCHA_DatumToString(mc, argv[1], &window_name_atom))
goto fail;
window_name = MOCHA_GetAtomName(mc, window_name_atom);
if (!lm_CheckWindowName(mc, window_name))
goto fail;
} else {
window_name = 0;
}
/* Check for window chrome options. */
XP_BZERO(&chrome, sizeof chrome);
if (argc > 2) {
if (!MOCHA_DatumToString(mc, argv[2], &atom))
goto fail;
options = (char *)MOCHA_GetAtomName(mc, atom);
chrome.show_button_bar = win_has_option(options, "toolbar");
chrome.show_url_bar = win_has_option(options, "location");
chrome.show_directory_buttons = win_has_option(options, "directories");
chrome.show_bottom_status_bar = win_has_option(options, "status");
chrome.show_menu = win_has_option(options, "menubar");
chrome.show_security_bar = FALSE;
chrome.w_hint = win_has_option(options, "width");
chrome.h_hint = win_has_option(options, "height");
chrome.is_modal = FALSE;
chrome.show_scrollbar = win_has_option(options, "scrollbars");
chrome.allow_resize = win_has_option(options, "resizable");
chrome.allow_close = TRUE;
chrome.copy_history = FALSE; /* XXX need data tainting */
/* Make sure windows are at least 100 by 100 pixels. */
if (chrome.w_hint && chrome.w_hint < 100) chrome.w_hint = 100;
if (chrome.h_hint && chrome.h_hint < 100) chrome.h_hint = 100;
MOCHA_DropAtom(mc, atom);
options = 0;
} else {
/* Make a fully chromed window, but don't copy history (XXX taint). */
chrome.show_button_bar = TRUE;
chrome.show_url_bar = TRUE;
chrome.show_directory_buttons = TRUE;
chrome.show_bottom_status_bar = TRUE;
chrome.show_menu = TRUE;
chrome.show_security_bar = FALSE;
chrome.w_hint = chrome.h_hint = 0;
chrome.is_modal = FALSE;
chrome.show_scrollbar = TRUE;
chrome.allow_resize = TRUE;
chrome.allow_close = TRUE;
chrome.copy_history = FALSE; /* XXX need data tainting */
}
old_context = decoder->window_context;
if (!old_context) goto fail;
if (window_name)
context = XP_FindNamedContextInList(old_context, (char*)window_name);
else
context = 0;
if (context) {
new_decoder = LM_GetMochaDecoder(context);
if (!new_decoder)
goto fail;
if (url_struct && !lm_GetURL(mc, new_decoder, url_struct, url_taint)) {
url_struct = 0;
goto fail;
}
/* lm_GetURL() stashed a url_struct pointer, and owns it now. */
url_struct = 0;
} else {
if (lm_window_count >= lm_window_limit)
goto fail;
context = FE_MakeNewWindow(old_context, url_struct, (char*)window_name,
&chrome);
if (!context)
goto fail;
/* FE_MakeNewWindow() stashed a url_struct pointer, and owns it now. */
url_struct = 0;
new_decoder = LM_GetMochaDecoder(context);
if (!new_decoder) {
(void) FE_SetTimeout(delete_window, context, 0);
goto fail;
}
}
MOCHA_INIT_DATUM(mc, rval, MOCHA_OBJECT, u.obj, obj);
if (!MOCHA_SetProperty(mc, new_decoder->window_object, lm_opener_str,
WIN_OPENER, *rval)) {
goto fail;
}
new_decoder->opener = MOCHA_HoldObject(mc, obj);
MOCHA_DropAtom(mc, window_name_atom);
new_decoder->in_window_quota = MOCHA_TRUE;
lm_window_count++;
LM_PutMochaDecoder(new_decoder);
MOCHA_INIT_DATUM(mc, rval, MOCHA_OBJECT, u.obj, new_decoder->window_object);
return MOCHA_TRUE;
fail:
MOCHA_DropAtom(mc, window_name_atom);
if (new_decoder)
LM_PutMochaDecoder(new_decoder);
if (url_struct)
NET_FreeURLStruct(url_struct);
*rval = MOCHA_null;
return MOCHA_TRUE;
}
static MochaBoolean
win_close(MochaContext *mc, MochaObject *obj,
uint argc, MochaDatum *argv, MochaDatum *rval)
{
MochaDecoder *decoder;
MWContext *context;
char *message;
MochaBoolean ok;
if (!MOCHA_InstanceOf(mc, obj, &lm_window_class, argv[-1].u.fun))
return MOCHA_FALSE;
decoder = obj->data;
context = decoder->window_context;
if (!context || context->grid_parent)
return MOCHA_TRUE;
if (context->type != MWContextBrowser ||
(!decoder->opener &&
(XP_ListCount(SHIST_GetList(context)) > 1 ||
decoder != MOCHA_GetGlobalObject(mc)->data))) {
/*
** Prevent this window.close() call if this window
** - is not a browser window, or
** - was not opened by javascript, and
** - has session history other than the current document, or
** - does not contain the script that is closing this window.
*/
message = PR_smprintf("%s Confirm:\nClose window%s%s?",
mocha_language_name,
context->name ? " " : "",
context->name ? context->name : "");
ok = FE_Confirm(context, message);
XP_FREE(message);
if (!ok)
return MOCHA_TRUE;
}
if (!decoder->win_close_toid)
decoder->win_close_toid = FE_SetTimeout(delete_window, context, 0);
return MOCHA_TRUE;
}
/*
** Timeout support.
*/
static uint lm_timeout_count = 0;
static uint lm_timeout_limit = 1000;
static int32 lm_timeout_slack = 100000; /* taint-free timeout slack in usec */
#define FREE_TIMEOUT(mc, timeout) { \
MOCHA_free(mc, (timeout)->expr); \
MOCHA_free(mc, (timeout)); \
lm_timeout_count--; \
}
static void
win_run_timeout(void *closure)
{
MochaTimeout *timeout, **top;
MochaDecoder *decoder;
MochaContext *mc;
MochaTaintInfo *info;
int64 now, slack, later;
MochaDatum result;
/* Remove this timeout from the decoder's timeout list. */
timeout = closure;
decoder = timeout->self;
for (top = &decoder->timeouts; *top != timeout; top = &(*top)->next)
if (*top == 0)
return;
*top = timeout->next;
/* Propagate taint from invoking script to timeout. */
mc = decoder->mocha_context;
info = MOCHA_GetTaintInfo(mc);
MOCHA_MIX_TAINT(mc, info->accum, timeout->taint);
/* Close timing channels by attributing branch taint to tardy timeouts. */
now = PR_Now();
LL_I2L(slack, lm_timeout_slack);
LL_ADD(later, timeout->when, slack);
if (LL_CMP(now, >, later)) {
MOCHA_MIX_TAINT(mc, info->accum, lm_branch_taint);
}
/* Evaluate the timeout expression. */
if (LM_EvaluateBuffer(decoder, timeout->expr, XP_STRLEN(timeout->expr), 0,
&result)) {
MOCHA_DropRef(decoder->mocha_context, &result);
}
/* Free timeout and drop lm_timeout_count *after*, to handle rearming. */
FREE_TIMEOUT(mc, timeout);
}
void
lm_ClearWindowTimeouts(MochaDecoder *decoder)
{
MochaTimeout *timeout, *next;
for (timeout = decoder->timeouts; timeout; timeout = next) {
next = timeout->next;
FE_ClearTimeout(timeout->toid);
FREE_TIMEOUT(decoder->mocha_context, timeout);
}
decoder->timeouts = 0;
}
static MochaBoolean
win_set_timeout(MochaContext *mc, MochaObject *obj,
uint argc, MochaDatum *argv, MochaDatum *rval)
{
MochaDecoder *decoder;
MochaFloat interval;
MochaAtom *atom;
char *expr;
MochaTimeout *timeout;
int64 now, delta;
if (lm_timeout_count >= lm_timeout_limit) {
MOCHA_ReportError(mc, "too many timeouts");
return MOCHA_FALSE;
}
if (!MOCHA_InstanceOf(mc, obj, &lm_window_class, argv[-1].u.fun))
return MOCHA_FALSE;
decoder = obj->data;
if (!lm_CheckOrigins(mc, decoder))
return MOCHA_FALSE;
if (!MOCHA_DatumToNumber(mc, argv[1], &interval))
return MOCHA_FALSE;
if (!MOCHA_DatumToString(mc, argv[0], &atom))
return MOCHA_FALSE;
expr = MOCHA_strdup(mc, MOCHA_GetAtomName(mc, atom));
MOCHA_DropAtom(mc, atom);
if (!expr)
return MOCHA_FALSE;
timeout = MOCHA_malloc(mc, sizeof *timeout);
if (!timeout) {
MOCHA_free(mc, expr);
return MOCHA_TRUE;
}
timeout->self = decoder;
timeout->expr = expr;
timeout->taint = MOCHA_GetTaintInfo(mc)->accum;
MOCHA_MIX_TAINT(mc, timeout->taint, argv[0].taint);
MOCHA_MIX_TAINT(mc, timeout->taint, argv[1].taint);
now = PR_Now();
LL_D2L(delta, (double)interval);
LL_ADD(timeout->when, now, delta);
timeout->next = decoder->timeouts;
decoder->timeouts = timeout;
timeout->toid = FE_SetTimeout(win_run_timeout, timeout, (uint32)interval);
if (!timeout->toid) {
MOCHA_free(mc, expr);
MOCHA_free(mc, timeout);
return MOCHA_FALSE;
}
if (lm_timeout_count++ == 0)
lm_branch_taint = MOCHA_TAINT_IDENTITY;
MOCHA_INIT_DATUM(mc, rval, MOCHA_INTERNAL, u.ptr, timeout->toid);
return MOCHA_TRUE;
}
static MochaBoolean
win_clear_timeout(MochaContext *mc, MochaObject *obj,
uint argc, MochaDatum *argv, MochaDatum *rval)
{
MochaDecoder *decoder;
void *toid;
MochaTimeout **top, *timeout;
if (!MOCHA_InstanceOf(mc, obj, &lm_window_class, argv[-1].u.fun))
return MOCHA_FALSE;
decoder = obj->data;
if (argv[0].tag != MOCHA_INTERNAL)
return MOCHA_TRUE;
toid = argv[0].u.ptr;
for (top = &decoder->timeouts; (timeout = *top); top = &timeout->next) {
if (timeout->toid == toid) {
*top = timeout->next;
FE_ClearTimeout(timeout->toid);
FREE_TIMEOUT(mc, timeout);
break;
}
}
return MOCHA_TRUE;
}
static MochaBoolean
win_escape(MochaContext *mc, MochaObject *obj,
uint argc, MochaDatum *argv, MochaDatum *rval)
{
MochaFloat fval;
MochaAtom *atom;
char *str;
int mask;
if (argc > 1) {
if (!MOCHA_DatumToNumber(mc, argv[1], &fval))
return MOCHA_FALSE;
mask = (int)fval;
if (mask & ~(URL_XALPHAS | URL_XPALPHAS | URL_PATH)) {
MOCHA_ReportError(mc, "invalid string escape mask %x", mask);
return MOCHA_FALSE;
}
} else {
mask = URL_XALPHAS | URL_XPALPHAS | URL_PATH;
}
if (!MOCHA_DatumToString(mc, argv[0], &atom))
return MOCHA_FALSE;
str = NET_Escape(MOCHA_GetAtomName(mc, atom), mask);
MOCHA_DropAtom(mc, atom);
if (!str) {
MOCHA_ReportOutOfMemory(mc);
return MOCHA_FALSE;
}
atom = MOCHA_Atomize(mc, str);
XP_FREE(str);
if (!atom) return MOCHA_FALSE;
MOCHA_INIT_DATUM(mc, rval, MOCHA_STRING, u.atom, atom);
return MOCHA_TRUE;
}
static MochaBoolean
win_unescape(MochaContext *mc, MochaObject *obj,
uint argc, MochaDatum *argv, MochaDatum *rval)
{
MochaAtom *atom;
char *str;
if (!MOCHA_DatumToString(mc, argv[0], &atom))
return MOCHA_FALSE;
if (!(str = MOCHA_strdup(mc, MOCHA_GetAtomName(mc, atom))))
return MOCHA_FALSE;
NET_UnEscape(str);
atom = MOCHA_Atomize(mc, str);
XP_FREE(str);
if (!atom) return MOCHA_FALSE;
MOCHA_INIT_DATUM(mc, rval, MOCHA_STRING, u.atom, atom);
return MOCHA_TRUE;
}
static MochaBoolean
win_focus(MochaContext *mc, MochaObject *obj,
uint argc, MochaDatum *argv, MochaDatum *rval)
{
MochaDecoder *decoder;
if (!MOCHA_InstanceOf(mc, obj, &lm_window_class, argv[-1].u.fun))
return MOCHA_FALSE;
decoder = obj->data;
if (decoder->window_context)
FE_FocusInputElement(decoder->window_context, 0);
return MOCHA_TRUE;
}
static MochaBoolean
win_blur(MochaContext *mc, MochaObject *obj,
uint argc, MochaDatum *argv, MochaDatum *rval)
{
MochaDecoder *decoder;
if (!MOCHA_InstanceOf(mc, obj, &lm_window_class, argv[-1].u.fun))
return MOCHA_FALSE;
decoder = obj->data;
if (decoder->window_context)
FE_BlurInputElement(decoder->window_context, 0);
return MOCHA_TRUE;
}
/*
** Scrolling support.
*/
static MochaBoolean
win_scroll(MochaContext *mc, MochaObject *obj,
uint argc, MochaDatum *argv, MochaDatum *rval)
{
MochaDecoder *decoder;
int32 x, y;
if (!MOCHA_InstanceOf(mc, obj, &lm_window_class, argv[-1].u.fun))
return MOCHA_FALSE;
decoder = obj->data;
if (argc != 2 ||
argv[0].tag != MOCHA_NUMBER ||
argv[1].tag != MOCHA_NUMBER ||
!decoder->window_context) {
return MOCHA_TRUE;
}
x = (int) argv[0].u.fval;
y = (int) argv[1].u.fval;
FE_ScrollDocTo(decoder->window_context, 0, x, y);
return MOCHA_TRUE;
}
/*
** Run load events from the timeout loop to avoid freeing contexts in use,
** e.g., parent.document.write() in a frame's BODY onLoad= attribute.
*/
static void
load_event(void *closure)
{
MochaDecoder *decoder;
MWContext *context;
MochaDatum rval;
decoder = closure;
context = decoder->window_context;
if (context) {
/* Can't call if the window was closed. */
(void) lm_SendEvent(context, decoder->window_object, lm_onLoad_str,
&rval);
}
LM_PutMochaDecoder(decoder);
}
static MochaBoolean
is_context_really_busy(MWContext *context)
{
lo_TopState *top_state;
int i;
XP_List *kids;
MWContext *kid;
if (!context)
return MOCHA_FALSE;
if ((top_state = lo_FetchTopState(XP_DOCID(context))) &&
top_state->mocha_has_onload &&
(top_state->mocha_loading_applets_count ||
top_state->mocha_loading_embeds_count)) {
return MOCHA_TRUE;
}
if ((kids = context->grid_children)) {
for (i = 1; (kid = XP_ListGetObjectNum(kids, i)); i++) {
if (is_context_really_busy(kid))
return MOCHA_TRUE;
}
}
return MOCHA_FALSE;
}
MochaBoolean
is_context_busy(MWContext *context)
{
if (XP_IsContextBusy(context))
return MOCHA_TRUE;
return is_context_really_busy(context);
}
static void
try_load_event(MWContext *context, MochaDecoder *decoder)
{
MochaContext *mc;
/*
** Don't do anything if we are waiting for URLs to load, or if applets
** have not been loaded and initialized.
*/
if (is_context_busy(context))
return;
/*
** Send a load event at most once. Also, if we can't create a timeout,
** we give up rather than risk flaming death.
*/
if (decoder && !decoder->load_event_sent) {
decoder->load_event_sent = MOCHA_TRUE;
mc = decoder->mocha_context;
MOCHA_HoldObject(mc, decoder->window_object);
if (!FE_SetTimeout(load_event, decoder, 0))
MOCHA_DropObject(mc, decoder->window_object);
}
/*
** Now that we may have scheduled a load event for this context, send
** a transfer-done event to our parent frameset, if any.
*/
if (context->grid_parent)
LM_SendLoadEvent(context->grid_parent, LM_XFER_DONE);
}
/*
** Entry point for the netlib to notify Mocha of load and unload events.
*/
void
LM_SendLoadEvent(MWContext *context, LM_LoadEvent event)
{
MochaDecoder *decoder;
MochaDatum rval;
decoder = context->mocha_context ? LM_GetMochaDecoder(context) : 0;
if (decoder && decoder->resize_reload)
goto out;
switch (event) {
case LM_LOAD:
if (decoder && decoder->mocha_context) {
if (context->win_csid & MULTIBYTE)
MOCHA_SetCharFilter(decoder->mocha_context, (MochaCharFilter) INTL_IsLeadByte, (void *) context->win_csid);
else
MOCHA_SetCharFilter(decoder->mocha_context, NULL, NULL);
}
if (decoder) {
if (decoder->url_struct && decoder->url_struct->resize_reload) {
decoder->resize_reload = MOCHA_TRUE;
goto out;
}
decoder->event_mask |= EVENT_LOAD;
}
try_load_event(context, decoder);
break;
case LM_UNLOAD:
if (decoder && decoder->mocha_context) {
if (context->win_csid & MULTIBYTE)
MOCHA_SetCharFilter(decoder->mocha_context, (MochaCharFilter) INTL_IsLeadByte, (void *) context->win_csid);
else
MOCHA_SetCharFilter(decoder->mocha_context, NULL, NULL);
}
if (decoder) {
decoder->event_mask &= ~EVENT_LOAD;
decoder->load_event_sent = MOCHA_FALSE;
(void) lm_SendEvent(context, decoder->window_object,
lm_onUnload_str, &rval);
}
break;
case LM_XFER_DONE:
if (decoder && (decoder->event_mask & EVENT_LOAD))
try_load_event(context, decoder);
if (context->grid_parent)
LM_SendLoadEvent(context->grid_parent, LM_XFER_DONE);
break;
default:;
}
out:
if (decoder)
LM_PutMochaDecoder(decoder);
return;
}
/*
** Entry point for front-ends to notify Mocha code of scroll events.
*/
void
LM_SendOnScroll(MWContext *context, int32 x, int32 y)
{
MochaDecoder *decoder;
MochaDatum rval;
if (!context->mocha_context)
return;
decoder = LM_GetMochaDecoder(context);
if (!(decoder->event_mask & EVENT_SCROLL)) {
decoder->event_mask |= EVENT_SCROLL;
(void) lm_SendEvent(context, decoder->window_object, lm_onScroll_str,
&rval);
decoder->event_mask &= ~EVENT_SCROLL;
}
LM_PutMochaDecoder(decoder);
}
static MochaFunctionSpec lm_window_methods[] = {
{"alert", win_alert, 1},
{"clearTimeout", win_clear_timeout, 1},
{"close", win_close, 0},
{"confirm", win_confirm, 1},
{"open", win_open, 1},
{"prompt", win_prompt, 2},
{"setTimeout", win_set_timeout, 2},
{"escape", win_escape, 2},
{"unescape", win_unescape, 1},
{lm_blur_str, win_blur, 0},
{lm_focus_str, win_focus, 0},
{lm_scroll_str, win_scroll, 2},
{0}
};
MochaDecoder *
lm_NewWindow(MWContext *context)
{
History_entry *he;
MochaDecoder *decoder;
MochaContext *mc;
MochaObject *obj;
switch (context->type) {
case MWContextBrowser:
if (EDT_IS_EDITOR(context))
return 0; /* No Mocha for editor contexts. */
/* FALL THROUGH */
case MWContextDialog:
case MWContextMail:
case MWContextNews:
break;
default:
return 0;
}
/* If this is a (resizing) frame, get its decoder from session history. */
he = context->is_grid_cell ? SHIST_GetCurrent(&context->hist) : 0;
if (he && he->savedData.Window) {
decoder = he->savedData.Window;
he->savedData.Window = 0;
mc = decoder->mocha_context;
obj = decoder->window_object;
decoder->window_context = context;
context->mocha_context = mc;
if (!lm_RestoreAnonymousImages(context, decoder)) {
MOCHA_DropObject(mc, obj);
return 0;
}
return decoder;
}
/* Otherwise, make a new decoder/context/object for this window. */
decoder = XP_NEW_ZAP(MochaDecoder);
if (!decoder)
return 0;
mc = MOCHA_NewContext(LM_STACK_SIZE);
if (!mc) {
XP_DELETE(decoder);
return 0;
}
obj = MOCHA_NewObject(mc, &lm_window_class, decoder, 0, 0, 0, 0);
if (!obj) {
MOCHA_DestroyContext(mc);
XP_DELETE(decoder);
return 0;
}
/* MWContext holds window_object even though it points at decoder */
decoder->window_object = MOCHA_HoldObject(mc, obj);
decoder->window_context = context;
decoder->mocha_context = mc;
context->mocha_context = mc;
if (!lm_InitWindowContent(decoder)) {
/* This drop will cause finalization and call lm_DestroyWindow(). */
MOCHA_DropObject(mc, obj);
return 0;
}
LM_LINK_OBJECT(decoder, obj, context->name);
decoder->url_list_tail = &decoder->next_url_list;
decoder->write_taint_accum = MOCHA_TAINT_IDENTITY;
MOCHA_SetBranchCallback(mc, lm_BranchCallback);
MOCHA_SetErrorReporter(mc, lm_ErrorReporter);
return decoder;
}
void
lm_DestroyWindow(MochaDecoder *decoder)
{
MochaContext *mc;
/* We must not have an MWContext at this point. */
XP_ASSERT(!decoder->window_context);
/* Drop decoder refs to window prototypes and sub-objects. */
lm_FreeWindowContent(decoder);
/* The opener property could be set even if no document was loaded. */
mc = decoder->mocha_context;
MOCHA_DropObject(mc, decoder->opener);
if (decoder->in_window_quota)
lm_window_count--;
/* Free Mocha context and decoder struct, window is gone. */
MOCHA_DestroyContext(mc);
XP_DELETE(decoder);
}
MochaBoolean
lm_InitWindowContent(MochaDecoder *decoder)
{
MochaContext *mc;
MochaObject *obj;
MochaDatum d;
mc = decoder->mocha_context;
obj = decoder->window_object;
/* XXX add properties first, else standard methods use their slots! */
if (!MOCHA_SetProperties(mc, obj, window_props))
return MOCHA_FALSE;
if (!MOCHA_SetGlobalObject(mc, obj))
return MOCHA_FALSE;
if (decoder->opener) {
MOCHA_INIT_FULL_DATUM(mc, &d, MOCHA_OBJECT, MDF_ENUMERATE,
MOCHA_TAINT_IDENTITY, u.obj, decoder->opener);
if (!MOCHA_SetProperty(mc, obj, lm_opener_str, WIN_OPENER, d))
return MOCHA_FALSE;
}
return MOCHA_DefineFunctions(mc, obj, lm_window_methods) &&
lm_DefineDocument(decoder) &&
lm_DefineHistory(decoder) &&
lm_DefineLocation(decoder) &&
lm_DefineNavigator(decoder) &&
lm_InitImageClass(decoder) &&
lm_InitInputClasses(decoder);
}
void
lm_FreeWindowContent(MochaDecoder *decoder)
{
MochaContext *mc;
MochaObject *obj;
/* Clear any stream that the user forgot to close. */
LM_ClearDecoderStream(decoder);
/* Clear any pending timeouts and URL fetches. */
lm_ClearWindowTimeouts(decoder);
lm_ClearNextURLList(decoder);
/* These flags should never be set, but if any are, we'll cope. */
decoder->replace_location = MOCHA_FALSE;
decoder->resize_reload = MOCHA_FALSE;
decoder->load_event_sent = MOCHA_FALSE;
decoder->domain_set = MOCHA_FALSE;
decoder->visited = MOCHA_FALSE;
decoder->writing_input = MOCHA_FALSE;
/* Reset written taint so lm_CheckGetURL doesn't misfire. */
decoder->write_taint_accum = MOCHA_TAINT_IDENTITY;
#define FREE_AND_CLEAR(p) if (p) { XP_FREE((char *)p); p = 0; }
FREE_AND_CLEAR(decoder->origin_url);
FREE_AND_CLEAR(decoder->source_url);
FREE_AND_CLEAR(decoder->nesting_url);
FREE_AND_CLEAR(decoder->buffer);
#undef FREE_AND_CLEAR
/* Forgive and forget all excessive errors. */
decoder->error_count = 0;
/* Clear the event mask. */
decoder->event_mask = 0;
mc = decoder->mocha_context;
#define DROP_AND_CLEAR(obj) MOCHA_DropObject(mc, obj); obj = 0
/* Clear all object prototype refs. */
DROP_AND_CLEAR(decoder->form_prototype);
DROP_AND_CLEAR(decoder->image_prototype);
DROP_AND_CLEAR(decoder->input_prototype);
DROP_AND_CLEAR(decoder->option_prototype);
DROP_AND_CLEAR(decoder->url_prototype);
/* Clear the active form weak link in case we're still in a form. */
decoder->active_form = 0;
/* Drop and clear window sub-object refs. */
DROP_AND_CLEAR(decoder->document);
DROP_AND_CLEAR(decoder->history);
DROP_AND_CLEAR(decoder->location);
DROP_AND_CLEAR(decoder->navigator);
/* Drop and clear document sub-object refs. */
DROP_AND_CLEAR(decoder->forms);
DROP_AND_CLEAR(decoder->links);
DROP_AND_CLEAR(decoder->anchors);
DROP_AND_CLEAR(decoder->applets);
DROP_AND_CLEAR(decoder->embeds);
DROP_AND_CLEAR(decoder->images);
#undef DROP_AND_CLEAR
obj = decoder->window_object;
if (obj) {
MOCHA_ClearScope(mc, obj);
(void) MOCHA_SetProperty(mc, obj, lm_closed_str, WIN_CLOSED,
MOCHA_false);
}
/* Clear this context's taint; taint for its origin may linger, however */
lm_ClearTaint(mc);
/* XXX fix this please! */
decoder->anon_image_clients = 0;
#ifdef DEBUG_brendan
lm_DumpLiveObjects(decoder);
#endif
}
/* XXX prototype in layout.h please! */
extern lo_GridCellRec *
lo_ContextToCell(MWContext *context, lo_GridRec **grid_ptr);
void
LM_RemoveWindowContext(MWContext *context)
{
MochaDecoder *decoder;
MochaContext *mc;
/* Do work only if this context has a Mocha decoder. */
mc = context->mocha_context;
if (!mc)
return;
decoder = MOCHA_GetGlobalObject(mc)->data;
/* Sever the bidirectional connection between context and decoder. */
XP_ASSERT(decoder->window_context == context);
context->mocha_context = 0;
decoder->window_context = 0;
LM_ClearDecoderStream(decoder);
/*
* Frames are special, because their contexts are destroyed and recreated
* when they're reloaded, even when resizing.
*/
if (context->is_grid_cell) {
lo_TopState *top_state;
top_state = lo_FetchTopState(XP_DOCID(context));
if (top_state && top_state->resize_reload) {
lo_GridRec *grid = 0;
lo_GridCellRec *rec = lo_ContextToCell(context, &grid);
if (rec && rec->history) {
History_entry *he = (History_entry *)rec->history;
/*
* Set current history entry's saved window from its layout cell.
* We need to do this rather than SHIST_SetCurrentDocWindowData()
* because FE_FreeGridWindow (who calls us indirectly) has already
* "stolen" session history for return to layout, who saves it in
* parent session history in lo_InternalDiscardDocument().
*/
he->savedData.Window = decoder;
lm_SaveAnonymousImages(context, decoder);
return;
}
if (context->hist.list_ptr) {
/*
* XXX Somehow the context's history list still exists (see
* SHIST_EndSession() and FE_FreeGridWindow() for two places
* that null it; but note unfixed shist.c bug: EndSession does
* not null cur_doc_ptr, which SHIST_GetCurrent() returns).
*
* This case "can't happen" (ha ha). But if it does, save the
* decoder in the intact context history list.
*/
SHIST_SetCurrentDocWindowData(context, decoder);
lm_SaveAnonymousImages(context, decoder);
return;
}
/* Else we must free the window object tree now, rather than leak. */
}
}
/* This might call lm_DestroyWindow(decoder), so do it last. */
MOCHA_DropObject(mc, decoder->window_object);
}
extern void
LM_DropSavedWindow(MWContext *context, void *window)
{
MochaDecoder *decoder = window;
MOCHA_DropObject(decoder->mocha_context, decoder->window_object);
}