/* The QuickJS document object implementation. */

#ifdef HAVE_CONFIG_H
#include "config.h"
#endif

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#ifdef CONFIG_LIBDOM
#include <dom/dom.h>
#include <dom/bindings/hubbub/parser.h>
#endif

#include "elinks.h"

#include "cookies/cookies.h"
#include "dialogs/status.h"
#include "document/document.h"
#include "document/libdom/doc.h"
#include "document/libdom/mapa.h"
#include "document/view.h"
#include "js/ecmascript.h"
#include "js/ecmascript-c.h"
#include "js/libdom/parse.h"
#include "js/quickjs/mapa.h"
#include "js/quickjs.h"
#include "js/quickjs/collection.h"
#include "js/quickjs/event.h"
#include "js/quickjs/form.h"
#include "js/quickjs/forms.h"
#include "js/quickjs/fragment.h"
#include "js/quickjs/implementation.h"
#include "js/quickjs/location.h"
#include "js/quickjs/document.h"
#include "js/quickjs/element.h"
#include "js/quickjs/node.h"
#include "js/quickjs/nodelist.h"
#include "js/quickjs/nodelist2.h"
#include "js/quickjs/text.h"
#include "js/quickjs/window.h"
#include "session/session.h"
#include "viewer/text/vs.h"

#define countof(x) (sizeof(x) / sizeof((x)[0]))

static JSClassID js_doctype_class_id;
static JSClassID js_document_class_id;

struct document_listener {
	LIST_HEAD_EL(struct document_listener);
	char *typ;
	JSValue fun;
};

enum readyState {
	LOADING = 0,
	INTERACTIVE,
	COMPLETE
};

struct js_document_private {
	LIST_OF(struct document_listener) listeners;
	struct ecmascript_interpreter *interpreter;
	JSValue thisval;
	JSValue images;
	dom_event_listener *listener;
	void *node;
	enum readyState state;
};

static void document_event_handler(dom_event *event, void *pw);

void *
js_doc_getopaque(JSValueConst obj)
{
	REF_JS(obj);

	struct js_document_private *res = (struct js_document_private *)JS_GetOpaque(obj, js_document_class_id);

	if (!res) {
		return NULL;
	}

	return res->node;
}

void *
document_get_node(JSValueConst obj)
{
	return js_doc_getopaque(obj);
}

static void
js_document_mark(JSRuntime *rt, JSValueConst val, JS_MarkFunc *mark_func)
{
#ifdef ECMASCRIPT_DEBUG
	fprintf(stderr, "%s:%s\n", __FILE__, __FUNCTION__);
#endif
	REF_JS(val);

	struct js_document_private *doc_private = (struct js_document_private *)JS_GetOpaque(val, js_document_class_id);

	if (doc_private) {
		struct document_listener *l;

		foreach(l, doc_private->listeners) {
			JS_MarkValue(rt, l->fun, mark_func);
		}
	}
}

static JSValue
js_document_get_property_anchors(JSContext *ctx, JSValueConst this_val)
{
#ifdef ECMASCRIPT_DEBUG
	fprintf(stderr, "%s:%s\n", __FILE__, __FUNCTION__);
#endif
	REF_JS(this_val);

	dom_html_document *doc = (struct dom_html_document *)js_doc_getopaque(this_val);

	if (!doc) {
		return JS_NULL;
	}
	//dom_node_ref(doc);
	dom_html_collection *anchors = NULL;
	dom_exception exc = dom_html_document_get_anchors(doc, &anchors);

	if (exc != DOM_NO_ERR || !anchors) {
		//dom_node_unref(doc);
		return JS_NULL;
	}
	JSValue rr = getCollection(ctx, anchors);
	//dom_node_unref(doc);

	RETURN_JS(rr);
}

static JSValue
js_document_get_property_baseURI(JSContext *ctx, JSValueConst this_val)
{
#ifdef ECMASCRIPT_DEBUG
	fprintf(stderr, "%s:%s\n", __FILE__, __FUNCTION__);
#endif
	REF_JS(this_val);

	struct view_state *vs;
	struct ecmascript_interpreter *interpreter = (struct ecmascript_interpreter *)JS_GetContextOpaque(ctx);
	vs = interpreter->vs;

	if (!vs) {
#ifdef ECMASCRIPT_DEBUG
	fprintf(stderr, "%s:%s %d\n", __FILE__, __FUNCTION__, __LINE__);
#endif
		return JS_NULL;
	}

	char *str = get_uri_string(vs->uri, URI_BASE);

	if (!str) {
#ifdef ECMASCRIPT_DEBUG
	fprintf(stderr, "%s:%s %d\n", __FILE__, __FUNCTION__, __LINE__);
#endif
		return JS_NULL;
	}

	JSValue ret = JS_NewString(ctx, str);
	mem_free(str);

	RETURN_JS(ret);
}

static JSValue
js_document_get_property_body(JSContext *ctx, JSValueConst this_val)
{
#ifdef ECMASCRIPT_DEBUG
	fprintf(stderr, "%s:%s\n", __FILE__, __FUNCTION__);
#endif
	REF_JS(this_val);

	dom_html_document *doc = (struct dom_html_document *)js_doc_getopaque(this_val);

	if (!doc) {
		return JS_NULL;
	}
	//dom_node_ref(doc);

	dom_html_element *body = NULL;
	dom_exception exc = dom_html_document_get_body(doc, &body);

	if (exc != DOM_NO_ERR || !body) {
		//dom_node_unref(doc);
		return JS_NULL;
	}
	//dom_node_unref(doc);
	JSValue rr = getNode(ctx, body);
#ifdef ECMASCRIPT_DEBUG
fprintf(stderr, "Before: %s:%d\n", __FUNCTION__, __LINE__);
#endif
	dom_node_unref(body);

	return rr;
}

static JSValue
js_document_set_property_body(JSContext *ctx, JSValueConst this_val, JSValue val)
{
#ifdef ECMASCRIPT_DEBUG
	fprintf(stderr, "%s:%s\n", __FILE__, __FUNCTION__);
#endif
	REF_JS(this_val);
	REF_JS(val);
	// TODO
	return JS_UNDEFINED;
}

#ifdef CONFIG_COOKIES
static JSValue
js_document_get_property_cookie(JSContext *ctx, JSValueConst this_val)
{
#ifdef ECMASCRIPT_DEBUG
	fprintf(stderr, "%s:%s\n", __FILE__, __FUNCTION__);
#endif
	REF_JS(this_val);

	struct ecmascript_interpreter *interpreter = (struct ecmascript_interpreter *)JS_GetContextOpaque(ctx);
	struct view_state *vs;
	struct string *cookies;
	vs = interpreter->vs;

	if (!vs) {
#ifdef ECMASCRIPT_DEBUG
	fprintf(stderr, "%s:%s %d\n", __FILE__, __FUNCTION__, __LINE__);
#endif
		return JS_NULL;
	}
	cookies = send_cookies_js(vs->uri);

	if (cookies) {
		static char cookiestr[1024];

		strncpy(cookiestr, cookies->source, 1023);
		done_string(cookies);

		JSValue r = JS_NewString(ctx, cookiestr);

		RETURN_JS(r);
	} else {
		JSValue rr = JS_NewStringLen(ctx, "", 0);

		RETURN_JS(rr);
	}
}

static JSValue
js_document_set_property_cookie(JSContext *ctx, JSValueConst this_val, JSValue val)
{
#ifdef ECMASCRIPT_DEBUG
	fprintf(stderr, "%s:%s\n", __FILE__, __FUNCTION__);
#endif
	REF_JS(this_val);
	REF_JS(val);

	struct ecmascript_interpreter *interpreter = (struct ecmascript_interpreter *)JS_GetContextOpaque(ctx);
	struct view_state *vs;
	vs = interpreter->vs;

	if (!vs) {
#ifdef ECMASCRIPT_DEBUG
	fprintf(stderr, "%s:%s %d\n", __FILE__, __FUNCTION__, __LINE__);
#endif
		return JS_NULL;
	}
	const char *text;
	char *str;
	size_t len;
	text = JS_ToCStringLen(ctx, &len, val);

	if (!text) {
		return JS_EXCEPTION;
	}
	str = stracpy(text);
	if (str) {
		set_cookie(vs->uri, str);
		mem_free(str);
	}
	JS_FreeCString(ctx, text);

	return JS_UNDEFINED;
}

#endif

static JSValue
js_document_get_property_charset(JSContext *ctx, JSValueConst this_val)
{
#ifdef ECMASCRIPT_DEBUG
	fprintf(stderr, "%s:%s\n", __FILE__, __FUNCTION__);
#endif
	REF_JS(this_val);

// TODO
	JSValue r = JS_NewStringLen(ctx, "utf-8", strlen("utf-8"));

	RETURN_JS(r);
}

static JSValue
js_document_get_property_childNodes(JSContext *ctx, JSValueConst this_val)
{
#ifdef ECMASCRIPT_DEBUG
	fprintf(stderr, "%s:%s\n", __FILE__, __FUNCTION__);
#endif
	REF_JS(this_val);

	dom_html_document *doc = (struct dom_html_document *)js_doc_getopaque(this_val);

	if (!doc) {
		return JS_NULL;
	}
	//dom_node_ref(doc);

	dom_element *root = NULL;
	dom_exception exc = dom_document_get_document_element(doc, &root);

	if (exc != DOM_NO_ERR || !root) {
		//dom_node_unref(doc);
		return JS_NULL;
	}
	dom_nodelist *nodes = NULL;
	exc = dom_node_get_child_nodes(root, &nodes);
#ifdef ECMASCRIPT_DEBUG
fprintf(stderr, "Before: %s:%d\n", __FUNCTION__, __LINE__);
#endif
	dom_node_unref(root);

	if (exc != DOM_NO_ERR || !nodes) {
		//dom_node_unref(doc);
		return JS_NULL;
	}
	//dom_node_unref(doc);

	return getNodeList(ctx, nodes);
}

static JSValue
js_document_get_property_currentScript(JSContext *ctx, JSValueConst this_val)
{
#ifdef ECMASCRIPT_DEBUG
	fprintf(stderr, "%s:%s\n", __FILE__, __FUNCTION__);
#endif
	REF_JS(this_val);

	struct ecmascript_interpreter *interpreter = (struct ecmascript_interpreter *)JS_GetContextOpaque(ctx);
	struct view_state *vs;
	struct document_view *doc_view;
	struct document *document;
	vs = interpreter->vs;

	if (!vs) {
#ifdef ECMASCRIPT_DEBUG
	fprintf(stderr, "%s:%s %d\n", __FILE__, __FUNCTION__, __LINE__);
#endif
		return JS_NULL;
	}
	doc_view = vs->doc_view;
	document = doc_view->document;
	void *mapa = (void *)document->element_map;

	if (mapa) {
		dom_node *elem = (dom_node *)find_in_map(mapa, interpreter->element_offset);

		if (elem) {
			dom_string *tag_name = NULL;
			dom_exception exc = dom_node_get_node_name(elem, &tag_name);

			if (exc != DOM_NO_ERR || !tag_name) {
				return JS_NULL;
			}
			bool isScript = !strcmp("SCRIPT", dom_string_data(tag_name));
			dom_string_unref(tag_name);

			if (isScript) {
				return getNode(ctx, elem);
			}
		}
	}

	return JS_NULL;
}


static JSValue
js_document_get_property_defaultView(JSContext *ctx, JSValueConst this_val)
{
#ifdef ECMASCRIPT_DEBUG
	fprintf(stderr, "%s:%s\n", __FILE__, __FUNCTION__);
#endif
	REF_JS(this_val);
	JSValue global_obj = JS_GetGlobalObject(ctx);

	RETURN_JS(global_obj);
}

static JSValue
js_document_get_property_doctype(JSContext *ctx, JSValueConst this_val)
{
#ifdef ECMASCRIPT_DEBUG
	fprintf(stderr, "%s:%s\n", __FILE__, __FUNCTION__);
#endif
	REF_JS(this_val);

	dom_html_document *doc = (struct dom_html_document *)js_doc_getopaque(this_val);

	if (!doc) {
		return JS_NULL;
	}
	//dom_node_ref(doc);

	dom_document_type *dtd;
	dom_document_get_doctype(doc, &dtd);
	//dom_node_unref(doc);

	return getDoctype(ctx, dtd);
}

static JSValue
js_document_get_property_documentElement(JSContext *ctx, JSValueConst this_val)
{
#ifdef ECMASCRIPT_DEBUG
	fprintf(stderr, "%s:%s\n", __FILE__, __FUNCTION__);
#endif
	REF_JS(this_val);

	dom_html_document *doc = (struct dom_html_document *)js_doc_getopaque(this_val);

	if (!doc) {
		return JS_NULL;
	}
	//dom_node_ref(doc);

	dom_html_element *root = NULL;
	dom_exception exc = dom_document_get_document_element(doc, &root);

	if (exc != DOM_NO_ERR || !root) {
		//dom_node_unref(doc);
		return JS_NULL;
	}
	//dom_node_unref(doc);
	JSValue rr = getNode(ctx, root);
#ifdef ECMASCRIPT_DEBUG
fprintf(stderr, "Before: %s:%d\n", __FUNCTION__, __LINE__);
#endif
	dom_node_unref(root);

	return rr;
}

static JSValue
js_document_get_property_documentURI(JSContext *ctx, JSValueConst this_val)
{
#ifdef ECMASCRIPT_DEBUG
	fprintf(stderr, "%s:%s\n", __FILE__, __FUNCTION__);
#endif
	REF_JS(this_val);

	struct view_state *vs;
	struct ecmascript_interpreter *interpreter = (struct ecmascript_interpreter *)JS_GetContextOpaque(ctx);
	vs = interpreter->vs;

	if (!vs) {
		return JS_NULL;
	}

	char *str = get_uri_string(vs->uri, URI_BASE);

	if (!str) {
#ifdef ECMASCRIPT_DEBUG
	fprintf(stderr, "%s:%s %d\n", __FILE__, __FUNCTION__, __LINE__);
#endif
		return JS_NULL;
	}

	JSValue ret = JS_NewString(ctx, str);
	mem_free(str);

	RETURN_JS(ret);
}

static JSValue
js_document_get_property_domain(JSContext *ctx, JSValueConst this_val)
{
#ifdef ECMASCRIPT_DEBUG
	fprintf(stderr, "%s:%s\n", __FILE__, __FUNCTION__);
#endif
	REF_JS(this_val);

	struct view_state *vs;
	struct ecmascript_interpreter *interpreter = (struct ecmascript_interpreter *)JS_GetContextOpaque(ctx);
	vs = interpreter->vs;

	if (!vs) {
#ifdef ECMASCRIPT_DEBUG
	fprintf(stderr, "%s:%s %d\n", __FILE__, __FUNCTION__, __LINE__);
#endif
		return JS_NULL;
	}

	char *str = get_uri_string(vs->uri, URI_HOST);

	if (!str) {
#ifdef ECMASCRIPT_DEBUG
	fprintf(stderr, "%s:%s %d\n", __FILE__, __FUNCTION__, __LINE__);
#endif
		return JS_NULL;
	}

	JSValue ret = JS_NewString(ctx, str);
	mem_free(str);

	RETURN_JS(ret);
}

static JSValue
js_document_get_property_forms(JSContext *ctx, JSValueConst this_val)
{
#ifdef ECMASCRIPT_DEBUG
	fprintf(stderr, "%s:%s\n", __FILE__, __FUNCTION__);
#endif
	REF_JS(this_val);

	dom_html_document *doc = (struct dom_html_document *)js_doc_getopaque(this_val);

	if (!doc) {
		return JS_NULL;
	}
	//dom_node_ref(doc);

	dom_html_collection *forms = NULL;
	dom_exception exc = dom_html_document_get_forms(doc, &forms);

	if (exc != DOM_NO_ERR || !forms) {
		//dom_node_unref(doc);
		return JS_NULL;
	}
	JSValue rr = getForms(ctx, forms);
	JS_FreeValue(ctx, rr);
	//dom_node_unref(doc);

	RETURN_JS(rr);
}

static JSValue
js_document_get_property_head(JSContext *ctx, JSValueConst this_val)
{
#ifdef ECMASCRIPT_DEBUG
	fprintf(stderr, "%s:%s\n", __FILE__, __FUNCTION__);
#endif
	REF_JS(this_val);

// TODO
	return JS_NULL;

//	return getNode(ctx, element);
}

static JSValue
js_document_get_property_images(JSContext *ctx, JSValueConst this_val)
{
#ifdef ECMASCRIPT_DEBUG
	fprintf(stderr, "%s:%s\n", __FILE__, __FUNCTION__);
#endif
	REF_JS(this_val);

	struct js_document_private *res = (struct js_document_private *)JS_GetOpaque(this_val, js_document_class_id);

	if (!res) {
		return JS_NULL;
	}

	if (!JS_IsNull(res->images)) {
		return JS_DupValue(ctx, res->images);
	}
	dom_html_document *doc = (struct dom_html_document *)res->node;

	if (!doc) {
		return JS_NULL;
	}
	//dom_node_ref(doc);

	dom_html_collection *images = NULL;
	dom_exception exc = dom_html_document_get_images(doc, &images);

	if (exc != DOM_NO_ERR || !images) {
		//dom_node_unref(doc);
		return JS_NULL;
	}
	res->images = getCollection(ctx, images);

	RETURN_JS(res->images);
}

static JSValue
js_document_get_property_implementation(JSContext *ctx, JSValueConst this_val)
{
#ifdef ECMASCRIPT_DEBUG
	fprintf(stderr, "%s:%s\n", __FILE__, __FUNCTION__);
#endif
	REF_JS(this_val);

	return getImplementation(ctx);
}

static JSValue
js_document_get_property_links(JSContext *ctx, JSValueConst this_val)
{
#ifdef ECMASCRIPT_DEBUG
	fprintf(stderr, "%s:%s\n", __FILE__, __FUNCTION__);
#endif
	REF_JS(this_val);

	dom_html_document *doc = (struct dom_html_document *)js_doc_getopaque(this_val);

	if (!doc) {
		return JS_NULL;
	}
	//dom_node_ref(doc);
	dom_html_collection *links = NULL;
	dom_exception exc = dom_html_document_get_links(doc, &links);

	if (exc != DOM_NO_ERR || !links) {
		//dom_node_unref(doc);
		return JS_NULL;
	}
	JSValue rr = getCollection(ctx, links);
	//dom_node_unref(doc);

	RETURN_JS(rr);
}

static JSValue
js_document_get_property_location(JSContext *ctx, JSValueConst this_val)
{
#ifdef ECMASCRIPT_DEBUG
	fprintf(stderr, "%s:%s\n", __FILE__, __FUNCTION__);
#endif
	REF_JS(this_val);
	struct ecmascript_interpreter *interpreter = (struct ecmascript_interpreter *)JS_GetContextOpaque(ctx);

	JSValue ret = interpreter->location_obj;

	JS_DupValue(ctx, ret);

	RETURN_JS(ret);
}

static JSValue
js_document_get_property_nodeType(JSContext *ctx, JSValueConst this_val)
{
#ifdef ECMASCRIPT_DEBUG
	fprintf(stderr, "%s:%s\n", __FILE__, __FUNCTION__);
#endif
	REF_JS(this_val);

	return JS_NewInt32(ctx, 9);
}

static JSValue
js_document_set_property_location(JSContext *ctx, JSValueConst this_val, JSValue val)
{
#ifdef ECMASCRIPT_DEBUG
	fprintf(stderr, "%s:%s\n", __FILE__, __FUNCTION__);
#endif
	REF_JS(this_val);
	REF_JS(val);

	struct ecmascript_interpreter *interpreter = (struct ecmascript_interpreter *)JS_GetContextOpaque(ctx);
	struct view_state *vs;
	struct document_view *doc_view;
	vs = interpreter->vs;

	if (!vs) {
#ifdef ECMASCRIPT_DEBUG
	fprintf(stderr, "%s:%s %d\n", __FILE__, __FUNCTION__, __LINE__);
#endif
		return JS_NULL;
	}
	doc_view = vs->doc_view;
	const char *url;
	size_t len;

	url = JS_ToCStringLen(ctx, &len, val);

	if (!url) {
		return JS_EXCEPTION;
	}

	location_goto_const(doc_view, url, 0);
	JS_FreeCString(ctx, url);

	return JS_UNDEFINED;
}

static JSValue
js_document_get_property_readyState(JSContext *ctx, JSValueConst this_val)
{
#ifdef ECMASCRIPT_DEBUG
	fprintf(stderr, "%s:%s\n", __FILE__, __FUNCTION__);
#endif
	REF_JS(this_val);
	struct js_document_private *doc_private = (struct js_document_private *)(JS_GetOpaque(this_val, js_document_class_id));

	if (!doc_private) {
		return JS_NULL;
	}
	JSValue r;

	switch (doc_private->state) {
	default:
	case LOADING:
		r = JS_NewString(ctx, "loading");
		break;
	case INTERACTIVE:
		r = JS_NewString(ctx, "interactive");
		break;
	case COMPLETE:
		r = JS_NewString(ctx, "complete");
		break;
	}
	RETURN_JS(r);
}

static JSValue
js_document_get_property_referrer(JSContext *ctx, JSValueConst this_val)
{
#ifdef ECMASCRIPT_DEBUG
	fprintf(stderr, "%s:%s\n", __FILE__, __FUNCTION__);
#endif
	REF_JS(this_val);

	struct ecmascript_interpreter *interpreter = (struct ecmascript_interpreter *)JS_GetContextOpaque(ctx);
	struct view_state *vs;
	struct document_view *doc_view;
	struct document *document;
	struct session *ses;
	char *str;
	vs = interpreter->vs;

	if (!vs) {
#ifdef ECMASCRIPT_DEBUG
	fprintf(stderr, "%s:%s %d\n", __FILE__, __FUNCTION__, __LINE__);
#endif
		return JS_NULL;
	}
	doc_view = vs->doc_view;
	document = doc_view->document;
	ses = doc_view->session;

	switch (get_opt_int("protocol.http.referer.policy", NULL)) {
	case REFERER_NONE:
		/* oh well */
		return JS_UNDEFINED;

	case REFERER_FAKE:
		return JS_NewString(ctx, get_opt_str("protocol.http.referer.fake", NULL));

	case REFERER_TRUE:
		/* XXX: Encode as in add_url_to_httset_prop_string(&prop, ) ? --pasky */
		if (ses->referrer) {
			str = get_uri_string(ses->referrer, URI_HTTP_REFERRER);

			if (str) {
				JSValue ret = JS_NewString(ctx, str);
				mem_free(str);

				RETURN_JS(ret);
			} else {
				return JS_UNDEFINED;
			}
		}
		break;

	case REFERER_SAME_URL:
		str = get_uri_string(document->uri, URI_HTTP_REFERRER);

		if (str) {
			JSValue ret = JS_NewString(ctx, str);
			mem_free(str);

			RETURN_JS(ret);
		} else {
			return JS_UNDEFINED;
		}
		break;
	}

	return JS_UNDEFINED;
}

static JSValue
js_document_get_property_scripts(JSContext *ctx, JSValueConst this_val)
{
#ifdef ECMASCRIPT_DEBUG
	fprintf(stderr, "%s:%s\n", __FILE__, __FUNCTION__);
#endif
	REF_JS(this_val);

// TODO
	//dom_html_document *doc = (dom_html_document *)document->dom;

	return JS_NULL;

//	JSValue rr = getCollection(ctx, elements);
//	JS_FreeValue(ctx, rr);

//	RETURN_JS(rr);
}

static JSValue
js_document_get_property_title(JSContext *ctx, JSValueConst this_val)
{
#ifdef ECMASCRIPT_DEBUG
	fprintf(stderr, "%s:%s\n", __FILE__, __FUNCTION__);
#endif
	REF_JS(this_val);

	struct ecmascript_interpreter *interpreter = (struct ecmascript_interpreter *)JS_GetContextOpaque(ctx);
	struct view_state *vs;
	struct document_view *doc_view;
	struct document *document;

	vs = interpreter->vs;

	if (!vs) {
#ifdef ECMASCRIPT_DEBUG
	fprintf(stderr, "%s:%s %d\n", __FILE__, __FUNCTION__, __LINE__);
#endif
		return JS_NULL;
	}
	doc_view = vs->doc_view;
	document = doc_view->document;
	JSValue r = JS_NewString(ctx, document->title);
	RETURN_JS(r);
}

static JSValue
js_document_set_property_title(JSContext *ctx, JSValueConst this_val, JSValue val)
{
#ifdef ECMASCRIPT_DEBUG
	fprintf(stderr, "%s:%s\n", __FILE__, __FUNCTION__);
#endif
	REF_JS(this_val);
	REF_JS(val);

	struct ecmascript_interpreter *interpreter = (struct ecmascript_interpreter *)JS_GetContextOpaque(ctx);
	struct view_state *vs;
	struct document_view *doc_view;
	struct document *document;
	vs = interpreter->vs;

	if (!vs || !vs->doc_view) {
#ifdef ECMASCRIPT_DEBUG
	fprintf(stderr, "%s:%s %d\n", __FILE__, __FUNCTION__, __LINE__);
#endif
		return JS_NULL;
	}
	doc_view = vs->doc_view;
	document = doc_view->document;

	const char *str;
	char *string;
	size_t len;

	str = JS_ToCStringLen(ctx, &len, val);

	if (!str) {
		return JS_EXCEPTION;
	}

	string = stracpy(str);
	JS_FreeCString(ctx, str);

	mem_free_set(&document->title, string);
	print_screen_status(doc_view->session);

	return JS_UNDEFINED;
}

static JSValue
js_document_get_property_url(JSContext *ctx, JSValueConst this_val)
{
#ifdef ECMASCRIPT_DEBUG
	fprintf(stderr, "%s:%s\n", __FILE__, __FUNCTION__);
#endif
	REF_JS(this_val);

	struct ecmascript_interpreter *interpreter = (struct ecmascript_interpreter *)JS_GetContextOpaque(ctx);
	struct view_state *vs;
	struct document_view *doc_view;
	struct document *document;
	vs = interpreter->vs;

	if (!vs) {
#ifdef ECMASCRIPT_DEBUG
	fprintf(stderr, "%s:%s %d\n", __FILE__, __FUNCTION__, __LINE__);
#endif
		return JS_NULL;
	}
	doc_view = vs->doc_view;
	document = doc_view->document;
	char *str = get_uri_string(document->uri, URI_ORIGINAL);

	if (str) {
		JSValue ret = JS_NewString(ctx, str);
		mem_free(str);

		RETURN_JS(ret);
	} else {
		return JS_UNDEFINED;
	}
}

static JSValue
js_document_set_property_url(JSContext *ctx, JSValueConst this_val, JSValue val)
{
#ifdef ECMASCRIPT_DEBUG
	fprintf(stderr, "%s:%s\n", __FILE__, __FUNCTION__);
#endif
	REF_JS(this_val);
	REF_JS(val);

	struct ecmascript_interpreter *interpreter = (struct ecmascript_interpreter *)JS_GetContextOpaque(ctx);
	struct view_state *vs;
	struct document_view *doc_view;
	vs = interpreter->vs;

	if (!vs) {
#ifdef ECMASCRIPT_DEBUG
	fprintf(stderr, "%s:%s %d\n", __FILE__, __FUNCTION__, __LINE__);
#endif
		return JS_NULL;
	}
	doc_view = vs->doc_view;
	const char *url;
	size_t len;

	url = JS_ToCStringLen(ctx, &len, val);

	if (!url) {
		return JS_EXCEPTION;
	}
	location_goto_const(doc_view, url, 0);
	JS_FreeCString(ctx, url);

	return JS_UNDEFINED;
}


static JSValue
js_document_write_do(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv, int newline)
{
#ifdef ECMASCRIPT_DEBUG
	fprintf(stderr, "%s:%s\n", __FILE__, __FUNCTION__);
#endif
	REF_JS(this_val);

	struct ecmascript_interpreter *interpreter = (struct ecmascript_interpreter *)JS_GetContextOpaque(ctx);

	if (argc >= 1) {
		int element_offset = interpreter->element_offset;
		struct string string;

		if (init_string(&string)) {
			for (int i = 0; i < argc; ++i) {
				const char *str;
				size_t len;

				str = JS_ToCStringLen(ctx, &len, argv[i]);

				if (!str) {
					done_string(&string);
					return JS_EXCEPTION;
				}
				add_bytes_to_string(&string, str, len);
				JS_FreeCString(ctx, str);
			}

			if (newline) {
				add_to_string(&string, "\n");
			}

			if (element_offset == interpreter->current_writecode->element_offset) {
				add_string_to_string(&interpreter->current_writecode->string, &string);
				done_string(&string);
			} else {
				(void)add_to_ecmascript_string_list(&interpreter->writecode, string.source, string.length, element_offset);
				done_string(&string);
				interpreter->current_writecode = interpreter->current_writecode->next;
			}
			interpreter->changed = 1;
			interpreter->was_write = 1;
		}
	}

#ifdef CONFIG_LEDS
	set_led_value(interpreter->vs->doc_view->session->status.ecmascript_led, 'J');
#endif
	return JS_FALSE;
}

/* @document_funcs{"write"} */
static JSValue
js_document_write(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv)
{
#ifdef ECMASCRIPT_DEBUG
	fprintf(stderr, "%s:%s\n", __FILE__, __FUNCTION__);
#endif
	REF_JS(this_val);

	return js_document_write_do(ctx, this_val, argc, argv, 0);
}

/* @document_funcs{"writeln"} */
static JSValue
js_document_writeln(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv)
{
#ifdef ECMASCRIPT_DEBUG
	fprintf(stderr, "%s:%s\n", __FILE__, __FUNCTION__);
#endif
	REF_JS(this_val);

	return js_document_write_do(ctx, this_val, argc, argv, 1);
}

/* @document_funcs{"replace"} */
static JSValue
js_document_replace(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv)
{
#ifdef ECMASCRIPT_DEBUG
	fprintf(stderr, "%s:%s\n", __FILE__, __FUNCTION__);
#endif
	REF_JS(this_val);

	struct ecmascript_interpreter *interpreter = (struct ecmascript_interpreter *)JS_GetContextOpaque(ctx);
	struct document_view *doc_view = interpreter->vs->doc_view;
	struct document *document;
	document = doc_view->document;

	if (argc != 2) {
		return JS_FALSE;
	}

	struct string needle;
	struct string heystack;

	if (!init_string(&needle)) {
		return JS_EXCEPTION;
	}
	if (!init_string(&heystack)) {
		done_string(&needle);
		return JS_EXCEPTION;
	}

	const char *str;
	size_t len;

	str = JS_ToCStringLen(ctx, &len, argv[0]);

	if (str) {
		add_bytes_to_string(&needle, str, len);
		JS_FreeCString(ctx, str);
	}

	str = JS_ToCStringLen(ctx, &len, argv[1]);

	if (str) {
		add_bytes_to_string(&heystack, str, len);
		JS_FreeCString(ctx, str);
	}
	//DBG("doc replace %s %s\n", needle.source, heystack.source);

	struct cache_entry *cached = doc_view->document->cached;
	struct fragment *f = get_cache_fragment(cached);

	if (f && f->length)
	{
		struct string f_data;
		if (init_string(&f_data)) {
			add_bytes_to_string(&f_data, f->data, f->length);

			struct string nu_str;
			if (init_string(&nu_str)) {
				el_string_replace(&nu_str, &f_data, &needle, &heystack);
				delete_entry_content(cached);
				/* TBD: somehow better rerender the document 
				 * now it's places on the session level in doc_loading_callback */
				add_fragment(cached, 0, nu_str.source, nu_str.length);
				normalize_cache_entry(cached, nu_str.length);
				document->ecmascript_counter++;
				done_string(&nu_str);
			}
			//DBG("doc replace %s %s\n", needle.source, heystack.source);
			done_string(&f_data);
		}
	}

	done_string(&needle);
	done_string(&heystack);

	return JS_TRUE;
}

static JSValue
js_document_addEventListener(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv)
{
#ifdef ECMASCRIPT_DEBUG
	fprintf(stderr, "%s:%s\n", __FILE__, __FUNCTION__);
#endif
	REF_JS(this_val);

	struct js_document_private *doc_private = (struct js_document_private *)(JS_GetOpaque(this_val, js_document_class_id));

	if (!doc_private) {
		return JS_NULL;
	}
	dom_html_document *doc = doc_private->node;

	if (argc < 2) {
		return JS_UNDEFINED;
	}
	const char *str;
	size_t len;
	str = JS_ToCStringLen(ctx, &len, argv[0]);

	if (!str) {
		return JS_EXCEPTION;
	}
	char *method = stracpy(str);
	JS_FreeCString(ctx, str);

	if (!method) {
		return JS_EXCEPTION;
	}
	JSValue fun = argv[1];
	struct document_listener *l;

	foreach(l, doc_private->listeners) {
		if (strcmp(l->typ, method)) {
			continue;
		}

		if (JS_VALUE_GET_PTR(l->fun) == JS_VALUE_GET_PTR(fun)) {
			mem_free(method);
			return JS_UNDEFINED;
		}
	}
	struct document_listener *n = (struct document_listener *)mem_calloc(1, sizeof(*n));

	if (!n) {
		return JS_UNDEFINED;
	}
	n->fun = JS_DupValue(ctx, fun);
	n->typ = method;
	add_to_list_end(doc_private->listeners, n);
	dom_exception exc;

	if (doc_private->listener) {
		dom_event_listener_ref(doc_private->listener);
	} else {
		exc = dom_event_listener_create(document_event_handler, doc_private, &doc_private->listener);

		if (exc != DOM_NO_ERR || !doc_private->listener) {
			return JS_UNDEFINED;
		}
	}
	dom_string *typ = NULL;
	exc = dom_string_create((const uint8_t *)method, strlen(method), &typ);

	if (exc != DOM_NO_ERR || !typ) {
		goto ex;
	}
	exc = dom_event_target_add_event_listener(doc, typ, doc_private->listener, false);

	if (exc == DOM_NO_ERR) {
		dom_event_listener_ref(doc_private->listener);
	}

ex:
	if (typ) {
		dom_string_unref(typ);
	}
	dom_event_listener_unref(doc_private->listener);

	return JS_UNDEFINED;
}

static JSValue
js_document_dispatchEvent(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv)
{
#ifdef ECMASCRIPT_DEBUG
	fprintf(stderr, "%s:%s\n", __FILE__, __FUNCTION__);
#endif
	REF_JS(this_val);

	struct js_document_private *doc_private = (struct js_document_private *)(JS_GetOpaque(this_val, js_document_class_id));

	if (!doc_private) {
		return JS_FALSE;
	}
	dom_html_document *doc = doc_private->node;

	if (!doc) {
		return JS_FALSE;
	}
	//dom_node_ref(doc);

	if (argc < 1) {
		//dom_node_unref(doc);
		return JS_FALSE;
	}
	JSValue eve = argv[0];
	dom_event *event = (dom_event *)(JS_GetOpaque(eve, js_event_class_id));

	if (event) {
		dom_event_ref(event);
	}
	bool result = false;
	(void)dom_event_target_dispatch_event(doc, event, &result);

	if (event) {
		dom_event_unref(event);
	}
	//dom_node_unref(doc);

	return JS_NewBool(ctx, result);
}

static JSValue
js_document_removeEventListener(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv)
{
#ifdef ECMASCRIPT_DEBUG
	fprintf(stderr, "%s:%s\n", __FILE__, __FUNCTION__);
#endif
	REF_JS(this_val);

	struct js_document_private *doc_private = (struct js_document_private *)(JS_GetOpaque(this_val, js_document_class_id));

	if (!doc_private) {
		return JS_NULL;
	}
	//dom_node_ref(doc);

	if (argc < 2) {
		//dom_node_unref(doc);
		return JS_UNDEFINED;
	}
	const char *str;
	size_t len;
	str = JS_ToCStringLen(ctx, &len, argv[0]);

	if (!str) {
		//dom_node_unref(doc);
		return JS_EXCEPTION;
	}
	char *method = stracpy(str);
	JS_FreeCString(ctx, str);

	if (!method) {
		//dom_node_unref(doc);
		return JS_EXCEPTION;
	}
	JSValue fun = argv[1];
	struct document_listener *l;

	foreach(l, doc_private->listeners) {
		if (strcmp(l->typ, method)) {
			continue;
		}

		if (JS_VALUE_GET_PTR(l->fun) == JS_VALUE_GET_PTR(fun)) {
			dom_string *typ = NULL;
			dom_exception exc = dom_string_create((const uint8_t *)method, strlen(method), &typ);

			if (exc != DOM_NO_ERR || !typ) {
				continue;
			}
			//dom_event_target_remove_event_listener(doc, typ, doc_private->listener, false);
			dom_string_unref(typ);

			del_from_list(l);
			mem_free_set(&l->typ, NULL);
			mem_free(l);
			mem_free(method);
			//dom_node_unref(doc);

			return JS_UNDEFINED;
		}
	}
	mem_free(method);
	//dom_node_unref(doc);

	return JS_UNDEFINED;
}

static JSValue
js_document_createComment(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv)
{
#ifdef ECMASCRIPT_DEBUG
	fprintf(stderr, "%s:%s\n", __FILE__, __FUNCTION__);
#endif
	REF_JS(this_val);

	if (argc != 1) {
		return JS_FALSE;
	}
	dom_html_document *doc = (struct dom_html_document *)js_doc_getopaque(this_val);

	if (!doc) {
		return JS_NULL;
	}
	//dom_node_ref(doc);

	dom_string *data = NULL;
	dom_exception exc;
	const char *str;
	size_t len;

	str = JS_ToCStringLen(ctx, &len, argv[0]);

	if (!str) {
		//dom_node_unref(doc);
		return JS_EXCEPTION;
	}
	exc = dom_string_create((const uint8_t *)str, len, &data);
	JS_FreeCString(ctx, str);

	if (exc != DOM_NO_ERR || !data) {
		//dom_node_unref(doc);
		return JS_NULL;
	}
	dom_comment *comment = NULL;
	exc = dom_document_create_comment(doc, data, &comment);
	dom_string_unref(data);

	if (exc != DOM_NO_ERR || !comment) {
		//dom_node_unref(doc);
		return JS_NULL;
	}
	//dom_node_unref(doc);

	return getNode(ctx, comment);
}

static JSValue
js_document_createDocumentFragment(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv)
{
#ifdef ECMASCRIPT_DEBUG
	fprintf(stderr, "%s:%s\n", __FILE__, __FUNCTION__);
#endif
	REF_JS(this_val);

	if (argc != 0) {
		return JS_FALSE;
	}
	dom_html_document *doc = (struct dom_html_document *)js_doc_getopaque(this_val);

	if (!doc) {
		return JS_NULL;
	}
	//dom_node_ref(doc);

	dom_document_fragment *fragment = NULL;
	dom_exception exc = dom_document_create_document_fragment(doc, &fragment);

	if (exc != DOM_NO_ERR || !fragment) {
		//dom_node_unref(doc);
		return JS_NULL;
	}
	//dom_node_unref(doc);

	return getDocumentFragment(ctx, fragment);
}

static JSValue
js_document_createElement(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv)
{
#ifdef ECMASCRIPT_DEBUG
	fprintf(stderr, "%s:%s\n", __FILE__, __FUNCTION__);
#endif
	REF_JS(this_val);

	if (argc != 1) {
		return JS_FALSE;
	}
	dom_html_document *doc = (struct dom_html_document *)js_doc_getopaque(this_val);

	if (!doc) {
		return JS_NULL;
	}
	//dom_node_ref(doc);

	dom_string *tag_name = NULL;
	dom_exception exc;
	const char *str;
	size_t len;

	str = JS_ToCStringLen(ctx, &len, argv[0]);

	if (!str) {
		//dom_node_unref(doc);
		return JS_EXCEPTION;
	}
	exc = dom_string_create((const uint8_t *)str, len, &tag_name);
	JS_FreeCString(ctx, str);

	if (exc != DOM_NO_ERR || !tag_name) {
		//dom_node_unref(doc);
		return JS_NULL;
	}
	dom_element *element = NULL;
	exc = dom_document_create_element(doc, tag_name, &element);
	dom_string_unref(tag_name);

	if (exc != DOM_NO_ERR || !element) {
		//dom_node_unref(doc);
		return JS_NULL;
	}
	//dom_node_unref(doc);
	JSValue rr = getNode(ctx, element);
#ifdef ECMASCRIPT_DEBUG
fprintf(stderr, "Before: %s:%d\n", __FUNCTION__, __LINE__);
#endif
	dom_node_unref(element);

	return rr;
}

static JSValue
js_document_createTextNode(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv)
{
#ifdef ECMASCRIPT_DEBUG
	fprintf(stderr, "%s:%s\n", __FILE__, __FUNCTION__);
#endif
	REF_JS(this_val);

	if (argc != 1) {
		return JS_FALSE;
	}
	dom_html_document *doc = (struct dom_html_document *)js_doc_getopaque(this_val);

	if (!doc) {
		return JS_NULL;
	}
	//dom_node_ref(doc);

	dom_string *data = NULL;
	dom_exception exc;
	const char *str;
	size_t len;

	str = JS_ToCStringLen(ctx, &len, argv[0]);

	if (!str) {
		//dom_node_unref(doc);
		return JS_EXCEPTION;
	}
	exc = dom_string_create((const uint8_t *)str, len, &data);
	JS_FreeCString(ctx, str);

	if (exc != DOM_NO_ERR || !data) {
		//dom_node_unref(doc);
		return JS_NULL;
	}
	dom_text *text_node = NULL;
	exc = dom_document_create_text_node(doc, data, &text_node);
	dom_string_unref(data);

	if (exc != DOM_NO_ERR || !text_node) {
		//dom_node_unref(doc);
		return JS_NULL;
	}
	//dom_node_unref(doc);

	return getText(ctx, text_node);
}

static JSValue
js_document_getElementById(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv)
{
#ifdef ECMASCRIPT_DEBUG
	fprintf(stderr, "%s:%s\n", __FILE__, __FUNCTION__);
#endif
	REF_JS(this_val);

	if (argc != 1) {
		return JS_FALSE;
	}
	dom_html_document *doc = (struct dom_html_document *)js_doc_getopaque(this_val);

	if (!doc) {
		return JS_NULL;
	}
	//dom_node_ref(doc);

	dom_string *id = NULL;
	dom_exception exc;
	const char *str;
	size_t len;

	str = JS_ToCStringLen(ctx, &len, argv[0]);

	if (!str) {
		//dom_node_unref(doc);
		return JS_EXCEPTION;
	}
	exc = dom_string_create((const uint8_t *)str, len, &id);
	JS_FreeCString(ctx, str);

	if (exc != DOM_NO_ERR || !id) {
		//dom_node_unref(doc);
		return JS_NULL;
	}
	dom_element *element = NULL;
	exc = dom_document_get_element_by_id(doc, id, &element);
	dom_string_unref(id);

	if (exc != DOM_NO_ERR || !element) {
		//dom_node_unref(doc);
		return JS_NULL;
	}
	//dom_node_unref(doc);
	JSValue rr = getNode(ctx, element);
#ifdef ECMASCRIPT_DEBUG
fprintf(stderr, "Before: %s:%d\n", __FUNCTION__, __LINE__);
#endif
	dom_node_unref(element);

	return rr;
}

static JSValue
js_document_getElementsByClassName(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv)
{
#ifdef ECMASCRIPT_DEBUG
	fprintf(stderr, "%s:%s\n", __FILE__, __FUNCTION__);
#endif
	REF_JS(this_val);
	dom_html_document *doc = (struct dom_html_document *)js_doc_getopaque(this_val);

	if (!doc) {
		return JS_NULL;
	}

	if (argc != 1) {
		return JS_NULL;
	}
	const char *classes = JS_ToCString(ctx, argv[0]);
	dom_html_collection *col = (dom_html_collection *)get_elements_by_class_name(doc, (dom_node *)doc, classes);

	if (classes) {
		JS_FreeCString(ctx, classes);
	}
	if (!col) {
		return JS_NULL;
	}
	JSValue ret = getCollection2(ctx, col);

	RETURN_JS(ret);
}

#if 0
static JSValue
js_document_getElementsByName(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv)
{
#ifdef ECMASCRIPT_DEBUG
	fprintf(stderr, "%s:%s\n", __FILE__, __FUNCTION__);
#endif
	REF_JS(this_val);

	if (argc != 1) {
		return JS_FALSE;
	}
	struct ecmascript_interpreter *interpreter = (struct ecmascript_interpreter *)JS_GetContextOpaque(ctx);

// TODO
	return JS_NULL;
#if 0
	xmlpp::Document *docu = (xmlpp::Document *)document->dom;
	xmlpp::Element* root = (xmlpp::Element *)docu->get_root_node();

	const char *str;
	size_t len;

	str = JS_ToCStringLen(ctx, &len, argv[0]);

	if (!str) {
		return JS_EXCEPTION;
	}
	xmlpp::ustring id = str;
	JS_FreeCString(ctx, str);
	xmlpp::ustring xpath = "//*[@id=\"";
	xpath += id;
	xpath += "\"]|//*[@name=\"";
	xpath += id;
	xpath += "\"]";
	xmlpp::Node::NodeSet *elements = new(std::nothrow) xmlpp::Node::NodeSet;

	if (!elements) {
		return JS_NULL;
	}
	*elements = root->find(xpath);
	JSValue rr = getCollection(ctx, elements);
	JS_FreeValue(ctx, rr);

	RETURN_JS(rr);
#endif
}
#endif

static JSValue
js_document_getElementsByTagName(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv)
{
#ifdef ECMASCRIPT_DEBUG
	fprintf(stderr, "%s:%s\n", __FILE__, __FUNCTION__);
#endif
	REF_JS(this_val);

	if (argc != 1) {
		return JS_FALSE;
	}
	dom_html_document *doc = (struct dom_html_document *)js_doc_getopaque(this_val);

	if (!doc) {
		return JS_NULL;
	}
	//dom_node_ref(doc);

	dom_string *tagname = NULL;
	dom_exception exc;
	const char *str;
	size_t len;

	str = JS_ToCStringLen(ctx, &len, argv[0]);

	if (!str) {
		//dom_node_unref(doc);
		return JS_EXCEPTION;
	}
	exc = dom_string_create((const uint8_t *)str, len, &tagname);
	JS_FreeCString(ctx, str);

	if (exc != DOM_NO_ERR || !tagname) {
		//dom_node_unref(doc);
		return JS_NULL;
	}
	dom_nodelist *nodes = NULL;
	exc = dom_document_get_elements_by_tag_name(doc, tagname, &nodes);
	dom_string_unref(tagname);

	if (exc != DOM_NO_ERR || !nodes) {
		//dom_node_unref(doc);
		return JS_NULL;
	}
	JSValue rr = getNodeList(ctx, nodes);
	//dom_node_unref(doc);

	RETURN_JS(rr);
}

static JSValue
js_document_querySelector(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv)
{
#ifdef ECMASCRIPT_DEBUG
	fprintf(stderr, "%s:%s\n", __FILE__, __FUNCTION__);
#endif
	REF_JS(this_val);

	if (argc != 1) {
		return JS_FALSE;
	}
	dom_html_document *doc = (struct dom_html_document *)js_doc_getopaque(this_val);

	if (!doc) {
		return JS_NULL;
	}
	//dom_node_ref(doc);

	dom_node *root = NULL; /* root element of document */
	/* Get root element */
	dom_exception exc = dom_document_get_document_element(doc, &root);

	if (exc != DOM_NO_ERR) {
		//dom_node_unref(doc);
		return JS_NULL;
	}
	size_t len;
	const char *selector = JS_ToCStringLen(ctx, &len, argv[0]);

	if (!selector) {
		//dom_node_unref(doc);
#ifdef ECMASCRIPT_DEBUG
fprintf(stderr, "Before: %s:%d\n", __FUNCTION__, __LINE__);
#endif
		dom_node_unref(root);
		return JS_NULL;
	}
	void *ret = walk_tree_query(root, selector, 0);
	JS_FreeCString(ctx, selector);
#ifdef ECMASCRIPT_DEBUG
fprintf(stderr, "Before: %s:%d\n", __FUNCTION__, __LINE__);
#endif
	dom_node_unref(root);

	if (!ret) {
		//dom_node_unref(doc);
		return JS_NULL;
	}
	//dom_node_unref(doc);

	return getNode(ctx, ret);
}

static JSValue
js_document_querySelectorAll(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv)
{
#ifdef ECMASCRIPT_DEBUG
	fprintf(stderr, "%s:%s\n", __FILE__, __FUNCTION__);
#endif
	REF_JS(this_val);

	if (argc != 1) {
		return JS_FALSE;
	}
	dom_html_document *doc = (struct dom_html_document *)js_doc_getopaque(this_val);

	if (!doc) {
		return JS_NULL;
	}
	//dom_node_ref(doc);
	dom_node *doc_root = NULL; /* root element of document */
	/* Get root element */
	dom_exception exc = dom_document_get_document_element(doc, &doc_root);

	if (exc != DOM_NO_ERR) {
		//dom_node_unref(doc);
		return JS_NULL;
	}
	size_t len;
	const char *selector = JS_ToCStringLen(ctx, &len, argv[0]);

	if (!selector) {
		//dom_node_unref(doc);
#ifdef ECMASCRIPT_DEBUG
fprintf(stderr, "Before: %s:%d\n", __FUNCTION__, __LINE__);
#endif
		dom_node_unref(doc_root);
		return JS_NULL;
	}
	LIST_OF(struct selector_node) *result_list = (LIST_OF(struct selector_node) *)mem_calloc(1, sizeof(*result_list));

	if (!result_list) {
		//dom_node_unref(doc);
#ifdef ECMASCRIPT_DEBUG
fprintf(stderr, "Before: %s:%d\n", __FUNCTION__, __LINE__);
#endif
		dom_node_unref(doc_root);
		JS_FreeCString(ctx, selector);
		return JS_NULL;
	}
	init_list(*result_list);

	walk_tree_query_append(doc_root, selector, 0, result_list);
#ifdef ECMASCRIPT_DEBUG
fprintf(stderr, "Before: %s:%d\n", __FUNCTION__, __LINE__);
#endif
	dom_node_unref(doc_root);
	JS_FreeCString(ctx, selector);
	//dom_node_unref(doc);

	JSValue rr = getNodeList2(ctx, result_list);
	free_list(*result_list);
	mem_free(result_list);

	return rr;
}

#if 0
JSClass doctype_class = {
	"doctype",
	JSCLASS_HAS_PRIVATE,
	&doctype_ops
};
#endif

static JSValue
js_doctype_get_property_name(JSContext *ctx, JSValueConst this_val)
{
#ifdef ECMASCRIPT_DEBUG
	fprintf(stderr, "%s:%s\n", __FILE__, __FUNCTION__);
#endif
	REF_JS(this_val);

	dom_document_type *dtd = (dom_document_type *)(JS_GetOpaque(this_val, js_doctype_class_id));

	if (!dtd) {
		return JS_NULL;
	}
	dom_string *name = NULL;
	dom_exception exc = dom_document_type_get_name(dtd, &name);

	if (exc != DOM_NO_ERR || !name) {
		return JS_NULL;
	}
	JSValue ret = JS_NewStringLen(ctx, dom_string_data(name), dom_string_length(name));
	dom_string_unref(name);

	return ret;
}

static JSValue
js_doctype_get_property_publicId(JSContext *ctx, JSValueConst this_val)
{
#ifdef ECMASCRIPT_DEBUG
	fprintf(stderr, "%s:%s\n", __FILE__, __FUNCTION__);
#endif
	REF_JS(this_val);

	dom_document_type *dtd = (dom_document_type *)(JS_GetOpaque(this_val, js_doctype_class_id));

	if (!dtd) {
		return JS_NULL;
	}
	dom_string *public_id = NULL;
	dom_exception exc = dom_document_type_get_public_id(dtd, &public_id);

	if (exc != DOM_NO_ERR || !public_id) {
		return JS_NULL;
	}
	JSValue ret = JS_NewStringLen(ctx, dom_string_data(public_id), dom_string_length(public_id));
	dom_string_unref(public_id);

	return ret;
}

static JSValue
js_doctype_get_property_systemId(JSContext *ctx, JSValueConst this_val)
{
#ifdef ECMASCRIPT_DEBUG
	fprintf(stderr, "%s:%s\n", __FILE__, __FUNCTION__);
#endif
	REF_JS(this_val);

	dom_document_type *dtd = (dom_document_type *)(JS_GetOpaque(this_val, js_doctype_class_id));

	if (!dtd) {
		return JS_NULL;
	}
	dom_string *system_id = NULL;
	dom_exception exc = dom_document_type_get_system_id(dtd, &system_id);

	if (exc != DOM_NO_ERR || !system_id) {
		return JS_NULL;
	}
	JSValue ret = JS_NewStringLen(ctx, dom_string_data(system_id), dom_string_length(system_id));
	dom_string_unref(system_id);

	return ret;
}

static JSValue
js_document_toString(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv)
{
#ifdef ECMASCRIPT_DEBUG
	fprintf(stderr, "%s:%s\n", __FILE__, __FUNCTION__);
#endif
	REF_JS(this_val);

	return JS_NewString(ctx, "[document object]");
}

static const JSCFunctionListEntry js_document_proto_funcs[] = {
	JS_CGETSET_DEF("anchors", js_document_get_property_anchors, NULL),
	JS_CGETSET_DEF("baseURI", js_document_get_property_baseURI, NULL),
	JS_CGETSET_DEF("body", js_document_get_property_body, js_document_set_property_body),
#ifdef CONFIG_COOKIES
	JS_CGETSET_DEF("cookie", js_document_get_property_cookie, js_document_set_property_cookie),
#endif
	JS_CGETSET_DEF("charset", js_document_get_property_charset, NULL),
	JS_CGETSET_DEF("characterSet", js_document_get_property_charset, NULL),
	JS_CGETSET_DEF("childNodes", js_document_get_property_childNodes, NULL),
	JS_CGETSET_DEF("currentScript", js_document_get_property_currentScript, NULL),
	JS_CGETSET_DEF("defaultView", js_document_get_property_defaultView, NULL),
	JS_CGETSET_DEF("doctype", js_document_get_property_doctype, NULL),
	JS_CGETSET_DEF("documentElement", js_document_get_property_documentElement, NULL),
	JS_CGETSET_DEF("documentURI", js_document_get_property_documentURI, NULL),
	JS_CGETSET_DEF("domain", js_document_get_property_domain, NULL),
	JS_CGETSET_DEF("forms", js_document_get_property_forms, NULL),
	JS_CGETSET_DEF("head", js_document_get_property_head, NULL),
	JS_CGETSET_DEF("images", js_document_get_property_images, NULL),
	JS_CGETSET_DEF("implementation", js_document_get_property_implementation, NULL),
	JS_CGETSET_DEF("inputEncoding", js_document_get_property_charset, NULL),
	JS_CGETSET_DEF("links", js_document_get_property_links, NULL),
	JS_CGETSET_DEF("location",	js_document_get_property_location, js_document_set_property_location),
	JS_CGETSET_DEF("nodeType", js_document_get_property_nodeType, NULL),
	JS_CGETSET_DEF("readyState", js_document_get_property_readyState, NULL),
	JS_CGETSET_DEF("referrer", js_document_get_property_referrer, NULL),
	JS_CGETSET_DEF("scripts", js_document_get_property_scripts, NULL),
	JS_CGETSET_DEF("title",	js_document_get_property_title, js_document_set_property_title), /* TODO: Charset? */
	JS_CGETSET_DEF("URL", js_document_get_property_url, js_document_set_property_url),

	JS_CFUNC_DEF("addEventListener",	3, js_document_addEventListener),
	JS_CFUNC_DEF("createComment",	1, js_document_createComment),
	JS_CFUNC_DEF("createDocumentFragment",	0, js_document_createDocumentFragment),
	JS_CFUNC_DEF("createElement",	1, js_document_createElement),
	JS_CFUNC_DEF("createTextNode",	1, js_document_createTextNode),
	JS_CFUNC_DEF("dispatchEvent",	1, js_document_dispatchEvent),
	JS_CFUNC_DEF("write",		1, js_document_write),
	JS_CFUNC_DEF("writeln",		1, js_document_writeln),
	JS_CFUNC_DEF("removeEventListener",	3, js_document_removeEventListener),
	JS_CFUNC_DEF("replace",		2, js_document_replace),
	JS_CFUNC_DEF("getElementById",	1, js_document_getElementById),
	JS_CFUNC_DEF("getElementsByClassName",	1, js_document_getElementsByClassName),
//	JS_CFUNC_DEF("getElementsByName",	1, js_document_getElementsByName),
	JS_CFUNC_DEF("getElementsByTagName",	1, js_document_getElementsByTagName),
	JS_CFUNC_DEF("querySelector",	1, js_document_querySelector),
	JS_CFUNC_DEF("querySelectorAll",	1, js_document_querySelectorAll),

	JS_CFUNC_DEF("toString", 0, js_document_toString)
};

static void
js_document_finalizer(JSRuntime *rt, JSValue val)
{
	REF_JS(val);

	struct js_document_private *doc_private = (struct js_document_private *)JS_GetOpaque(val, js_document_class_id);

	if (doc_private) {
		struct document_listener *l;

		if (doc_private->listener) {
			dom_event_listener_unref(doc_private->listener);
		}

		foreach(l, doc_private->listeners) {
			mem_free_set(&l->typ, NULL);
			JS_FreeValueRT(rt, l->fun);
		}
		free_list(doc_private->listeners);

		if (doc_private->node) {
#ifdef ECMASCRIPT_DEBUG
fprintf(stderr, "Before: %s:%d\n", __FUNCTION__, __LINE__);
#endif
			dom_node_unref((dom_node *)doc_private->node);
		}
		mem_free(doc_private);
	}
}

static JSClassDef js_document_class = {
	"document",
	.finalizer = js_document_finalizer,
	.gc_mark = js_document_mark
};

static JSValue
js_doctype_toString(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv)
{
#ifdef ECMASCRIPT_DEBUG
	fprintf(stderr, "%s:%s\n", __FILE__, __FUNCTION__);
#endif
	REF_JS(this_val);

	return JS_NewString(ctx, "[doctype object]");
}

static const JSCFunctionListEntry js_doctype_proto_funcs[] = {
	JS_CGETSET_DEF("name", js_doctype_get_property_name, NULL),
	JS_CGETSET_DEF("publicId", js_doctype_get_property_publicId, NULL),
	JS_CGETSET_DEF("systemId", js_doctype_get_property_systemId, NULL),
	JS_CFUNC_DEF("toString", 0, js_doctype_toString)
};

void *map_doctypes;

static void
js_doctype_finalizer(JSRuntime *rt, JSValue val)
{
	REF_JS(val);

	dom_node *node = (dom_node *)JS_GetOpaque(val, js_doctype_class_id);

	if (node) {
#ifdef ECMASCRIPT_DEBUG
fprintf(stderr, "Before: %s:%d\n", __FUNCTION__, __LINE__);
#endif
		dom_node_unref(node);
	}
}

static JSClassDef js_doctype_class = {
	"doctype",
	js_doctype_finalizer
};

int
js_doctype_init(JSContext *ctx)
{
	JSValue doctype_proto;

	/* create the doctype class */
	JS_NewClassID(&js_doctype_class_id);
	JS_NewClass(JS_GetRuntime(ctx), js_doctype_class_id, &js_doctype_class);

	JSValue global_obj = JS_GetGlobalObject(ctx);
	REF_JS(global_obj);

	doctype_proto = JS_NewObject(ctx);
	REF_JS(doctype_proto);

	JS_SetPropertyFunctionList(ctx, doctype_proto, js_doctype_proto_funcs, countof(js_doctype_proto_funcs));
	JS_SetClassProto(ctx, js_doctype_class_id, doctype_proto);
	JS_SetPropertyStr(ctx, global_obj, "doctype", JS_DupValue(ctx, doctype_proto));

	JS_FreeValue(ctx, global_obj);

	return 0;
}

JSValue
getDoctype(JSContext *ctx, void *node)
{
#ifdef ECMASCRIPT_DEBUG
	fprintf(stderr, "%s:%s\n", __FILE__, __FUNCTION__);
#endif
	static int initialized;
	/* create the element class */
	if (!initialized) {
		JS_NewClassID(&js_doctype_class_id);
		JS_NewClass(JS_GetRuntime(ctx), js_doctype_class_id, &js_doctype_class);
		initialized = 1;
	}
	JSValue doctype_obj = JS_NewObjectClass(ctx, js_doctype_class_id);
	JS_SetPropertyFunctionList(ctx, doctype_obj, js_doctype_proto_funcs, countof(js_doctype_proto_funcs));
	JS_SetClassProto(ctx, js_doctype_class_id, doctype_obj);
	JS_SetOpaque(doctype_obj, node);

	JSValue rr = JS_DupValue(ctx, doctype_obj);
	RETURN_JS(rr);
}

JSValue
getDocument(JSContext *ctx, void *doc)
{
#ifdef ECMASCRIPT_DEBUG
	fprintf(stderr, "%s:%s\n", __FILE__, __FUNCTION__);
#endif
	struct js_document_private *doc_private = (struct js_document_private *)mem_calloc(1, sizeof(*doc_private));

	if (!doc_private) {
		return JS_NULL;
	}
	init_list(doc_private->listeners);

	if (doc) {
#ifdef ECMASCRIPT_DEBUG
fprintf(stderr, "Before: %s:%d\n", __FUNCTION__, __LINE__);
#endif
		dom_node_ref((dom_node *)doc);
	}
	doc_private->node = doc;
	struct ecmascript_interpreter *interpreter = (struct ecmascript_interpreter *)JS_GetContextOpaque(ctx);
	doc_private->interpreter = interpreter;
	static int initialized;

	JSValue global_obj = JS_GetGlobalObject(ctx);

	/* create the element class */
	if (!initialized) {
		JS_NewClassID(&js_document_class_id);
		JS_NewClass(JS_GetRuntime(ctx), js_document_class_id, &js_document_class);
		initialized = 1;
	}
	//REF_JS(global_obj);

	JSValue document_obj = JS_NewObjectClass(ctx, js_document_class_id);
	JS_SetPropertyFunctionList(ctx, document_obj, js_document_proto_funcs, countof(js_document_proto_funcs));
//	document_class = JS_NewCFunction2(ctx, js_document_ctor, "document", 0, JS_CFUNC_constructor, 0);
//	JS_SetConstructor(ctx, document_class, document_obj);
	JS_SetOpaque(document_obj, doc_private);
//	JS_SetClassProto(ctx, js_document_class_id, document_obj);
	JS_SetPropertyStr(ctx, global_obj, "document", document_obj);
	JS_FreeValue(ctx, global_obj);
	doc_private->thisval = document_obj;
	doc_private->images = JS_NULL;

	RETURN_JS(document_obj);
}

JSValue
getDocument2(JSContext *ctx, void *doc)
{
#ifdef ECMASCRIPT_DEBUG
	fprintf(stderr, "%s:%s\n", __FILE__, __FUNCTION__);
#endif
	struct js_document_private *doc_private = (struct js_document_private *)mem_calloc(1, sizeof(*doc_private));

	if (!doc_private) {
		return JS_NULL;
	}
	init_list(doc_private->listeners);

//	if (doc) {
//		dom_node_ref((dom_node *)doc);
//	}
	doc_private->node = doc;
	struct ecmascript_interpreter *interpreter = (struct ecmascript_interpreter *)JS_GetContextOpaque(ctx);
	doc_private->interpreter = interpreter;

	JSValue document_obj = JS_NewObjectClass(ctx, js_document_class_id);
	JS_SetPropertyFunctionList(ctx, document_obj, js_document_proto_funcs, countof(js_document_proto_funcs));
	JS_SetOpaque(document_obj, doc_private);
	doc_private->thisval = document_obj;
	doc_private->images = JS_NULL;

	RETURN_JS(document_obj);
}

static JSValue
js_document_constructor(JSContext *ctx, JSValueConst new_target, int argc, JSValueConst *argv)
{
#ifdef ECMASCRIPT_DEBUG
	fprintf(stderr, "%s:%s\n", __FILE__, __FUNCTION__);
#endif
	REF_JS(new_target);

	JSValue document_obj = JS_NewObjectClass(ctx, js_document_class_id);

	if (JS_IsException(document_obj)) {
		return document_obj;
	}
	struct js_document_private *doc_private = (struct js_document_private *)mem_calloc(1, sizeof(*doc_private));

	if (!doc_private) {
		return JS_NULL;
	}
	init_list(doc_private->listeners);

	struct string str;

	if (!init_string(&str)) {
		return JS_NULL;
	}
	add_to_string(&str, "<!doctype html>\n<html><head></head><body></body></html>");
	void *doc = document_parse_text("utf-8", str.source, str.length);
	done_string(&str);

	if (doc) {
		dom_node_ref((dom_node *)doc);
	}
	doc_private->node = doc;
	struct ecmascript_interpreter *interpreter = (struct ecmascript_interpreter *)JS_GetContextOpaque(ctx);
	doc_private->interpreter = interpreter;

	JS_SetPropertyFunctionList(ctx, document_obj, js_document_proto_funcs, countof(js_document_proto_funcs));
	JS_SetOpaque(document_obj, doc_private);
	doc_private->thisval = document_obj;

	RETURN_JS(document_obj);
}

static void
JS_NewGlobalCConstructor2(JSContext *ctx, JSValue func_obj, const char *name, JSValueConst proto)
{
	REF_JS(func_obj);
	REF_JS(proto);

	JSValue global_object = JS_GetGlobalObject(ctx);

	JS_DefinePropertyValueStr(ctx, global_object, name,
		JS_DupValue(ctx, func_obj), JS_PROP_WRITABLE | JS_PROP_CONFIGURABLE);
	JS_SetConstructor(ctx, func_obj, proto);
	JS_FreeValue(ctx, func_obj);
	JS_FreeValue(ctx, global_object);
}

static JSValueConst
JS_NewGlobalCConstructor(JSContext *ctx, const char *name, JSCFunction *func, int length, JSValueConst proto)
{
	JSValue func_obj;
	func_obj = JS_NewCFunction2(ctx, func, name, length, JS_CFUNC_constructor_or_func, 0);
	REF_JS(func_obj);
	REF_JS(proto);

	JS_NewGlobalCConstructor2(ctx, func_obj, name, proto);

	return func_obj;
}

int
js_document_init(JSContext *ctx)
{
#ifdef ECMASCRIPT_DEBUG
	fprintf(stderr, "%s:%s\n", __FILE__, __FUNCTION__);
#endif
	JSValue proto;

	proto = JS_NewObject(ctx);
	REF_JS(proto);

	JS_SetPropertyFunctionList(ctx, proto, js_document_proto_funcs, countof(js_document_proto_funcs));
	JS_SetClassProto(ctx, js_document_class_id, proto);

	/* Event object */
	(void)JS_NewGlobalCConstructor(ctx, "Document", js_document_constructor, 1, proto);
	//REF_JS(obj);
	return 0;
}


static void
document_event_handler(dom_event *event, void *pw)
{
#ifdef ECMASCRIPT_DEBUG
	fprintf(stderr, "%s:%s\n", __FILE__, __FUNCTION__);
#endif
	struct js_document_private *doc_private = (struct js_document_private *)pw;
	struct ecmascript_interpreter *interpreter = (struct ecmascript_interpreter *)doc_private->interpreter;

	if (!interp_find_in_map(map_interp, interpreter)) {
		return;
	}
	JSContext *ctx = (JSContext *)interpreter->backend_data;

	if (!event) {
		return;
	}

	dom_string *typ = NULL;
	dom_exception exc = dom_event_get_type(event, &typ);

	if (exc != DOM_NO_ERR || !typ) {
		return;
	}

	if (!strcmp("DOMContentLoaded", dom_string_data(typ))) {
		if (doc_private->state == COMPLETE) {
			dom_string_unref(typ);
			return;
		}
		doc_private->state = COMPLETE;
	}
//	interpreter->heartbeat = add_heartbeat(interpreter);

	struct document_listener *l, *next;

	foreachsafe(l, next, doc_private->listeners) {
		if (strcmp(l->typ, dom_string_data(typ))) {
			continue;
		}
		JSValue func = JS_DupValue(ctx, l->fun);
		JSValue arg = getEvent(ctx, event);
		JSValue ret = JS_Call(ctx, func, doc_private->thisval, 1, (JSValueConst *) &arg);
		JS_FreeValue(ctx, ret);
		JS_FreeValue(ctx, func);
		JS_FreeValue(ctx, arg);
	}
//	done_heartbeat(interpreter->heartbeat);
	check_for_rerender(interpreter, dom_string_data(typ));
	dom_string_unref(typ);
}
