/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
*
* 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.
*/
#include "xp.h"
/*
* SCRIPT tag support.
*/
#include "libmocha.h"
#include "lo_ele.h"
#include "net.h"
#include "pa_parse.h"
#include "pa_tags.h"
#include "layout.h"
#include "libevent.h"
#include "css.h"
#include "laystyle.h"
#include "mcom_db.h"
#include "laylayer.h"
#include "prefapi.h"
#include "timing.h"
static char lo_jsAllowFileSrcFromNonFile[]
= "javascript.allow.file_src_from_non_file";
/*
* XXX Should dynamic layer resize-reload really rerun scripts, potentially
* XXX destroying and reinitializing JS state? An onResize event handler
* XXX would be consistent with how windows and static layers resize-reload.
*/
#define SCRIPT_EXEC_OK(top_state, state, type, script_type) \
(top_state->resize_reload == FALSE || \
type != script_type || \
(lo_InsideLayer(state) && (lo_IsAnyCurrentAncestorDynamic(state) || \
lo_IsAnyCurrentAncestorSourced(state))))
static void
lo_ParseScriptLanguage(MWContext * context, PA_Tag * tag, int8 * type,
JSVersion * version)
{
PA_Block buff;
char * str;
buff = lo_FetchParamValue(context, tag, PARAM_LANGUAGE);
if (buff != NULL) {
PA_LOCK(str, char *, buff);
if ((XP_STRCASECMP(str, js_language_name) == 0) ||
(XP_STRCASECMP(str, "LiveScript") == 0) ||
(XP_STRCASECMP(str, "Mocha") == 0)) {
*type = SCRIPT_TYPE_MOCHA;
*version = JSVERSION_DEFAULT;
}
else if (XP_STRCASECMP(str, "JavaScript1.1") == 0) {
*type = SCRIPT_TYPE_MOCHA;
*version = JSVERSION_1_1;
}
else if (XP_STRCASECMP(str, "JavaScript1.2") == 0) {
*type = SCRIPT_TYPE_MOCHA;
*version = JSVERSION_1_2;
}
else if (XP_STRCASECMP(str, "JavaScript1.3") == 0) {
*type = SCRIPT_TYPE_MOCHA;
*version = JSVERSION_1_3;
}
else if (XP_STRCASECMP(str, "JavaScript1.4") == 0) {
*type = SCRIPT_TYPE_MOCHA;
*version = JSVERSION_1_4;
}
else {
*type = SCRIPT_TYPE_UNKNOWN;
}
PA_FREE(buff);
}
}
void
lo_BlockScriptTag(MWContext *context, lo_DocState *state, PA_Tag *tag)
{
lo_TopState *top_state;
pa_DocData *doc_data;
if (!LM_CanDoJS(context))
return;
/*
* Find the parser stream from its private data, doc_data, pointed at by
* a top_state member for just this situation.
*/
top_state = state->top_state;
doc_data = top_state->doc_data;
XP_ASSERT(doc_data != NULL && doc_data->url_struct != NULL);
if (doc_data == NULL || doc_data->url_struct == NULL) /* XXX paranoia */
return;
/*
* The parser must not parse ahead after a script or style end-tag, or
* it could get into a brute_tag state (XMP, PRE, etc.) and enter a mode
* that would mess up document.writes coming from the script or style.
* So we put it into overflow mode here, and decrement overflow when we
* unblock.
*
* We test tag first to handle the call from lo_ProcessScriptTag, which
* wants us to create an input state if needed, but not to count comment
* bytes via lo_data. Sad to say, this means lo_ProcessScriptTag must
* also increment doc_data->overflow, but only if tag->is_end == 1 (a.k.a.
* PR_TRUE), meaning the script tag was not blocked by an earlier blocking
* tag that caused it to go through here and set is_end to 2. XXX we can
* do this only because all layout and libparse tests of is_end are of the
* form "tag->is_end == FALSE".
*/
if (tag) {
/* Tag non-null means we're called from lo_BlockTag in layout.c. */
if (tag->is_end == FALSE) {
int8 script_type;
JSVersion ver;
/* Keep track of whether we're in a script or style container... */
script_type = SCRIPT_TYPE_UNKNOWN;
lo_ParseScriptLanguage(context, tag, &script_type, &ver);
if (tag->type == P_STYLE || tag->type == P_LINK ||
script_type != SCRIPT_TYPE_UNKNOWN)
top_state->in_blocked_script = TRUE;
}
else if (top_state->in_blocked_script) {
/* ...so we can avoid overflowing the parser on superfluous end
* tags, such as those emitted by libmime.
*/
top_state->in_blocked_script = FALSE;
if (SCRIPT_EXEC_OK(top_state, state, tag->type, P_SCRIPT)) {
tag->is_end = (PRPackedBool)2;
PA_PushOverflow(doc_data);
doc_data->overflow_depth ++;
}
}
}
/*
* Count the bytes of HTML comments and heuristicly "lost" newlines in
* tag so lo_ProcessScriptTag can recover it there later, in preference
* to the (then far-advanced) value of doc_data->comment_bytes. Ensure
* that this hack is not confused for NULL by adding 1 here and taking
* it away later. Big XXX for future cleanup, needless to say.
*/
if (tag)
tag->lo_data = (void *)(doc_data->comment_bytes + 1);
if (top_state->script_tag_count++ == 0)
ET_SetDecoderStream(context, doc_data->parser_stream,
doc_data->url_struct, JS_FALSE);
}
typedef struct {
MWContext *context;
lo_DocState *state;
PA_Tag *tag;
char *url, *archiveSrc, *id, *codebase;
char *buffer;
uint32 bufferSize;
JSVersion version;
JSBool inlineSigned;
} ScriptData;
void
lo_DestroyScriptData(void *arg)
{
ScriptData *data = arg;
if (data == NULL)
return;
XP_FREEIF(data->url);
XP_FREEIF(data->archiveSrc);
XP_FREEIF(data->id);
XP_FREEIF(data->codebase);
XP_FREEIF(data->buffer);
if (data->tag)
PA_FreeTag(data->tag);
XP_FREE(data);
}
static void
lo_script_src_exit_fn(URL_Struct *url_struct, int status, MWContext *context);
static void
lo_script_archive_exit_fn(URL_Struct *url_struct, int status, MWContext *context);
static Bool
lo_create_script_blockage(MWContext *context, lo_DocState *state, int type)
{
lo_TopState *top_state;
LO_Element *block_ele;
top_state = state->top_state;
if (!top_state) {
XP_ASSERT(0);
return FALSE;
}
block_ele = lo_NewElement(context, state, LO_SCRIPT, NULL, 0);
if (block_ele == NULL) {
top_state->out_of_memory = TRUE;
return FALSE;
}
block_ele->type = type;
block_ele->lo_any.ele_id = NEXT_ELEMENT;
top_state->layout_blocking_element = block_ele;
if (type == LO_SCRIPT)
top_state->current_script = block_ele;
TIMING_STARTCLOCK_OBJECT("lo:blk-js", block_ele);
return TRUE;
}
/*
* used for ARCHIVE= and SRC=
* Create name of form "archive.jar/src.js"
*/
static char *
lo_BuildJSArchiveURL( char *archive_name, char *filename )
{
uint32 len = XP_STRLEN(archive_name) + XP_STRLEN(filename) + 2;
char *path = XP_ALLOC(len);
if (path)
{
XP_STRCPY(path, archive_name);
XP_STRCAT(path, "/");
XP_STRCAT(path, filename);
}
return path;
}
/*
* Load a document from an external URL. Assumes that layout is
* already blocked
*/
static void
lo_GetScriptFromURL(ScriptData *data, int script_type)
{
URL_Struct *url_struct;
lo_TopState *top_state = data->state->top_state;
PA_Tag *tag = data->tag;
url_struct = NET_CreateURLStruct(data->url, top_state->force_reload);
if (url_struct == NULL) {
top_state->out_of_memory = TRUE;
lo_DestroyScriptData(data);
return;
}
url_struct->must_cache = TRUE;
url_struct->preset_content_type = TRUE;
if (script_type == SCRIPT_TYPE_CSS) {
StrAllocCopy(url_struct->content_type, TEXT_CSS);
} else {
if (tag->type == P_STYLE || tag->type == P_LINK) {
StrAllocCopy(url_struct->content_type, TEXT_JSSS);
} else {
StrAllocCopy(url_struct->content_type, js_content_type);
}
}
XP_ASSERT(top_state->layout_blocking_element);
if (!url_struct->content_type)
goto out;
if (!data->inlineSigned) {
if (data->archiveSrc) {
/* ARCHIVE= and SRC= */
/*
* Need to set nesting url. Create name of form
* "archive.jar/src.js"
*/
char *path = lo_BuildJSArchiveURL(url_struct->address,
data->archiveSrc);
if (!path)
goto out;
ET_SetNestingUrl(data->context, path);
/* version taken care of in lo_script_archive_exit_fn */
XP_FREE(path);
} else {
/* SRC= but no ARCHIVE= */
ET_SetNestingUrl(data->context, url_struct->address);
ET_SetVersion(data->context, data->version);
}
}
url_struct->fe_data = data;
if (data->archiveSrc != NULL || data->inlineSigned) {
NET_GetURL(url_struct,
FO_CACHE_ONLY,
data->context,
lo_script_archive_exit_fn);
} else {
NET_GetURL(url_struct,
FO_CACHE_AND_PRESENT,
data->context,
lo_script_src_exit_fn);
}
return;
out:
top_state->out_of_memory = TRUE;
NET_FreeURLStruct(url_struct);
lo_DestroyScriptData(data);
return;
}
/*
* A script has just completed. If we are blocked on that script
* free the blockage
*/
static void
lo_unblock_script_tag(MWContext * context, Bool messWithParser)
{
lo_TopState *top_state;
lo_DocState *state;
LO_Element * block_ele;
LO_Element * current_script;
PA_Tag * tag;
NET_StreamClass stream;
top_state = lo_FetchTopState(XP_DOCID(context));
if (!top_state || !top_state->doc_state)
return;
state = top_state->doc_state;
/*
* Remember the fake element we created but clear the current
* script flag of the top_state since the FlushBlockage call
* might just turn around and block us on another script
*/
current_script = top_state->current_script;
top_state->current_script = NULL;
/*
* if we are finishing a nested script make sure the current_script
* points to our parent script
*/
for (tag = top_state->tags; tag; tag = tag->next) {
if (tag->type == P_NSCP_REBLOCK) {
top_state->current_script = tag->lo_data;
break;
}
}
/* Flush tags blocked by this and
* have the inline script to verify.
*/
top_state->scriptData = data;
}
else {
XP_FREE(url);
XP_FREEIF(id);
XP_FREEIF(archiveSrc);
}
}
}
else {
/*
* We are in the tag now...
*/
size_t line_buf_len;
intn script_type;
char *scope_to=NULL;
char *untransformed = NULL;
script_type = top_state->in_script;
top_state->in_script = SCRIPT_TYPE_NOT;
/* guard against superfluous end tags */
if (script_type == SCRIPT_TYPE_NOT)
goto end_tag_out;
/* convert from CSS to JavaScript here */
if (tag->type != P_LINK && script_type == SCRIPT_TYPE_CSS) {
char *new_buffer;
int32 new_buffer_length;
CSS_ConvertToJS((char *)state->line_buf,
state->line_buf_len,
&new_buffer,
&new_buffer_length);
if (!new_buffer) {
/* css translator error, unblock layout and return */
state->text_divert = P_UNKNOWN;
state->line_buf_len = 0; /* clear script text */
goto end_tag_out;
}
untransformed = (char *) state->line_buf;
state->line_buf = (PA_Block) new_buffer;
state->line_buf_len = new_buffer_length;
state->line_buf_size = new_buffer_length;
if (state->line_buf_len)
state->line_buf_len--; /* hack: subtract one to remove final \n */
script_type = SCRIPT_TYPE_JSSS;
}
if (tag->type == P_STYLE) {
/* mocha scoped to document == jsss */
scope_to = "document";
}
/*
* Reset these before potentially recursing indirectly through
* the document.write() built-in function, which writes to the
* very same doc_data->parser_stream that this and still have scriptData set here, it must
* be left over from an error case above, so we free it.
*/
if (top_state->scriptData) {
XP_ASSERT(!top_state->layout_blocking_element);
lo_DestroyScriptData(top_state->scriptData);
top_state->scriptData = NULL;
}
XP_FREEIF(untransformed);
}
}
static char script_reblock_tag[] = "<" PT_NSCP_REBLOCK ">";
/*
* Create a tag that will reblock us
*/
void
LO_CreateReblockTag(MWContext * context, LO_Element * current_script)
{
lo_TopState *top_state;
pa_DocData *doc_data;
PA_Tag * tag/*, ** tag_ptr*/;
top_state = lo_FetchTopState(XP_DOCID(context));
doc_data = (pa_DocData *)top_state->doc_data;
tag = pa_CreateMDLTag(doc_data,
script_reblock_tag,
sizeof script_reblock_tag - 1);
if (tag == NULL)
return;
tag->lo_data = current_script;
/*
* Kludge in the write level for sanity check in lo_LayoutTag.
*/
tag->newline_count = (uint16)top_state->input_write_level;
/*
* Add the reblock tag to the tags list but don't advance the write_point
* in case we just wrote a nested script that also writes: the inner
* script must insert before the outer reblock tag, but after all the
* earlier tags.
*/
/* tag_ptr = top_state->input_write_point; */
lo_BlockTag(context, NULL, tag);
/* top_state->input_write_point = tag_ptr; */
}