diff --git a/engine/Makefile b/engine/Makefile
index c9b067933..0c33df8c5 100644
--- a/engine/Makefile
+++ b/engine/Makefile
@@ -2281,16 +2281,16 @@ endif
 web-rel:
 	@PATH="$(EMSCRIPTENPATH)" $(MAKE) gl-rel FTE_TARGET=web CC="$(EMCC)"
 	cp $(BASE_DIR)/web/fteshell.html $(RELEASE_DIR)/ftewebgl.html
-	@gzip -f $(RELEASE_DIR)/ftewebgl.html
-	@gzip -f $(RELEASE_DIR)/ftewebgl.js
-	@gzip -f $(RELEASE_DIR)/ftewebgl.wasm
+	@gzip -kf $(RELEASE_DIR)/ftewebgl.html
+	@gzip -kf $(RELEASE_DIR)/ftewebgl.js
+	@gzip -kf $(RELEASE_DIR)/ftewebgl.wasm
 
 web-dbg:
 	@PATH="$(EMSCRIPTENPATH)" $(MAKE) gl-dbg FTE_TARGET=web CC="$(EMCC)"
 	cp $(BASE_DIR)/web/fteshell.html $(DEBUG_DIR)/ftewebgl.html
-	@gzip -f $(DEBUG_DIR)/ftewebgl.html
-	@gzip -f $(DEBUG_DIR)/ftewebgl.js
-	@gzip -f $(DEBUG_DIR)/ftewebgl.wasm
+	@gzip -kf $(DEBUG_DIR)/ftewebgl.html
+	@gzip -kf $(DEBUG_DIR)/ftewebgl.js
+	@gzip -kf $(DEBUG_DIR)/ftewebgl.wasm
 
 #################################################
 #android
diff --git a/engine/http/httpclient.c b/engine/http/httpclient.c
index 6fbe1284d..169655cab 100644
--- a/engine/http/httpclient.c
+++ b/engine/http/httpclient.c
@@ -13,12 +13,7 @@ static struct dl_download *activedownloads;
 #if defined(FTE_TARGET_WEB)
 
 
-#define MYJS 1
-#if MYJS
 #include "web/ftejslib.h"
-#else
-#include <emscripten/emscripten.h>
-#endif
 
 vfsfile_t *FSWEB_OpenTempHandle(int f);
 
@@ -32,7 +27,6 @@ static void DL_OnLoad(void *c, int buf)
 	//also fires from 404s.
 	struct dl_download *dl = c;
 	vfsfile_t *tempfile = FSWEB_OpenTempHandle(buf);
-
 	//make sure the file is 'open'.
 	if (!dl->file)
 	{
@@ -76,26 +70,15 @@ static void DL_OnLoad(void *c, int buf)
 		VFS_CLOSE(tempfile);
 
 	dl->replycode = 200;
-#if !MYJS
-	dl->completed += datasize;
-#endif
 }
-#if MYJS
 static void DL_OnError(void *c, int ecode)
-#else
-static void DL_OnError(void *c)
-#endif
 {
 	struct dl_download *dl = c;
 	//fires from cross-domain blocks, tls errors, etc.
 	//anything which doesn't yield an http response (404 is NOT an error as far as js is aware).
 
-#if MYJS
 	dl->replycode = ecode;
-#else
-	dl->replycode = 404;	//we don't actually know. should we not do this?
-#endif
-	Con_Printf(CON_WARNING"dl error: %s\n", dl->url);
+	Con_Printf(CON_WARNING"dl error(%i): %s\n", ecode, dl->url);
 	dl->status = DL_FAILED;
 }
 static void DL_OnProgress(void *c, int position, int totalsize)
@@ -112,9 +95,8 @@ qboolean DL_Decide(struct dl_download *dl)
 	const char *url = dl->redir;
 	if (!*url)
 		url = dl->url;
-
 	if (dl->postdata)
-	{
+	{	//not supported...
 		DL_Cancel(dl);
 		return false;	//safe to destroy it now
 	}
@@ -129,19 +111,18 @@ qboolean DL_Decide(struct dl_download *dl)
 			return false;
 		}
 	}
-	else
+	else if (dl->status == DL_PENDING)
 	{
 		dl->status = DL_ACTIVE;
 		dl->abort = DL_Cancel;
 		dl->ctx = dl;
 
-#if MYJS
 		emscriptenfte_async_wget_data2(url, dl, DL_OnLoad, DL_OnError, DL_OnProgress);
-#else
-		//annoyingly, emscripten doesn't provide an onprogress callback, unlike firefox etc, so we can't actually tell how far its got.
-		//we'd need to provide our own js library to fix this. it can be done, I'm just lazy.
-		emscripten_async_wget_data(url, dl, DL_OnLoad, DL_OnError);
-#endif
+	}
+	else if (dl->status == DL_ACTIVE)
+	{	//canceled?
+		dl->status = DL_FAILED;
+		return false;
 	}
 
 	return true;
diff --git a/engine/web/ftejslib.h b/engine/web/ftejslib.h
index d6d35f540..017b83a1a 100644
--- a/engine/web/ftejslib.h
+++ b/engine/web/ftejslib.h
@@ -31,7 +31,7 @@ void emscriptenfte_rtc_candidate(int sock, const char *offer);				//adds a remot
 //misc stuff for printf replacements
 void emscriptenfte_alert(const char *msg);
 void emscriptenfte_print(const char *msg);
-void emscriptenfte_setupmainloop(int(*mainloop)(void));
+void emscriptenfte_setupmainloop(int(*mainloop)(double timestamp));
 NORETURN void emscriptenfte_abortmainloop(const char *caller);
 
 //we're trying to avoid including libpng+libjpeg+libogg in javascript due to it being redundant bloat.
@@ -42,6 +42,7 @@ void emscriptenfte_al_loadaudiofile(int al_buf, void *data, int datasize);
 //avoid all of emscripten's sdl emulation.
 //this resolves input etc issues.
 unsigned long emscriptenfte_ticks_ms(void);
+double emscriptenfte_uptime_ms(void);
 void emscriptenfte_updatepointerlock(int wantpointerlock, int hidecursor);
 void emscriptenfte_polljoyevents(void);
 void emscriptenfte_settitle(const char *text);
diff --git a/engine/web/ftejslib.js b/engine/web/ftejslib.js
index 6ea69db16..2a9b53ed5 100644
--- a/engine/web/ftejslib.js
+++ b/engine/web/ftejslib.js
@@ -156,7 +156,7 @@ mergeInto(LibraryManager.library,
 					if (FTEC.pointerwantlock != 0 && FTEC.pointerislocked == 0)
 					{
 						FTEC.pointerislocked = -1;  //don't repeat the request on every click. firefox has a fit at that, so require the mouse to leave the element or something before we retry.
-						Module['canvas'].requestPointerLock();
+						Module['canvas'].requestPointerLock({unadjustedMovement: true});
 					}
 
 					if (FTEC.usevr)
@@ -547,7 +547,7 @@ mergeInto(LibraryManager.library,
 			
 			try	//this try is needed to handle Host_EndGame properly.
 			{
-				dovsync = {{{makeDynCall('i')}}}(fnc);
+				dovsync = {{{makeDynCall('if')}}}(fnc,timestamp);
 			}
 			catch(err)
 			{
@@ -563,17 +563,21 @@ mergeInto(LibraryManager.library,
 					Browser.requestAnimationFrame(Module["sched"]);
 			}
 			else
-				setTimeout(Module["sched"], 0);
+				setTimeout(Module["sched"], 0, performance.now());
 		};
 		Module["sched"] = step;
 		//don't start it instantly, so we can distinguish between types of errors (emscripten sucks!).
-		setTimeout(step, 1);
+		setTimeout(step, 1, performance.now());
 	},
 
 	emscriptenfte_ticks_ms : function()
-	{
+	{	//milliseconds...
 		return Date.now();
 	},
+	emscriptenfte_uptime_ms : function()
+	{	//milliseconds...
+		return performance.now();
+	},
 
 	emscriptenfte_buf_create__deps : ['emscriptenfte_handle_alloc'],
 	emscriptenfte_buf_create : function()
diff --git a/engine/web/sys_web.c b/engine/web/sys_web.c
index b801ba7d9..7551feff9 100644
--- a/engine/web/sys_web.c
+++ b/engine/web/sys_web.c
@@ -57,6 +57,21 @@ void Sys_Printf (char *fmt, ...)
 	va_end (argptr);
 }
 
+#if 1
+//use Performance.now() instead of Date.now() - its likely to both provide higher precision and no NTP/etc issues.
+double Sys_DoubleTime (void)
+{
+	double t = emscriptenfte_uptime_ms()/1000;	//we need it as seconds...
+	static double old = -99999999;
+	if (t < old)
+		t = old;	//don't let t step backwards, ever. this shouldn't happen, but some CPUs don't keep their high-precision timers synced properly.
+	return old=t;
+}
+unsigned int Sys_Milliseconds(void)
+{
+	return Sys_DoubleTime() * (uint64_t)1000;
+}
+#else
 unsigned int Sys_Milliseconds(void)
 {
 	static int first = true;
@@ -83,6 +98,7 @@ double Sys_DoubleTime (void)
 {
 	return Sys_Milliseconds() / 1000.0;
 }
+#endif
 
 //create a directory
 void Sys_mkdir (const char *path)
@@ -196,12 +212,18 @@ void Sys_CloseTerminal (void)
 {
 }
 
-int Sys_MainLoop(void)
+int Sys_MainLoop(double newtime)
 {
 	extern cvar_t vid_vsync;
-	static float oldtime;
-	float newtime, time;
-	newtime = Sys_DoubleTime ();
+	static double oldtime;
+	double time;
+
+	if (newtime)
+		newtime /= 1000;	//use RAF's timing for slightly greater precision.
+	else
+		newtime = Sys_DoubleTime ();	//otherwise fall back on internally consistent timing...
+	if (newtime < oldtime)
+		newtime = oldtime;	//don't let ourselves go backwards...
 	if (!oldtime)
 		oldtime = newtime;
 	time = newtime - oldtime;