diff --git a/platform/javascript/SCsub b/platform/javascript/SCsub
index 72396489378..48f9ee4b76d 100644
--- a/platform/javascript/SCsub
+++ b/platform/javascript/SCsub
@@ -32,7 +32,6 @@ env.Depends(build, js_modules)
 
 engine = [
     "engine/preloader.js",
-    "engine/loader.js",
     "engine/utils.js",
     "engine/engine.js",
 ]
diff --git a/platform/javascript/detect.py b/platform/javascript/detect.py
index 9486e10717f..c1c48a4b939 100644
--- a/platform/javascript/detect.py
+++ b/platform/javascript/detect.py
@@ -164,3 +164,6 @@ def configure(env):
 
     # callMain for manual start, FS for preloading.
     env.Append(LINKFLAGS=["-s", 'EXTRA_EXPORTED_RUNTIME_METHODS=["callMain", "FS"]'])
+
+    # Add code that allow exiting runtime.
+    env.Append(LINKFLAGS=['-s', 'EXIT_RUNTIME=1'])
diff --git a/platform/javascript/engine/engine.js b/platform/javascript/engine/engine.js
index 6d7509377fc..69c5eeb3873 100644
--- a/platform/javascript/engine/engine.js
+++ b/platform/javascript/engine/engine.js
@@ -1,16 +1,8 @@
 Function('return this')()['Engine'] = (function() {
-
-	var unloadAfterInit = true;
-	var canvas = null;
-	var resizeCanvasOnStart = false;
-	var customLocale = 'en_US';
-	var wasmExt = '.wasm';
-
 	var preloader = new Preloader();
-	var loader = new Loader();
-	var rtenv = null;
 
-	var executableName = '';
+	var wasmExt = '.wasm';
+	var unloadAfterInit = true;
 	var loadPath = '';
 	var loadPromise = null;
 	var initPromise = null;
@@ -33,31 +25,42 @@ Function('return this')()['Engine'] = (function() {
 	};
 
 	/** @constructor */
-	function Engine() {};
+	function Engine() {
+		this.canvas = null;
+		this.executableName = '';
+		this.rtenv = null;
+		this.customLocale = null;
+		this.resizeCanvasOnStart = false;
+		this.onExit = null;
+	};
 
 	Engine.prototype.init = /** @param {string=} basePath */ function(basePath) {
 		if (initPromise) {
 			return initPromise;
 		}
-		if (!loadPromise) {
+		if (loadPromise == null) {
 			if (!basePath) {
 				initPromise = Promise.reject(new Error("A base path must be provided when calling `init` and the engine is not loaded."));
 				return initPromise;
 			}
 			load(basePath);
 		}
-		var config = {}
+		var config = {};
 		if (typeof stdout === 'function')
 			config.print = stdout;
 		if (typeof stderr === 'function')
 			config.printErr = stderr;
-		initPromise = loader.init(loadPromise, loadPath, config).then(function() {
-			return new Promise(function(resolve, reject) {
-				rtenv = loader.env;
+		var me = this;
+		initPromise = new Promise(function(resolve, reject) {
+			config['locateFile'] = Utils.createLocateRewrite(loadPath);
+			config['instantiateWasm'] = Utils.createInstantiatePromise(loadPromise);
+			Godot(config).then(function(module) {
+				me.rtenv = module;
 				if (unloadAfterInit) {
-					loadPromise = null;
+					unload();
 				}
 				resolve();
+				config = null;
 			});
 		});
 		return initPromise;
@@ -76,33 +79,71 @@ Function('return this')()['Engine'] = (function() {
 			args.push(arguments[i]);
 		}
 		var me = this;
-		return new Promise(function(resolve, reject) {
-			return me.init().then(function() {
-				if (!(canvas instanceof HTMLCanvasElement)) {
-					canvas = Utils.findCanvas();
-				}
-				rtenv['locale'] = customLocale;
-				rtenv['canvas'] = canvas;
-				rtenv['thisProgram'] = executableName;
-				rtenv['resizeCanvasOnStart'] = resizeCanvasOnStart;
-				loader.start(preloader.preloadedFiles, args).then(function() {
-					loader = null;
-					initPromise = null;
-					resolve();
+		return me.init().then(function() {
+			if (!me.rtenv) {
+				reject(new Error('The engine must be initialized before it can be started'));
+			}
+
+			if (!(me.canvas instanceof HTMLCanvasElement)) {
+				me.canvas = Utils.findCanvas();
+			}
+
+			// Canvas can grab focus on click, or key events won't work.
+			if (me.canvas.tabIndex < 0) {
+				me.canvas.tabIndex = 0;
+			}
+
+			// Disable right-click context menu.
+			me.canvas.addEventListener('contextmenu', function(ev) {
+				ev.preventDefault();
+			}, false);
+
+			// Until context restoration is implemented warn the user of context loss.
+			me.canvas.addEventListener('webglcontextlost', function(ev) {
+				alert("WebGL context lost, please reload the page");
+				ev.preventDefault();
+			}, false);
+
+			// Browser locale, or custom one if defined.
+			var locale = me.customLocale;
+			if (!locale) {
+				locale = navigator.languages ? navigator.languages[0] : navigator.language;
+				locale = locale.split('.')[0];
+			}
+			me.rtenv['locale'] = locale;
+			me.rtenv['canvas'] = me.canvas;
+			me.rtenv['thisProgram'] = me.executableName;
+			me.rtenv['resizeCanvasOnStart'] = me.resizeCanvasOnStart;
+			me.rtenv['noExitRuntime'] = true;
+			me.rtenv['onExit'] = function(code) {
+				if (me.onExit)
+					me.onExit(code);
+				me.rtenv = null;
+			}
+			return new Promise(function(resolve, reject) {
+				preloader.preloadedFiles.forEach(function(file) {
+					Utils.copyToFS(me.rtenv['FS'], file.path, file.buffer);
 				});
+				preloader.preloadedFiles.length = 0; // Clear memory
+				me.rtenv['callMain'](args);
+				initPromise = null;
+				resolve();
 			});
 		});
 	};
 
-	Engine.prototype.startGame = function(execName, mainPack) {
+	Engine.prototype.startGame = function(execName, mainPack, extraArgs) {
 		// Start and init with execName as loadPath if not inited.
-		executableName = execName;
+		this.executableName = execName;
 		var me = this;
 		return Promise.all([
 			this.init(execName),
 			this.preloadFile(mainPack, mainPack)
 		]).then(function() {
-			return me.start('--main-pack', mainPack);
+			var args = ['--main-pack', mainPack];
+			if (extraArgs)
+				args = args.concat(extraArgs);
+			return me.start.apply(me, args);
 		});
 	};
 
@@ -118,67 +159,78 @@ Function('return this')()['Engine'] = (function() {
 	};
 
 	Engine.prototype.setCanvas = function(canvasElem) {
-		canvas = canvasElem;
+		this.canvas = canvasElem;
 	};
 
 	Engine.prototype.setCanvasResizedOnStart = function(enabled) {
-		resizeCanvasOnStart = enabled;
+		this.resizeCanvasOnStart = enabled;
 	};
 
 	Engine.prototype.setLocale = function(locale) {
-		customLocale = locale;
+		this.customLocale = locale;
 	};
 
 	Engine.prototype.setExecutableName = function(newName) {
-		executableName = newName;
+		this.executableName = newName;
 	};
 
 	Engine.prototype.setProgressFunc = function(func) {
 		progressFunc = func;
-	}
+	};
 
 	Engine.prototype.setStdoutFunc = function(func) {
-
 		var print = function(text) {
 			if (arguments.length > 1) {
 				text = Array.prototype.slice.call(arguments).join(" ");
 			}
 			func(text);
 		};
-		if (rtenv)
-			rtenv.print = print;
+		if (this.rtenv)
+			this.rtenv.print = print;
 		stdout = print;
 	};
 
 	Engine.prototype.setStderrFunc = function(func) {
-
 		var printErr = function(text) {
 			if (arguments.length > 1)
 				text = Array.prototype.slice.call(arguments).join(" ");
 			func(text);
 		};
-		if (rtenv)
-			rtenv.printErr = printErr;
+		if (this.rtenv)
+			this.rtenv.printErr = printErr;
 		stderr = printErr;
 	};
 
+	Engine.prototype.setOnExit = function(onExit) {
+		this.onExit = onExit;
+	}
+
+	Engine.prototype.copyToFS = function(path, buffer) {
+		if (this.rtenv == null) {
+			throw new Error("Engine must be inited before copying files");
+		}
+		Utils.copyToFS(this.rtenv['FS'], path, buffer);
+	}
+
 	// Closure compiler exported engine methods.
 	/** @export */
 	Engine['isWebGLAvailable'] = Utils.isWebGLAvailable;
 	Engine['load'] = load;
 	Engine['unload'] = unload;
-	Engine.prototype['init'] = Engine.prototype.init
-	Engine.prototype['preloadFile'] = Engine.prototype.preloadFile
-	Engine.prototype['start'] = Engine.prototype.start
-	Engine.prototype['startGame'] = Engine.prototype.startGame
-	Engine.prototype['setWebAssemblyFilenameExtension'] = Engine.prototype.setWebAssemblyFilenameExtension
-	Engine.prototype['setUnloadAfterInit'] = Engine.prototype.setUnloadAfterInit
-	Engine.prototype['setCanvas'] = Engine.prototype.setCanvas
-	Engine.prototype['setCanvasResizedOnStart'] = Engine.prototype.setCanvasResizedOnStart
-	Engine.prototype['setLocale'] = Engine.prototype.setLocale
-	Engine.prototype['setExecutableName'] = Engine.prototype.setExecutableName
-	Engine.prototype['setProgressFunc'] = Engine.prototype.setProgressFunc
-	Engine.prototype['setStdoutFunc'] = Engine.prototype.setStdoutFunc
-	Engine.prototype['setStderrFunc'] = Engine.prototype.setStderrFunc
+	Engine.prototype['init'] = Engine.prototype.init;
+	Engine.prototype['preloadFile'] = Engine.prototype.preloadFile;
+	Engine.prototype['start'] = Engine.prototype.start;
+	Engine.prototype['startGame'] = Engine.prototype.startGame;
+	Engine.prototype['setWebAssemblyFilenameExtension'] = Engine.prototype.setWebAssemblyFilenameExtension;
+	Engine.prototype['setUnloadAfterInit'] = Engine.prototype.setUnloadAfterInit;
+	Engine.prototype['setCanvas'] = Engine.prototype.setCanvas;
+	Engine.prototype['setCanvasResizedOnStart'] = Engine.prototype.setCanvasResizedOnStart;
+	Engine.prototype['setLocale'] = Engine.prototype.setLocale;
+	Engine.prototype['setExecutableName'] = Engine.prototype.setExecutableName;
+	Engine.prototype['setProgressFunc'] = Engine.prototype.setProgressFunc;
+	Engine.prototype['setStdoutFunc'] = Engine.prototype.setStdoutFunc;
+	Engine.prototype['setStderrFunc'] = Engine.prototype.setStderrFunc;
+	Engine.prototype['setOnExit'] = Engine.prototype.setOnExit;
+	Engine.prototype['copyToFS'] = Engine.prototype.copyToFS;
 	return Engine;
 })();
diff --git a/platform/javascript/engine/loader.js b/platform/javascript/engine/loader.js
deleted file mode 100644
index d27fbf612ee..00000000000
--- a/platform/javascript/engine/loader.js
+++ /dev/null
@@ -1,33 +0,0 @@
-var Loader = /** @constructor */ function() {
-
-	this.env = null;
-
-	this.init = function(loadPromise, basePath, config) {
-		var me = this;
-		return new Promise(function(resolve, reject) {
-			var cfg = config || {};
-			cfg['locateFile'] = Utils.createLocateRewrite(basePath);
-			cfg['instantiateWasm'] = Utils.createInstantiatePromise(loadPromise);
-			loadPromise = null;
-			Godot(cfg).then(function(module) {
-				me.env = module;
-				resolve();
-			});
-		});
-	}
-
-	this.start = function(preloadedFiles, args) {
-		var me = this;
-		return new Promise(function(resolve, reject) {
-			if (!me.env) {
-				reject(new Error('The engine must be initialized before it can be started'));
-			}
-			preloadedFiles.forEach(function(file) {
-				Utils.copyToFS(me.env['FS'], file.path, file.buffer);
-			});
-			preloadedFiles.length = 0; // Clear memory
-			me.env['callMain'](args);
-			resolve();
-		});
-	}
-};