From 0e17e9a2e2c28c81f03cf0303240e6ab7c2f6640 Mon Sep 17 00:00:00 2001
From: DarkGod <darkgod@net-core.org>
Date: Thu, 20 Mar 2014 18:26:57 +0100
Subject: [PATCH] Added a generic web rendered (not available on OSX yet): a
 module or addon can render UI, or parts of UI in html5/css3/javascript now.

---
 game/engines/default/data/html/test.html      |  13 +-
 game/engines/default/data/html/test.js        |   1 -
 game/engines/default/engine/init.lua          |   3 +
 game/engines/default/engine/ui/WebView.lua    |  27 ++-
 game/engines/default/engine/webcore.lua       |  62 +++++++
 .../default/modules/boot/dialogs/MainMenu.lua |   2 +-
 src/web-awesomium/web.cpp                     | 136 +++++++++++---
 src/web-awesomium/web.h                       |  13 +-
 src/web-external.h                            |  25 +++
 src/web.c                                     | 167 +++++++++++++++---
 10 files changed, 393 insertions(+), 56 deletions(-)
 create mode 100644 game/engines/default/engine/webcore.lua

diff --git a/game/engines/default/data/html/test.html b/game/engines/default/data/html/test.html
index 0babbb5e8f..590e11bb1f 100644
--- a/game/engines/default/data/html/test.html
+++ b/game/engines/default/data/html/test.html
@@ -1,6 +1,15 @@
-<script type="text/javascript" src='asset://te4/data/html/jquery-1.10.2.min.js'></script>
+<script type="text/javascript" src='asset://te4/html/jquery-1.10.2.min.js'></script>
 
 <p><strong>lol</strong> lolll</p>
 
-<script type="text/javascript" src='asset://te4/data/html/test.js'></script>
+<script type="text/javascript" src='asset://te4/html/test.js'></script>
+
+<script type="text/javascript">
+
+te4.lua("print('plop')");
+console.log("=====ret: " + te4.lolzor(1, "plop"));
+
+$('strong').css("color", "red");
+
+</script>
 
diff --git a/game/engines/default/data/html/test.js b/game/engines/default/data/html/test.js
index 51bf74c9c0..2fd232a703 100644
--- a/game/engines/default/data/html/test.js
+++ b/game/engines/default/data/html/test.js
@@ -1,4 +1,3 @@
 $('strong').click(function() {
 	$(this).html("CLICKED");
-	te4core.testclick("theclick", 7);
 });
\ No newline at end of file
diff --git a/game/engines/default/engine/init.lua b/game/engines/default/engine/init.lua
index 07807a24e4..a3b3afb8ec 100644
--- a/game/engines/default/engine/init.lua
+++ b/game/engines/default/engine/init.lua
@@ -139,6 +139,9 @@ core.display.setGamma(config.settings.gamma_correction / 100)
 if not config.settings.fbo_active then core.display.disableFBO() print("Disabling FBO") end
 if not config.settings.shaders_active then core.shader.disable() print("Disabling Shaders") end
 
+-- Webcore local request resolver
+dofile("/engine/webcore.lua")
+
 -- Load profile configs
 core.profile.createThread()
 profile = engine.PlayerProfile.new()
diff --git a/game/engines/default/engine/ui/WebView.lua b/game/engines/default/engine/ui/WebView.lua
index 01c0934164..9b66004eef 100644
--- a/game/engines/default/engine/ui/WebView.lua
+++ b/game/engines/default/engine/ui/WebView.lua
@@ -34,6 +34,7 @@ function _M:init(t)
 	self.never_clean = t.never_clean
 	self.allow_popup = t.allow_popup
 	self.allow_login = t.allow_login
+	self.custom_calls = t.custom_calls or {}
 	if self.allow_login == nil then self.allow_login = true end
 
 	if self.allow_login and self.url:find("^http://te4%.org/") and profile.auth then
@@ -60,18 +61,28 @@ function _M:generate()
 	self.key:reset()
 
 	local handlers = {
-		on_title = function(view, title) if self.on_title then self.on_title(title) end end,
-		on_popup = function(view, url, w, h) if self.allow_popup then
+		on_title = function(title) if self.on_title then self.on_title(title) end end,
+		on_popup = function(url, w, h) if self.allow_popup then
 			local Dialog = require "engine.ui.Dialog"
 			Dialog:webPopup(url, w, h)
 		end end,
-		on_loading = function(view, url, status)
-			print("===loading", url, status)
+		on_loading = function(url, status)
 			self.loading = status
 		end,
 	}
 	if self.allow_downloads then self:onDownload(handlers) end
-	self.view = core.webview.new(self.w, self.h, self.url, handlers)
+	self.view = core.webview.new(self.w, self.h, handlers)
+
+	self.custom_calls.lolzor = function(nb, str)
+		print("call from js got: ", nb, str)
+		return "PLAP"
+	end
+
+	for name, fct in pairs(self.custom_calls) do 
+		handlers[name] = fct
+		self.view:setMethod(name)
+	end
+	self.view:loadURL(self.url)
 	self.loading = 0
 	self.loading_rotation = 0
 	self.scroll_inertia = 0
@@ -155,7 +166,7 @@ end
 function _M:onDownload(handlers)
 	local Dialog = require "engine.ui.Dialog"
 
-	handlers.on_download_request = function(view, downid, url, file, mime)
+	handlers.on_download_request = function(downid, url, file, mime)
 		if mime == "application/t-engine-addon" and self.allow_downloads.addons and url:find("^http://te4%.org/") then
 			local path = fs.getRealPath("/addons/")
 			if path then
@@ -192,12 +203,12 @@ function _M:onDownload(handlers)
 		self.view:downloadAction(downid, false)
 	end
 
-	handlers.on_download_update = function(view, downid, cur_size, total_size, percent, speed)
+	handlers.on_download_update = function(downid, cur_size, total_size, percent, speed)
 		if not self.download_dialog then return end
 		self.download_dialog:updateFill(cur_size, total_size, ("%d%% - %d KB/s"):format(cur_size * 100 / total_size, speed / 1024))
 	end
 
-	handlers.on_download_finish = function(view, downid)
+	handlers.on_download_finish = function(downid)
 		if not self.download_dialog then return end
 		game:unregisterDialog(self.download_dialog)
 		if self.download_dialog.install_kind == "Addon" then
diff --git a/game/engines/default/engine/webcore.lua b/game/engines/default/engine/webcore.lua
new file mode 100644
index 0000000000..6dcb8fc9a3
--- /dev/null
+++ b/game/engines/default/engine/webcore.lua
@@ -0,0 +1,62 @@
+-- TE4 - T-Engine 4
+-- Copyright (C) 2009 - 2014 Nicolas Casalini
+--
+-- This program is free software: you can redistribute it and/or modify
+-- it under the terms of the GNU General Public License as published by
+-- the Free Software Foundation, either version 3 of the License, or
+-- (at your option) any later version.
+--
+-- This program is distributed in the hope that it will be useful,
+-- but WITHOUT ANY WARRANTY; without even the implied warranty of
+-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+-- GNU General Public License for more details.
+--
+-- You should have received a copy of the GNU General Public License
+-- along with this program.  If not, see <http://www.gnu.org/licenses/>.
+--
+-- Nicolas Casalini "DarkGod"
+-- darkgod@te4.org
+
+if not core.webview then return end
+
+local class = require "class"
+
+core.webview.paths = {}
+
+function core.webview.responder(id, path)
+	path = "/"..path
+	print("[WEBCORE] path request: ", path)
+
+	-- Let hook do their stuff
+	local reply = {}
+	if class:triggerHook{"Web:request", path=path, reply=reply} and reply.data then
+		core.webview.localReplyData(id, reply.mime, reply.data)
+		return
+	end
+
+	-- No hooks, perhaps we have a registered path matching
+	for mpath, fct in pairs(core.webview.paths) do
+		local r = {path:find("^"..mpath)}
+		if r[1] then
+			table.remove(r, 1) table.remove(r, 1)
+			local mime, data = fct(path, unpack(r))
+			if mime and data then
+				core.webview.localReplyData(id, mime, data)
+				return
+			end
+		end
+	end
+
+	-- Default, check for a file in /data/
+	local mime = "application/octet-stream"
+	if path:find("%.html$") then mime = "text/html"
+	elseif path:find("%.js$") then mime = "text/javascript"
+	elseif path:find("%.css$") then mime = "text/css"
+	elseif path:find("%.png$") then mime = "image/png"
+	end
+	core.webview.localReplyFile(id, mime, "/data"..path)
+end
+
+core.webview.paths["/example/(.*)"] = function(path, sub)
+	return "text/html", "example sub url was: "..sub
+end
diff --git a/game/engines/default/modules/boot/dialogs/MainMenu.lua b/game/engines/default/modules/boot/dialogs/MainMenu.lua
index deb2e7bc94..72420b7c8c 100644
--- a/game/engines/default/modules/boot/dialogs/MainMenu.lua
+++ b/game/engines/default/modules/boot/dialogs/MainMenu.lua
@@ -57,7 +57,7 @@ function _M:init()
 	l[#l+1] = {name="Credits", fct=function() game:registerDialog(require("mod.dialogs.Credits").new()) end}
 	l[#l+1] = {name="Exit", fct=function() game:onQuit() end}
 	if config.settings.cheat then l[#l+1] = {name="Reboot", fct=function() util.showMainMenu() end} end
-	-- if config.settings.cheat then l[#l+1] = {name="webtest", fct=function() util.browserOpenUrl("http://te4.org/addons/tome?_te4") end} end
+	if config.settings.cheat then l[#l+1] = {name="webtest", fct=function() util.browserOpenUrl("asset://te4/html/test.html") end} end
 
 	self.c_background = Button.new{text=game.stopped and "Enable background" or "Disable background", fct=function() self:switchBackground() end}
 	self.c_version = Textzone.new{auto_width=true, auto_height=true, text=("#{bold}##B9E100#T-Engine4 version: %d.%d.%d"):format(engine.version[1], engine.version[2], engine.version[3])}
diff --git a/src/web-awesomium/web.cpp b/src/web-awesomium/web.cpp
index 4fc86094b8..e8875e593f 100644
--- a/src/web-awesomium/web.cpp
+++ b/src/web-awesomium/web.cpp
@@ -27,14 +27,15 @@ unsigned int (*web_make_texture)(int w, int h);
 void (*web_del_texture)(unsigned int tex);
 void (*web_texture_update)(unsigned int tex, int w, int h, const void* buffer);
 static void (*web_key_mods)(bool *shift, bool *ctrl, bool *alt, bool *meta);
+static void (*web_instant_js)(int handlers, const char *fct, int nb_args, WebJsValue *args, WebJsValue *ret);
 
 using namespace Awesomium;
 
-class PhysfsDataSource;
+class TE4DataSource;
 
 static WebCore *web_core = NULL;
 static WebSession *web_session = NULL;
-static PhysfsDataSource *web_data_source = NULL;
+static TE4DataSource *web_data_source = NULL;
 
 class WebListener;
 
@@ -58,6 +59,7 @@ class WebListener :
 private:
 	int handlers;
 public:
+	JSObject te4_js;
 	WebListener(int handlers) { this->handlers = handlers; }
 
 	virtual void OnChangeTitle(Awesomium::WebView* caller, const Awesomium::WebString& title) {
@@ -85,6 +87,11 @@ public:
 	}
 
 	virtual void OnAddConsoleMessage(Awesomium::WebView* caller, const Awesomium::WebString& message, int line_number, const Awesomium::WebString& source) {
+		char *msg = webstring_to_buf(message, NULL);
+		char *src = webstring_to_buf(source, NULL);
+		printf("[WEBCORE:console %s:%d] %s\n", src, line_number, msg);
+		free(msg);
+		free(src);
 	}
 
 	virtual void OnShowCreatedWebView(Awesomium::WebView* caller, Awesomium::WebView* new_view, const Awesomium::WebURL& opener_url, const Awesomium::WebURL& target_url, const Awesomium::Rect& initial_pos, bool is_popup) {
@@ -101,7 +108,7 @@ public:
 		event->data.popup.h = initial_pos.height;
 		push_event(event);
 
-		printf("[WEB] stopped popup to %s (%dx%d), pushing event...\n", url, event->data.popup.w, event->data.popup.h);
+		printf("[WEBCORE] stopped popup to %s (%dx%d), pushing event...\n", url, event->data.popup.w, event->data.popup.h);
 	}
 
 	void OnRequestDownload(WebView* caller, int download_id, const WebURL& wurl, const WebString& suggested_filename, const WebString& mime_type) {
@@ -109,7 +116,7 @@ public:
 		const char *mime = webstring_to_buf(mime_type, NULL);
 		const char *url = webstring_to_buf(rurl, NULL);
 		const char *name = webstring_to_buf(suggested_filename, NULL);
-		printf("[WEB] Download request [name: %s] [mime: %s] [url: %s]\n", name, mime, url);
+		printf("[WEBCORE] Download request [name: %s] [mime: %s] [url: %s]\n", name, mime, url);
 
 		WebEvent *event = new WebEvent();
 		event->kind = TE4_WEB_EVENT_DOWNLOAD_REQUEST;
@@ -187,17 +194,79 @@ public:
 	}
 
 	virtual void OnMethodCall(WebView* caller, unsigned int remote_object_id, const WebString& method_name, const JSArray& args) {
+		if (remote_object_id == te4_js.remote_id() && method_name == WSLit("lua")) {
+			JSValue arg = args[0];
+			WebString wcode = arg.ToString();
+			const char *code = webstring_to_buf(wcode, NULL);
+
+			WebEvent *event = new WebEvent();
+			event->kind = TE4_WEB_EVENT_RUN_LUA;
+			event->handlers = handlers;
+			event->data.run_lua.code = code;
+			push_event(event);
+		}
 	}
 
-	virtual JSValue OnMethodCallWithReturnValue(WebView* caller, unsigned int remote_object_id, const WebString& method_name, const JSArray& args) {
-		JSValue ret(false);
-		return ret;
+	virtual JSValue OnMethodCallWithReturnValue(WebView* caller, unsigned int remote_object_id, const WebString& method_name, const JSArray& jsargs) {
+		if (remote_object_id != te4_js.remote_id()) {
+			JSValue ret(false);
+			return ret;
+		}
+
+		const char *fct = webstring_to_buf(method_name, NULL);
+		WebJsValue ret;
+		int nb_args = jsargs.size();
+		WebJsValue *args = new WebJsValue[nb_args];
+		for (int i = 0; i < nb_args; i++) {
+			WebJsValue *wv = &args[i];
+			JSValue v = jsargs[i];
+			if (v.IsNull()) {
+				wv->kind = TE4_WEB_JS_NULL;
+			} else if (v.IsBoolean()) {
+				wv->kind = TE4_WEB_JS_BOOLEAN;
+				wv->data.b = v.ToBoolean();
+			} else if (v.IsNumber()) {
+				wv->kind = TE4_WEB_JS_NUMBER;
+				wv->data.n = v.ToDouble();
+			} else if (v.IsString()) {
+				wv->kind = TE4_WEB_JS_STRING;
+				const char *s = webstring_to_buf(v.ToString(), NULL);
+				wv->data.s = s;
+			}
+		}
+
+		web_instant_js(handlers, fct, nb_args, args, &ret);
+
+		// Free the fucking strings. I love GC. I want a GC :/
+		for (int i = 0; i < nb_args; i++) {
+			WebJsValue *wv = &args[i];
+			JSValue v = jsargs[i];
+			if (v.IsString()) free((void*)wv->data.s);
+		}
+		delete args;
+		free((void*)fct);
+
+		if (ret.kind == TE4_WEB_JS_NULL) return JSValue::Null();
+		else if (ret.kind == TE4_WEB_JS_BOOLEAN) return JSValue(ret.data.b);
+		else if (ret.kind == TE4_WEB_JS_NUMBER) return JSValue(ret.data.n);
+		else if (ret.kind == TE4_WEB_JS_STRING) {
+			WebString s = WebString::CreateFromUTF8(ret.data.s, strlen(ret.data.s));
+			return JSValue(s);
+		}
+		return JSValue();
 	}
 };
 
-class PhysfsDataSource : public DataSource {
+class TE4DataSource : public DataSource {
 public:
-	virtual void OnRequest(int request_id, const WebString& path) {
+	virtual void OnRequest(int request_id, const WebString& wpath) {
+		const char *path = webstring_to_buf(wpath, NULL);
+
+		WebEvent *event = new WebEvent();
+		event->kind = TE4_WEB_EVENT_LOCAL_REQUEST;
+		event->data.local_request.id = request_id;
+		event->data.local_request.path = path;
+		push_event(event);
 	}
 };
 
@@ -206,12 +275,9 @@ class WebViewOpaque {
 public:
 	WebView *view;
 	WebListener *listener;
-	JSObject *te4core;
 };
 
-void te4_web_new(web_view_type *view, const char *url, int w, int h) {
-	size_t urllen = strlen(url);
-	
+void te4_web_new(web_view_type *view, int w, int h) {
 	WebViewOpaque *opaque = new WebViewOpaque();
 	view->opaque = (void*)opaque;
 
@@ -220,15 +286,16 @@ void te4_web_new(web_view_type *view, const char *url, int w, int h) {
 	opaque->view->set_view_listener(opaque->listener);
 	opaque->view->set_download_listener(opaque->listener);
 	opaque->view->set_load_listener(opaque->listener);
-	opaque->te4core = NULL;
+	opaque->view->set_js_method_handler(opaque->listener);
 	view->w = w;
 	view->h = h;
 	view->closed = false;
 
-	WebURL lurl(WebString::CreateFromUTF8(url, urllen));
-	opaque->view->LoadURL(lurl);
+	opaque->listener->te4_js = (opaque->view->CreateGlobalJavascriptObject(WSLit("te4"))).ToObject();
+	opaque->listener->te4_js.SetCustomMethod(WSLit("lua"), false);
+
 	opaque->view->SetTransparent(true);
-	printf("Created webview: %s\n", url);
+	printf("Created webview: %dx%d\n", w, h);
 }
 
 bool te4_web_close(web_view_type *view) {
@@ -237,13 +304,28 @@ bool te4_web_close(web_view_type *view) {
 		opaque->view->Destroy();
 		delete opaque->listener;
 		view->closed = true;
-		if (opaque->te4core) delete opaque->te4core;
 		printf("Destroyed webview\n");
 		return true;
 	}
 	return false;
 }
 
+void te4_web_load_url(web_view_type *view, const char *url) {
+	WebViewOpaque *opaque = (WebViewOpaque*)view->opaque;
+	if (view->closed) return;
+
+	size_t urllen = strlen(url);
+	WebURL lurl(WebString::CreateFromUTF8(url, urllen));
+	opaque->view->LoadURL(lurl);
+}
+
+void te4_web_set_js_call(web_view_type *view, const char *name) {
+	WebViewOpaque *opaque = (WebViewOpaque*)view->opaque;
+	if (view->closed) return;
+
+	opaque->listener->te4_js.SetCustomMethod(WebString::CreateFromUTF8(name, strlen(name)), true);
+}
+
 bool te4_web_toscreen(web_view_type *view, int *w, int *h, unsigned int *tex) {
 	WebViewOpaque *opaque = (WebViewOpaque*)view->opaque;
 	if (view->closed) return false;
@@ -352,6 +434,12 @@ void te4_web_download_action(web_view_type *view, long id, const char *path) {
 	}
 }
 
+void te4_web_reply_local(int id, const char *mime, const char *result, size_t len) {
+	WebString wmime = WebString::CreateFromUTF8(mime, strlen(mime));
+	web_data_source->SendResponse(id, len, (unsigned char *)result, wmime);
+}
+
+
 void te4_web_do_update(void (*cb)(WebEvent*)) {
 	if (!web_core) return;
 
@@ -375,6 +463,12 @@ void te4_web_do_update(void (*cb)(WebEvent*)) {
 			case TE4_WEB_EVENT_LOADING:
 				free((void*)event->data.loading.url);
 				break;
+			case TE4_WEB_EVENT_LOCAL_REQUEST:
+				free((void*)event->data.local_request.path);
+				break;
+			case TE4_WEB_EVENT_RUN_LUA:
+				free((void*)event->data.run_lua.code);
+				break;
 		}
 
 		delete event;
@@ -385,7 +479,8 @@ void te4_web_setup(
 	int argc, char **gargv, char *spawnc,
 	void*(*mutex_create)(), void(*mutex_destroy)(void*), void(*mutex_lock)(void*), void(*mutex_unlock)(void*),
 	unsigned int (*make_texture)(int, int), void (*del_texture)(unsigned int), void (*texture_update)(unsigned int, int, int, const void*),
-	void (*key_mods)(bool*, bool*, bool*, bool*)
+	void (*key_mods)(bool*, bool*, bool*, bool*),
+	void (*instant_js)(int handlers, const char *fct, int nb_args, WebJsValue *args, WebJsValue *ret)
 	) {
 
 	web_mutex_create = mutex_create;
@@ -396,11 +491,12 @@ void te4_web_setup(
 	web_del_texture = del_texture;
 	web_texture_update = texture_update;
 	web_key_mods = key_mods;
+	web_instant_js = instant_js;
 	if (!web_core) {
 		web_core = WebCore::Initialize(WebConfig());
 		web_core->set_surface_factory(new GLTextureSurfaceFactory());
 		web_session = web_core->CreateWebSession(WSLit(""), WebPreferences());
-		web_data_source = new PhysfsDataSource();
+		web_data_source = new TE4DataSource();
 		web_session->AddDataSource(WSLit("te4"), web_data_source);
 	}
 }
diff --git a/src/web-awesomium/web.h b/src/web-awesomium/web.h
index 9c48d336c5..fc428718bb 100644
--- a/src/web-awesomium/web.h
+++ b/src/web-awesomium/web.h
@@ -22,10 +22,16 @@
 #define WEB_TE4_API
 #endif
 
-WEB_TE4_API void te4_web_setup(int argc, char **argv, char *spawn, void*(*mutex_create)(), void(*mutex_destroy)(void*), void(*mutex_lock)(void*), void(*mutex_unlock)(void*), unsigned int (*make_texture)(int, int), void (*del_texture)(unsigned int), void (*texture_update)(unsigned int, int, int, const void*), void (*)(bool*, bool*, bool*, bool*));
+WEB_TE4_API void te4_web_setup(
+	int argc, char **argv, char *spawn,
+	void*(*mutex_create)(), void(*mutex_destroy)(void*), void(*mutex_lock)(void*), void(*mutex_unlock)(void*),
+	unsigned int (*make_texture)(int, int), void (*del_texture)(unsigned int), void (*texture_update)(unsigned int, int, int, const void*),
+	void (*key_mods)(bool*, bool*, bool*, bool*),
+	void (*web_instant_js)(int handlers, const char *fct, int nb_args, WebJsValue *args, WebJsValue *ret)
+);
 WEB_TE4_API void te4_web_initialize();
 WEB_TE4_API void te4_web_do_update(void (*cb)(WebEvent*));
-WEB_TE4_API void te4_web_new(web_view_type *view, const char *url, int w, int h);
+WEB_TE4_API void te4_web_new(web_view_type *view, int w, int h);
 WEB_TE4_API bool te4_web_close(web_view_type *view);
 WEB_TE4_API bool te4_web_toscreen(web_view_type *view, int *w, int *h, unsigned int *tex);
 WEB_TE4_API bool te4_web_loading(web_view_type *view);
@@ -35,5 +41,8 @@ WEB_TE4_API void te4_web_inject_mouse_wheel(web_view_type *view, int x, int y);
 WEB_TE4_API void te4_web_inject_mouse_button(web_view_type *view, int kind, bool up);
 WEB_TE4_API void te4_web_inject_key(web_view_type *view, int scancode, int asymb, const char *uni, int unilen, bool up);
 WEB_TE4_API void te4_web_download_action(web_view_type *view, long id, const char *path);
+WEB_TE4_API void te4_web_reply_local(int id, const char *mime, const char *result, size_t len);
+WEB_TE4_API void te4_web_load_url(web_view_type *view, const char *url);
+WEB_TE4_API void te4_web_set_js_call(web_view_type *view, const char *name);
 
 #endif
diff --git a/src/web-external.h b/src/web-external.h
index fad9f0f41e..47f53ebf61 100644
--- a/src/web-external.h
+++ b/src/web-external.h
@@ -15,6 +15,8 @@ enum web_event_kind {
 	TE4_WEB_EVENT_DOWNLOAD_UPDATE,
 	TE4_WEB_EVENT_DOWNLOAD_FINISH,
 	TE4_WEB_EVENT_LOADING,
+	TE4_WEB_EVENT_LOCAL_REQUEST,
+	TE4_WEB_EVENT_RUN_LUA,
 };
 
 typedef struct {
@@ -44,9 +46,32 @@ typedef struct {
 			const char *url;
 			signed char status;
 		} loading;
+		struct {
+			int id;
+			const char *path;
+		} local_request;
+		struct {
+			const char *code;
+		} run_lua;
 	} data;
 } WebEvent;
 
+enum web_js_kind {
+	TE4_WEB_JS_NULL,
+	TE4_WEB_JS_BOOLEAN,
+	TE4_WEB_JS_NUMBER,
+	TE4_WEB_JS_STRING,
+};
+
+typedef struct {
+	enum web_js_kind kind;
+	union {
+		bool b;
+		double n;
+		const char *s;
+	} data;
+} WebJsValue;
+
 typedef struct {
 	void *opaque;
 	int w, h;
diff --git a/src/web.c b/src/web.c
index 51fc103c31..275563b688 100644
--- a/src/web.c
+++ b/src/web.c
@@ -24,6 +24,7 @@
 #include "lauxlib.h"
 #include "lualib.h"
 #include "auxiliar.h"
+#include "physfs.h"
 #include "core_lua.h"
 #include "types.h"
 #include "main.h"
@@ -35,10 +36,16 @@
  * Grab web browser methods -- availabe only here
  */
 static bool webcore = FALSE;
-static void (*te4_web_setup)(int, char**, char*, void*(*)(), void(*)(void*), void(*)(void*), void(*)(void*), unsigned int (*)(int, int), void (*)(unsigned int), void (*)(unsigned int, int, int, const void*), void (*)(bool*, bool*, bool*, bool*));
+static void (*te4_web_setup)(
+	int, char**, char*,
+	void*(*)(), void(*)(void*), void(*)(void*), void(*)(void*),
+	unsigned int (*)(int, int), void (*)(unsigned int), void (*)(unsigned int, int, int, const void*),
+	void (*)(bool*, bool*, bool*, bool*),
+	void (*)(int handlers, const char *fct, int nb_args, WebJsValue *args, WebJsValue *ret)
+);
 static void (*te4_web_initialize)();
 static void (*te4_web_do_update)(void (*cb)(WebEvent*));
-static void (*te4_web_new)(web_view_type *view, const char *url, int w, int h);
+static void (*te4_web_new)(web_view_type *view, int w, int h);
 static bool (*te4_web_close)(web_view_type *view);
 static bool (*te4_web_toscreen)(web_view_type *view, int *w, int *h, unsigned int *tex);
 static bool (*te4_web_loading)(web_view_type *view);
@@ -48,19 +55,21 @@ static void (*te4_web_inject_mouse_wheel)(web_view_type *view, int x, int y);
 static void (*te4_web_inject_mouse_button)(web_view_type *view, int kind, bool up);
 static void (*te4_web_inject_key)(web_view_type *view, int scancode, int asymb, const char *uni, int unilen, bool up);
 static void (*te4_web_download_action)(web_view_type *view, long id, const char *path);
+static void (*te4_web_reply_local)(int id, const char *mime, const char *result, size_t len);
+static void (*te4_web_load_url)(web_view_type *view, const char *url);
+static void (*te4_web_set_js_call)(web_view_type *view, const char *name);
 
 static int lua_web_new(lua_State *L) {
 	int w = luaL_checknumber(L, 1);
 	int h = luaL_checknumber(L, 2);
-	const char* url = luaL_checkstring(L, 3);
 
 	web_view_type *view = (web_view_type*)lua_newuserdata(L, sizeof(web_view_type));
 	auxiliar_setclass(L, "web{view}", -1);
 
-	lua_pushvalue(L, 4);
+	lua_pushvalue(L, 3);
 	view->handlers = luaL_ref(L, LUA_REGISTRYINDEX);
 
-	te4_web_new(view, url, w, h);
+	te4_web_new(view, w, h);
 
 	return 1;
 }
@@ -73,6 +82,13 @@ static int lua_web_close(lua_State *L) {
 	return 0;
 }
 
+static int lua_web_load_url(lua_State *L) {
+	web_view_type *view = (web_view_type*)auxiliar_checkclass(L, "web{view}", 1);
+	const char* url = luaL_checkstring(L, 2);
+	te4_web_load_url(view, url);
+	return 0;
+}
+
 static int lua_web_toscreen(lua_State *L) {
 	web_view_type *view = (web_view_type*)auxiliar_checkclass(L, "web{view}", 1);
 	int x = luaL_checknumber(L, 2);
@@ -177,10 +193,53 @@ static int lua_web_download_action(lua_State *L) {
 	return 0;
 }
 
+static int lua_web_set_method(lua_State *L) {
+	web_view_type *view = (web_view_type*)auxiliar_checkclass(L, "web{view}", 1);
+	const char *name = luaL_checkstring(L, 2);
+	te4_web_set_js_call(view, name);
+	return 0;
+}
+
+static int lua_web_local_reply_file(lua_State *L) {
+	int id = lua_tonumber(L, 1);
+	const char *mime = luaL_checkstring(L, 2);
+	const char *file = luaL_checkstring(L, 3);
+
+	PHYSFS_file *f = PHYSFS_openRead(file);
+	if (!f) {
+		te4_web_reply_local(id, mime, NULL, 0);
+		return 0;
+	}
+
+	size_t len = PHYSFS_fileLength(f);
+	char *data = malloc(len * sizeof(char));
+	size_t read = 0;
+	while (read < len) {
+		size_t rl = PHYSFS_read(f, data + read, sizeof(char), len - read);
+		if (rl <= 0) break;
+		read += rl;
+	}
+	PHYSFS_close(f);
+
+	te4_web_reply_local(id, mime, data, read);
+	return 0;
+}
+
+static int lua_web_local_reply_data(lua_State *L) {
+	int id = lua_tonumber(L, 1);
+	const char *mime = luaL_checkstring(L, 2);
+	size_t len;
+	const char *data = luaL_checklstring(L, 3, &len);
+
+	te4_web_reply_local(id, mime, data, len);
+	return 0;
+}
+
 static const struct luaL_Reg view_reg[] =
 {
 	{"__gc", lua_web_close},
 	{"downloadAction", lua_web_download_action},
+	{"loadURL", lua_web_load_url},
 	{"toScreen", lua_web_toscreen},
 	{"focus", lua_web_focus},
 	{"loading", lua_web_loading},
@@ -188,13 +247,15 @@ static const struct luaL_Reg view_reg[] =
 	{"injectMouseWheel", lua_web_inject_mouse_wheel},
 	{"injectMouseButton", lua_web_inject_mouse_button},
 	{"injectKey", lua_web_inject_key},
-//	{"setMethod", lua_web_set_method},
+	{"setMethod", lua_web_set_method},
 	{NULL, NULL},
 };
 
 static const struct luaL_Reg weblib[] =
 {
 	{"new", lua_web_new},
+	{"localReplyData", lua_web_local_reply_data},
+	{"localReplyFile", lua_web_local_reply_file},
 	{NULL, NULL},
 };
 
@@ -207,9 +268,8 @@ static void handle_event(WebEvent *event) {
 			lua_gettable(he_L, -2);
 			lua_remove(he_L, -2);
 			if (!lua_isnil(he_L, -1)) {
-				lua_rawgeti(he_L, LUA_REGISTRYINDEX, event->handlers);
 				lua_pushstring(he_L, event->data.title);
-				docall(he_L, 2, 0);
+				docall(he_L, 1, 0);
 			} else lua_pop(he_L, 1);
 			break;
 
@@ -219,11 +279,10 @@ static void handle_event(WebEvent *event) {
 			lua_gettable(he_L, -2);
 			lua_remove(he_L, -2);
 			if (!lua_isnil(he_L, -1)) {
-				lua_rawgeti(he_L, LUA_REGISTRYINDEX, event->handlers);
 				lua_pushstring(he_L, event->data.popup.url);
 				lua_pushnumber(he_L, event->data.popup.w);
 				lua_pushnumber(he_L, event->data.popup.h);
-				docall(he_L, 4, 0);
+				docall(he_L, 3, 0);
 			} else lua_pop(he_L, 1);
 			break;
 
@@ -233,12 +292,11 @@ static void handle_event(WebEvent *event) {
 			lua_gettable(he_L, -2);
 			lua_remove(he_L, -2);
 			if (!lua_isnil(he_L, -1)) {
-				lua_rawgeti(he_L, LUA_REGISTRYINDEX, event->handlers);
 				lua_pushnumber(he_L, event->data.download_request.id);
 				lua_pushstring(he_L, event->data.download_request.url);
 				lua_pushstring(he_L, event->data.download_request.name);
 				lua_pushstring(he_L, event->data.download_request.mime);
-				docall(he_L, 5, 0);
+				docall(he_L, 4, 0);
 			} else lua_pop(he_L, 1);
 			break;
 
@@ -248,13 +306,12 @@ static void handle_event(WebEvent *event) {
 			lua_gettable(he_L, -2);
 			lua_remove(he_L, -2);
 			if (!lua_isnil(he_L, -1)) {
-				lua_rawgeti(he_L, LUA_REGISTRYINDEX, event->handlers);
 				lua_pushnumber(he_L, event->data.download_update.id);
 				lua_pushnumber(he_L, event->data.download_update.got);
 				lua_pushnumber(he_L, event->data.download_update.total);
 				lua_pushnumber(he_L, event->data.download_update.percent);
 				lua_pushnumber(he_L, event->data.download_update.speed);
-				docall(he_L, 6, 0);
+				docall(he_L, 5, 0);
 			} else lua_pop(he_L, 1);
 			break;
 
@@ -264,9 +321,8 @@ static void handle_event(WebEvent *event) {
 			lua_gettable(he_L, -2);
 			lua_remove(he_L, -2);
 			if (!lua_isnil(he_L, -1)) {
-				lua_rawgeti(he_L, LUA_REGISTRYINDEX, event->handlers);
 				lua_pushnumber(he_L, event->data.download_update.id);
-				docall(he_L, 2, 0);
+				docall(he_L, 1, 0);
 			} else lua_pop(he_L, 1);
 			break;
 
@@ -276,19 +332,40 @@ static void handle_event(WebEvent *event) {
 			lua_gettable(he_L, -2);
 			lua_remove(he_L, -2);
 			if (!lua_isnil(he_L, -1)) {
-				lua_rawgeti(he_L, LUA_REGISTRYINDEX, event->handlers);
 				lua_pushstring(he_L, event->data.loading.url);
 				lua_pushnumber(he_L, event->data.loading.status);
-				docall(he_L, 3, 0);
+				docall(he_L, 2, 0);
+			} else lua_pop(he_L, 1);
+			break;
+
+		case TE4_WEB_EVENT_LOCAL_REQUEST:
+			lua_getglobal(he_L, "core");
+			lua_getfield(he_L, -1, "webview");
+			lua_getfield(he_L, -1, "responder");
+			lua_remove(he_L, -2);
+			lua_remove(he_L, -2);
+			if (!lua_isnil(he_L, -1)) {
+				lua_pushnumber(he_L, event->data.local_request.id);
+				lua_pushstring(he_L, event->data.local_request.path);
+				docall(he_L, 2, 0);
 			} else lua_pop(he_L, 1);
 			break;
+
+		case TE4_WEB_EVENT_RUN_LUA:
+			if (!luaL_loadstring(he_L, event->data.run_lua.code)) {
+				docall(he_L, 0, 0);
+			} else {
+				printf("[WEBCORE] Failed to run lua code:\n%s\n ==>> Error: %s\n", event->data.run_lua.code, lua_tostring(he_L, -1));
+				lua_pop(he_L, 1);
+			}
+			break;
 	}
 }
 
 void te4_web_update(lua_State *L) {
 	if (webcore) {
 		he_L = L;
-		te4_web_do_update(handle_event);
+		te4_web_do_update(handle_event);		
 	}
 }
 
@@ -353,6 +430,42 @@ static void web_key_mods(bool *shift, bool *ctrl, bool *alt, bool *meta) {
 	if (smod & KMOD_GUI) *meta = TRUE;
 }
 
+static void web_instant_js(int handlers, const char *fct, int nb_args, WebJsValue *args, WebJsValue *ret) {
+	lua_rawgeti(he_L, LUA_REGISTRYINDEX, handlers);
+	lua_pushstring(he_L, fct);
+	lua_gettable(he_L, -2);
+	lua_remove(he_L, -2);
+	if (!lua_isnil(he_L, -1)) {
+		int i;
+		for (i = 0; i < nb_args; i++) {
+			if (args[i].kind == TE4_WEB_JS_NULL) lua_pushnil(he_L);
+			else if (args[i].kind == TE4_WEB_JS_BOOLEAN) lua_pushboolean(he_L, args[i].data.b);
+			else if (args[i].kind == TE4_WEB_JS_NUMBER) lua_pushnumber(he_L, args[i].data.n);
+			else if (args[i].kind == TE4_WEB_JS_STRING) lua_pushstring(he_L, args[i].data.s);
+		}
+		if (!docall(he_L, nb_args, 1)) {
+			if (lua_isnumber(he_L, -1)) {
+				ret->kind = TE4_WEB_JS_NUMBER;
+				ret->data.n = lua_tonumber(he_L, -1);
+			} else if (lua_isstring(he_L, -1)) {
+				ret->kind = TE4_WEB_JS_STRING;
+				ret->data.s = lua_tostring(he_L, -1);
+			} else if (lua_isboolean(he_L, -1)) {
+				ret->kind = TE4_WEB_JS_BOOLEAN;
+				ret->data.b = lua_toboolean(he_L, -1);
+			} else {
+				ret->kind = TE4_WEB_JS_NULL;
+			}
+			lua_pop(he_L, 1);
+		} else {
+			ret->kind = TE4_WEB_JS_NULL;
+		}
+	} else {
+		ret->kind = TE4_WEB_JS_NULL;
+		lua_pop(he_L, 1);
+	}
+}
+
 void te4_web_load() {
 #if defined(SELFEXE_LINUX)
 	void *web = SDL_LoadObject("libte4-web.so");
@@ -369,10 +482,16 @@ void te4_web_load() {
 
 	if (web) {
 		webcore = TRUE;
-		te4_web_setup = (void (*)(int, char**, char*, void*(*)(), void(*)(void*), void(*)(void*), void(*)(void*), unsigned int (*)(int, int), void (*)(unsigned int), void (*)(unsigned int, int, int, const void*), void (*)(bool*, bool*, bool*, bool*) )) SDL_LoadFunction(web, "te4_web_setup");
+		te4_web_setup = (void (*)(
+			int, char**, char*,
+			void*(*)(), void(*)(void*), void(*)(void*), void(*)(void*),
+			unsigned int (*)(int, int), void (*)(unsigned int), void (*)(unsigned int, int, int, const void*),
+			void (*)(bool*, bool*, bool*, bool*),
+			void (*)(int handlers, const char *fct, int nb_args, WebJsValue *args, WebJsValue *ret)
+		)) SDL_LoadFunction(web, "te4_web_setup");
 		te4_web_initialize = (void (*)()) SDL_LoadFunction(web, "te4_web_initialize");
 		te4_web_do_update = (void (*)(void (*cb)(WebEvent*))) SDL_LoadFunction(web, "te4_web_do_update");
-		te4_web_new = (void (*)(web_view_type *view, const char *url, int w, int h)) SDL_LoadFunction(web, "te4_web_new");
+		te4_web_new = (void (*)(web_view_type *view, int w, int h)) SDL_LoadFunction(web, "te4_web_new");
 		te4_web_close = (bool (*)(web_view_type *view)) SDL_LoadFunction(web, "te4_web_close");
 		te4_web_toscreen = (bool (*)(web_view_type *view, int *w, int *h, unsigned int *tex)) SDL_LoadFunction(web, "te4_web_toscreen");
 		te4_web_loading = (bool (*)(web_view_type *view)) SDL_LoadFunction(web, "te4_web_loading");
@@ -382,12 +501,16 @@ void te4_web_load() {
 		te4_web_inject_mouse_button = (void (*)(web_view_type *view, int kind, bool up)) SDL_LoadFunction(web, "te4_web_inject_mouse_button");
 		te4_web_inject_key = (void (*)(web_view_type *view, int scancode, int asymb, const char *uni, int unilen, bool up)) SDL_LoadFunction(web, "te4_web_inject_key");
 		te4_web_download_action = (void (*)(web_view_type *view, long id, const char *path)) SDL_LoadFunction(web, "te4_web_download_action");
+		te4_web_reply_local = (void (*)(int id, const char *mime, const char *result, size_t len)) SDL_LoadFunction(web, "te4_web_reply_local");
+		te4_web_load_url = (void (*)(web_view_type *view, const char *url)) SDL_LoadFunction(web, "te4_web_load_url");
+		te4_web_set_js_call = (void (*)(web_view_type *view, const char *name)) SDL_LoadFunction(web, "te4_web_set_js_call");
 
 		te4_web_setup(
 			g_argc, g_argv, NULL,
 			web_mutex_create, web_mutex_destroy, web_mutex_lock, web_mutex_unlock,
 			web_make_texture, web_del_texture, web_texture_update,
-			web_key_mods
+			web_key_mods,
+			web_instant_js
 			);
 	}
 }
-- 
GitLab