/* -*- Mode: c++; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*-
**
** Copyright (C) 2002 Opera Software AS.  All rights reserved.
**
** This file is part of the Opera web browser.  It may not be distributed
** under any circumstances.
**
*/

#ifndef ES_UTILS_ESLOAD_H
#define ES_UTILS_ESLOAD_H

class ES_Thread;
class ES_InlineScriptThread;
class ES_DocumentGenerationListener;
class ES_ExternalInlineScriptThread;
class HLDocProfile;
class BlockBuffer;
class HTML_Element;
class ByteBuffer;
class ES_SharedThreadInfo;
class ES_ThreadSchedulerImpl;

#include "modules/ecmascript/ecmascript.h"
#include "modules/util/simset.h"

class ES_LoadManager
{
public:
	ES_LoadManager(HLDocProfile *hld_profile);
	~ES_LoadManager();

	BOOL IsBlocked();
	/**< Returns TRUE if the current inline script thread (inline_thread) is
	     not blocked in a call to document.write{,ln} or if there are no
	     inline script threads but a generating thread and it is not blocked in
	     a call to document.write{,ln}.
	     @return OpBoolean::IS_TRUE, OpBoolean::IS_FALSE or
	             OpStatus::ERR_NO_MEMORY. */

	BOOL IsWriting();
	/**< Returns TRUE if there is a current inline script thread and that
	     thread has written data using document.write() and it has not yet
	     finished writing it's data.
	     @return TRUE or FALSE. */

	BOOL IsFinished();
	/**< Returns TRUE if there are no inline script threads, no generating
	     thread, we are not waiting for an external script to be loaded and
	     there is no script generated data that hasn't been consumed.
	     @return TRUE or FALSE. */

	BOOL IsGenerationIdle();
	/**< Returns TRUE if the document being generated by a script and
	     there is currently no activity and no live external thread. */

	BOOL IsInlineThread(ES_Thread *thread);
	/**< Returns TRUE if this is a thread that should block document loading
	     @return TRUE or FALSE. */

	BOOL HasData();
	/**< Returns TRUE if GetCurrentLoadBuffer() returns non-NULL and the buffer
	     returned is not empty.
	     @return TRUE or FALSE. */

	BOOL MoreData();
	/**< Returns TRUE if scripts are expected to generate more data.
	     @return TRUE or FALSE. */

	void UpdateCurrentScriptElm(BOOL set);
	/**< Will set the data_script_elm to the first script element that has
	     written data, or set it to NULL if no writing is going on.
	     @param set If TRUE, the data_script_elm will be set to the first
	                script element that has data. If FALSE, it will be set
	                to NULL. */

	OP_STATUS Write(ES_Thread *thread, const uni_char *str, unsigned str_len, BOOL end_with_newline);
	/**< Writes a string to the current load buffer, adding a "\n" if
	     'end_with_newline' is TRUE.  If possible, the data is parsed and
	     consumed, if not, the thread is blocked and a MSG_URL_DATA_LOADED
	     is posted so the regular document loading mechanism can handle
	     the data.

	     @param thread The thread that caused the data to be written, can not
	                   be NULL.
	     @param str The string to write, can not be NULL.
	     @param str_len Length of the string in uni_chars
	     @param end_with_newline If TRUE, a "\n" is written after 'str'.
	     @return OpStatus::ERR_NO_MEMORY or OpStatus::OK. */

	void SignalWriteFinished();
	/**< Called by the parser to signal that it has handled the entire
		 written buffer */

	ES_Thread *GetThread(HTML_Element *script_element);
	ES_Thread *GetInterruptedThread(HTML_Element *script_element);

	OP_STATUS RegisterScript(HTML_Element *element, BOOL is_external, BOOL is_inline_script, ES_Thread *interrupt_thread = NULL);
	/**< See ScriptElm class members for a definition of parameters. */

	OP_BOOLEAN SetScript(HTML_Element *element, ES_ProgramText *program_array,
	                     int program_array_length, BOOL allow_cross_origin_errors = FALSE);
	/**< Add an inline script element for the HTML element, or start a script
	     element that was added with AddExternalScript after the script has
	     been loaded.
	     @param element The HTML "script" element.
	     @param program_array Program text array.
	     @param program_array_length Program text array length.
	     @param allow_cross_origin_errors If TRUE then allow compilation errors to be
	            reported in full, irrespective of origin of script and
	            the thread's runtime.
	     @return OpBoolean::IS_TRUE if the inline script was compiled and
	             started correctly, OpBoolean::IS_FALSE if there was a
	             compilation error, OpStatus::ERR_OUT_OF_RANGE if the
	             script was not scheduled due to excessive script recursion
	             or OpStatus::ERR_NO_MEMORY. */

	OP_BOOLEAN SetScript(HTML_Element *element, ES_Program *program, BOOL allow_cross_origin_errors);
	/**< Stage 2 of the two preceding SetScript methods */

	OP_STATUS CancelInlineScript(HTML_Element *element);
	/**< Cancel the inline script element for the HTML element.  The inline
	     script element must have been added with AddExternalScript and
	     not yet have started with AddInlineScript.
	     @param element The HTML "script" element.
	     @return OpStatus::OK or OpStatus::ERR_NO_MEMORY. */

	OP_STATUS ReportScriptError(HTML_Element *element, const uni_char *error_message);
	/**< Report an error encountered while loading and processing an inline script element.
	     The inline script element must have been added with AddExternalScript and
	     not yet have started with AddInlineScript. The error is reported to the
	     document runtime's error handler. The error is reported as a triple: the
	     supplied error message, the inline script's URL and the document line number
	     it appeared at (if known.) The error information is not to redacted if 
	     the origins of the runtime and script doesn't match.

	     @param element The HTML "script" element.
	     @param error_message The error message to report.
	     @return OpStatus::OK or OpStatus::ERR_NO_MEMORY. */

	OP_STATUS CancelInlineThreads();
	/**< Cancel all inline script threads.  Called from
	     FramesDocument::StopLoading() and ES_LoadManager::SetScript().
	     If the ScriptElm pointed by ES_LoadManager::data_script_elm
	     is destroyed in this function, it'll be set to NULL
	     @return OpStatus::ERR_NO_MEMORY or OpStatus::OK. */

	BOOL GetScriptGeneratingDoc() const;
	/**< Returns TRUE if the document is being generated entirely by a script.
	     @return TRUE or FALSE. */

	BOOL HasScript(HTML_Element *element);
	/**< Returns TRUE if an inline script element has been created for the given
	     HTML element (that is, if AddExternalScript or AddInlineScript has
	     been called with the same 'element' argument).
	     @return TRUE or FALSE. */

	BOOL HasScripts();
	/**< Returns TRUE if there are any inline script elements blocking the
	     document loading process.
	     @return TRUE or FALSE. */

	OP_STATUS OpenDocument(ES_Thread *thread);
	/**< Called when document.open() is called (explicitly or implicitly by
	     document.write or document.writeln).
	     @param thread The ECMAScript thread that called document.open.
	     @return OpStatus::OK or OpStatus::ERR_NO_MEMORY. */

	OP_STATUS CloseDocument(ES_Thread *thread);
	/**< Called when document.close() is called.
	     @param thread The ECMAScript thread that called document.close.
	     @return OpStatus::OK or OpStatus::ERR_NO_MEMORY. */

	ES_ScriptType GetLastScriptType() const;
	/**< Return the genesis of the most recent script (see SetLastScriptGenesis()). */

	void SetLastScriptType(ES_ScriptType type);
	/**< Set whether the most recently handled script was generated
	     (by a script), inline in the document or loaded from a URL.
	     The value is volatile -- it is used only for communicating
	     information from code that knows to code that needs to know,
	     during a single descent in the call graph.
	     @param how How the script came to be. */

	void HandleOOM();
	/**< Handle an out of memory during parsing of written data.  Does nothing yet. */

	void StopLoading();
	/**< Notification that the document has finished loading.  Unblocks the
	     thread blocked by a call to document.close if there is such a thread. */

	void StopLoading(ES_Thread *thread);
	/**< A script called window.stop() to stop loading.  If the script's thread
	     is an inline script thread, it needs to finish before we actually stop
	     loading since it would otherwise be killed.
	     @param thread The script's thread. */

	BOOL IsStopped();
	/**< Returns TRUE if the document has finished loading (that is, if
	     StopLoading() has been called. */

	BOOL IsClosing();
	/**< Returns TRUE if the document is in the process of closing (that is, if
	     CloseDocument() has been called.) */

	BOOL IsHandlingExternalScript(ES_Thread *thread);
	/**< Returns TRUE if there is a data_script_elm and it is external. */

	BOOL SupportsWrite(ES_Thread* thread);
	/**< Returns TRUE if ES_LoadManager::Write can accept data from the thread */

	BOOL HasParserBlockingScript();
	/**< Returns TRUE if there is a script inserted by the parser in the queue
	     that is blocking loading */

private:
	friend class ES_DocumentGenerationListener;
	friend class ES_ExternalInlineScriptThread;
	friend class ES_ThreadSchedulerImpl;

	class ScriptElm
		: public Link
	{
	public:
		enum State
		{
			INITIAL,
			BLOCKED_LOAD,
			BLOCKED_WRITE,
			BLOCKED_CLOSE,
			BLOCKED_INTERRUPTED,
			RUNNING
		};

		ScriptElm();
		~ScriptElm();

		void Out();

		ES_Thread *thread;
		ES_DocumentGenerationListener *listener;
		BOOL is_finished_writing;
		HTML_Element *element;

		ScriptElm *interrupted;
		ES_SharedThreadInfo *shared_thread_info;

		State state;
		unsigned interrupted_count;

		bool is_external:1;
		/**< True if this is an external script, that is, a script started by a
		     script element with a SRC attribute.  False if the source of the
		     script is the text content of the script element.  Irrelevant if
		     this script wasn't started by a script element, that is, if
		     'is_script_in_document' is false. */
		bool is_script_in_document:1;
		/**< True if this is a script element started by this load manager and
		     executing in this document.  False if this is a script executing in
		     some other document that has been registered with this load manager
		     after calling document.write() to write data into this document.

		     Note: Also false for a thread that has been migrated into this
		     document from another document after calling document.open() to
		     replace its own document even though technically such a thread is
		     executing in this document's scheduler. */
		bool is_inline_script:1;
		/**< True if this is a script element that was created by the HTML
		     parser during the parsing of the document, or if its thread has
		     interrupted such a script thread.  False if this is any other type
		     of script, such as one created and instead via appendChild by an
		     event handler.  Always false if 'is_script_in_document' is false.

		     Key features of scripts for which this flag is true is that they
		     are automatically killed if the loading of the document is aborted,
		     and that they can generate document content, by calling
		     document.write(), that is parsed as if it had been part of the
		     original document source immediately succeeding the script's end
		     tag. */
		bool node_protected:1;
		bool supports_doc_write:1;
		bool has_generated_external:1;
	};

	void AbortLoading();

	void AddScriptElm(ScriptElm *new_script_elm, BOOL do_interrupt = TRUE, BOOL insert_first = FALSE);
	ScriptElm *FindScriptElm(HTML_Element *element);

	ScriptElm *FindScriptElm(ES_Thread *thread, BOOL create = TRUE, BOOL find_related = FALSE);
	/**< Find the ScriptElm for 'thread'.  If there is no such ScriptElm and 'create'
	     is TRUE, a new ScriptElm is created and returned.  If 'find_related' is TRUE
	     and no ScriptElm is found for 'thread' but for a thread that 'thread' has
	     interrupted, that ScriptElm is returned instead. */

	BOOL BlockedByPred(ScriptElm *script_elm);

	OP_STATUS FinishedInlineScript(ES_Thread *thread);
	OP_STATUS FinishedInlineScript(ScriptElm *script_elm);

	void SetCurrentScriptElm(ScriptElm *new_data_script_elm, BOOL from_run_state = FALSE);
	/**< Replaces the manager's current ScriptElm. The 'from_run_state' flag
	     indicates if the script was in a running state when being marked
	     as current. */

	void PostURLMessage();

	HLDocProfile *hld_profile;
	/**< The HLDocProfile that owns this load manager. */

	Head script_elms;

	ScriptElm *data_script_elm;
	/**< Internal variable which points to the current ScriptElm
	     whose ES_Thread is writing data to the document using
	     Write(). */

	BOOL data_script_elm_was_running;
	/**< If TRUE, the current ScriptElm was in running state when initiating
	     its Write(). If this write can be handled without blocking the
	     ScriptElm and nor its thread, revert the ScriptElm's state back
	     to running (before resetting current ScriptElm.) */

	BOOL is_cancelling;
	/**< Flag set while cancelling inline script threads from CancelInlineThreads(),
	     to make RemoveInlineThread() behave a little more efficient. */

	BOOL is_open;
	/**< Flag set when an non-inline script has called document.open() and
	     cleared when document.close() is called. */

	BOOL is_closing;
	/**< Flag set when document.close() is called and cleared when the document
	     is done loading. */

	BOOL is_stopped;
	/**< Flag set by StopLoading which is called when the document is done
	     loading. */

	BOOL abort_loading;
	/**< Flag set if loading should be aborted as soon as no inline scripts are
	     executing. */

	BOOL first_script;
	/**< Flag set initially, cleared after the first script is started. */

	ES_ScriptType last_script_type;
	/**< The kind of script most recently handled.  VOLATILE. */
};

#endif // ES_UTILS_ESLOAD_H
