/* -*- 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; */ }