diff --git a/misc/dist/html/default.html b/misc/dist/html/default.html
new file mode 100644
index 00000000000..9fae34f97e1
--- /dev/null
+++ b/misc/dist/html/default.html
@@ -0,0 +1,386 @@
+
+
+
+
+
+
+$GODOT_HEAD_INCLUDE
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/misc/dist/html_fs/godotfs.js b/misc/dist/html_fs/godotfs.js
deleted file mode 100644
index 676ee689fb7..00000000000
--- a/misc/dist/html_fs/godotfs.js
+++ /dev/null
@@ -1,151 +0,0 @@
-
-var Module;
-if (typeof Module === 'undefined') Module = eval('(function() { try { return Module || {} } catch(e) { return {} } })()');
-if (!Module.expectedDataFileDownloads) {
- Module.expectedDataFileDownloads = 0;
- Module.finishedDataFileDownloads = 0;
-}
-Module.expectedDataFileDownloads++;
-(function() {
-
- const PACK_FILE_NAME = '$GODOT_PACK_NAME';
- const PACK_FILE_SIZE = $GODOT_PACK_SIZE;
- function fetchRemotePackage(packageName, callback, errback) {
- var xhr = new XMLHttpRequest();
- xhr.open('GET', packageName, true);
- xhr.responseType = 'arraybuffer';
- xhr.onprogress = function(event) {
- var url = packageName;
- if (event.loaded && event.total) {
- if (!xhr.addedTotal) {
- xhr.addedTotal = true;
- if (!Module.dataFileDownloads) Module.dataFileDownloads = {};
- Module.dataFileDownloads[url] = {
- loaded: event.loaded,
- total: event.total
- };
- } else {
- Module.dataFileDownloads[url].loaded = event.loaded;
- }
- var total = 0;
- var loaded = 0;
- var num = 0;
- for (var download in Module.dataFileDownloads) {
- var data = Module.dataFileDownloads[download];
- total += data.total;
- loaded += data.loaded;
- num++;
- }
- total = Math.ceil(total * Module.expectedDataFileDownloads/num);
- if (Module['setStatus']) Module['setStatus']('Downloading data... (' + loaded + '/' + total + ')');
- } else if (!Module.dataFileDownloads) {
- if (Module['setStatus']) Module['setStatus']('Downloading data...');
- }
- };
- xhr.onload = function(event) {
- var packageData = xhr.response;
- callback(packageData);
- };
- xhr.send(null);
- };
-
- function handleError(error) {
- console.error('package error:', error);
- };
-
- var fetched = null, fetchedCallback = null;
- fetchRemotePackage(PACK_FILE_NAME, function(data) {
- if (fetchedCallback) {
- fetchedCallback(data);
- fetchedCallback = null;
- } else {
- fetched = data;
- }
- }, handleError);
-
- function runWithFS() {
-
-function assert(check, msg) {
- if (!check) throw msg + new Error().stack;
-}
-
- function DataRequest(start, end, crunched, audio) {
- this.start = start;
- this.end = end;
- this.crunched = crunched;
- this.audio = audio;
- }
- DataRequest.prototype = {
- requests: {},
- open: function(mode, name) {
- this.name = name;
- this.requests[name] = this;
- Module['addRunDependency']('fp ' + this.name);
- },
- send: function() {},
- onload: function() {
- var byteArray = this.byteArray.subarray(this.start, this.end);
-
- this.finish(byteArray);
-
- },
- finish: function(byteArray) {
- var that = this;
- Module['FS_createPreloadedFile'](this.name, null, byteArray, true, true, function() {
- Module['removeRunDependency']('fp ' + that.name);
- }, function() {
- if (that.audio) {
- Module['removeRunDependency']('fp ' + that.name); // workaround for chromium bug 124926 (still no audio with this, but at least we don't hang)
- } else {
- Module.printErr('Preloading file ' + that.name + ' failed');
- }
- }, false, true); // canOwn this data in the filesystem, it is a slide into the heap that will never change
- this.requests[this.name] = null;
- },
- };
- new DataRequest(0, PACK_FILE_SIZE, 0, 0).open('GET', '/' + PACK_FILE_NAME);
-
- var PACKAGE_PATH;
- if (typeof window === 'object') {
- PACKAGE_PATH = window['encodeURIComponent'](window.location.pathname.toString().substring(0, window.location.pathname.toString().lastIndexOf('/')) + '/');
- } else {
- // worker
- PACKAGE_PATH = encodeURIComponent(location.pathname.toString().substring(0, location.pathname.toString().lastIndexOf('/')) + '/');
- }
- var PACKAGE_NAME = PACK_FILE_NAME;
- var REMOTE_PACKAGE_NAME = PACK_FILE_NAME;
- var PACKAGE_UUID = 'b39761ce-0348-4959-9b16-302ed8e1592e';
-
- function processPackageData(arrayBuffer) {
- Module.finishedDataFileDownloads++;
- assert(arrayBuffer, 'Loading data file failed.');
- var byteArray = new Uint8Array(arrayBuffer);
- var curr;
-
- // Reuse the bytearray from the XHR as the source for file reads.
- DataRequest.prototype.byteArray = byteArray;
- DataRequest.prototype.requests['/' + PACK_FILE_NAME].onload();
- Module['removeRunDependency']('datafile_datapack');
-
- };
- Module['addRunDependency']('datafile_datapack');
-
- if (!Module.preloadResults) Module.preloadResults = {};
-
- Module.preloadResults[PACKAGE_NAME] = {fromCache: false};
- if (fetched) {
- processPackageData(fetched);
- fetched = null;
- } else {
- fetchedCallback = processPackageData;
- }
-
- }
- if (Module['calledRun']) {
- runWithFS();
- } else {
- if (!Module['preRun']) Module['preRun'] = [];
- Module["preRun"].push(runWithFS); // FS is not initialized yet, wait for it
- }
-
-})();
diff --git a/platform/javascript/SCsub b/platform/javascript/SCsub
index b804863ee1f..e2820417457 100644
--- a/platform/javascript/SCsub
+++ b/platform/javascript/SCsub
@@ -20,33 +20,32 @@ for x in javascript_files:
javascript_objects.append(env_javascript.Object(x))
env.Append(LINKFLAGS=["-s", "EXPORTED_FUNCTIONS=\"['_main','_audio_server_mix_function','_main_after_fs_sync','_send_notification']\""])
-env.Append(LINKFLAGS=["--shell-file", '"platform/javascript/godot_shell.html"'])
# output file name without file extension
basename = "godot" + env["PROGSUFFIX"]
target_dir = env.Dir("#bin")
-js_file = target_dir.File(basename + ".js")
-implicit_targets = [js_file]
zip_dir = target_dir.Dir('.javascript_zip')
-zip_files = env.InstallAs([zip_dir.File("godot.js"), zip_dir.File("godotfs.js")], [js_file, "#misc/dist/html_fs/godotfs.js"])
+zip_files = env.InstallAs(zip_dir.File('godot.html'), '#misc/dist/html/default.html')
+implicit_targets = []
if env['wasm'] == 'yes':
- wasm_file = target_dir.File(basename+'.wasm')
- implicit_targets.append(wasm_file)
- zip_files.append(InstallAs(zip_dir.File('godot.wasm'), wasm_file))
+ wasm = target_dir.File(basename + '.wasm')
+ implicit_targets.append(wasm)
+ zip_files.append(InstallAs(zip_dir.File('godot.wasm'), wasm))
+ prejs = env.File('pre_wasm.js')
else:
- asmjs_files = [target_dir.File(basename+'.asm.js'), target_dir.File(basename+'.html.mem')]
- zip_files.append(InstallAs([zip_dir.File('godot.asm.js'), zip_dir.File('godot.mem')], asmjs_files))
+ asmjs_files = [target_dir.File(basename + '.asm.js'), target_dir.File(basename + '.js.mem')]
implicit_targets.extend(asmjs_files)
+ zip_files.append(InstallAs([zip_dir.File('godot.asm.js'), zip_dir.File('godot.mem')], asmjs_files))
+ prejs = env.File('pre_asmjs.js')
-# HTML file must be the first target in the list
-html_file = env.Program(["#bin/godot"] + implicit_targets, javascript_objects, PROGSUFFIX=env["PROGSUFFIX"]+".html")[0]
-Depends(html_file, "godot_shell.html")
+js = env.Program(['#bin/godot'] + implicit_targets, javascript_objects, PROGSUFFIX=env['PROGSUFFIX'] + '.js')[0];
+zip_files.append(InstallAs(zip_dir.File('godot.js'), js))
-# Emscripten hardcodes file names, so replace common base name with
-# placeholder while leaving extension; also change `.html.mem` to just `.mem`
-fixup_html = env.Substfile(html_file, SUBST_DICT=[(basename, '$$GODOT_BASE'), ('.html.mem', '.mem')], SUBSTFILESUFFIX='.fixup.html')
+postjs = env.File('engine.js')
+env.Depends(js, [prejs, postjs])
+env.Append(LINKFLAGS=['--pre-js', prejs.path])
+env.Append(LINKFLAGS=['--post-js', postjs.path])
-zip_files.append(InstallAs(zip_dir.File('godot.html'), fixup_html))
-Zip('#bin/godot', zip_files, ZIPSUFFIX=env['PROGSUFFIX']+env['ZIPSUFFIX'], ZIPROOT=zip_dir, ZIPCOMSTR="Archving $SOURCES as $TARGET")
+Zip('#bin/godot', zip_files, ZIPSUFFIX=env['PROGSUFFIX'] + env['ZIPSUFFIX'], ZIPROOT=zip_dir, ZIPCOMSTR="Archving $SOURCES as $TARGET")
diff --git a/platform/javascript/detect.py b/platform/javascript/detect.py
index 68c8d1eea52..b1bb1adb2fd 100644
--- a/platform/javascript/detect.py
+++ b/platform/javascript/detect.py
@@ -100,6 +100,7 @@ def configure(env):
## Link flags
+ env.Append(LINKFLAGS=['-s', 'EXTRA_EXPORTED_RUNTIME_METHODS="[\'FS\']"'])
env.Append(LINKFLAGS=['-s', 'USE_WEBGL2=1'])
if (env['wasm'] == 'yes'):
@@ -112,6 +113,7 @@ def configure(env):
else:
env.Append(LINKFLAGS=['-s', 'ASM_JS=1'])
env.Append(LINKFLAGS=['--separate-asm'])
+ env.Append(LINKFLAGS=['--memory-init-file', '1'])
# TODO: Move that to opus module's config
if("module_opus_enabled" in env and env["module_opus_enabled"] != "no"):
diff --git a/platform/javascript/engine.js b/platform/javascript/engine.js
new file mode 100644
index 00000000000..552f5a7e021
--- /dev/null
+++ b/platform/javascript/engine.js
@@ -0,0 +1,366 @@
+ return Module;
+ },
+};
+
+(function() {
+ var engine = Engine;
+
+ var USING_WASM = engine.USING_WASM;
+ var DOWNLOAD_ATTEMPTS_MAX = 4;
+
+ var basePath = null;
+ var engineLoadPromise = null;
+
+ var loadingFiles = {};
+
+ function getBasePath(path) {
+
+ if (path.endsWith('/'))
+ path = path.slice(0, -1);
+ if (path.lastIndexOf('.') > path.lastIndexOf('/'))
+ path = path.slice(0, path.lastIndexOf('.'));
+ return path;
+ }
+
+ function getBaseName(path) {
+
+ path = getBasePath(path);
+ return path.slice(path.lastIndexOf('/') + 1);
+ }
+
+ Engine = function Engine() {
+
+ this.rtenv = null;
+
+ var gameInitPromise = null;
+ var unloadAfterInit = true;
+ var memorySize = 268435456;
+
+ var progressFunc = null;
+ var pckProgressTracker = {};
+ var lastProgress = { loaded: 0, total: 0 };
+
+ var canvas = null;
+ var stdout = null;
+ var stderr = null;
+
+ this.initGame = function(mainPack) {
+
+ if (!gameInitPromise) {
+
+ if (mainPack === undefined) {
+ if (basePath !== null) {
+ mainPack = basePath + '.pck';
+ } else {
+ return Promise.reject(new Error("No main pack to load specified"));
+ }
+ }
+ if (basePath === null)
+ basePath = getBasePath(mainPack);
+
+ gameInitPromise = Engine.initEngine().then(
+ instantiate.bind(this)
+ );
+ var gameLoadPromise = loadPromise(mainPack, pckProgressTracker).then(function(xhr) { return xhr.response; });
+ gameInitPromise = Promise.all([gameLoadPromise, gameInitPromise]).then(function(values) {
+ // resolve with pck
+ return new Uint8Array(values[0]);
+ });
+ if (unloadAfterInit)
+ gameInitPromise.then(Engine.unloadEngine);
+ requestAnimationFrame(animateProgress);
+ }
+ return gameInitPromise;
+ };
+
+ function instantiate(initializer) {
+
+ var rtenvOpts = {
+ noInitialRun: true,
+ thisProgram: getBaseName(basePath),
+ engine: this,
+ };
+ if (typeof stdout === 'function')
+ rtenvOpts.print = stdout;
+ if (typeof stderr === 'function')
+ rtenvOpts.printErr = stderr;
+ if (typeof WebAssembly === 'object' && initializer instanceof WebAssembly.Module) {
+ rtenvOpts.instantiateWasm = function(imports, onSuccess) {
+ WebAssembly.instantiate(initializer, imports).then(function(result) {
+ onSuccess(result);
+ });
+ return {};
+ };
+ } else if (initializer.asm && initializer.mem) {
+ rtenvOpts.asm = initializer.asm;
+ rtenvOpts.memoryInitializerRequest = initializer.mem;
+ rtenvOpts.TOTAL_MEMORY = memorySize;
+ } else {
+ throw new Error("Invalid initializer");
+ }
+
+ return new Promise(function(resolve, reject) {
+ rtenvOpts.onRuntimeInitialized = resolve;
+ rtenvOpts.onAbort = reject;
+ rtenvOpts.engine.rtenv = Engine.RuntimeEnvironment(rtenvOpts);
+ });
+ }
+
+ this.start = function(mainPack) {
+
+ return this.initGame(mainPack).then(synchronousStart.bind(this));
+ };
+
+ function synchronousStart(pckView) {
+ // TODO don't expect canvas when runninng as cli tool
+ if (canvas instanceof HTMLCanvasElement) {
+ this.rtenv.canvas = canvas;
+ } else {
+ var firstCanvas = document.getElementsByTagName('canvas')[0];
+ if (firstCanvas instanceof HTMLCanvasElement) {
+ this.rtenv.canvas = firstCanvas;
+ } else {
+ throw new Error("No canvas found");
+ }
+ }
+
+ var actualCanvas = this.rtenv.canvas;
+ var context = false;
+ try {
+ context = actualCanvas.getContext('webgl2') || actualCanvas.getContext('experimental-webgl2');
+ } catch (e) {}
+ if (!context) {
+ throw new Error("WebGL 2 not available");
+ }
+
+ // canvas can grab focus on click
+ if (actualCanvas.tabIndex < 0) {
+ actualCanvas.tabIndex = 0;
+ }
+ // necessary to calculate cursor coordinates correctly
+ actualCanvas.style.padding = 0;
+ actualCanvas.style.borderWidth = 0;
+ actualCanvas.style.borderStyle = 'none';
+ // until context restoration is implemented
+ actualCanvas.addEventListener('webglcontextlost', function(ev) {
+ alert("WebGL context lost, please reload the page");
+ ev.preventDefault();
+ }, false);
+
+ this.rtenv.FS.createDataFile('/', this.rtenv.thisProgram + '.pck', pckView, true, true, true);
+ gameInitPromise = null;
+ this.rtenv.callMain();
+ }
+
+ this.setProgressFunc = function(func) {
+ progressFunc = func;
+ };
+
+ function animateProgress() {
+
+ var loaded = 0;
+ var total = 0;
+ var totalIsValid = true;
+ var progressIsFinal = true;
+
+ [loadingFiles, pckProgressTracker].forEach(function(tracker) {
+ Object.keys(tracker).forEach(function(file) {
+ if (!tracker[file].final)
+ progressIsFinal = false;
+ if (!totalIsValid || tracker[file].total === 0) {
+ totalIsValid = false;
+ total = 0;
+ } else {
+ total += tracker[file].total;
+ }
+ loaded += tracker[file].loaded;
+ });
+ });
+ if (loaded !== lastProgress.loaded || total !== lastProgress.total) {
+ lastProgress.loaded = loaded;
+ lastProgress.total = total;
+ if (typeof progressFunc === 'function')
+ progressFunc(loaded, total);
+ }
+ if (!progressIsFinal)
+ requestAnimationFrame(animateProgress);
+ }
+
+ this.setCanvas = function(elem) {
+ canvas = elem;
+ };
+
+ this.setAsmjsMemorySize = function(size) {
+ memorySize = size;
+ };
+
+ this.setUnloadAfterInit = function(enabled) {
+
+ if (enabled && !unloadAfterInit && gameInitPromise) {
+ gameInitPromise.then(Engine.unloadEngine);
+ }
+ unloadAfterInit = enabled;
+ };
+
+ this.setStdoutFunc = function(func) {
+
+ var print = function(text) {
+ if (arguments.length > 1) {
+ text = Array.prototype.slice.call(arguments).join(" ");
+ }
+ func(text);
+ };
+ if (this.rtenv)
+ this.rtenv.print = print;
+ stdout = print;
+ };
+
+ this.setStderrFunc = function(func) {
+
+ var printErr = function(text) {
+ if (arguments.length > 1)
+ text = Array.prototype.slice.call(arguments).join(" ");
+ func(text);
+ };
+ if (this.rtenv)
+ this.rtenv.printErr = printErr;
+ stderr = printErr;
+ };
+
+
+ }; // Engine()
+
+ Engine.RuntimeEnvironment = engine.RuntimeEnvironment;
+
+ Engine.initEngine = function(newBasePath) {
+
+ if (newBasePath !== undefined) basePath = getBasePath(newBasePath);
+ if (engineLoadPromise === null) {
+ if (USING_WASM) {
+ if (typeof WebAssembly !== 'object')
+ return Promise.reject(new Error("Browser doesn't support WebAssembly"));
+ // TODO cache/retrieve module to/from idb
+ engineLoadPromise = loadPromise(basePath + '.wasm').then(function(xhr) {
+ return WebAssembly.compile(xhr.response);
+ });
+ } else {
+ var asmjsPromise = loadPromise(basePath + '.asm.js').then(function(xhr) {
+ return asmjsModulePromise(xhr.response);
+ });
+ var memPromise = loadPromise(basePath + '.mem');
+ engineLoadPromise = Promise.all([asmjsPromise, memPromise]).then(function(values) {
+ return { asm: values[0], mem: values[1] };
+ });
+ }
+ engineLoadPromise = engineLoadPromise.catch(function(err) {
+ engineLoadPromise = null;
+ throw err;
+ });
+ }
+ return engineLoadPromise;
+ };
+
+ function asmjsModulePromise(module) {
+ var elem = document.createElement('script');
+ var script = new Blob([
+ 'Engine.asm = (function() { var Module = {};',
+ module,
+ 'return Module.asm; })();'
+ ]);
+ var url = URL.createObjectURL(script);
+ elem.src = url;
+ return new Promise(function(resolve, reject) {
+ elem.addEventListener('load', function() {
+ URL.revokeObjectURL(url);
+ var asm = Engine.asm;
+ Engine.asm = undefined;
+ setTimeout(function() {
+ // delay to reclaim compilation memory
+ resolve(asm);
+ }, 1);
+ });
+ elem.addEventListener('error', function() {
+ URL.revokeObjectURL(url);
+ reject("asm.js faiilure");
+ });
+ document.body.appendChild(elem);
+ });
+ }
+
+ Engine.unloadEngine = function() {
+ engineLoadPromise = null;
+ };
+
+ function loadPromise(file, tracker) {
+ if (tracker === undefined)
+ tracker = loadingFiles;
+ return new Promise(function(resolve, reject) {
+ loadXHR(resolve, reject, file, tracker);
+ });
+ }
+
+ function loadXHR(resolve, reject, file, tracker) {
+
+ var xhr = new XMLHttpRequest;
+ xhr.open('GET', file);
+ if (!file.endsWith('.js')) {
+ xhr.responseType = 'arraybuffer';
+ }
+ ['loadstart', 'progress', 'load', 'error', 'timeout', 'abort'].forEach(function(ev) {
+ xhr.addEventListener(ev, onXHREvent.bind(xhr, resolve, reject, file, tracker));
+ });
+ xhr.send();
+ }
+
+ function onXHREvent(resolve, reject, file, tracker, ev) {
+
+ if (this.status >= 400) {
+
+ if (this.status < 500 || ++tracker[file].attempts >= DOWNLOAD_ATTEMPTS_MAX) {
+ reject(new Error("Failed loading file '" + file + "': " + this.statusText));
+ this.abort();
+ return;
+ } else {
+ loadXHR(resolve, reject, file);
+ }
+ }
+
+ switch (ev.type) {
+ case 'loadstart':
+ if (tracker[file] === undefined) {
+ tracker[file] = {
+ total: ev.total,
+ loaded: ev.loaded,
+ attempts: 0,
+ final: false,
+ };
+ }
+ break;
+
+ case 'progress':
+ tracker[file].loaded = ev.loaded;
+ tracker[file].total = ev.total;
+ break;
+
+ case 'load':
+ tracker[file].final = true;
+ resolve(this);
+ break;
+
+ case 'error':
+ case 'timeout':
+ if (++tracker[file].attempts >= DOWNLOAD_ATTEMPTS_MAX) {
+ tracker[file].final = true;
+ reject(new Error("Failed loading file '" + file + "'"));
+ } else {
+ loadXHR(resolve, reject, file);
+ }
+ break;
+
+ case 'abort':
+ tracker[file].final = true;
+ reject(new Error("Loading file '" + file + "' was aborted."));
+ break;
+ }
+ }
+})();
diff --git a/platform/javascript/export/export.cpp b/platform/javascript/export/export.cpp
index b436d523639..c5b460e7205 100644
--- a/platform/javascript/export/export.cpp
+++ b/platform/javascript/export/export.cpp
@@ -100,8 +100,8 @@ void EditorExportPlatformJavaScript::_fix_html(Vector &p_html, const Re
for (int i = 0; i < lines.size(); i++) {
String current_line = lines[i];
- current_line = current_line.replace("$GODOT_TMEM", itos(memory_mb * 1024 * 1024));
- current_line = current_line.replace("$GODOT_BASE", p_name);
+ current_line = current_line.replace("$GODOT_TOTAL_MEMORY", itos(memory_mb * 1024 * 1024));
+ current_line = current_line.replace("$GODOT_BASENAME", p_name);
current_line = current_line.replace("$GODOT_HEAD_INCLUDE", p_preset->get("html/head_include"));
current_line = current_line.replace("$GODOT_DEBUG_ENABLED", p_debug ? "true" : "false");
str_export += current_line + "\n";
@@ -114,28 +114,6 @@ void EditorExportPlatformJavaScript::_fix_html(Vector &p_html, const Re
}
}
-void EditorExportPlatformJavaScript::_fix_fsloader_js(Vector &p_js, const String &p_pack_name, uint64_t p_pack_size) {
-
- String str_template = String::utf8(reinterpret_cast(p_js.ptr()), p_js.size());
- String str_export;
- Vector lines = str_template.split("\n");
- for (int i = 0; i < lines.size(); i++) {
- if (lines[i].find("$GODOT_PACK_NAME") != -1) {
- str_export += lines[i].replace("$GODOT_PACK_NAME", p_pack_name);
- } else if (lines[i].find("$GODOT_PACK_SIZE") != -1) {
- str_export += lines[i].replace("$GODOT_PACK_SIZE", itos(p_pack_size));
- } else {
- str_export += lines[i] + "\n";
- }
- }
-
- CharString cs = str_export.utf8();
- p_js.resize(cs.length());
- for (int i = 0; i < cs.length(); i++) {
- p_js[i] = cs[i];
- }
-}
-
void EditorExportPlatformJavaScript::get_preset_features(const Ref &p_preset, List *r_features) {
if (p_preset->get("texture_format/s3tc")) {
@@ -286,10 +264,6 @@ Error EditorExportPlatformJavaScript::export_project(const Ref
-
-
-
-
-
-$GODOT_HEAD_INCLUDE
-
-
-
-
-
- Downloading page...
-
-
-
-
-
-
-
-{{{ SCRIPT }}}
-
-
-
diff --git a/platform/javascript/pre_asmjs.js b/platform/javascript/pre_asmjs.js
new file mode 100644
index 00000000000..3c497721b68
--- /dev/null
+++ b/platform/javascript/pre_asmjs.js
@@ -0,0 +1,3 @@
+var Engine = {
+ USING_WASM: false,
+ RuntimeEnvironment: function(Module) {
diff --git a/platform/javascript/pre_wasm.js b/platform/javascript/pre_wasm.js
new file mode 100644
index 00000000000..be4383c8c99
--- /dev/null
+++ b/platform/javascript/pre_wasm.js
@@ -0,0 +1,3 @@
+var Engine = {
+ USING_WASM: true,
+ RuntimeEnvironment: function(Module) {