summaryrefslogtreecommitdiff
path: root/platform/javascript
diff options
context:
space:
mode:
Diffstat (limited to 'platform/javascript')
-rw-r--r--platform/javascript/.eslintrc.engine.js10
-rw-r--r--platform/javascript/.eslintrc.js43
-rw-r--r--platform/javascript/.eslintrc.libs.js25
-rw-r--r--platform/javascript/SCsub113
-rw-r--r--platform/javascript/api/api.cpp14
-rw-r--r--platform/javascript/api/api.h4
-rw-r--r--platform/javascript/api/javascript_eval.h6
-rw-r--r--platform/javascript/api/javascript_tools_editor_plugin.cpp135
-rw-r--r--platform/javascript/api/javascript_tools_editor_plugin.h58
-rw-r--r--platform/javascript/audio_driver_javascript.cpp370
-rw-r--r--platform/javascript/audio_driver_javascript.h76
-rw-r--r--platform/javascript/detect.py110
-rw-r--r--platform/javascript/display_server_javascript.cpp1043
-rw-r--r--platform/javascript/display_server_javascript.h205
-rw-r--r--platform/javascript/dom_keys.inc542
-rw-r--r--platform/javascript/emscripten_helpers.py35
-rw-r--r--platform/javascript/engine/engine.js184
-rw-r--r--platform/javascript/engine/loader.js33
-rw-r--r--platform/javascript/engine/preloader.js139
-rw-r--r--platform/javascript/engine/utils.js69
-rw-r--r--platform/javascript/export/export.cpp236
-rw-r--r--platform/javascript/export/export.h4
-rw-r--r--platform/javascript/godot_audio.h63
-rw-r--r--platform/javascript/godot_js.h87
-rw-r--r--platform/javascript/http_client.h.inc20
-rw-r--r--platform/javascript/http_client_javascript.cpp50
-rw-r--r--platform/javascript/http_request.h6
-rw-r--r--platform/javascript/javascript_eval.cpp109
-rw-r--r--platform/javascript/javascript_main.cpp82
-rw-r--r--platform/javascript/javascript_runtime.cpp (renamed from platform/javascript/id_handler.js)42
-rw-r--r--platform/javascript/js/dynlink.pre.js1
-rw-r--r--platform/javascript/js/engine/engine.externs.js (renamed from platform/javascript/engine/externs.js)0
-rw-r--r--platform/javascript/js/engine/engine.js292
-rw-r--r--platform/javascript/js/engine/preloader.js127
-rw-r--r--platform/javascript/js/engine/utils.js58
-rw-r--r--platform/javascript/js/libs/audio.worklet.js186
-rw-r--r--platform/javascript/js/libs/library_godot_audio.js362
-rw-r--r--platform/javascript/js/libs/library_godot_display.js497
-rw-r--r--platform/javascript/js/libs/library_godot_editor_tools.js57
-rw-r--r--platform/javascript/js/libs/library_godot_eval.js86
-rw-r--r--platform/javascript/js/libs/library_godot_http_request.js (renamed from platform/javascript/http_request.js)108
-rw-r--r--platform/javascript/js/libs/library_godot_os.js288
-rw-r--r--platform/javascript/js/libs/library_godot_runtime.js120
-rw-r--r--platform/javascript/os_javascript.cpp1267
-rw-r--r--platform/javascript/os_javascript.h139
-rw-r--r--platform/javascript/package-lock.json1605
-rw-r--r--platform/javascript/package.json24
-rw-r--r--platform/javascript/platform_config.h4
48 files changed, 6475 insertions, 2659 deletions
diff --git a/platform/javascript/.eslintrc.engine.js b/platform/javascript/.eslintrc.engine.js
new file mode 100644
index 0000000000..00f0f147a9
--- /dev/null
+++ b/platform/javascript/.eslintrc.engine.js
@@ -0,0 +1,10 @@
+module.exports = {
+ "extends": [
+ "./.eslintrc.js",
+ ],
+ "globals": {
+ "Godot": true,
+ "Preloader": true,
+ "Utils": true,
+ },
+};
diff --git a/platform/javascript/.eslintrc.js b/platform/javascript/.eslintrc.js
new file mode 100644
index 0000000000..0ff9d67d26
--- /dev/null
+++ b/platform/javascript/.eslintrc.js
@@ -0,0 +1,43 @@
+module.exports = {
+ "env": {
+ "browser": true,
+ "es2021": true,
+ },
+ "extends": [
+ "airbnb-base",
+ ],
+ "parserOptions": {
+ "ecmaVersion": 12,
+ },
+ "ignorePatterns": "*.externs.js",
+ "rules": {
+ "func-names": "off",
+ // Use tabs for consistency with the C++ codebase.
+ "indent": ["error", "tab"],
+ "max-len": "off",
+ "no-else-return": ["error", {allowElseIf: true}],
+ "curly": ["error", "all"],
+ "brace-style": ["error", "1tbs", { "allowSingleLine": false }],
+ "no-bitwise": "off",
+ "no-continue": "off",
+ "no-self-assign": "off",
+ "no-tabs": "off",
+ "no-param-reassign": ["error", { "props": false }],
+ "no-plusplus": "off",
+ "no-unused-vars": ["error", { "args": "none" }],
+ "prefer-destructuring": "off",
+ "prefer-rest-params": "off",
+ "prefer-spread": "off",
+ "camelcase": "off",
+ "no-underscore-dangle": "off",
+ "max-classes-per-file": "off",
+ "prefer-arrow-callback": "off",
+ // Messes up with copyright headers in source files.
+ "spaced-comment": "off",
+ // Completely breaks emscripten libraries.
+ "object-shorthand": "off",
+ // Closure compiler (exported properties)
+ "quote-props": ["error", "consistent"],
+ "dot-notation": "off",
+ }
+};
diff --git a/platform/javascript/.eslintrc.libs.js b/platform/javascript/.eslintrc.libs.js
new file mode 100644
index 0000000000..81b1b8c864
--- /dev/null
+++ b/platform/javascript/.eslintrc.libs.js
@@ -0,0 +1,25 @@
+module.exports = {
+ "extends": [
+ "./.eslintrc.js",
+ ],
+ "globals": {
+ "LibraryManager": true,
+ "mergeInto": true,
+ "autoAddDeps": true,
+ "HEAP8": true,
+ "HEAPU8": true,
+ "HEAP32": true,
+ "HEAPF32": true,
+ "ERRNO_CODES": true,
+ "FS": true,
+ "IDBFS": true,
+ "GodotOS": true,
+ "GodotConfig": true,
+ "GodotRuntime": true,
+ "GodotFS": true,
+ "IDHandler": true,
+ "Browser": true,
+ "GL": true,
+ "XRWebGLLayer": true,
+ },
+};
diff --git a/platform/javascript/SCsub b/platform/javascript/SCsub
index 7239648937..b0302a5f88 100644
--- a/platform/javascript/SCsub
+++ b/platform/javascript/SCsub
@@ -4,39 +4,82 @@ Import("env")
javascript_files = [
"audio_driver_javascript.cpp",
+ "display_server_javascript.cpp",
"http_client_javascript.cpp",
"javascript_eval.cpp",
"javascript_main.cpp",
"os_javascript.cpp",
+ "api/javascript_tools_editor_plugin.cpp",
]
-build_targets = ["#bin/godot${PROGSUFFIX}.js", "#bin/godot${PROGSUFFIX}.wasm"]
-if env["threads_enabled"]:
- build_targets.append("#bin/godot${PROGSUFFIX}.worker.js")
+sys_env = env.Clone()
+sys_env.AddJSLibraries(
+ [
+ "js/libs/library_godot_audio.js",
+ "js/libs/library_godot_display.js",
+ "js/libs/library_godot_http_request.js",
+ "js/libs/library_godot_os.js",
+ "js/libs/library_godot_runtime.js",
+ ]
+)
-build = env.add_program(build_targets, javascript_files)
+if env["tools"]:
+ sys_env.AddJSLibraries(["js/libs/library_godot_editor_tools.js"])
+if env["javascript_eval"]:
+ sys_env.AddJSLibraries(["js/libs/library_godot_eval.js"])
-js_libraries = [
- "http_request.js",
-]
-for lib in js_libraries:
- env.Append(LINKFLAGS=["--js-library", env.File(lib).path])
-env.Depends(build, js_libraries)
+for lib in sys_env["JS_LIBS"]:
+ sys_env.Append(LINKFLAGS=["--js-library", lib])
+for js in env["JS_PRE"]:
+ sys_env.Append(LINKFLAGS=["--pre-js", env.File(js).path])
+for ext in env["JS_EXTERNS"]:
+ sys_env["ENV"]["EMCC_CLOSURE_ARGS"] += " --externs " + ext.path
-js_modules = [
- "id_handler.js",
-]
-for module in js_modules:
- env.Append(LINKFLAGS=["--pre-js", env.File(module).path])
-env.Depends(build, js_modules)
+build = []
+if env["gdnative_enabled"]:
+ build_targets = ["#bin/godot${PROGSUFFIX}.js", "#bin/godot${PROGSUFFIX}.wasm"]
+ # Reset libraries. The main runtime will only link emscripten libraries, not godot ones.
+ sys_env["LIBS"] = []
+ # We use IDBFS. Since Emscripten 1.39.1 it needs to be linked explicitly.
+ sys_env.Append(LIBS=["idbfs.js"])
+ # JS prepended to the module code loading the side library.
+ sys_env.Append(LINKFLAGS=["--pre-js", sys_env.File("js/dynlink.pre.js")])
+ # Configure it as a main module (dynamic linking support).
+ sys_env.Append(CCFLAGS=["-s", "MAIN_MODULE=1"])
+ sys_env.Append(LINKFLAGS=["-s", "MAIN_MODULE=1"])
+ sys_env.Append(CCFLAGS=["-s", "EXPORT_ALL=1"])
+ sys_env.Append(LINKFLAGS=["-s", "EXPORT_ALL=1"])
+ # Force exporting the standard library (printf, malloc, etc.)
+ sys_env["ENV"]["EMCC_FORCE_STDLIBS"] = "libc,libc++,libc++abi"
+ # The main emscripten runtime, with exported standard libraries.
+ sys = sys_env.Program(build_targets, ["javascript_runtime.cpp"])
+ sys_env.Depends(sys, "js/dynlink.pre.js")
+
+ # The side library, containing all Godot code.
+ wasm_env = env.Clone()
+ wasm_env.Append(CPPDEFINES=["WASM_GDNATIVE"]) # So that OS knows it can run GDNative libraries.
+ wasm_env.Append(CCFLAGS=["-s", "SIDE_MODULE=2"])
+ wasm_env.Append(LINKFLAGS=["-s", "SIDE_MODULE=2"])
+ wasm = wasm_env.add_program("#bin/godot.side${PROGSUFFIX}.wasm", javascript_files)
+ build = [sys[0], sys[1], wasm[0]]
+else:
+ build_targets = ["#bin/godot${PROGSUFFIX}.js", "#bin/godot${PROGSUFFIX}.wasm"]
+ if env["threads_enabled"]:
+ build_targets.append("#bin/godot${PROGSUFFIX}.worker.js")
+ # We use IDBFS. Since Emscripten 1.39.1 it needs to be linked explicitly.
+ sys_env.Append(LIBS=["idbfs.js"])
+ build = sys_env.Program(build_targets, javascript_files + ["javascript_runtime.cpp"])
+
+sys_env.Depends(build[0], sys_env["JS_LIBS"])
+sys_env.Depends(build[0], sys_env["JS_PRE"])
+sys_env.Depends(build[0], sys_env["JS_EXTERNS"])
engine = [
- "engine/preloader.js",
- "engine/loader.js",
- "engine/utils.js",
- "engine/engine.js",
+ "js/engine/preloader.js",
+ "js/engine/utils.js",
+ "js/engine/engine.js",
]
-externs = [env.File("#platform/javascript/engine/externs.js")]
+externs = [env.File("#platform/javascript/js/engine/engine.externs.js")]
js_engine = env.CreateEngineFile("#bin/godot${PROGSUFFIX}.engine.js", engine, externs)
env.Depends(js_engine, externs)
@@ -47,11 +90,27 @@ wrap_list = [
js_wrapped = env.Textfile("#bin/godot", [env.File(f) for f in wrap_list], TEXTFILESUFFIX="${PROGSUFFIX}.wrapped.js")
zip_dir = env.Dir("#bin/.javascript_zip")
-out_files = [zip_dir.File("godot.js"), zip_dir.File("godot.wasm"), zip_dir.File("godot.html")]
-in_files = [js_wrapped, build[1], "#misc/dist/html/full-size.html"]
-if env["threads_enabled"]:
- in_files.append(build[2])
- out_files.append(zip_dir.File("godot.worker.js"))
+binary_name = "godot.tools" if env["tools"] else "godot"
+out_files = [
+ zip_dir.File(binary_name + ".js"),
+ zip_dir.File(binary_name + ".wasm"),
+ zip_dir.File(binary_name + ".html"),
+ zip_dir.File(binary_name + ".audio.worklet.js"),
+]
+html_file = "#misc/dist/html/editor.html" if env["tools"] else "#misc/dist/html/full-size.html"
+in_files = [js_wrapped, build[1], html_file, "#platform/javascript/js/libs/audio.worklet.js"]
+if env["gdnative_enabled"]:
+ in_files.append(build[2]) # Runtime
+ out_files.append(zip_dir.File(binary_name + ".side.wasm"))
+elif env["threads_enabled"]:
+ in_files.append(build[2]) # Worker
+ out_files.append(zip_dir.File(binary_name + ".worker.js"))
+
+if env["tools"]:
+ in_files.append("#misc/dist/html/logo.svg")
+ out_files.append(zip_dir.File("logo.svg"))
+ in_files.append("#icon.png")
+ out_files.append(zip_dir.File("favicon.png"))
zip_files = env.InstallAs(out_files, in_files)
env.Zip(
@@ -59,5 +118,5 @@ env.Zip(
zip_files,
ZIPROOT=zip_dir,
ZIPSUFFIX="${PROGSUFFIX}${ZIPSUFFIX}",
- ZIPCOMSTR="Archving $SOURCES as $TARGET",
+ ZIPCOMSTR="Archiving $SOURCES as $TARGET",
)
diff --git a/platform/javascript/api/api.cpp b/platform/javascript/api/api.cpp
index 45cb82b351..2f7bde065f 100644
--- a/platform/javascript/api/api.cpp
+++ b/platform/javascript/api/api.cpp
@@ -5,8 +5,8 @@
/* GODOT ENGINE */
/* https://godotengine.org */
/*************************************************************************/
-/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */
-/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */
+/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
@@ -29,32 +29,30 @@
/*************************************************************************/
#include "api.h"
-#include "core/engine.h"
+#include "core/config/engine.h"
#include "javascript_eval.h"
+#include "javascript_tools_editor_plugin.h"
static JavaScript *javascript_eval;
void register_javascript_api() {
-
+ JavaScriptToolsEditorPlugin::initialize();
ClassDB::register_virtual_class<JavaScript>();
javascript_eval = memnew(JavaScript);
Engine::get_singleton()->add_singleton(Engine::Singleton("JavaScript", javascript_eval));
}
void unregister_javascript_api() {
-
memdelete(javascript_eval);
}
JavaScript *JavaScript::singleton = nullptr;
JavaScript *JavaScript::get_singleton() {
-
return singleton;
}
JavaScript::JavaScript() {
-
ERR_FAIL_COND_MSG(singleton != nullptr, "JavaScript singleton already exist.");
singleton = this;
}
@@ -62,13 +60,11 @@ JavaScript::JavaScript() {
JavaScript::~JavaScript() {}
void JavaScript::_bind_methods() {
-
ClassDB::bind_method(D_METHOD("eval", "code", "use_global_execution_context"), &JavaScript::eval, DEFVAL(false));
}
#if !defined(JAVASCRIPT_ENABLED) || !defined(JAVASCRIPT_EVAL_ENABLED)
Variant JavaScript::eval(const String &p_code, bool p_use_global_exec_context) {
-
return Variant();
}
#endif
diff --git a/platform/javascript/api/api.h b/platform/javascript/api/api.h
index 8afe0f33ce..2ac7333cdd 100644
--- a/platform/javascript/api/api.h
+++ b/platform/javascript/api/api.h
@@ -5,8 +5,8 @@
/* GODOT ENGINE */
/* https://godotengine.org */
/*************************************************************************/
-/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */
-/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */
+/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
diff --git a/platform/javascript/api/javascript_eval.h b/platform/javascript/api/javascript_eval.h
index 29229de8e3..24f7648ed9 100644
--- a/platform/javascript/api/javascript_eval.h
+++ b/platform/javascript/api/javascript_eval.h
@@ -5,8 +5,8 @@
/* GODOT ENGINE */
/* https://godotengine.org */
/*************************************************************************/
-/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */
-/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */
+/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
@@ -31,7 +31,7 @@
#ifndef JAVASCRIPT_EVAL_H
#define JAVASCRIPT_EVAL_H
-#include "core/object.h"
+#include "core/object/class_db.h"
class JavaScript : public Object {
private:
diff --git a/platform/javascript/api/javascript_tools_editor_plugin.cpp b/platform/javascript/api/javascript_tools_editor_plugin.cpp
new file mode 100644
index 0000000000..8355faccc2
--- /dev/null
+++ b/platform/javascript/api/javascript_tools_editor_plugin.cpp
@@ -0,0 +1,135 @@
+/*************************************************************************/
+/* javascript_tools_editor_plugin.cpp */
+/*************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/*************************************************************************/
+/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/*************************************************************************/
+
+#if defined(TOOLS_ENABLED) && defined(JAVASCRIPT_ENABLED)
+#include "javascript_tools_editor_plugin.h"
+
+#include "core/config/engine.h"
+#include "core/config/project_settings.h"
+#include "core/os/dir_access.h"
+#include "core/os/file_access.h"
+#include "editor/editor_node.h"
+
+#include <emscripten/emscripten.h>
+
+// JavaScript functions defined in library_godot_editor_tools.js
+extern "C" {
+extern void godot_js_editor_download_file(const char *p_path, const char *p_name, const char *p_mime);
+}
+
+static void _javascript_editor_init_callback() {
+ EditorNode::get_singleton()->add_editor_plugin(memnew(JavaScriptToolsEditorPlugin(EditorNode::get_singleton())));
+}
+
+void JavaScriptToolsEditorPlugin::initialize() {
+ EditorNode::add_init_callback(_javascript_editor_init_callback);
+}
+
+JavaScriptToolsEditorPlugin::JavaScriptToolsEditorPlugin(EditorNode *p_editor) {
+ add_tool_menu_item("Download Project Source", callable_mp(this, &JavaScriptToolsEditorPlugin::_download_zip));
+}
+
+void JavaScriptToolsEditorPlugin::_download_zip(Variant p_v) {
+ if (!Engine::get_singleton() || !Engine::get_singleton()->is_editor_hint()) {
+ WARN_PRINT("Project download is only available in Editor mode");
+ return;
+ }
+ String resource_path = ProjectSettings::get_singleton()->get_resource_path();
+
+ FileAccess *src_f;
+ zlib_filefunc_def io = zipio_create_io_from_file(&src_f);
+ zipFile zip = zipOpen2("/tmp/project.zip", APPEND_STATUS_CREATE, NULL, &io);
+ String base_path = resource_path.substr(0, resource_path.rfind("/")) + "/";
+ _zip_recursive(resource_path, base_path, zip);
+ zipClose(zip, NULL);
+ godot_js_editor_download_file("/tmp/project.zip", "project.zip", "application/zip");
+}
+
+void JavaScriptToolsEditorPlugin::_zip_file(String p_path, String p_base_path, zipFile p_zip) {
+ FileAccess *f = FileAccess::open(p_path, FileAccess::READ);
+ if (!f) {
+ WARN_PRINT("Unable to open file for zipping: " + p_path);
+ return;
+ }
+ Vector<uint8_t> data;
+ int len = f->get_len();
+ data.resize(len);
+ f->get_buffer(data.ptrw(), len);
+ f->close();
+ memdelete(f);
+
+ String path = p_path.replace_first(p_base_path, "");
+ zipOpenNewFileInZip(p_zip,
+ path.utf8().get_data(),
+ NULL,
+ NULL,
+ 0,
+ NULL,
+ 0,
+ NULL,
+ Z_DEFLATED,
+ Z_DEFAULT_COMPRESSION);
+ zipWriteInFileInZip(p_zip, data.ptr(), data.size());
+ zipCloseFileInZip(p_zip);
+}
+
+void JavaScriptToolsEditorPlugin::_zip_recursive(String p_path, String p_base_path, zipFile p_zip) {
+ DirAccess *dir = DirAccess::open(p_path);
+ if (!dir) {
+ WARN_PRINT("Unable to open dir for zipping: " + p_path);
+ return;
+ }
+ dir->list_dir_begin();
+ String cur = dir->get_next();
+ while (!cur.is_empty()) {
+ String cs = p_path.plus_file(cur);
+ if (cur == "." || cur == ".." || cur == ".import") {
+ // Skip
+ } else if (dir->current_is_dir()) {
+ String path = cs.replace_first(p_base_path, "") + "/";
+ zipOpenNewFileInZip(p_zip,
+ path.utf8().get_data(),
+ NULL,
+ NULL,
+ 0,
+ NULL,
+ 0,
+ NULL,
+ Z_DEFLATED,
+ Z_DEFAULT_COMPRESSION);
+ zipCloseFileInZip(p_zip);
+ _zip_recursive(cs, p_base_path, p_zip);
+ } else {
+ _zip_file(cs, p_base_path, p_zip);
+ }
+ cur = dir->get_next();
+ }
+}
+#endif
diff --git a/platform/javascript/api/javascript_tools_editor_plugin.h b/platform/javascript/api/javascript_tools_editor_plugin.h
new file mode 100644
index 0000000000..557821d627
--- /dev/null
+++ b/platform/javascript/api/javascript_tools_editor_plugin.h
@@ -0,0 +1,58 @@
+/*************************************************************************/
+/* javascript_tools_editor_plugin.h */
+/*************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/*************************************************************************/
+/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/*************************************************************************/
+
+#ifndef JAVASCRIPT_TOOLS_EDITOR_PLUGIN_H
+#define JAVASCRIPT_TOOLS_EDITOR_PLUGIN_H
+
+#if defined(TOOLS_ENABLED) && defined(JAVASCRIPT_ENABLED)
+#include "core/io/zip_io.h"
+#include "editor/editor_plugin.h"
+
+class JavaScriptToolsEditorPlugin : public EditorPlugin {
+ GDCLASS(JavaScriptToolsEditorPlugin, EditorPlugin);
+
+private:
+ void _zip_file(String p_path, String p_base_path, zipFile p_zip);
+ void _zip_recursive(String p_path, String p_base_path, zipFile p_zip);
+ void _download_zip(Variant p_v);
+
+public:
+ static void initialize();
+
+ JavaScriptToolsEditorPlugin(EditorNode *p_editor);
+};
+#else
+class JavaScriptToolsEditorPlugin {
+public:
+ static void initialize() {}
+};
+#endif
+
+#endif // JAVASCRIPT_TOOLS_EDITOR_PLUGIN_H
diff --git a/platform/javascript/audio_driver_javascript.cpp b/platform/javascript/audio_driver_javascript.cpp
index 8f857478e5..6395fdf721 100644
--- a/platform/javascript/audio_driver_javascript.cpp
+++ b/platform/javascript/audio_driver_javascript.cpp
@@ -5,8 +5,8 @@
/* GODOT ENGINE */
/* https://godotengine.org */
/*************************************************************************/
-/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */
-/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */
+/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
@@ -30,242 +30,258 @@
#include "audio_driver_javascript.h"
+#include "core/config/project_settings.h"
+
#include <emscripten.h>
AudioDriverJavaScript *AudioDriverJavaScript::singleton = nullptr;
-const char *AudioDriverJavaScript::get_name() const {
+bool AudioDriverJavaScript::is_available() {
+ return godot_audio_is_available() != 0;
+}
+const char *AudioDriverJavaScript::get_name() const {
return "JavaScript";
}
-extern "C" EMSCRIPTEN_KEEPALIVE void audio_driver_js_mix() {
-
- AudioDriverJavaScript::singleton->mix_to_js();
+void AudioDriverJavaScript::_state_change_callback(int p_state) {
+ singleton->state = p_state;
}
-extern "C" EMSCRIPTEN_KEEPALIVE void audio_driver_process_capture(float sample) {
-
- AudioDriverJavaScript::singleton->process_capture(sample);
+void AudioDriverJavaScript::_latency_update_callback(float p_latency) {
+ singleton->output_latency = p_latency;
}
-void AudioDriverJavaScript::mix_to_js() {
+void AudioDriverJavaScript::_audio_driver_process(int p_from, int p_samples) {
+ int32_t *stream_buffer = reinterpret_cast<int32_t *>(output_rb);
+ const int max_samples = memarr_len(output_rb);
- int channel_count = get_total_channels_by_speaker_mode(get_speaker_mode());
- int sample_count = memarr_len(internal_buffer) / channel_count;
- int32_t *stream_buffer = reinterpret_cast<int32_t *>(internal_buffer);
- audio_server_process(sample_count, stream_buffer);
- for (int i = 0; i < sample_count * channel_count; i++) {
- internal_buffer[i] = float(stream_buffer[i] >> 16) / 32768.f;
+ int write_pos = p_from;
+ int to_write = p_samples;
+ if (to_write == 0) {
+ to_write = max_samples;
+ }
+ // High part
+ if (write_pos + to_write > max_samples) {
+ const int samples_high = max_samples - write_pos;
+ audio_server_process(samples_high / channel_count, &stream_buffer[write_pos]);
+ for (int i = write_pos; i < max_samples; i++) {
+ output_rb[i] = float(stream_buffer[i] >> 16) / 32768.f;
+ }
+ to_write -= samples_high;
+ write_pos = 0;
+ }
+ // Leftover
+ audio_server_process(to_write / channel_count, &stream_buffer[write_pos]);
+ for (int i = write_pos; i < write_pos + to_write; i++) {
+ output_rb[i] = float(stream_buffer[i] >> 16) / 32768.f;
}
}
-void AudioDriverJavaScript::process_capture(float sample) {
+void AudioDriverJavaScript::_audio_driver_capture(int p_from, int p_samples) {
+ if (get_input_buffer().size() == 0) {
+ return; // Input capture stopped.
+ }
+ const int max_samples = memarr_len(input_rb);
- int32_t sample32 = int32_t(sample * 32768.f) * (1U << 16);
- input_buffer_write(sample32);
+ int read_pos = p_from;
+ int to_read = p_samples;
+ if (to_read == 0) {
+ to_read = max_samples;
+ }
+ // High part
+ if (read_pos + to_read > max_samples) {
+ const int samples_high = max_samples - read_pos;
+ for (int i = read_pos; i < max_samples; i++) {
+ input_buffer_write(int32_t(input_rb[i] * 32768.f) * (1U << 16));
+ }
+ to_read -= samples_high;
+ read_pos = 0;
+ }
+ // Leftover
+ for (int i = read_pos; i < read_pos + to_read; i++) {
+ input_buffer_write(int32_t(input_rb[i] * 32768.f) * (1U << 16));
+ }
}
Error AudioDriverJavaScript::init() {
-
- /* clang-format off */
- _driver_id = EM_ASM_INT({
- return Module.IDHandler.add({
- 'context': new (window.AudioContext || window.webkitAudioContext),
- 'input': null,
- 'stream': null,
- 'script': null
- });
- });
- /* clang-format on */
-
- int channel_count = get_total_channels_by_speaker_mode(get_speaker_mode());
- /* clang-format off */
- buffer_length = EM_ASM_INT({
- var ref = Module.IDHandler.get($0);
- var ctx = ref['context'];
- var CHANNEL_COUNT = $1;
-
- var channelCount = ctx.destination.channelCount;
- var script = null;
- try {
- // Try letting the browser recommend a buffer length.
- script = ctx.createScriptProcessor(0, 2, channelCount);
- } catch (e) {
- // ...otherwise, default to 4096.
- script = ctx.createScriptProcessor(4096, 2, channelCount);
- }
- script.connect(ctx.destination);
- ref['script'] = script;
-
- return script.bufferSize;
- }, _driver_id, channel_count);
- /* clang-format on */
- if (!buffer_length) {
- return FAILED;
+ mix_rate = GLOBAL_GET("audio/mix_rate");
+ int latency = GLOBAL_GET("audio/output_latency");
+
+ channel_count = godot_audio_init(mix_rate, latency, &_state_change_callback, &_latency_update_callback);
+ buffer_length = closest_power_of_2((latency * mix_rate / 1000));
+#ifndef NO_THREADS
+ node = memnew(WorkletNode);
+#else
+ node = memnew(ScriptProcessorNode);
+#endif
+ buffer_length = node->create(buffer_length, channel_count);
+ if (output_rb) {
+ memdelete_arr(output_rb);
}
-
- if (!internal_buffer || (int)memarr_len(internal_buffer) != buffer_length * channel_count) {
- if (internal_buffer)
- memdelete_arr(internal_buffer);
- internal_buffer = memnew_arr(float, buffer_length *channel_count);
+ output_rb = memnew_arr(float, buffer_length *channel_count);
+ if (!output_rb) {
+ return ERR_OUT_OF_MEMORY;
}
-
- return internal_buffer ? OK : ERR_OUT_OF_MEMORY;
+ if (input_rb) {
+ memdelete_arr(input_rb);
+ }
+ input_rb = memnew_arr(float, buffer_length *channel_count);
+ if (!input_rb) {
+ return ERR_OUT_OF_MEMORY;
+ }
+ return OK;
}
void AudioDriverJavaScript::start() {
-
- /* clang-format off */
- EM_ASM({
- const ref = Module.IDHandler.get($0);
- var INTERNAL_BUFFER_PTR = $1;
-
- var audioDriverMixFunction = cwrap('audio_driver_js_mix');
- var audioDriverProcessCapture = cwrap('audio_driver_process_capture', null, ['number']);
- ref['script'].onaudioprocess = function(audioProcessingEvent) {
- audioDriverMixFunction();
-
- var input = audioProcessingEvent.inputBuffer;
- var output = audioProcessingEvent.outputBuffer;
- var internalBuffer = HEAPF32.subarray(
- INTERNAL_BUFFER_PTR / HEAPF32.BYTES_PER_ELEMENT,
- INTERNAL_BUFFER_PTR / HEAPF32.BYTES_PER_ELEMENT + output.length * output.numberOfChannels);
-
- for (var channel = 0; channel < output.numberOfChannels; channel++) {
- var outputData = output.getChannelData(channel);
- // Loop through samples.
- for (var sample = 0; sample < outputData.length; sample++) {
- outputData[sample] = internalBuffer[sample * output.numberOfChannels + channel];
- }
- }
-
- if (ref['input']) {
- var inputDataL = input.getChannelData(0);
- var inputDataR = input.getChannelData(1);
- for (var i = 0; i < inputDataL.length; i++) {
- audioDriverProcessCapture(inputDataL[i]);
- audioDriverProcessCapture(inputDataR[i]);
- }
- }
- };
- }, _driver_id, internal_buffer);
- /* clang-format on */
+ if (node) {
+ node->start(output_rb, memarr_len(output_rb), input_rb, memarr_len(input_rb));
+ }
}
void AudioDriverJavaScript::resume() {
- /* clang-format off */
- EM_ASM({
- const ref = Module.IDHandler.get($0);
- if (ref && ref['context'] && ref['context'].resume)
- ref['context'].resume();
- }, _driver_id);
- /* clang-format on */
+ if (state == 0) { // 'suspended'
+ godot_audio_resume();
+ }
}
-int AudioDriverJavaScript::get_mix_rate() const {
+float AudioDriverJavaScript::get_latency() {
+ return output_latency + (float(buffer_length) / mix_rate);
+}
- /* clang-format off */
- return EM_ASM_INT({
- const ref = Module.IDHandler.get($0);
- return ref && ref['context'] ? ref['context'].sampleRate : 0;
- }, _driver_id);
- /* clang-format on */
+int AudioDriverJavaScript::get_mix_rate() const {
+ return mix_rate;
}
AudioDriver::SpeakerMode AudioDriverJavaScript::get_speaker_mode() const {
-
- /* clang-format off */
- return get_speaker_mode_by_total_channels(EM_ASM_INT({
- const ref = Module.IDHandler.get($0);
- return ref && ref['context'] ? ref['context'].destination.channelCount : 0;
- }, _driver_id));
- /* clang-format on */
+ return get_speaker_mode_by_total_channels(channel_count);
}
-// No locking, as threads are not supported.
void AudioDriverJavaScript::lock() {
+ if (node) {
+ node->unlock();
+ }
}
void AudioDriverJavaScript::unlock() {
+ if (node) {
+ node->unlock();
+ }
}
void AudioDriverJavaScript::finish() {
-
- /* clang-format off */
- EM_ASM({
- Module.IDHandler.remove($0);
- }, _driver_id);
- /* clang-format on */
-
- if (internal_buffer) {
- memdelete_arr(internal_buffer);
- internal_buffer = nullptr;
+ if (node) {
+ node->finish();
+ memdelete(node);
+ node = nullptr;
+ }
+ if (output_rb) {
+ memdelete_arr(output_rb);
+ output_rb = nullptr;
+ }
+ if (input_rb) {
+ memdelete_arr(input_rb);
+ input_rb = nullptr;
}
- _driver_id = 0;
}
Error AudioDriverJavaScript::capture_start() {
-
+ lock();
input_buffer_init(buffer_length);
+ unlock();
+ if (godot_audio_capture_start()) {
+ return FAILED;
+ }
+ return OK;
+}
- /* clang-format off */
- EM_ASM({
- function gotMediaInput(stream) {
- var ref = Module.IDHandler.get($0);
- ref['stream'] = stream;
- ref['input'] = ref['context'].createMediaStreamSource(stream);
- ref['input'].connect(ref['script']);
- }
-
- function gotMediaInputError(e) {
- out(e);
- }
+Error AudioDriverJavaScript::capture_stop() {
+ godot_audio_capture_stop();
+ lock();
+ input_buffer.clear();
+ unlock();
+ return OK;
+}
- if (navigator.mediaDevices.getUserMedia) {
- navigator.mediaDevices.getUserMedia({"audio": true}).then(gotMediaInput, gotMediaInputError);
- } else {
- if (!navigator.getUserMedia)
- navigator.getUserMedia = navigator.webkitGetUserMedia || navigator.mozGetUserMedia;
- navigator.getUserMedia({"audio": true}, gotMediaInput, gotMediaInputError);
- }
- }, _driver_id);
- /* clang-format on */
+AudioDriverJavaScript::AudioDriverJavaScript() {
+ singleton = this;
+}
- return OK;
+#ifdef NO_THREADS
+/// ScriptProcessorNode implementation
+void AudioDriverJavaScript::ScriptProcessorNode::_process_callback() {
+ AudioDriverJavaScript::singleton->_audio_driver_capture();
+ AudioDriverJavaScript::singleton->_audio_driver_process();
}
-Error AudioDriverJavaScript::capture_stop() {
+int AudioDriverJavaScript::ScriptProcessorNode::create(int p_buffer_samples, int p_channels) {
+ return godot_audio_script_create(p_buffer_samples, p_channels);
+}
- /* clang-format off */
- EM_ASM({
- var ref = Module.IDHandler.get($0);
- if (ref['stream']) {
- const tracks = ref['stream'].getTracks();
- for (var i = 0; i < tracks.length; i++) {
- tracks[i].stop();
+void AudioDriverJavaScript::ScriptProcessorNode::start(float *p_out_buf, int p_out_buf_size, float *p_in_buf, int p_in_buf_size) {
+ godot_audio_script_start(p_in_buf, p_in_buf_size, p_out_buf, p_out_buf_size, &_process_callback);
+}
+#else
+/// AudioWorkletNode implementation
+void AudioDriverJavaScript::WorkletNode::_audio_thread_func(void *p_data) {
+ AudioDriverJavaScript::WorkletNode *obj = static_cast<AudioDriverJavaScript::WorkletNode *>(p_data);
+ AudioDriverJavaScript *driver = AudioDriverJavaScript::singleton;
+ const int out_samples = memarr_len(driver->output_rb);
+ const int in_samples = memarr_len(driver->input_rb);
+ int wpos = 0;
+ int to_write = out_samples;
+ int rpos = 0;
+ int to_read = 0;
+ int32_t step = 0;
+ while (!obj->quit) {
+ if (to_read) {
+ driver->lock();
+ driver->_audio_driver_capture(rpos, to_read);
+ godot_audio_worklet_state_add(obj->state, STATE_SAMPLES_IN, -to_read);
+ driver->unlock();
+ rpos += to_read;
+ if (rpos >= in_samples) {
+ rpos -= in_samples;
}
- ref['stream'] = null;
}
-
- if (ref['input']) {
- ref['input'].disconnect();
- ref['input'] = null;
+ if (to_write) {
+ driver->lock();
+ driver->_audio_driver_process(wpos, to_write);
+ godot_audio_worklet_state_add(obj->state, STATE_SAMPLES_OUT, to_write);
+ driver->unlock();
+ wpos += to_write;
+ if (wpos >= out_samples) {
+ wpos -= out_samples;
+ }
}
+ step = godot_audio_worklet_state_wait(obj->state, STATE_PROCESS, step, 1);
+ to_write = out_samples - godot_audio_worklet_state_get(obj->state, STATE_SAMPLES_OUT);
+ to_read = godot_audio_worklet_state_get(obj->state, STATE_SAMPLES_IN);
+ }
+}
- }, _driver_id);
- /* clang-format on */
-
- input_buffer.clear();
+int AudioDriverJavaScript::WorkletNode::create(int p_buffer_size, int p_channels) {
+ godot_audio_worklet_create(p_channels);
+ return p_buffer_size;
+}
- return OK;
+void AudioDriverJavaScript::WorkletNode::start(float *p_out_buf, int p_out_buf_size, float *p_in_buf, int p_in_buf_size) {
+ godot_audio_worklet_start(p_in_buf, p_in_buf_size, p_out_buf, p_out_buf_size, state);
+ thread = Thread::create(_audio_thread_func, this);
}
-AudioDriverJavaScript::AudioDriverJavaScript() {
+void AudioDriverJavaScript::WorkletNode::lock() {
+ mutex.lock();
+}
- _driver_id = 0;
- internal_buffer = nullptr;
- buffer_length = 0;
+void AudioDriverJavaScript::WorkletNode::unlock() {
+ mutex.unlock();
+}
- singleton = this;
+void AudioDriverJavaScript::WorkletNode::finish() {
+ quit = true; // Ask thread to quit.
+ Thread::wait_to_finish(thread);
+ memdelete(thread);
+ thread = nullptr;
}
+#endif
diff --git a/platform/javascript/audio_driver_javascript.h b/platform/javascript/audio_driver_javascript.h
index f6f2dacd4e..d55ec261a4 100644
--- a/platform/javascript/audio_driver_javascript.h
+++ b/platform/javascript/audio_driver_javascript.h
@@ -5,8 +5,8 @@
/* GODOT ENGINE */
/* https://godotengine.org */
/*************************************************************************/
-/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */
-/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */
+/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
@@ -31,18 +31,78 @@
#ifndef AUDIO_DRIVER_JAVASCRIPT_H
#define AUDIO_DRIVER_JAVASCRIPT_H
+#include "core/os/mutex.h"
+#include "core/os/thread.h"
#include "servers/audio_server.h"
+#include "godot_audio.h"
+
class AudioDriverJavaScript : public AudioDriver {
+public:
+ class AudioNode {
+ public:
+ virtual int create(int p_buffer_size, int p_output_channels) = 0;
+ virtual void start(float *p_out_buf, int p_out_buf_size, float *p_in_buf, int p_in_buf_size) = 0;
+ virtual void finish() {}
+ virtual void lock() {}
+ virtual void unlock() {}
+ virtual ~AudioNode() {}
+ };
+
+ class WorkletNode : public AudioNode {
+ private:
+ enum {
+ STATE_LOCK,
+ STATE_PROCESS,
+ STATE_SAMPLES_IN,
+ STATE_SAMPLES_OUT,
+ STATE_MAX,
+ };
+ Mutex mutex;
+ Thread *thread = nullptr;
+ bool quit = false;
+ int32_t state[STATE_MAX] = { 0 };
+
+ static void _audio_thread_func(void *p_data);
+
+ public:
+ int create(int p_buffer_size, int p_output_channels) override;
+ void start(float *p_out_buf, int p_out_buf_size, float *p_in_buf, int p_in_buf_size) override;
+ void finish() override;
+ void lock() override;
+ void unlock() override;
+ };
+
+ class ScriptProcessorNode : public AudioNode {
+ private:
+ static void _process_callback();
- float *internal_buffer;
+ public:
+ int create(int p_buffer_samples, int p_channels) override;
+ void start(float *p_out_buf, int p_out_buf_size, float *p_in_buf, int p_in_buf_size) override;
+ };
- int _driver_id;
- int buffer_length;
+private:
+ AudioNode *node = nullptr;
+
+ float *output_rb = nullptr;
+ float *input_rb = nullptr;
+
+ int buffer_length = 0;
+ int mix_rate = 0;
+ int channel_count = 0;
+ int state = 0;
+ float output_latency = 0.0;
+
+ static void _state_change_callback(int p_state);
+ static void _latency_update_callback(float p_latency);
+
+protected:
+ void _audio_driver_process(int p_from = 0, int p_samples = 0);
+ void _audio_driver_capture(int p_from = 0, int p_samples = 0);
public:
- void mix_to_js();
- void process_capture(float sample);
+ static bool is_available();
static AudioDriverJavaScript *singleton;
@@ -51,6 +111,7 @@ public:
virtual Error init();
virtual void start();
void resume();
+ virtual float get_latency();
virtual int get_mix_rate() const;
virtual SpeakerMode get_speaker_mode() const;
virtual void lock();
@@ -62,5 +123,4 @@ public:
AudioDriverJavaScript();
};
-
#endif
diff --git a/platform/javascript/detect.py b/platform/javascript/detect.py
index 9486e10717..0d57f8aad1 100644
--- a/platform/javascript/detect.py
+++ b/platform/javascript/detect.py
@@ -1,6 +1,9 @@
import os
+import sys
-from emscripten_helpers import parse_config, run_closure_compiler, create_engine_file
+from emscripten_helpers import run_closure_compiler, create_engine_file, add_js_libraries, add_js_pre, add_js_externs
+from methods import get_compiler_version
+from SCons.Util import WhereIs
def is_active():
@@ -12,17 +15,25 @@ def get_name():
def can_build():
- return "EM_CONFIG" in os.environ or os.path.exists(os.path.expanduser("~/.emscripten"))
+ return WhereIs("emcc") is not None
def get_opts():
from SCons.Variables import BoolVariable
return [
+ ("initial_memory", "Initial WASM memory (in MiB)", 16),
+ BoolVariable("use_assertions", "Use Emscripten runtime assertions", False),
+ BoolVariable("use_thinlto", "Use ThinLTO", False),
+ BoolVariable("use_ubsan", "Use Emscripten undefined behavior sanitizer (UBSAN)", False),
+ BoolVariable("use_asan", "Use Emscripten address sanitizer (ASAN)", False),
+ BoolVariable("use_lsan", "Use Emscripten leak sanitizer (LSAN)", False),
+ BoolVariable("use_safe_heap", "Use Emscripten SAFE_HEAP sanitizer", False),
# eval() can be a security concern, so it can be disabled.
BoolVariable("javascript_eval", "Enable JavaScript eval interface", True),
BoolVariable("threads_enabled", "Enable WebAssembly Threads support (limited browser support)", False),
- BoolVariable("use_closure_compiler", "Use closure compiler to minimize Javascript code", False),
+ BoolVariable("gdnative_enabled", "Enable WebAssembly GDNative support (produces bigger binaries)", False),
+ BoolVariable("use_closure_compiler", "Use closure compiler to minimize JavaScript code", False),
]
@@ -39,6 +50,9 @@ def get_flags():
def configure(env):
+ if not isinstance(env["initial_memory"], int):
+ print("Initial memory must be a valid integer")
+ sys.exit(255)
## Build type
@@ -57,19 +71,24 @@ def configure(env):
env.Append(CPPDEFINES=["DEBUG_ENABLED"])
# Retain function names for backtraces at the cost of file size.
env.Append(LINKFLAGS=["--profiling-funcs"])
- else: # 'debug'
+ else: # "debug"
env.Append(CPPDEFINES=["DEBUG_ENABLED"])
env.Append(CCFLAGS=["-O1", "-g"])
env.Append(LINKFLAGS=["-O1", "-g"])
+ env["use_assertions"] = True
+
+ if env["use_assertions"]:
env.Append(LINKFLAGS=["-s", "ASSERTIONS=1"])
if env["tools"]:
if not env["threads_enabled"]:
- raise RuntimeError(
- "Threads must be enabled to build the editor. Please add the 'threads_enabled=yes' option"
- )
- # Tools need more memory. Initial stack memory in bytes. See `src/settings.js` in emscripten repository (will be renamed to INITIAL_MEMORY).
- env.Append(LINKFLAGS=["-s", "TOTAL_MEMORY=33554432"])
+ print("Threads must be enabled to build the editor. Please add the 'threads_enabled=yes' option")
+ sys.exit(255)
+ if env["initial_memory"] < 32:
+ print("Editor build requires at least 32MiB of initial memory. Forcing it.")
+ env["initial_memory"] = 32
+ elif env["builtin_icu"]:
+ env.Append(CCFLAGS=["-frtti"])
else:
# Disable exceptions and rtti on non-tools (template) builds
# These flags help keep the file size down.
@@ -77,14 +96,32 @@ def configure(env):
# Don't use dynamic_cast, necessary with no-rtti.
env.Append(CPPDEFINES=["NO_SAFE_CAST"])
+ env.Append(LINKFLAGS=["-s", "INITIAL_MEMORY=%sMB" % env["initial_memory"]])
+
## Copy env variables.
env["ENV"] = os.environ
# LTO
- if env["use_lto"]:
- env.Append(CCFLAGS=["-s", "WASM_OBJECT_FILES=0"])
- env.Append(LINKFLAGS=["-s", "WASM_OBJECT_FILES=0"])
- env.Append(LINKFLAGS=["--llvm-lto", "1"])
+ if env["use_thinlto"]:
+ env.Append(CCFLAGS=["-flto=thin"])
+ env.Append(LINKFLAGS=["-flto=thin"])
+ elif env["use_lto"]:
+ env.Append(CCFLAGS=["-flto=full"])
+ env.Append(LINKFLAGS=["-flto=full"])
+
+ # Sanitizers
+ if env["use_ubsan"]:
+ env.Append(CCFLAGS=["-fsanitize=undefined"])
+ env.Append(LINKFLAGS=["-fsanitize=undefined"])
+ if env["use_asan"]:
+ env.Append(CCFLAGS=["-fsanitize=address"])
+ env.Append(LINKFLAGS=["-fsanitize=address"])
+ if env["use_lsan"]:
+ env.Append(CCFLAGS=["-fsanitize=leak"])
+ env.Append(LINKFLAGS=["-fsanitize=leak"])
+ if env["use_safe_heap"]:
+ env.Append(CCFLAGS=["-s", "SAFE_HEAP=1"])
+ env.Append(LINKFLAGS=["-s", "SAFE_HEAP=1"])
# Closure compiler
if env["use_closure_compiler"]:
@@ -94,18 +131,22 @@ def configure(env):
jscc = env.Builder(generator=run_closure_compiler, suffix=".cc.js", src_suffix=".js")
env.Append(BUILDERS={"BuildJS": jscc})
+ # Add helper method for adding libraries, externs, pre-js.
+ env["JS_LIBS"] = []
+ env["JS_PRE"] = []
+ env["JS_EXTERNS"] = []
+ env.AddMethod(add_js_libraries, "AddJSLibraries")
+ env.AddMethod(add_js_pre, "AddJSPre")
+ env.AddMethod(add_js_externs, "AddJSExterns")
+
# Add method that joins/compiles our Engine files.
env.AddMethod(create_engine_file, "CreateEngineFile")
# Closure compiler extern and support for ecmascript specs (const, let, etc).
env["ENV"]["EMCC_CLOSURE_ARGS"] = "--language_in ECMASCRIPT6"
- em_config = parse_config()
- env.PrependENVPath("PATH", em_config["EMCC_ROOT"])
-
env["CC"] = "emcc"
env["CXX"] = "em++"
- env["LINK"] = "emcc"
env["AR"] = "emar"
env["RANLIB"] = "emranlib"
@@ -132,25 +173,35 @@ def configure(env):
if env["javascript_eval"]:
env.Append(CPPDEFINES=["JAVASCRIPT_EVAL_ENABLED"])
+ if env["threads_enabled"] and env["gdnative_enabled"]:
+ print("Threads and GDNative support can't be both enabled due to WebAssembly limitations")
+ sys.exit(255)
+
# Thread support (via SharedArrayBuffer).
if env["threads_enabled"]:
env.Append(CPPDEFINES=["PTHREAD_NO_RENAME"])
env.Append(CCFLAGS=["-s", "USE_PTHREADS=1"])
env.Append(LINKFLAGS=["-s", "USE_PTHREADS=1"])
- env.Append(LINKFLAGS=["-s", "PTHREAD_POOL_SIZE=4"])
+ env.Append(LINKFLAGS=["-s", "PTHREAD_POOL_SIZE=8"])
env.Append(LINKFLAGS=["-s", "WASM_MEM_MAX=2048MB"])
+ env.extra_suffix = ".threads" + env.extra_suffix
else:
env.Append(CPPDEFINES=["NO_THREADS"])
+ if env["gdnative_enabled"]:
+ major, minor, patch = get_compiler_version(env)
+ if major < 2 or (major == 2 and minor == 0 and patch < 10):
+ print("GDNative support requires emscripten >= 2.0.10, detected: %s.%s.%s" % (major, minor, patch))
+ sys.exit(255)
+ env.Append(CCFLAGS=["-s", "RELOCATABLE=1"])
+ env.Append(LINKFLAGS=["-s", "RELOCATABLE=1"])
+ env.extra_suffix = ".gdnative" + env.extra_suffix
+
# Reduce code size by generating less support code (e.g. skip NodeJS support).
env.Append(LINKFLAGS=["-s", "ENVIRONMENT=web,worker"])
- # We use IDBFS in javascript_main.cpp. Since Emscripten 1.39.1 it needs to
- # be linked explicitly.
- env.Append(LIBS=["idbfs.js"])
-
- env.Append(LINKFLAGS=["-s", "BINARYEN=1"])
- env.Append(LINKFLAGS=["-s", "MODULARIZE=1", "-s", 'EXPORT_NAME="Godot"'])
+ # Wrap the JavaScript support code around a closure named Godot.
+ env.Append(LINKFLAGS=["-s", "MODULARIZE=1", "-s", "EXPORT_NAME='Godot'"])
# Allow increasing memory buffer size during runtime. This is efficient
# when using WebAssembly (in comparison to asm.js) and works well for
@@ -160,7 +211,14 @@ def configure(env):
# This setting just makes WebGL 2 APIs available, it does NOT disable WebGL 1.
env.Append(LINKFLAGS=["-s", "USE_WEBGL2=1"])
+ # Do not call main immediately when the support code is ready.
env.Append(LINKFLAGS=["-s", "INVOKE_RUN=0"])
- # callMain for manual start, FS for preloading.
- env.Append(LINKFLAGS=["-s", 'EXTRA_EXPORTED_RUNTIME_METHODS=["callMain", "FS"]'])
+ # Allow use to take control of swapping WebGL buffers.
+ env.Append(LINKFLAGS=["-s", "OFFSCREEN_FRAMEBUFFER=1"])
+
+ # callMain for manual start.
+ env.Append(LINKFLAGS=["-s", "EXTRA_EXPORTED_RUNTIME_METHODS=['callMain']"])
+
+ # Add code that allow exiting runtime.
+ env.Append(LINKFLAGS=["-s", "EXIT_RUNTIME=1"])
diff --git a/platform/javascript/display_server_javascript.cpp b/platform/javascript/display_server_javascript.cpp
new file mode 100644
index 0000000000..f10627b0b6
--- /dev/null
+++ b/platform/javascript/display_server_javascript.cpp
@@ -0,0 +1,1043 @@
+/*************************************************************************/
+/* display_server_javascript.cpp */
+/*************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/*************************************************************************/
+/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/*************************************************************************/
+
+#include "platform/javascript/display_server_javascript.h"
+
+#include "drivers/dummy/rasterizer_dummy.h"
+#include "platform/javascript/os_javascript.h"
+
+#include <emscripten.h>
+#include <png.h>
+
+#include "dom_keys.inc"
+#include "godot_js.h"
+
+#define DOM_BUTTON_LEFT 0
+#define DOM_BUTTON_MIDDLE 1
+#define DOM_BUTTON_RIGHT 2
+#define DOM_BUTTON_XBUTTON1 3
+#define DOM_BUTTON_XBUTTON2 4
+
+DisplayServerJavaScript *DisplayServerJavaScript::get_singleton() {
+ return static_cast<DisplayServerJavaScript *>(DisplayServer::get_singleton());
+}
+
+// Window (canvas)
+void DisplayServerJavaScript::focus_canvas() {
+ godot_js_display_canvas_focus();
+}
+
+bool DisplayServerJavaScript::is_canvas_focused() {
+ return godot_js_display_canvas_is_focused() != 0;
+}
+
+bool DisplayServerJavaScript::check_size_force_redraw() {
+ int canvas_width;
+ int canvas_height;
+ emscripten_get_canvas_element_size(DisplayServerJavaScript::canvas_id, &canvas_width, &canvas_height);
+ if (last_width != canvas_width || last_height != canvas_height) {
+ last_width = canvas_width;
+ last_height = canvas_height;
+ // Update the framebuffer size for redraw.
+ emscripten_set_canvas_element_size(DisplayServerJavaScript::canvas_id, canvas_width, canvas_height);
+ return true;
+ }
+ return false;
+}
+
+Point2 DisplayServerJavaScript::compute_position_in_canvas(int p_x, int p_y) {
+ DisplayServerJavaScript *display = get_singleton();
+ int canvas_x;
+ int canvas_y;
+ godot_js_display_canvas_bounding_rect_position_get(&canvas_x, &canvas_y);
+ int canvas_width;
+ int canvas_height;
+ emscripten_get_canvas_element_size(display->canvas_id, &canvas_width, &canvas_height);
+
+ double element_width;
+ double element_height;
+ emscripten_get_element_css_size(display->canvas_id, &element_width, &element_height);
+
+ return Point2((int)(canvas_width / element_width * (p_x - canvas_x)),
+ (int)(canvas_height / element_height * (p_y - canvas_y)));
+}
+
+EM_BOOL DisplayServerJavaScript::fullscreen_change_callback(int p_event_type, const EmscriptenFullscreenChangeEvent *p_event, void *p_user_data) {
+ DisplayServerJavaScript *display = get_singleton();
+ // Empty ID is canvas.
+ String target_id = String::utf8(p_event->id);
+ if (target_id.is_empty() || target_id == String::utf8(display->canvas_id)) {
+ // This event property is the only reliable data on
+ // browser fullscreen state.
+ if (p_event->isFullscreen) {
+ display->window_mode = WINDOW_MODE_FULLSCREEN;
+ } else {
+ display->window_mode = WINDOW_MODE_WINDOWED;
+ }
+ }
+ return false;
+}
+
+// Drag and drop callback.
+void DisplayServerJavaScript::drop_files_js_callback(char **p_filev, int p_filec) {
+ DisplayServerJavaScript *ds = get_singleton();
+ if (!ds) {
+ ERR_FAIL_MSG("Unable to drop files because the DisplayServer is not active");
+ }
+ if (ds->drop_files_callback.is_null()) {
+ return;
+ }
+ Vector<String> files;
+ for (int i = 0; i < p_filec; i++) {
+ files.push_back(String::utf8(p_filev[i]));
+ }
+ Variant v = files;
+ Variant *vp = &v;
+ Variant ret;
+ Callable::CallError ce;
+ ds->drop_files_callback.call((const Variant **)&vp, 1, ret, ce);
+}
+
+// JavaScript quit request callback.
+void DisplayServerJavaScript::request_quit_callback() {
+ DisplayServerJavaScript *ds = get_singleton();
+ if (ds && !ds->window_event_callback.is_null()) {
+ Variant event = int(DisplayServer::WINDOW_EVENT_CLOSE_REQUEST);
+ Variant *eventp = &event;
+ Variant ret;
+ Callable::CallError ce;
+ ds->window_event_callback.call((const Variant **)&eventp, 1, ret, ce);
+ }
+}
+
+// Keys
+
+template <typename T>
+void DisplayServerJavaScript::dom2godot_mod(T *emscripten_event_ptr, Ref<InputEventWithModifiers> godot_event) {
+ godot_event->set_shift(emscripten_event_ptr->shiftKey);
+ godot_event->set_alt(emscripten_event_ptr->altKey);
+ godot_event->set_control(emscripten_event_ptr->ctrlKey);
+ godot_event->set_metakey(emscripten_event_ptr->metaKey);
+}
+
+Ref<InputEventKey> DisplayServerJavaScript::setup_key_event(const EmscriptenKeyboardEvent *emscripten_event) {
+ Ref<InputEventKey> ev;
+ ev.instance();
+ ev->set_echo(emscripten_event->repeat);
+ dom2godot_mod(emscripten_event, ev);
+ ev->set_keycode(dom_code2godot_scancode(emscripten_event->code, emscripten_event->key, false));
+ ev->set_physical_keycode(dom_code2godot_scancode(emscripten_event->code, emscripten_event->key, true));
+
+ String unicode = String::utf8(emscripten_event->key);
+ // Check if empty or multi-character (e.g. `CapsLock`).
+ if (unicode.length() != 1) {
+ // Might be empty as well, but better than nonsense.
+ unicode = String::utf8(emscripten_event->charValue);
+ }
+ if (unicode.length() == 1) {
+ ev->set_unicode(unicode[0]);
+ }
+
+ return ev;
+}
+
+EM_BOOL DisplayServerJavaScript::keydown_callback(int p_event_type, const EmscriptenKeyboardEvent *p_event, void *p_user_data) {
+ DisplayServerJavaScript *display = get_singleton();
+ Ref<InputEventKey> ev = setup_key_event(p_event);
+ ev->set_pressed(true);
+ if (ev->get_unicode() == 0 && keycode_has_unicode(ev->get_keycode())) {
+ // Defer to keypress event for legacy unicode retrieval.
+ display->deferred_key_event = ev;
+ // Do not suppress keypress event.
+ return false;
+ }
+ Input::get_singleton()->parse_input_event(ev);
+ return true;
+}
+
+EM_BOOL DisplayServerJavaScript::keypress_callback(int p_event_type, const EmscriptenKeyboardEvent *p_event, void *p_user_data) {
+ DisplayServerJavaScript *display = get_singleton();
+ display->deferred_key_event->set_unicode(p_event->charCode);
+ Input::get_singleton()->parse_input_event(display->deferred_key_event);
+ return true;
+}
+
+EM_BOOL DisplayServerJavaScript::keyup_callback(int p_event_type, const EmscriptenKeyboardEvent *p_event, void *p_user_data) {
+ Ref<InputEventKey> ev = setup_key_event(p_event);
+ ev->set_pressed(false);
+ Input::get_singleton()->parse_input_event(ev);
+ return ev->get_keycode() != KEY_UNKNOWN && ev->get_keycode() != 0;
+}
+
+// Mouse
+
+EM_BOOL DisplayServerJavaScript::mouse_button_callback(int p_event_type, const EmscriptenMouseEvent *p_event, void *p_user_data) {
+ DisplayServerJavaScript *display = get_singleton();
+
+ Ref<InputEventMouseButton> ev;
+ ev.instance();
+ ev->set_pressed(p_event_type == EMSCRIPTEN_EVENT_MOUSEDOWN);
+ ev->set_position(compute_position_in_canvas(p_event->clientX, p_event->clientY));
+ ev->set_global_position(ev->get_position());
+ dom2godot_mod(p_event, ev);
+
+ switch (p_event->button) {
+ case DOM_BUTTON_LEFT:
+ ev->set_button_index(BUTTON_LEFT);
+ break;
+ case DOM_BUTTON_MIDDLE:
+ ev->set_button_index(BUTTON_MIDDLE);
+ break;
+ case DOM_BUTTON_RIGHT:
+ ev->set_button_index(BUTTON_RIGHT);
+ break;
+ case DOM_BUTTON_XBUTTON1:
+ ev->set_button_index(BUTTON_XBUTTON1);
+ break;
+ case DOM_BUTTON_XBUTTON2:
+ ev->set_button_index(BUTTON_XBUTTON2);
+ break;
+ default:
+ return false;
+ }
+
+ if (ev->is_pressed()) {
+ double diff = emscripten_get_now() - display->last_click_ms;
+
+ if (ev->get_button_index() == display->last_click_button_index) {
+ if (diff < 400 && Point2(display->last_click_pos).distance_to(ev->get_position()) < 5) {
+ display->last_click_ms = 0;
+ display->last_click_pos = Point2(-100, -100);
+ display->last_click_button_index = -1;
+ ev->set_doubleclick(true);
+ }
+
+ } else {
+ display->last_click_button_index = ev->get_button_index();
+ }
+
+ if (!ev->is_doubleclick()) {
+ display->last_click_ms += diff;
+ display->last_click_pos = ev->get_position();
+ }
+ }
+
+ Input *input = Input::get_singleton();
+ int mask = input->get_mouse_button_mask();
+ int button_flag = 1 << (ev->get_button_index() - 1);
+ if (ev->is_pressed()) {
+ // Since the event is consumed, focus manually. The containing iframe,
+ // if exists, may not have focus yet, so focus even if already focused.
+ focus_canvas();
+ mask |= button_flag;
+ } else if (mask & button_flag) {
+ mask &= ~button_flag;
+ } else {
+ // Received release event, but press was outside the canvas, so ignore.
+ return false;
+ }
+ ev->set_button_mask(mask);
+
+ input->parse_input_event(ev);
+ // Prevent multi-click text selection and wheel-click scrolling anchor.
+ // Context menu is prevented through contextmenu event.
+ return true;
+}
+
+EM_BOOL DisplayServerJavaScript::mousemove_callback(int p_event_type, const EmscriptenMouseEvent *p_event, void *p_user_data) {
+ DisplayServerJavaScript *ds = get_singleton();
+ Input *input = Input::get_singleton();
+ int input_mask = input->get_mouse_button_mask();
+ Point2 pos = compute_position_in_canvas(p_event->clientX, p_event->clientY);
+ // For motion outside the canvas, only read mouse movement if dragging
+ // started inside the canvas; imitating desktop app behaviour.
+ if (!ds->cursor_inside_canvas && !input_mask)
+ return false;
+
+ Ref<InputEventMouseMotion> ev;
+ ev.instance();
+ dom2godot_mod(p_event, ev);
+ ev->set_button_mask(input_mask);
+
+ ev->set_position(pos);
+ ev->set_global_position(ev->get_position());
+
+ ev->set_relative(Vector2(p_event->movementX, p_event->movementY));
+ input->set_mouse_position(ev->get_position());
+ ev->set_speed(input->get_last_mouse_speed());
+
+ input->parse_input_event(ev);
+ // Don't suppress mouseover/-leave events.
+ return false;
+}
+
+// Cursor
+const char *DisplayServerJavaScript::godot2dom_cursor(DisplayServer::CursorShape p_shape) {
+ switch (p_shape) {
+ case DisplayServer::CURSOR_ARROW:
+ return "auto";
+ case DisplayServer::CURSOR_IBEAM:
+ return "text";
+ case DisplayServer::CURSOR_POINTING_HAND:
+ return "pointer";
+ case DisplayServer::CURSOR_CROSS:
+ return "crosshair";
+ case DisplayServer::CURSOR_WAIT:
+ return "progress";
+ case DisplayServer::CURSOR_BUSY:
+ return "wait";
+ case DisplayServer::CURSOR_DRAG:
+ return "grab";
+ case DisplayServer::CURSOR_CAN_DROP:
+ return "grabbing";
+ case DisplayServer::CURSOR_FORBIDDEN:
+ return "no-drop";
+ case DisplayServer::CURSOR_VSIZE:
+ return "ns-resize";
+ case DisplayServer::CURSOR_HSIZE:
+ return "ew-resize";
+ case DisplayServer::CURSOR_BDIAGSIZE:
+ return "nesw-resize";
+ case DisplayServer::CURSOR_FDIAGSIZE:
+ return "nwse-resize";
+ case DisplayServer::CURSOR_MOVE:
+ return "move";
+ case DisplayServer::CURSOR_VSPLIT:
+ return "row-resize";
+ case DisplayServer::CURSOR_HSPLIT:
+ return "col-resize";
+ case DisplayServer::CURSOR_HELP:
+ return "help";
+ default:
+ return "auto";
+ }
+}
+
+void DisplayServerJavaScript::cursor_set_shape(CursorShape p_shape) {
+ ERR_FAIL_INDEX(p_shape, CURSOR_MAX);
+ if (cursor_shape == p_shape) {
+ return;
+ }
+ cursor_shape = p_shape;
+ godot_js_display_cursor_set_shape(godot2dom_cursor(cursor_shape));
+}
+
+DisplayServer::CursorShape DisplayServerJavaScript::cursor_get_shape() const {
+ return cursor_shape;
+}
+
+void DisplayServerJavaScript::cursor_set_custom_image(const RES &p_cursor, CursorShape p_shape, const Vector2 &p_hotspot) {
+ if (p_cursor.is_valid()) {
+ Ref<Texture2D> texture = p_cursor;
+ Ref<AtlasTexture> atlas_texture = p_cursor;
+ Ref<Image> image;
+ Size2 texture_size;
+ Rect2 atlas_rect;
+
+ if (texture.is_valid()) {
+ image = texture->get_data();
+ }
+
+ if (!image.is_valid() && atlas_texture.is_valid()) {
+ texture = atlas_texture->get_atlas();
+
+ atlas_rect.size.width = texture->get_width();
+ atlas_rect.size.height = texture->get_height();
+ atlas_rect.position.x = atlas_texture->get_region().position.x;
+ atlas_rect.position.y = atlas_texture->get_region().position.y;
+
+ texture_size.width = atlas_texture->get_region().size.x;
+ texture_size.height = atlas_texture->get_region().size.y;
+ } else if (image.is_valid()) {
+ texture_size.width = texture->get_width();
+ texture_size.height = texture->get_height();
+ }
+
+ ERR_FAIL_COND(!texture.is_valid());
+ ERR_FAIL_COND(p_hotspot.x < 0 || p_hotspot.y < 0);
+ ERR_FAIL_COND(texture_size.width > 256 || texture_size.height > 256);
+ ERR_FAIL_COND(p_hotspot.x > texture_size.width || p_hotspot.y > texture_size.height);
+
+ image = texture->get_data();
+
+ ERR_FAIL_COND(!image.is_valid());
+
+ image = image->duplicate();
+
+ if (atlas_texture.is_valid())
+ image->crop_from_point(
+ atlas_rect.position.x,
+ atlas_rect.position.y,
+ texture_size.width,
+ texture_size.height);
+
+ if (image->get_format() != Image::FORMAT_RGBA8) {
+ image->convert(Image::FORMAT_RGBA8);
+ }
+
+ png_image png_meta;
+ memset(&png_meta, 0, sizeof png_meta);
+ png_meta.version = PNG_IMAGE_VERSION;
+ png_meta.width = texture_size.width;
+ png_meta.height = texture_size.height;
+ png_meta.format = PNG_FORMAT_RGBA;
+
+ PackedByteArray png;
+ size_t len;
+ PackedByteArray data = image->get_data();
+ ERR_FAIL_COND(!png_image_write_get_memory_size(png_meta, len, 0, data.ptr(), 0, nullptr));
+
+ png.resize(len);
+ ERR_FAIL_COND(!png_image_write_to_memory(&png_meta, png.ptrw(), &len, 0, data.ptr(), 0, nullptr));
+
+ godot_js_display_cursor_set_custom_shape(godot2dom_cursor(p_shape), png.ptr(), len, p_hotspot.x, p_hotspot.y);
+
+ } else {
+ godot_js_display_cursor_set_custom_shape(godot2dom_cursor(p_shape), NULL, 0, 0, 0);
+ }
+
+ cursor_set_shape(cursor_shape);
+}
+
+// Mouse mode
+void DisplayServerJavaScript::mouse_set_mode(MouseMode p_mode) {
+ ERR_FAIL_COND_MSG(p_mode == MOUSE_MODE_CONFINED, "MOUSE_MODE_CONFINED is not supported for the HTML5 platform.");
+ if (p_mode == mouse_get_mode())
+ return;
+
+ if (p_mode == MOUSE_MODE_VISIBLE) {
+ godot_js_display_cursor_set_visible(1);
+ emscripten_exit_pointerlock();
+
+ } else if (p_mode == MOUSE_MODE_HIDDEN) {
+ godot_js_display_cursor_set_visible(0);
+ emscripten_exit_pointerlock();
+
+ } else if (p_mode == MOUSE_MODE_CAPTURED) {
+ godot_js_display_cursor_set_visible(1);
+ EMSCRIPTEN_RESULT result = emscripten_request_pointerlock(canvas_id, false);
+ ERR_FAIL_COND_MSG(result == EMSCRIPTEN_RESULT_FAILED_NOT_DEFERRED, "MOUSE_MODE_CAPTURED can only be entered from within an appropriate input callback.");
+ ERR_FAIL_COND_MSG(result != EMSCRIPTEN_RESULT_SUCCESS, "MOUSE_MODE_CAPTURED can only be entered from within an appropriate input callback.");
+ }
+}
+
+DisplayServer::MouseMode DisplayServerJavaScript::mouse_get_mode() const {
+ if (godot_js_display_cursor_is_hidden()) {
+ return MOUSE_MODE_HIDDEN;
+ }
+
+ EmscriptenPointerlockChangeEvent ev;
+ emscripten_get_pointerlock_status(&ev);
+ return (ev.isActive && String::utf8(ev.id) == String::utf8(canvas_id)) ? MOUSE_MODE_CAPTURED : MOUSE_MODE_VISIBLE;
+}
+
+// Wheel
+EM_BOOL DisplayServerJavaScript::wheel_callback(int p_event_type, const EmscriptenWheelEvent *p_event, void *p_user_data) {
+ ERR_FAIL_COND_V(p_event_type != EMSCRIPTEN_EVENT_WHEEL, false);
+ DisplayServerJavaScript *ds = get_singleton();
+ if (!is_canvas_focused()) {
+ if (ds->cursor_inside_canvas) {
+ focus_canvas();
+ } else {
+ return false;
+ }
+ }
+
+ Input *input = Input::get_singleton();
+ Ref<InputEventMouseButton> ev;
+ ev.instance();
+ ev->set_position(input->get_mouse_position());
+ ev->set_global_position(ev->get_position());
+
+ ev->set_shift(input->is_key_pressed(KEY_SHIFT));
+ ev->set_alt(input->is_key_pressed(KEY_ALT));
+ ev->set_control(input->is_key_pressed(KEY_CONTROL));
+ ev->set_metakey(input->is_key_pressed(KEY_META));
+
+ if (p_event->deltaY < 0)
+ ev->set_button_index(BUTTON_WHEEL_UP);
+ else if (p_event->deltaY > 0)
+ ev->set_button_index(BUTTON_WHEEL_DOWN);
+ else if (p_event->deltaX > 0)
+ ev->set_button_index(BUTTON_WHEEL_LEFT);
+ else if (p_event->deltaX < 0)
+ ev->set_button_index(BUTTON_WHEEL_RIGHT);
+ else
+ return false;
+
+ // Different browsers give wildly different delta values, and we can't
+ // interpret deltaMode, so use default value for wheel events' factor.
+
+ int button_flag = 1 << (ev->get_button_index() - 1);
+
+ ev->set_pressed(true);
+ ev->set_button_mask(input->get_mouse_button_mask() | button_flag);
+ input->parse_input_event(ev);
+
+ ev->set_pressed(false);
+ ev->set_button_mask(input->get_mouse_button_mask() & ~button_flag);
+ input->parse_input_event(ev);
+
+ return true;
+}
+
+// Touch
+EM_BOOL DisplayServerJavaScript::touch_press_callback(int p_event_type, const EmscriptenTouchEvent *p_event, void *p_user_data) {
+ DisplayServerJavaScript *display = get_singleton();
+ Ref<InputEventScreenTouch> ev;
+ ev.instance();
+ int lowest_id_index = -1;
+ for (int i = 0; i < p_event->numTouches; ++i) {
+ const EmscriptenTouchPoint &touch = p_event->touches[i];
+ if (lowest_id_index == -1 || touch.identifier < p_event->touches[lowest_id_index].identifier)
+ lowest_id_index = i;
+ if (!touch.isChanged)
+ continue;
+ ev->set_index(touch.identifier);
+ ev->set_position(compute_position_in_canvas(touch.clientX, touch.clientY));
+ display->touches[i] = ev->get_position();
+ ev->set_pressed(p_event_type == EMSCRIPTEN_EVENT_TOUCHSTART);
+
+ Input::get_singleton()->parse_input_event(ev);
+ }
+ // Resume audio context after input in case autoplay was denied.
+ return true;
+}
+
+EM_BOOL DisplayServerJavaScript::touchmove_callback(int p_event_type, const EmscriptenTouchEvent *p_event, void *p_user_data) {
+ DisplayServerJavaScript *display = get_singleton();
+ Ref<InputEventScreenDrag> ev;
+ ev.instance();
+ int lowest_id_index = -1;
+ for (int i = 0; i < p_event->numTouches; ++i) {
+ const EmscriptenTouchPoint &touch = p_event->touches[i];
+ if (lowest_id_index == -1 || touch.identifier < p_event->touches[lowest_id_index].identifier)
+ lowest_id_index = i;
+ if (!touch.isChanged)
+ continue;
+ ev->set_index(touch.identifier);
+ ev->set_position(compute_position_in_canvas(touch.clientX, touch.clientY));
+ Point2 &prev = display->touches[i];
+ ev->set_relative(ev->get_position() - prev);
+ prev = ev->get_position();
+
+ Input::get_singleton()->parse_input_event(ev);
+ }
+ return true;
+}
+
+bool DisplayServerJavaScript::screen_is_touchscreen(int p_screen) const {
+ return godot_js_display_touchscreen_is_available();
+}
+
+// Gamepad
+
+EM_BOOL DisplayServerJavaScript::gamepad_change_callback(int p_event_type, const EmscriptenGamepadEvent *p_event, void *p_user_data) {
+ Input *input = Input::get_singleton();
+ if (p_event_type == EMSCRIPTEN_EVENT_GAMEPADCONNECTED) {
+ String guid = "";
+ if (String::utf8(p_event->mapping) == "standard")
+ guid = "Default HTML5 Gamepad";
+ input->joy_connection_changed(p_event->index, true, String::utf8(p_event->id), guid);
+ } else {
+ input->joy_connection_changed(p_event->index, false, "");
+ }
+ return true;
+}
+
+void DisplayServerJavaScript::process_joypads() {
+ int joypad_count = emscripten_get_num_gamepads();
+ Input *input = Input::get_singleton();
+ for (int joypad = 0; joypad < joypad_count; joypad++) {
+ EmscriptenGamepadEvent state;
+ EMSCRIPTEN_RESULT query_result = emscripten_get_gamepad_status(joypad, &state);
+ // Chromium reserves gamepads slots, so NO_DATA is an expected result.
+ ERR_CONTINUE(query_result != EMSCRIPTEN_RESULT_SUCCESS &&
+ query_result != EMSCRIPTEN_RESULT_NO_DATA);
+ if (query_result == EMSCRIPTEN_RESULT_SUCCESS && state.connected) {
+ int button_count = MIN(state.numButtons, 18);
+ int axis_count = MIN(state.numAxes, 8);
+ for (int button = 0; button < button_count; button++) {
+ float value = state.analogButton[button];
+ input->joy_button(joypad, button, value);
+ }
+ for (int axis = 0; axis < axis_count; axis++) {
+ Input::JoyAxis joy_axis;
+ joy_axis.min = -1;
+ joy_axis.value = state.axis[axis];
+ input->joy_axis(joypad, axis, joy_axis);
+ }
+ }
+ }
+}
+
+#if 0
+bool DisplayServerJavaScript::is_joy_known(int p_device) {
+ return Input::get_singleton()->is_joy_mapped(p_device);
+}
+
+
+String DisplayServerJavaScript::get_joy_guid(int p_device) const {
+ return Input::get_singleton()->get_joy_guid_remapped(p_device);
+}
+#endif
+
+Vector<String> DisplayServerJavaScript::get_rendering_drivers_func() {
+ Vector<String> drivers;
+ drivers.push_back("dummy");
+ return drivers;
+}
+
+// Clipboard
+void DisplayServerJavaScript::update_clipboard_callback(const char *p_text) {
+ get_singleton()->clipboard = p_text;
+}
+
+void DisplayServerJavaScript::clipboard_set(const String &p_text) {
+ clipboard = p_text;
+ int err = godot_js_display_clipboard_set(p_text.utf8().get_data());
+ ERR_FAIL_COND_MSG(err, "Clipboard API is not supported.");
+}
+
+String DisplayServerJavaScript::clipboard_get() const {
+ godot_js_display_clipboard_get(update_clipboard_callback);
+ return clipboard;
+}
+
+void DisplayServerJavaScript::send_window_event_callback(int p_notification) {
+ DisplayServerJavaScript *ds = get_singleton();
+ if (!ds) {
+ return;
+ }
+ if (p_notification == DisplayServer::WINDOW_EVENT_MOUSE_ENTER || p_notification == DisplayServer::WINDOW_EVENT_MOUSE_EXIT) {
+ ds->cursor_inside_canvas = p_notification == DisplayServer::WINDOW_EVENT_MOUSE_ENTER;
+ }
+ if (!ds->window_event_callback.is_null()) {
+ Variant event = int(p_notification);
+ Variant *eventp = &event;
+ Variant ret;
+ Callable::CallError ce;
+ ds->window_event_callback.call((const Variant **)&eventp, 1, ret, ce);
+ }
+}
+
+void DisplayServerJavaScript::alert(const String &p_alert, const String &p_title) {
+ godot_js_display_alert(p_alert.utf8().get_data());
+}
+
+void DisplayServerJavaScript::set_icon(const Ref<Image> &p_icon) {
+ ERR_FAIL_COND(p_icon.is_null());
+ Ref<Image> icon = p_icon;
+ if (icon->is_compressed()) {
+ icon = icon->duplicate();
+ ERR_FAIL_COND(icon->decompress() != OK);
+ }
+ if (icon->get_format() != Image::FORMAT_RGBA8) {
+ if (icon == p_icon)
+ icon = icon->duplicate();
+ icon->convert(Image::FORMAT_RGBA8);
+ }
+
+ png_image png_meta;
+ memset(&png_meta, 0, sizeof png_meta);
+ png_meta.version = PNG_IMAGE_VERSION;
+ png_meta.width = icon->get_width();
+ png_meta.height = icon->get_height();
+ png_meta.format = PNG_FORMAT_RGBA;
+
+ PackedByteArray png;
+ size_t len;
+ PackedByteArray data = icon->get_data();
+ ERR_FAIL_COND(!png_image_write_get_memory_size(png_meta, len, 0, data.ptr(), 0, nullptr));
+
+ png.resize(len);
+ ERR_FAIL_COND(!png_image_write_to_memory(&png_meta, png.ptrw(), &len, 0, data.ptr(), 0, nullptr));
+
+ godot_js_display_window_icon_set(png.ptr(), len);
+}
+
+void DisplayServerJavaScript::_dispatch_input_event(const Ref<InputEvent> &p_event) {
+ OS_JavaScript *os = OS_JavaScript::get_singleton();
+
+ // Resume audio context after input in case autoplay was denied.
+ os->resume_audio();
+
+ Callable cb = get_singleton()->input_event_callback;
+ if (!cb.is_null()) {
+ Variant ev = p_event;
+ Variant *evp = &ev;
+ Variant ret;
+ Callable::CallError ce;
+ cb.call((const Variant **)&evp, 1, ret, ce);
+ }
+}
+
+DisplayServer *DisplayServerJavaScript::create_func(const String &p_rendering_driver, DisplayServer::WindowMode p_mode, uint32_t p_flags, const Vector2i &p_resolution, Error &r_error) {
+ return memnew(DisplayServerJavaScript(p_rendering_driver, p_mode, p_flags, p_resolution, r_error));
+}
+
+DisplayServerJavaScript::DisplayServerJavaScript(const String &p_rendering_driver, WindowMode p_mode, uint32_t p_flags, const Vector2i &p_resolution, Error &r_error) {
+ r_error = OK; // Always succeeds for now.
+
+ // Ensure the canvas ID.
+ godot_js_config_canvas_id_get(canvas_id, 256);
+
+ // Check if it's windows.
+ swap_cancel_ok = godot_js_display_is_swap_ok_cancel() == 1;
+
+ // Expose method for requesting quit.
+ godot_js_os_request_quit_cb(request_quit_callback);
+
+ RasterizerDummy::make_current(); // TODO GLES2 in Godot 4.0... or webgpu?
+#if 0
+ EmscriptenWebGLContextAttributes attributes;
+ emscripten_webgl_init_context_attributes(&attributes);
+ attributes.alpha = GLOBAL_GET("display/window/per_pixel_transparency/allowed");
+ attributes.antialias = false;
+ ERR_FAIL_INDEX_V(p_video_driver, VIDEO_DRIVER_MAX, ERR_INVALID_PARAMETER);
+
+ if (p_desired.layered) {
+ set_window_per_pixel_transparency_enabled(true);
+ }
+
+ bool gl_initialization_error = false;
+
+ if (RasterizerGLES2::is_viable() == OK) {
+ attributes.majorVersion = 1;
+ RasterizerGLES2::register_config();
+ RasterizerGLES2::make_current();
+ } else {
+ gl_initialization_error = true;
+ }
+
+ EMSCRIPTEN_WEBGL_CONTEXT_HANDLE ctx = emscripten_webgl_create_context(canvas_id, &attributes);
+ if (emscripten_webgl_make_context_current(ctx) != EMSCRIPTEN_RESULT_SUCCESS) {
+ gl_initialization_error = true;
+ }
+
+ if (gl_initialization_error) {
+ OS::get_singleton()->alert("Your browser does not seem to support WebGL. Please update your browser version.",
+ "Unable to initialize video driver");
+ return ERR_UNAVAILABLE;
+ }
+
+ video_driver_index = p_video_driver;
+#endif
+
+ window_set_mode(p_mode);
+ if (godot_js_config_is_resize_on_start()) {
+ window_set_size(p_resolution);
+ }
+
+ EMSCRIPTEN_RESULT result;
+#define EM_CHECK(ev) \
+ if (result != EMSCRIPTEN_RESULT_SUCCESS) \
+ ERR_PRINT("Error while setting " #ev " callback: Code " + itos(result));
+#define SET_EM_CALLBACK(target, ev, cb) \
+ result = emscripten_set_##ev##_callback(target, nullptr, true, &cb); \
+ EM_CHECK(ev)
+#define SET_EM_WINDOW_CALLBACK(ev, cb) \
+ result = emscripten_set_##ev##_callback(EMSCRIPTEN_EVENT_TARGET_WINDOW, NULL, false, &cb); \
+ EM_CHECK(ev)
+#define SET_EM_CALLBACK_NOTARGET(ev, cb) \
+ result = emscripten_set_##ev##_callback(nullptr, true, &cb); \
+ EM_CHECK(ev)
+ // These callbacks from Emscripten's html5.h suffice to access most
+ // JavaScript APIs.
+ SET_EM_CALLBACK(canvas_id, mousedown, mouse_button_callback)
+ SET_EM_WINDOW_CALLBACK(mousemove, mousemove_callback)
+ SET_EM_WINDOW_CALLBACK(mouseup, mouse_button_callback)
+ SET_EM_CALLBACK(canvas_id, wheel, wheel_callback)
+ SET_EM_CALLBACK(canvas_id, touchstart, touch_press_callback)
+ SET_EM_CALLBACK(canvas_id, touchmove, touchmove_callback)
+ SET_EM_CALLBACK(canvas_id, touchend, touch_press_callback)
+ SET_EM_CALLBACK(canvas_id, touchcancel, touch_press_callback)
+ SET_EM_CALLBACK(canvas_id, keydown, keydown_callback)
+ SET_EM_CALLBACK(canvas_id, keypress, keypress_callback)
+ SET_EM_CALLBACK(canvas_id, keyup, keyup_callback)
+ SET_EM_CALLBACK(EMSCRIPTEN_EVENT_TARGET_DOCUMENT, fullscreenchange, fullscreen_change_callback)
+ SET_EM_CALLBACK_NOTARGET(gamepadconnected, gamepad_change_callback)
+ SET_EM_CALLBACK_NOTARGET(gamepaddisconnected, gamepad_change_callback)
+#undef SET_EM_CALLBACK_NOTARGET
+#undef SET_EM_CALLBACK
+#undef EM_CHECK
+
+ // For APIs that are not (sufficiently) exposed, a
+ // library is used below (implemented in library_godot_display.js).
+ godot_js_display_notification_cb(&send_window_event_callback,
+ WINDOW_EVENT_MOUSE_ENTER,
+ WINDOW_EVENT_MOUSE_EXIT,
+ WINDOW_EVENT_FOCUS_IN,
+ WINDOW_EVENT_FOCUS_OUT);
+ godot_js_display_paste_cb(update_clipboard_callback);
+ godot_js_display_drop_files_cb(drop_files_js_callback);
+
+ Input::get_singleton()->set_event_dispatch_function(_dispatch_input_event);
+}
+
+DisplayServerJavaScript::~DisplayServerJavaScript() {
+ //emscripten_webgl_commit_frame();
+ //emscripten_webgl_destroy_context(webgl_ctx);
+}
+
+bool DisplayServerJavaScript::has_feature(Feature p_feature) const {
+ switch (p_feature) {
+ //case FEATURE_CONSOLE_WINDOW:
+ //case FEATURE_GLOBAL_MENU:
+ //case FEATURE_HIDPI:
+ //case FEATURE_IME:
+ case FEATURE_ICON:
+ case FEATURE_CLIPBOARD:
+ case FEATURE_CURSOR_SHAPE:
+ case FEATURE_CUSTOM_CURSOR_SHAPE:
+ case FEATURE_MOUSE:
+ case FEATURE_TOUCHSCREEN:
+ return true;
+ //case FEATURE_MOUSE_WARP:
+ //case FEATURE_NATIVE_DIALOG:
+ //case FEATURE_NATIVE_ICON:
+ //case FEATURE_NATIVE_VIDEO:
+ //case FEATURE_WINDOW_TRANSPARENCY:
+ //case FEATURE_KEEP_SCREEN_ON:
+ //case FEATURE_ORIENTATION:
+ //case FEATURE_VIRTUAL_KEYBOARD:
+ default:
+ return false;
+ }
+}
+
+void DisplayServerJavaScript::register_javascript_driver() {
+ register_create_function("javascript", create_func, get_rendering_drivers_func);
+}
+
+String DisplayServerJavaScript::get_name() const {
+ return "javascript";
+}
+
+int DisplayServerJavaScript::get_screen_count() const {
+ return 1;
+}
+
+Point2i DisplayServerJavaScript::screen_get_position(int p_screen) const {
+ return Point2i(); // TODO offsetX/Y?
+}
+
+Size2i DisplayServerJavaScript::screen_get_size(int p_screen) const {
+ EmscriptenFullscreenChangeEvent ev;
+ EMSCRIPTEN_RESULT result = emscripten_get_fullscreen_status(&ev);
+ ERR_FAIL_COND_V(result != EMSCRIPTEN_RESULT_SUCCESS, Size2i());
+ return Size2i(ev.screenWidth, ev.screenHeight);
+}
+
+Rect2i DisplayServerJavaScript::screen_get_usable_rect(int p_screen) const {
+ int canvas[2];
+ emscripten_get_canvas_element_size(canvas_id, canvas, canvas + 1);
+ return Rect2i(0, 0, canvas[0], canvas[1]);
+}
+
+int DisplayServerJavaScript::screen_get_dpi(int p_screen) const {
+ return 96; // TODO maybe check pixel ratio via window.devicePixelRatio * 96? Inexact.
+}
+
+Vector<DisplayServer::WindowID> DisplayServerJavaScript::get_window_list() const {
+ Vector<WindowID> ret;
+ ret.push_back(MAIN_WINDOW_ID);
+ return ret;
+}
+
+DisplayServerJavaScript::WindowID DisplayServerJavaScript::get_window_at_screen_position(const Point2i &p_position) const {
+ return MAIN_WINDOW_ID;
+}
+
+void DisplayServerJavaScript::window_attach_instance_id(ObjectID p_instance, WindowID p_window) {
+ window_attached_instance_id = p_instance;
+}
+
+ObjectID DisplayServerJavaScript::window_get_attached_instance_id(WindowID p_window) const {
+ return window_attached_instance_id;
+}
+
+void DisplayServerJavaScript::window_set_rect_changed_callback(const Callable &p_callable, WindowID p_window) {
+ // Not supported.
+}
+
+void DisplayServerJavaScript::window_set_window_event_callback(const Callable &p_callable, WindowID p_window) {
+ window_event_callback = p_callable;
+}
+
+void DisplayServerJavaScript::window_set_input_event_callback(const Callable &p_callable, WindowID p_window) {
+ input_event_callback = p_callable;
+}
+
+void DisplayServerJavaScript::window_set_input_text_callback(const Callable &p_callable, WindowID p_window) {
+ input_text_callback = p_callable; // TODO unused... do I need this?
+}
+
+void DisplayServerJavaScript::window_set_drop_files_callback(const Callable &p_callable, WindowID p_window) {
+ drop_files_callback = p_callable;
+}
+
+void DisplayServerJavaScript::window_set_title(const String &p_title, WindowID p_window) {
+ godot_js_display_window_title_set(p_title.utf8().get_data());
+}
+
+int DisplayServerJavaScript::window_get_current_screen(WindowID p_window) const {
+ return 1;
+}
+
+void DisplayServerJavaScript::window_set_current_screen(int p_screen, WindowID p_window) {
+ // Not implemented.
+}
+
+Point2i DisplayServerJavaScript::window_get_position(WindowID p_window) const {
+ return Point2i(); // TODO Does this need implementation?
+}
+
+void DisplayServerJavaScript::window_set_position(const Point2i &p_position, WindowID p_window) {
+ // Not supported.
+}
+
+void DisplayServerJavaScript::window_set_transient(WindowID p_window, WindowID p_parent) {
+ // Not supported.
+}
+
+void DisplayServerJavaScript::window_set_max_size(const Size2i p_size, WindowID p_window) {
+ // Not supported.
+}
+
+Size2i DisplayServerJavaScript::window_get_max_size(WindowID p_window) const {
+ return Size2i();
+}
+
+void DisplayServerJavaScript::window_set_min_size(const Size2i p_size, WindowID p_window) {
+ // Not supported.
+}
+
+Size2i DisplayServerJavaScript::window_get_min_size(WindowID p_window) const {
+ return Size2i();
+}
+
+void DisplayServerJavaScript::window_set_size(const Size2i p_size, WindowID p_window) {
+ last_width = p_size.x;
+ last_height = p_size.y;
+ double scale = godot_js_display_pixel_ratio_get();
+ emscripten_set_canvas_element_size(canvas_id, p_size.x, p_size.y);
+ emscripten_set_element_css_size(canvas_id, p_size.x / scale, p_size.y / scale);
+}
+
+Size2i DisplayServerJavaScript::window_get_size(WindowID p_window) const {
+ int canvas[2];
+ emscripten_get_canvas_element_size(canvas_id, canvas, canvas + 1);
+ return Size2(canvas[0], canvas[1]);
+}
+
+Size2i DisplayServerJavaScript::window_get_real_size(WindowID p_window) const {
+ return window_get_size(p_window);
+}
+
+void DisplayServerJavaScript::window_set_mode(WindowMode p_mode, WindowID p_window) {
+ if (window_mode == p_mode)
+ return;
+
+ switch (p_mode) {
+ case WINDOW_MODE_WINDOWED: {
+ if (window_mode == WINDOW_MODE_FULLSCREEN) {
+ emscripten_exit_fullscreen();
+ }
+ window_mode = WINDOW_MODE_WINDOWED;
+ window_set_size(Size2i(last_width, last_height));
+ } break;
+ case WINDOW_MODE_FULLSCREEN: {
+ EmscriptenFullscreenStrategy strategy;
+ strategy.scaleMode = EMSCRIPTEN_FULLSCREEN_SCALE_STRETCH;
+ strategy.canvasResolutionScaleMode = EMSCRIPTEN_FULLSCREEN_CANVAS_SCALE_STDDEF;
+ strategy.filteringMode = EMSCRIPTEN_FULLSCREEN_FILTERING_DEFAULT;
+ strategy.canvasResizedCallback = nullptr;
+ EMSCRIPTEN_RESULT result = emscripten_request_fullscreen_strategy(canvas_id, false, &strategy);
+ ERR_FAIL_COND_MSG(result == EMSCRIPTEN_RESULT_FAILED_NOT_DEFERRED, "Enabling fullscreen is only possible from an input callback for the HTML5 platform.");
+ ERR_FAIL_COND_MSG(result != EMSCRIPTEN_RESULT_SUCCESS, "Enabling fullscreen is only possible from an input callback for the HTML5 platform.");
+ } break;
+ case WINDOW_MODE_MAXIMIZED:
+ case WINDOW_MODE_MINIMIZED:
+ WARN_PRINT("WindowMode MAXIMIZED and MINIMIZED are not supported in HTML5 platform.");
+ break;
+ default:
+ break;
+ }
+}
+
+DisplayServerJavaScript::WindowMode DisplayServerJavaScript::window_get_mode(WindowID p_window) const {
+ return window_mode;
+}
+
+bool DisplayServerJavaScript::window_is_maximize_allowed(WindowID p_window) const {
+ return false;
+}
+
+void DisplayServerJavaScript::window_set_flag(WindowFlags p_flag, bool p_enabled, WindowID p_window) {
+ // Not supported.
+}
+
+bool DisplayServerJavaScript::window_get_flag(WindowFlags p_flag, WindowID p_window) const {
+ return false;
+}
+
+void DisplayServerJavaScript::window_request_attention(WindowID p_window) {
+ // Not supported.
+}
+
+void DisplayServerJavaScript::window_move_to_foreground(WindowID p_window) {
+ // Not supported.
+}
+
+bool DisplayServerJavaScript::window_can_draw(WindowID p_window) const {
+ return true;
+}
+
+bool DisplayServerJavaScript::can_any_window_draw() const {
+ return true;
+}
+
+void DisplayServerJavaScript::process_events() {
+ if (emscripten_sample_gamepad_data() == EMSCRIPTEN_RESULT_SUCCESS)
+ process_joypads();
+}
+
+int DisplayServerJavaScript::get_current_video_driver() const {
+ return 1;
+}
+
+bool DisplayServerJavaScript::get_swap_cancel_ok() {
+ return swap_cancel_ok;
+}
+
+void DisplayServerJavaScript::swap_buffers() {
+ //emscripten_webgl_commit_frame();
+}
diff --git a/platform/javascript/display_server_javascript.h b/platform/javascript/display_server_javascript.h
new file mode 100644
index 0000000000..916be1ae45
--- /dev/null
+++ b/platform/javascript/display_server_javascript.h
@@ -0,0 +1,205 @@
+/*************************************************************************/
+/* display_server_javascript.h */
+/*************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/*************************************************************************/
+/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/*************************************************************************/
+
+#ifndef DISPLAY_SERVER_JAVASCRIPT_H
+#define DISPLAY_SERVER_JAVASCRIPT_H
+
+#include "servers/display_server.h"
+
+#include <emscripten.h>
+#include <emscripten/html5.h>
+
+class DisplayServerJavaScript : public DisplayServer {
+private:
+ WindowMode window_mode = WINDOW_MODE_WINDOWED;
+ ObjectID window_attached_instance_id = {};
+
+ Callable window_event_callback;
+ Callable input_event_callback;
+ Callable input_text_callback;
+ Callable drop_files_callback;
+
+ String clipboard;
+ Ref<InputEventKey> deferred_key_event;
+ Point2 touches[32];
+
+ char canvas_id[256] = { 0 };
+ bool cursor_inside_canvas = true;
+ CursorShape cursor_shape = CURSOR_ARROW;
+ Point2i last_click_pos = Point2(-100, -100); // TODO check this again.
+ double last_click_ms = 0;
+ int last_click_button_index = -1;
+
+ int last_width = 0;
+ int last_height = 0;
+
+ bool swap_cancel_ok = false;
+
+ // utilities
+ static Point2 compute_position_in_canvas(int p_x, int p_y);
+ static void focus_canvas();
+ static bool is_canvas_focused();
+ template <typename T>
+ static void dom2godot_mod(T *emscripten_event_ptr, Ref<InputEventWithModifiers> godot_event);
+ static Ref<InputEventKey> setup_key_event(const EmscriptenKeyboardEvent *emscripten_event);
+ static const char *godot2dom_cursor(DisplayServer::CursorShape p_shape);
+
+ // events
+ static EM_BOOL fullscreen_change_callback(int p_event_type, const EmscriptenFullscreenChangeEvent *p_event, void *p_user_data);
+
+ static EM_BOOL keydown_callback(int p_event_type, const EmscriptenKeyboardEvent *p_event, void *p_user_data);
+ static EM_BOOL keypress_callback(int p_event_type, const EmscriptenKeyboardEvent *p_event, void *p_user_data);
+ static EM_BOOL keyup_callback(int p_event_type, const EmscriptenKeyboardEvent *p_event, void *p_user_data);
+
+ static EM_BOOL mousemove_callback(int p_event_type, const EmscriptenMouseEvent *p_event, void *p_user_data);
+ static EM_BOOL mouse_button_callback(int p_event_type, const EmscriptenMouseEvent *p_event, void *p_user_data);
+
+ static EM_BOOL wheel_callback(int p_event_type, const EmscriptenWheelEvent *p_event, void *p_user_data);
+
+ static EM_BOOL touch_press_callback(int p_event_type, const EmscriptenTouchEvent *p_event, void *p_user_data);
+ static EM_BOOL touchmove_callback(int p_event_type, const EmscriptenTouchEvent *p_event, void *p_user_data);
+
+ static EM_BOOL gamepad_change_callback(int p_event_type, const EmscriptenGamepadEvent *p_event, void *p_user_data);
+ void process_joypads();
+
+ static Vector<String> get_rendering_drivers_func();
+ static DisplayServer *create_func(const String &p_rendering_driver, DisplayServer::WindowMode p_mode, uint32_t p_flags, const Vector2i &p_resolution, Error &r_error);
+
+ static void _dispatch_input_event(const Ref<InputEvent> &p_event);
+
+ static void request_quit_callback();
+ static void update_clipboard_callback(const char *p_text);
+ static void send_window_event_callback(int p_notification);
+ static void drop_files_js_callback(char **p_filev, int p_filec);
+
+protected:
+ int get_current_video_driver() const;
+
+public:
+ // Override return type to make writing static callbacks less tedious.
+ static DisplayServerJavaScript *get_singleton();
+
+ // utilities
+ bool check_size_force_redraw();
+
+ // from DisplayServer
+ void alert(const String &p_alert, const String &p_title = "ALERT!") override;
+ bool has_feature(Feature p_feature) const override;
+ String get_name() const override;
+
+ // cursor
+ void cursor_set_shape(CursorShape p_shape) override;
+ CursorShape cursor_get_shape() const override;
+ void cursor_set_custom_image(const RES &p_cursor, CursorShape p_shape = CURSOR_ARROW, const Vector2 &p_hotspot = Vector2()) override;
+
+ // mouse
+ void mouse_set_mode(MouseMode p_mode) override;
+ MouseMode mouse_get_mode() const override;
+
+ // touch
+ bool screen_is_touchscreen(int p_screen = SCREEN_OF_MAIN_WINDOW) const override;
+
+ // clipboard
+ void clipboard_set(const String &p_text) override;
+ String clipboard_get() const override;
+
+ // screen
+ int get_screen_count() const override;
+ Point2i screen_get_position(int p_screen = SCREEN_OF_MAIN_WINDOW) const override;
+ Size2i screen_get_size(int p_screen = SCREEN_OF_MAIN_WINDOW) const override;
+ Rect2i screen_get_usable_rect(int p_screen = SCREEN_OF_MAIN_WINDOW) const override;
+ int screen_get_dpi(int p_screen = SCREEN_OF_MAIN_WINDOW) const override;
+
+ // windows
+ Vector<DisplayServer::WindowID> get_window_list() const override;
+ WindowID get_window_at_screen_position(const Point2i &p_position) const override;
+
+ void window_attach_instance_id(ObjectID p_instance, WindowID p_window = MAIN_WINDOW_ID) override;
+ ObjectID window_get_attached_instance_id(WindowID p_window = MAIN_WINDOW_ID) const override;
+
+ void window_set_rect_changed_callback(const Callable &p_callable, WindowID p_window = MAIN_WINDOW_ID) override;
+
+ void window_set_window_event_callback(const Callable &p_callable, WindowID p_window = MAIN_WINDOW_ID) override;
+ void window_set_input_event_callback(const Callable &p_callable, WindowID p_window = MAIN_WINDOW_ID) override;
+ void window_set_input_text_callback(const Callable &p_callable, WindowID p_window = MAIN_WINDOW_ID) override;
+
+ void window_set_drop_files_callback(const Callable &p_callable, WindowID p_window = MAIN_WINDOW_ID) override;
+
+ void window_set_title(const String &p_title, WindowID p_window = MAIN_WINDOW_ID) override;
+
+ int window_get_current_screen(WindowID p_window = MAIN_WINDOW_ID) const override;
+ void window_set_current_screen(int p_screen, WindowID p_window = MAIN_WINDOW_ID) override;
+
+ Point2i window_get_position(WindowID p_window = MAIN_WINDOW_ID) const override;
+ void window_set_position(const Point2i &p_position, WindowID p_window = MAIN_WINDOW_ID) override;
+
+ void window_set_transient(WindowID p_window, WindowID p_parent) override;
+
+ void window_set_max_size(const Size2i p_size, WindowID p_window = MAIN_WINDOW_ID) override;
+ Size2i window_get_max_size(WindowID p_window = MAIN_WINDOW_ID) const override;
+
+ void window_set_min_size(const Size2i p_size, WindowID p_window = MAIN_WINDOW_ID) override;
+ Size2i window_get_min_size(WindowID p_window = MAIN_WINDOW_ID) const override;
+
+ void window_set_size(const Size2i p_size, WindowID p_window = MAIN_WINDOW_ID) override;
+ Size2i window_get_size(WindowID p_window = MAIN_WINDOW_ID) const override;
+ Size2i window_get_real_size(WindowID p_window = MAIN_WINDOW_ID) const override;
+
+ void window_set_mode(WindowMode p_mode, WindowID p_window = MAIN_WINDOW_ID) override;
+ WindowMode window_get_mode(WindowID p_window = MAIN_WINDOW_ID) const override;
+
+ bool window_is_maximize_allowed(WindowID p_window = MAIN_WINDOW_ID) const override;
+
+ void window_set_flag(WindowFlags p_flag, bool p_enabled, WindowID p_window = MAIN_WINDOW_ID) override;
+ bool window_get_flag(WindowFlags p_flag, WindowID p_window = MAIN_WINDOW_ID) const override;
+
+ void window_request_attention(WindowID p_window = MAIN_WINDOW_ID) override;
+ void window_move_to_foreground(WindowID p_window = MAIN_WINDOW_ID) override;
+
+ bool window_can_draw(WindowID p_window = MAIN_WINDOW_ID) const override;
+
+ bool can_any_window_draw() const override;
+
+ // events
+ void process_events() override;
+
+ // icon
+ void set_icon(const Ref<Image> &p_icon) override;
+
+ // others
+ bool get_swap_cancel_ok() override;
+ void swap_buffers() override;
+
+ static void register_javascript_driver();
+ DisplayServerJavaScript(const String &p_rendering_driver, WindowMode p_mode, uint32_t p_flags, const Vector2i &p_resolution, Error &r_error);
+ ~DisplayServerJavaScript();
+};
+
+#endif // DISPLAY_SERVER_JAVASCRIPT_H
diff --git a/platform/javascript/dom_keys.inc b/platform/javascript/dom_keys.inc
index fd9df765d2..7902efafe0 100644
--- a/platform/javascript/dom_keys.inc
+++ b/platform/javascript/dom_keys.inc
@@ -5,8 +5,8 @@
/* GODOT ENGINE */
/* https://godotengine.org */
/*************************************************************************/
-/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */
-/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */
+/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
@@ -30,351 +30,203 @@
#include "core/os/keyboard.h"
-// https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent/keyCode#Constants_for_keyCode_value
-#define DOM_VK_CANCEL 0x03
-#define DOM_VK_HELP 0x06
-#define DOM_VK_BACK_SPACE 0x08
-#define DOM_VK_TAB 0x09
-#define DOM_VK_CLEAR 0x0C
-#define DOM_VK_RETURN 0x0D
-#define DOM_VK_ENTER 0x0E // "Reserved, but not used."
-#define DOM_VK_SHIFT 0x10
-#define DOM_VK_CONTROL 0x11
-#define DOM_VK_ALT 0x12
-#define DOM_VK_PAUSE 0x13
-#define DOM_VK_CAPS_LOCK 0x14
-#define DOM_VK_KANA 0x15
-#define DOM_VK_HANGUL 0x15
-#define DOM_VK_EISU 0x16
-#define DOM_VK_JUNJA 0x17
-#define DOM_VK_FINAL 0x18
-#define DOM_VK_HANJA 0x19
-#define DOM_VK_KANJI 0x19
-#define DOM_VK_ESCAPE 0x1B
-#define DOM_VK_CONVERT 0x1C
-#define DOM_VK_NONCONVERT 0x1D
-#define DOM_VK_ACCEPT 0x1E
-#define DOM_VK_MODECHANGE 0x1F
-#define DOM_VK_SPACE 0x20
-#define DOM_VK_PAGE_UP 0x21
-#define DOM_VK_PAGE_DOWN 0x22
-#define DOM_VK_END 0x23
-#define DOM_VK_HOME 0x24
-#define DOM_VK_LEFT 0x25
-#define DOM_VK_UP 0x26
-#define DOM_VK_RIGHT 0x27
-#define DOM_VK_DOWN 0x28
-#define DOM_VK_SELECT 0x29
-#define DOM_VK_PRINT 0x2A
-#define DOM_VK_EXECUTE 0x2B
-#define DOM_VK_PRINTSCREEN 0x2C
-#define DOM_VK_INSERT 0x2D
-#define DOM_VK_DELETE 0x2E
-#define DOM_VK_0 0x30
-#define DOM_VK_1 0x31
-#define DOM_VK_2 0x32
-#define DOM_VK_3 0x33
-#define DOM_VK_4 0x34
-#define DOM_VK_5 0x35
-#define DOM_VK_6 0x36
-#define DOM_VK_7 0x37
-#define DOM_VK_8 0x38
-#define DOM_VK_9 0x39
-#define DOM_VK_COLON 0x3A
-#define DOM_VK_SEMICOLON 0x3B
-#define DOM_VK_LESS_THAN 0x3C
-#define DOM_VK_EQUALS 0x3D
-#define DOM_VK_GREATER_THAN 0x3E
-#define DOM_VK_QUESTION_MARK 0x3F
-#define DOM_VK_AT 0x40
-#define DOM_VK_A 0x41
-#define DOM_VK_B 0x42
-#define DOM_VK_C 0x43
-#define DOM_VK_D 0x44
-#define DOM_VK_E 0x45
-#define DOM_VK_F 0x46
-#define DOM_VK_G 0x47
-#define DOM_VK_H 0x48
-#define DOM_VK_I 0x49
-#define DOM_VK_J 0x4A
-#define DOM_VK_K 0x4B
-#define DOM_VK_L 0x4C
-#define DOM_VK_M 0x4D
-#define DOM_VK_N 0x4E
-#define DOM_VK_O 0x4F
-#define DOM_VK_P 0x50
-#define DOM_VK_Q 0x51
-#define DOM_VK_R 0x52
-#define DOM_VK_S 0x53
-#define DOM_VK_T 0x54
-#define DOM_VK_U 0x55
-#define DOM_VK_V 0x56
-#define DOM_VK_W 0x57
-#define DOM_VK_X 0x58
-#define DOM_VK_Y 0x59
-#define DOM_VK_Z 0x5A
-#define DOM_VK_WIN 0x5B
-#define DOM_VK_CONTEXT_MENU 0x5D
-#define DOM_VK_SLEEP 0x5F
-#define DOM_VK_NUMPAD0 0x60
-#define DOM_VK_NUMPAD1 0x61
-#define DOM_VK_NUMPAD2 0x62
-#define DOM_VK_NUMPAD3 0x63
-#define DOM_VK_NUMPAD4 0x64
-#define DOM_VK_NUMPAD5 0x65
-#define DOM_VK_NUMPAD6 0x66
-#define DOM_VK_NUMPAD7 0x67
-#define DOM_VK_NUMPAD8 0x68
-#define DOM_VK_NUMPAD9 0x69
-#define DOM_VK_MULTIPLY 0x6A
-#define DOM_VK_ADD 0x6B
-#define DOM_VK_SEPARATOR 0x6C
-#define DOM_VK_SUBTRACT 0x6D
-#define DOM_VK_DECIMAL 0x6E
-#define DOM_VK_DIVIDE 0x6F
-#define DOM_VK_F1 0x70
-#define DOM_VK_F2 0x71
-#define DOM_VK_F3 0x72
-#define DOM_VK_F4 0x73
-#define DOM_VK_F5 0x74
-#define DOM_VK_F6 0x75
-#define DOM_VK_F7 0x76
-#define DOM_VK_F8 0x77
-#define DOM_VK_F9 0x78
-#define DOM_VK_F10 0x79
-#define DOM_VK_F11 0x7A
-#define DOM_VK_F12 0x7B
-#define DOM_VK_F13 0x7C
-#define DOM_VK_F14 0x7D
-#define DOM_VK_F15 0x7E
-#define DOM_VK_F16 0x7F
-#define DOM_VK_F17 0x80
-#define DOM_VK_F18 0x81
-#define DOM_VK_F19 0x82
-#define DOM_VK_F20 0x83
-#define DOM_VK_F21 0x84
-#define DOM_VK_F22 0x85
-#define DOM_VK_F23 0x86
-#define DOM_VK_F24 0x87
-#define DOM_VK_NUM_LOCK 0x90
-#define DOM_VK_SCROLL_LOCK 0x91
-#define DOM_VK_WIN_OEM_FJ_JISHO 0x92
-#define DOM_VK_WIN_OEM_FJ_MASSHOU 0x93
-#define DOM_VK_WIN_OEM_FJ_TOUROKU 0x94
-#define DOM_VK_WIN_OEM_FJ_LOYA 0x95
-#define DOM_VK_WIN_OEM_FJ_ROYA 0x96
-#define DOM_VK_CIRCUMFLEX 0xA0
-#define DOM_VK_EXCLAMATION 0xA1
-#define DOM_VK_DOUBLE_QUOTE 0xA2
-#define DOM_VK_HASH 0xA3
-#define DOM_VK_DOLLAR 0xA4
-#define DOM_VK_PERCENT 0xA5
-#define DOM_VK_AMPERSAND 0xA6
-#define DOM_VK_UNDERSCORE 0xA7
-#define DOM_VK_OPEN_PAREN 0xA8
-#define DOM_VK_CLOSE_PAREN 0xA9
-#define DOM_VK_ASTERISK 0xAA
-#define DOM_VK_PLUS 0xAB
-#define DOM_VK_PIPE 0xAC
-#define DOM_VK_HYPHEN_MINUS 0xAD
-#define DOM_VK_OPEN_CURLY_BRACKET 0xAE
-#define DOM_VK_CLOSE_CURLY_BRACKET 0xAF
-#define DOM_VK_TILDE 0xB0
-#define DOM_VK_VOLUME_MUTE 0xB5
-#define DOM_VK_VOLUME_DOWN 0xB6
-#define DOM_VK_VOLUME_UP 0xB7
-#define DOM_VK_COMMA 0xBC
-#define DOM_VK_PERIOD 0xBE
-#define DOM_VK_SLASH 0xBF
-#define DOM_VK_BACK_QUOTE 0xC0
-#define DOM_VK_OPEN_BRACKET 0xDB
-#define DOM_VK_BACK_SLASH 0xDC
-#define DOM_VK_CLOSE_BRACKET 0xDD
-#define DOM_VK_QUOTE 0xDE
-#define DOM_VK_META 0xE0
-#define DOM_VK_ALTGR 0xE1
-#define DOM_VK_WIN_ICO_HELP 0xE3
-#define DOM_VK_WIN_ICO_00 0xE4
-#define DOM_VK_WIN_ICO_CLEAR 0xE6
-#define DOM_VK_WIN_OEM_RESET 0xE9
-#define DOM_VK_WIN_OEM_JUMP 0xEA
-#define DOM_VK_WIN_OEM_PA1 0xEB
-#define DOM_VK_WIN_OEM_PA2 0xEC
-#define DOM_VK_WIN_OEM_PA3 0xED
-#define DOM_VK_WIN_OEM_WSCTRL 0xEE
-#define DOM_VK_WIN_OEM_CUSEL 0xEF
-#define DOM_VK_WIN_OEM_ATTN 0xF0
-#define DOM_VK_WIN_OEM_FINISH 0xF1
-#define DOM_VK_WIN_OEM_COPY 0xF2
-#define DOM_VK_WIN_OEM_AUTO 0xF3
-#define DOM_VK_WIN_OEM_ENLW 0xF4
-#define DOM_VK_WIN_OEM_BACKTAB 0xF5
-#define DOM_VK_ATTN 0xF6
-#define DOM_VK_CRSEL 0xF7
-#define DOM_VK_EXSEL 0xF8
-#define DOM_VK_EREOF 0xF9
-#define DOM_VK_PLAY 0xFA
-#define DOM_VK_ZOOM 0xFB
-#define DOM_VK_PA1 0xFD
-#define DOM_VK_WIN_OEM_CLEAR 0xFE
-
-int dom2godot_keycode(int dom_keycode) {
-
- if (DOM_VK_0 <= dom_keycode && dom_keycode <= DOM_VK_Z) {
- // ASCII intersection
- return dom_keycode;
- }
-
- if (DOM_VK_NUMPAD0 <= dom_keycode && dom_keycode <= DOM_VK_NUMPAD9) {
- // Numpad numbers
- return KEY_KP_0 + (dom_keycode - DOM_VK_NUMPAD0);
+// See https://w3c.github.io/uievents-code/#code-value-tables
+int dom_code2godot_scancode(EM_UTF8 const p_code[32], EM_UTF8 const p_key[32], bool p_physical) {
+#define DOM2GODOT(p_str, p_godot_code) \
+ if (memcmp((const void *)p_str, (void *)p_code, strlen(p_str) + 1) == 0) { \
+ return KEY_##p_godot_code; \
}
- if (DOM_VK_F1 <= dom_keycode && dom_keycode <= DOM_VK_F16) {
- // F1-F16
- return KEY_F1 + (dom_keycode - DOM_VK_F1);
+ // Numpad section.
+ DOM2GODOT("NumLock", NUMLOCK);
+ DOM2GODOT("Numpad0", KP_0);
+ DOM2GODOT("Numpad1", KP_1);
+ DOM2GODOT("Numpad2", KP_2);
+ DOM2GODOT("Numpad3", KP_3);
+ DOM2GODOT("Numpad4", KP_4);
+ DOM2GODOT("Numpad5", KP_5);
+ DOM2GODOT("Numpad6", KP_6);
+ DOM2GODOT("Numpad7", KP_7);
+ DOM2GODOT("Numpad8", KP_8);
+ DOM2GODOT("Numpad9", KP_9);
+ DOM2GODOT("NumpadAdd", KP_ADD);
+ DOM2GODOT("NumpadBackspace", BACKSPACE);
+ DOM2GODOT("NumpadClear", CLEAR);
+ DOM2GODOT("NumpadClearEntry", CLEAR);
+ //DOM2GODOT("NumpadComma", UNKNOWN);
+ DOM2GODOT("NumpadDecimal", KP_PERIOD);
+ DOM2GODOT("NumpadDivide", KP_DIVIDE);
+ DOM2GODOT("NumpadEnter", KP_ENTER);
+ DOM2GODOT("NumpadEqual", EQUAL);
+ //DOM2GODOT("NumpadHash", UNKNOWN);
+ //DOM2GODOT("NumpadMemoryAdd", UNKNOWN);
+ //DOM2GODOT("NumpadMemoryClear", UNKNOWN);
+ //DOM2GODOT("NumpadMemoryRecall", UNKNOWN);
+ //DOM2GODOT("NumpadMemoryStore", UNKNOWN);
+ //DOM2GODOT("NumpadMemorySubtract", UNKNOWN);
+ DOM2GODOT("NumpadMultiply", KP_MULTIPLY);
+ DOM2GODOT("NumpadParenLeft", PARENLEFT);
+ DOM2GODOT("NumpadParenRight", PARENRIGHT);
+ DOM2GODOT("NumpadStar", KP_MULTIPLY); // or ASTERISK ?
+ DOM2GODOT("NumpadSubtract", KP_SUBTRACT);
+
+ // Printable ASCII.
+ if (!p_physical) {
+ uint8_t b0 = (uint8_t)p_key[0];
+ uint8_t b1 = (uint8_t)p_key[1];
+ uint8_t b2 = (uint8_t)p_key[2];
+ if (b1 == 0 && b0 > 0x1F && b0 < 0x7F) { // ASCII.
+ if (b0 > 0x60 && b0 < 0x7B) { // Lowercase ASCII.
+ b0 -= 32;
+ }
+ return b0;
+ }
+
+#define _U_2BYTES_MASK 0xE0
+#define _U_2BYTES 0xC0
+ // Latin-1 codes.
+ if (b2 == 0 && (b0 & _U_2BYTES_MASK) == _U_2BYTES) { // 2-bytes utf8, only known latin.
+ uint32_t key = ((b0 & ~_U_2BYTES_MASK) << 6) | (b1 & 0x3F);
+ if (key >= 0xA0 && key <= 0xDF) {
+ return key;
+ }
+ if (key >= 0xE0 && key <= 0xFF) { // Lowercase known latin.
+ key -= 0x20;
+ return key;
+ }
+ }
+#undef _U_2BYTES_MASK
+#undef _U_2BYTES
}
- switch (dom_keycode) {
- //case DOM_VK_CANCEL: return KEY_UNKNOWN;
- case DOM_VK_HELP: return KEY_HELP;
- case DOM_VK_BACK_SPACE: return KEY_BACKSPACE;
- case DOM_VK_TAB: return KEY_TAB;
-
- case DOM_VK_CLEAR:
- case DOM_VK_WIN_OEM_CLEAR: // OEM duplicate
- return KEY_CLEAR;
-
- case DOM_VK_RETURN:
- case DOM_VK_ENTER: // unused according to MDN
- return KEY_ENTER;
-
- case DOM_VK_SHIFT: return KEY_SHIFT;
- case DOM_VK_CONTROL: return KEY_CONTROL;
-
- case DOM_VK_ALT:
- case DOM_VK_ALTGR:
- return KEY_ALT;
-
- case DOM_VK_PAUSE: return KEY_PAUSE;
- case DOM_VK_CAPS_LOCK:
- return KEY_CAPSLOCK;
-
- /*
- case DOM_VK_KANA: return KEY_UNKNOWN;
- case DOM_VK_HANGUL: return KEY_UNKNOWN;
- case DOM_VK_EISU: return KEY_UNKNOWN;
- case DOM_VK_JUNJA: return KEY_UNKNOWN;
- case DOM_VK_FINAL: return KEY_UNKNOWN;
- case DOM_VK_HANJA: return KEY_UNKNOWN;
- case DOM_VK_KANJI: return KEY_UNKNOWN;
- */
-
- case DOM_VK_ESCAPE:
- return KEY_ESCAPE;
- /*
- case DOM_VK_CONVERT: return KEY_UNKNOWN;
- case DOM_VK_NONCONVERT: return KEY_UNKNOWN;
- case DOM_VK_ACCEPT: return KEY_UNKNOWN;
- case DOM_VK_MODECHANGE: return KEY_UNKNOWN;
- */
-
- case DOM_VK_SPACE: return KEY_SPACE;
- case DOM_VK_PAGE_UP: return KEY_PAGEUP;
- case DOM_VK_PAGE_DOWN: return KEY_PAGEDOWN;
- case DOM_VK_END: return KEY_END;
- case DOM_VK_HOME: return KEY_HOME;
- case DOM_VK_LEFT: return KEY_LEFT;
- case DOM_VK_UP: return KEY_UP;
- case DOM_VK_RIGHT: return KEY_RIGHT;
- case DOM_VK_DOWN:
- return KEY_DOWN;
-
- //case DOM_VK_SELECT: return KEY_UNKNOWN;
-
- case DOM_VK_PRINTSCREEN:
- case DOM_VK_PRINT:
- return KEY_PRINT;
-
- //case DOM_VK_EXECUTE: return KEY_UNKNOWN;
- case DOM_VK_INSERT: return KEY_INSERT;
- case DOM_VK_DELETE: return KEY_DELETE;
-
- case DOM_VK_META:
- case DOM_VK_WIN:
- return KEY_META;
-
- case DOM_VK_CONTEXT_MENU: return KEY_MENU;
- case DOM_VK_SLEEP:
- return KEY_STANDBY;
-
- // Numpad keys
- case DOM_VK_MULTIPLY: return KEY_KP_MULTIPLY;
- case DOM_VK_ADD: return KEY_KP_ADD;
- case DOM_VK_SEPARATOR:
- return KEY_KP_PERIOD; // Good enough?
- case DOM_VK_SUBTRACT: return KEY_KP_SUBTRACT;
- case DOM_VK_DECIMAL: return KEY_KP_PERIOD;
- case DOM_VK_DIVIDE:
- return KEY_KP_DIVIDE;
-
- /*
- case DOM_VK_F17: return KEY_UNKNOWN;
- case DOM_VK_F18: return KEY_UNKNOWN;
- case DOM_VK_F19: return KEY_UNKNOWN;
- case DOM_VK_F20: return KEY_UNKNOWN;
- case DOM_VK_F21: return KEY_UNKNOWN;
- case DOM_VK_F22: return KEY_UNKNOWN;
- case DOM_VK_F23: return KEY_UNKNOWN;
- case DOM_VK_F24: return KEY_UNKNOWN;
- */
-
- case DOM_VK_NUM_LOCK: return KEY_NUMLOCK;
- case DOM_VK_SCROLL_LOCK:
- return KEY_SCROLLLOCK;
-
- /*
- case DOM_VK_WIN_OEM_FJ_JISHO: return KEY_UNKNOWN;
- case DOM_VK_WIN_OEM_FJ_MASSHOU: return KEY_UNKNOWN;
- case DOM_VK_WIN_OEM_FJ_TOUROKU: return KEY_UNKNOWN;
- case DOM_VK_WIN_OEM_FJ_LOYA: return KEY_UNKNOWN;
- case DOM_VK_WIN_OEM_FJ_ROYA: return KEY_UNKNOWN;
- */
-
- case DOM_VK_CIRCUMFLEX: return KEY_ASCIICIRCUM;
- case DOM_VK_EXCLAMATION: return KEY_EXCLAM;
- case DOM_VK_DOUBLE_QUOTE: return KEY_QUOTEDBL;
- case DOM_VK_HASH: return KEY_NUMBERSIGN;
- case DOM_VK_DOLLAR: return KEY_DOLLAR;
- case DOM_VK_PERCENT: return KEY_PERCENT;
- case DOM_VK_AMPERSAND: return KEY_AMPERSAND;
- case DOM_VK_UNDERSCORE: return KEY_UNDERSCORE;
- case DOM_VK_OPEN_PAREN: return KEY_PARENLEFT;
- case DOM_VK_CLOSE_PAREN: return KEY_PARENRIGHT;
- case DOM_VK_ASTERISK: return KEY_ASTERISK;
- case DOM_VK_PLUS: return KEY_PLUS;
- case DOM_VK_PIPE: return KEY_BAR;
- case DOM_VK_HYPHEN_MINUS: return KEY_MINUS;
- case DOM_VK_OPEN_CURLY_BRACKET: return KEY_BRACELEFT;
- case DOM_VK_CLOSE_CURLY_BRACKET: return KEY_BRACERIGHT;
- case DOM_VK_TILDE: return KEY_ASCIITILDE;
-
- case DOM_VK_VOLUME_MUTE: return KEY_VOLUMEMUTE;
- case DOM_VK_VOLUME_DOWN: return KEY_VOLUMEDOWN;
- case DOM_VK_VOLUME_UP: return KEY_VOLUMEUP;
-
- case DOM_VK_COMMA: return KEY_COMMA;
- case DOM_VK_PERIOD: return KEY_PERIOD;
- case DOM_VK_SLASH: return KEY_SLASH;
- case DOM_VK_BACK_QUOTE: return KEY_QUOTELEFT;
- case DOM_VK_OPEN_BRACKET: return KEY_BRACKETLEFT;
- case DOM_VK_BACK_SLASH: return KEY_BACKSLASH;
- case DOM_VK_CLOSE_BRACKET: return KEY_BRACKETRIGHT;
- case DOM_VK_QUOTE:
- return KEY_APOSTROPHE;
-
- // The rest is OEM/unusual.
-
- default: return KEY_UNKNOWN;
- };
+ // Alphanumeric section.
+ DOM2GODOT("Backquote", QUOTELEFT);
+ DOM2GODOT("Backslash", BACKSLASH);
+ DOM2GODOT("BracketLeft", BRACKETLEFT);
+ DOM2GODOT("BracketRight", BRACKETRIGHT);
+ DOM2GODOT("Comma", COMMA);
+ DOM2GODOT("Digit0", 0);
+ DOM2GODOT("Digit1", 1);
+ DOM2GODOT("Digit2", 2);
+ DOM2GODOT("Digit3", 3);
+ DOM2GODOT("Digit4", 4);
+ DOM2GODOT("Digit5", 5);
+ DOM2GODOT("Digit6", 6);
+ DOM2GODOT("Digit7", 7);
+ DOM2GODOT("Digit8", 8);
+ DOM2GODOT("Digit9", 9);
+ DOM2GODOT("Equal", EQUAL);
+ DOM2GODOT("IntlBackslash", BACKSLASH);
+ //DOM2GODOT("IntlRo", UNKNOWN);
+ DOM2GODOT("IntlYen", YEN);
+
+ DOM2GODOT("KeyA", A);
+ DOM2GODOT("KeyB", B);
+ DOM2GODOT("KeyC", C);
+ DOM2GODOT("KeyD", D);
+ DOM2GODOT("KeyE", E);
+ DOM2GODOT("KeyF", F);
+ DOM2GODOT("KeyG", G);
+ DOM2GODOT("KeyH", H);
+ DOM2GODOT("KeyI", I);
+ DOM2GODOT("KeyJ", J);
+ DOM2GODOT("KeyK", K);
+ DOM2GODOT("KeyL", L);
+ DOM2GODOT("KeyM", M);
+ DOM2GODOT("KeyN", N);
+ DOM2GODOT("KeyO", O);
+ DOM2GODOT("KeyP", P);
+ DOM2GODOT("KeyQ", Q);
+ DOM2GODOT("KeyR", R);
+ DOM2GODOT("KeyS", S);
+ DOM2GODOT("KeyT", T);
+ DOM2GODOT("KeyU", U);
+ DOM2GODOT("KeyV", V);
+ DOM2GODOT("KeyW", W);
+ DOM2GODOT("KeyX", X);
+ DOM2GODOT("KeyY", Y);
+ DOM2GODOT("KeyZ", Z);
+
+ DOM2GODOT("Minus", MINUS);
+ DOM2GODOT("Period", PERIOD);
+ DOM2GODOT("Quote", APOSTROPHE);
+ DOM2GODOT("Semicolon", SEMICOLON);
+ DOM2GODOT("Slash", SLASH);
+
+ // Functional keys in the Alphanumeric section.
+ DOM2GODOT("AltLeft", ALT);
+ DOM2GODOT("AltRight", ALT);
+ DOM2GODOT("Backspace", BACKSPACE);
+ DOM2GODOT("CapsLock", CAPSLOCK);
+ DOM2GODOT("ContextMenu", MENU);
+ DOM2GODOT("ControlLeft", CONTROL);
+ DOM2GODOT("ControlRight", CONTROL);
+ DOM2GODOT("Enter", ENTER);
+ DOM2GODOT("MetaLeft", SUPER_L);
+ DOM2GODOT("MetaRight", SUPER_R);
+ DOM2GODOT("ShiftLeft", SHIFT);
+ DOM2GODOT("ShiftRight", SHIFT);
+ DOM2GODOT("Space", SPACE);
+ DOM2GODOT("Tab", TAB);
+
+ // ControlPad section.
+ DOM2GODOT("Delete", DELETE);
+ DOM2GODOT("End", END);
+ DOM2GODOT("Help", HELP);
+ DOM2GODOT("Home", HOME);
+ DOM2GODOT("Insert", INSERT);
+ DOM2GODOT("PageDown", PAGEDOWN);
+ DOM2GODOT("PageUp", PAGEUP);
+
+ // ArrowPad section.
+ DOM2GODOT("ArrowDown", DOWN);
+ DOM2GODOT("ArrowLeft", LEFT);
+ DOM2GODOT("ArrowRight", RIGHT);
+ DOM2GODOT("ArrowUp", UP);
+
+ // Function section.
+ DOM2GODOT("Escape", ESCAPE);
+ DOM2GODOT("F1", F1);
+ DOM2GODOT("F2", F2);
+ DOM2GODOT("F3", F3);
+ DOM2GODOT("F4", F4);
+ DOM2GODOT("F5", F5);
+ DOM2GODOT("F6", F6);
+ DOM2GODOT("F7", F7);
+ DOM2GODOT("F8", F8);
+ DOM2GODOT("F9", F9);
+ DOM2GODOT("F10", F10);
+ DOM2GODOT("F11", F11);
+ DOM2GODOT("F12", F12);
+ //DOM2GODOT("Fn", UNKNOWN); // never actually fired, but included in the standard draft.
+ //DOM2GODOT("FnLock", UNKNOWN);
+ DOM2GODOT("PrintScreen", PRINT);
+ DOM2GODOT("ScrollLock", SCROLLLOCK);
+ DOM2GODOT("Pause", PAUSE);
+
+ // Media keys section.
+ DOM2GODOT("BrowserBack", BACK);
+ DOM2GODOT("BrowserFavorites", FAVORITES);
+ DOM2GODOT("BrowserForward", FORWARD);
+ DOM2GODOT("BrowserHome", OPENURL);
+ DOM2GODOT("BrowserRefresh", REFRESH);
+ DOM2GODOT("BrowserSearch", SEARCH);
+ DOM2GODOT("BrowserStop", STOP);
+ //DOM2GODOT("Eject", UNKNOWN);
+ DOM2GODOT("LaunchApp1", LAUNCH0);
+ DOM2GODOT("LaunchApp2", LAUNCH1);
+ DOM2GODOT("LaunchMail", LAUNCHMAIL);
+ DOM2GODOT("MediaPlayPause", MEDIAPLAY);
+ DOM2GODOT("MediaSelect", LAUNCHMEDIA);
+ DOM2GODOT("MediaStop", MEDIASTOP);
+ DOM2GODOT("MediaTrackNext", MEDIANEXT);
+ DOM2GODOT("MediaTrackPrevious", MEDIAPREVIOUS);
+ //DOM2GODOT("Power", UNKNOWN);
+ //DOM2GODOT("Sleep", UNKNOWN);
+ DOM2GODOT("AudioVolumeDown", VOLUMEDOWN);
+ DOM2GODOT("AudioVolumeMute", VOLUMEMUTE);
+ DOM2GODOT("AudioVolumeUp", VOLUMEUP);
+ //DOM2GODOT("WakeUp", UNKNOWN);
+ return KEY_UNKNOWN;
+#undef DOM2GODOT
}
diff --git a/platform/javascript/emscripten_helpers.py b/platform/javascript/emscripten_helpers.py
index a55c9d3f48..8b8c492e22 100644
--- a/platform/javascript/emscripten_helpers.py
+++ b/platform/javascript/emscripten_helpers.py
@@ -1,28 +1,11 @@
import os
-
-def parse_config():
- em_config_file = os.getenv("EM_CONFIG") or os.path.expanduser("~/.emscripten")
- if not os.path.exists(em_config_file):
- raise RuntimeError("Emscripten configuration file '%s' does not exist" % em_config_file)
-
- normalized = {}
- em_config = {}
- with open(em_config_file) as f:
- try:
- # Emscripten configuration file is a Python file with simple assignments.
- exec(f.read(), em_config)
- except StandardError as e:
- raise RuntimeError("Emscripten configuration file '%s' is invalid:\n%s" % (em_config_file, e))
- normalized["EMCC_ROOT"] = em_config.get("EMSCRIPTEN_ROOT")
- normalized["NODE_JS"] = em_config.get("NODE_JS")
- normalized["CLOSURE_BIN"] = os.path.join(normalized["EMCC_ROOT"], "node_modules", ".bin", "google-closure-compiler")
- return normalized
+from SCons.Util import WhereIs
def run_closure_compiler(target, source, env, for_signature):
- cfg = parse_config()
- cmd = [cfg["NODE_JS"], cfg["CLOSURE_BIN"]]
+ closure_bin = os.path.join(os.path.dirname(WhereIs("emcc")), "node_modules", ".bin", "google-closure-compiler")
+ cmd = [WhereIs("node"), closure_bin]
cmd.extend(["--compilation_level", "ADVANCED_OPTIMIZATIONS"])
for f in env["JSEXTERNS"]:
cmd.extend(["--externs", f.get_abspath()])
@@ -36,3 +19,15 @@ def create_engine_file(env, target, source, externs):
if env["use_closure_compiler"]:
return env.BuildJS(target, source, JSEXTERNS=externs)
return env.Textfile(target, [env.File(s) for s in source])
+
+
+def add_js_libraries(env, libraries):
+ env.Append(JS_LIBS=env.File(libraries))
+
+
+def add_js_pre(env, js_pre):
+ env.Append(JS_PRE=env.File(js_pre))
+
+
+def add_js_externs(env, externs):
+ env.Append(JS_EXTERNS=env.File(externs))
diff --git a/platform/javascript/engine/engine.js b/platform/javascript/engine/engine.js
deleted file mode 100644
index 6d7509377f..0000000000
--- a/platform/javascript/engine/engine.js
+++ /dev/null
@@ -1,184 +0,0 @@
-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 loadPath = '';
- var loadPromise = null;
- var initPromise = null;
- var stderr = null;
- var stdout = null;
- var progressFunc = null;
-
- function load(basePath) {
- if (loadPromise == null) {
- loadPath = basePath;
- loadPromise = preloader.loadPromise(basePath + wasmExt);
- preloader.setProgressFunc(progressFunc);
- requestAnimationFrame(preloader.animateProgress);
- }
- return loadPromise;
- };
-
- function unload() {
- loadPromise = null;
- };
-
- /** @constructor */
- function Engine() {};
-
- Engine.prototype.init = /** @param {string=} basePath */ function(basePath) {
- if (initPromise) {
- return initPromise;
- }
- if (!loadPromise) {
- 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 = {}
- 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;
- if (unloadAfterInit) {
- loadPromise = null;
- }
- resolve();
- });
- });
- return initPromise;
- };
-
- /** @type {function(string, string):Object} */
- Engine.prototype.preloadFile = function(file, path) {
- return preloader.preload(file, path);
- };
-
- /** @type {function(...string):Object} */
- Engine.prototype.start = function() {
- // Start from arguments.
- var args = [];
- for (var i = 0; i < arguments.length; i++) {
- 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();
- });
- });
- });
- };
-
- Engine.prototype.startGame = function(execName, mainPack) {
- // Start and init with execName as loadPath if not inited.
- executableName = execName;
- var me = this;
- return Promise.all([
- this.init(execName),
- this.preloadFile(mainPack, mainPack)
- ]).then(function() {
- return me.start('--main-pack', mainPack);
- });
- };
-
- Engine.prototype.setWebAssemblyFilenameExtension = function(override) {
- if (String(override).length === 0) {
- throw new Error('Invalid WebAssembly filename extension override');
- }
- wasmExt = String(override);
- };
-
- Engine.prototype.setUnloadAfterInit = function(enabled) {
- unloadAfterInit = enabled;
- };
-
- Engine.prototype.setCanvas = function(canvasElem) {
- canvas = canvasElem;
- };
-
- Engine.prototype.setCanvasResizedOnStart = function(enabled) {
- resizeCanvasOnStart = enabled;
- };
-
- Engine.prototype.setLocale = function(locale) {
- customLocale = locale;
- };
-
- Engine.prototype.setExecutableName = function(newName) {
- 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;
- 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;
- stderr = printErr;
- };
-
- // 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
- return Engine;
-})();
diff --git a/platform/javascript/engine/loader.js b/platform/javascript/engine/loader.js
deleted file mode 100644
index d27fbf612e..0000000000
--- 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();
- });
- }
-};
diff --git a/platform/javascript/engine/preloader.js b/platform/javascript/engine/preloader.js
deleted file mode 100644
index 17918eae38..0000000000
--- a/platform/javascript/engine/preloader.js
+++ /dev/null
@@ -1,139 +0,0 @@
-var Preloader = /** @constructor */ function() {
-
- var DOWNLOAD_ATTEMPTS_MAX = 4;
- var progressFunc = null;
- var lastProgress = { loaded: 0, total: 0 };
-
- var loadingFiles = {};
- this.preloadedFiles = [];
-
- 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', '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 {
- setTimeout(loadXHR.bind(null, resolve, reject, file, tracker), 1000);
- }
- }
-
- 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':
- if (++tracker[file].attempts >= DOWNLOAD_ATTEMPTS_MAX) {
- tracker[file].final = true;
- reject(new Error("Failed loading file '" + file + "'"));
- } else {
- setTimeout(loadXHR.bind(null, resolve, reject, file, tracker), 1000);
- }
- break;
-
- case 'abort':
- tracker[file].final = true;
- reject(new Error("Loading file '" + file + "' was aborted."));
- break;
- }
- }
-
- this.loadPromise = function(file) {
- return new Promise(function(resolve, reject) {
- loadXHR(resolve, reject, file, loadingFiles);
- });
- }
-
- this.preload = function(pathOrBuffer, destPath) {
- if (pathOrBuffer instanceof ArrayBuffer) {
- pathOrBuffer = new Uint8Array(pathOrBuffer);
- } else if (ArrayBuffer.isView(pathOrBuffer)) {
- pathOrBuffer = new Uint8Array(pathOrBuffer.buffer);
- }
- if (pathOrBuffer instanceof Uint8Array) {
- this.preloadedFiles.push({
- path: destPath,
- buffer: pathOrBuffer
- });
- return Promise.resolve();
- } else if (typeof pathOrBuffer === 'string') {
- var me = this;
- return this.loadPromise(pathOrBuffer).then(function(xhr) {
- me.preloadedFiles.push({
- path: destPath || pathOrBuffer,
- buffer: xhr.response
- });
- return Promise.resolve();
- });
- } else {
- throw Promise.reject("Invalid object for preloading");
- }
- };
-
- var animateProgress = function() {
-
- var loaded = 0;
- var total = 0;
- var totalIsValid = true;
- var progressIsFinal = true;
-
- Object.keys(loadingFiles).forEach(function(file) {
- const stat = loadingFiles[file];
- if (!stat.final) {
- progressIsFinal = false;
- }
- if (!totalIsValid || stat.total === 0) {
- totalIsValid = false;
- total = 0;
- } else {
- total += stat.total;
- }
- loaded += stat.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.animateProgress = animateProgress; // Also exposed to start it.
-
- this.setProgressFunc = function(callback) {
- progressFunc = callback;
- }
-};
diff --git a/platform/javascript/engine/utils.js b/platform/javascript/engine/utils.js
deleted file mode 100644
index fdff90a923..0000000000
--- a/platform/javascript/engine/utils.js
+++ /dev/null
@@ -1,69 +0,0 @@
-var Utils = {
-
- createLocateRewrite: function(execName) {
- function rw(path) {
- if (path.endsWith('.worker.js')) {
- return execName + '.worker.js';
- } else if (path.endsWith('.js')) {
- return execName + '.js';
- } else if (path.endsWith('.wasm')) {
- return execName + '.wasm';
- }
- }
- return rw;
- },
-
- createInstantiatePromise: function(wasmLoader) {
- function instantiateWasm(imports, onSuccess) {
- wasmLoader.then(function(xhr) {
- WebAssembly.instantiate(xhr.response, imports).then(function(result) {
- onSuccess(result['instance'], result['module']);
- });
- });
- wasmLoader = null;
- return {};
- };
-
- return instantiateWasm;
- },
-
- copyToFS: function(fs, path, buffer) {
- var p = path.lastIndexOf("/");
- var dir = "/";
- if (p > 0) {
- dir = path.slice(0, path.lastIndexOf("/"));
- }
- try {
- fs.stat(dir);
- } catch (e) {
- if (e.errno !== 44) { // 'ENOENT', see https://github.com/emscripten-core/emscripten/blob/master/system/lib/libc/musl/arch/emscripten/bits/errno.h
- throw e;
- }
- fs['mkdirTree'](dir);
- }
- // With memory growth, canOwn should be false.
- fs['writeFile'](path, new Uint8Array(buffer), {'flags': 'wx+'});
- },
-
- findCanvas: function() {
- var nodes = document.getElementsByTagName('canvas');
- if (nodes.length && nodes[0] instanceof HTMLCanvasElement) {
- return nodes[0];
- }
- throw new Error("No canvas found");
- },
-
- isWebGLAvailable: function(majorVersion = 1) {
-
- var testContext = false;
- try {
- var testCanvas = document.createElement('canvas');
- if (majorVersion === 1) {
- testContext = testCanvas.getContext('webgl') || testCanvas.getContext('experimental-webgl');
- } else if (majorVersion === 2) {
- testContext = testCanvas.getContext('webgl2') || testCanvas.getContext('experimental-webgl2');
- }
- } catch (e) {}
- return !!testContext;
- }
-};
diff --git a/platform/javascript/export/export.cpp b/platform/javascript/export/export.cpp
index 36076a2af9..48ccc1f87a 100644
--- a/platform/javascript/export/export.cpp
+++ b/platform/javascript/export/export.cpp
@@ -5,8 +5,8 @@
/* GODOT ENGINE */
/* https://godotengine.org */
/*************************************************************************/
-/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */
-/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */
+/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
@@ -28,6 +28,7 @@
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/*************************************************************************/
+#include "core/io/json.h"
#include "core/io/tcp_server.h"
#include "core/io/zip_io.h"
#include "editor/editor_export.h"
@@ -36,17 +37,13 @@
#include "platform/javascript/logo.gen.h"
#include "platform/javascript/run_icon.gen.h"
-#define EXPORT_TEMPLATE_WEBASSEMBLY_RELEASE "webassembly_release.zip"
-#define EXPORT_TEMPLATE_WEBASSEMBLY_DEBUG "webassembly_debug.zip"
-
class EditorHTTPServer : public Reference {
-
private:
Ref<TCP_Server> server;
Ref<StreamPeerTCP> connection;
- uint64_t time;
+ uint64_t time = 0;
uint8_t req_buf[4096];
- int req_pos;
+ int req_pos = 0;
void _clear_client() {
connection = Ref<StreamPeerTCP>();
@@ -85,33 +82,44 @@ public:
// Wrong protocol
ERR_FAIL_COND_MSG(req[0] != "GET" || req[2] != "HTTP/1.1", "Invalid method or HTTP version.");
- String filepath = EditorSettings::get_singleton()->get_cache_dir().plus_file("tmp_js_export");
+ const String cache_path = EditorSettings::get_singleton()->get_cache_dir();
const String basereq = "/tmp_js_export";
- String ctype = "";
+ String filepath;
+ String ctype;
if (req[1] == basereq + ".html") {
- filepath += ".html";
+ filepath = cache_path.plus_file(req[1].get_file());
ctype = "text/html";
} else if (req[1] == basereq + ".js") {
- filepath += ".js";
+ filepath = cache_path.plus_file(req[1].get_file());
+ ctype = "application/javascript";
+ } else if (req[1] == basereq + ".audio.worklet.js") {
+ filepath = cache_path.plus_file(req[1].get_file());
ctype = "application/javascript";
} else if (req[1] == basereq + ".worker.js") {
- filepath += ".worker.js";
+ filepath = cache_path.plus_file(req[1].get_file());
ctype = "application/javascript";
} else if (req[1] == basereq + ".pck") {
- filepath += ".pck";
+ filepath = cache_path.plus_file(req[1].get_file());
ctype = "application/octet-stream";
} else if (req[1] == basereq + ".png" || req[1] == "/favicon.png") {
// Also allow serving the generated favicon for a smoother loading experience.
if (req[1] == "/favicon.png") {
filepath = EditorSettings::get_singleton()->get_cache_dir().plus_file("favicon.png");
} else {
- filepath += ".png";
+ filepath = basereq + ".png";
}
ctype = "image/png";
+ } else if (req[1] == basereq + ".side.wasm") {
+ filepath = cache_path.plus_file(req[1].get_file());
+ ctype = "application/wasm";
} else if (req[1] == basereq + ".wasm") {
- filepath += ".wasm";
+ filepath = cache_path.plus_file(req[1].get_file());
ctype = "application/wasm";
- } else {
+ } else if (req[1].ends_with(".wasm")) {
+ filepath = cache_path.plus_file(req[1].get_file()); // TODO dangerous?
+ ctype = "application/wasm";
+ }
+ if (filepath.is_empty() || !FileAccess::exists(filepath)) {
String s = "HTTP/1.1 404 Not Found\r\n";
s += "Connection: Close\r\n";
s += "\r\n";
@@ -124,6 +132,9 @@ public:
String s = "HTTP/1.1 200 OK\r\n";
s += "Connection: Close\r\n";
s += "Content-Type: " + ctype + "\r\n";
+ s += "Access-Control-Allow-Origin: *\r\n";
+ s += "Cross-Origin-Opener-Policy: same-origin\r\n";
+ s += "Cross-Origin-Embedder-Policy: require-corp\r\n";
s += "\r\n";
CharString cs = s.utf8();
Error err = connection->put_data((const uint8_t *)cs.get_data(), cs.size() - 1);
@@ -148,11 +159,13 @@ public:
}
void poll() {
- if (!server->is_listening())
+ if (!server->is_listening()) {
return;
+ }
if (connection.is_null()) {
- if (!server->is_connection_available())
+ if (!server->is_connection_available()) {
return;
+ }
connection = server->take_connection();
time = OS::get_singleton()->get_ticks_usec();
}
@@ -160,11 +173,11 @@ public:
_clear_client();
return;
}
- if (connection->get_status() != StreamPeerTCP::STATUS_CONNECTED)
+ if (connection->get_status() != StreamPeerTCP::STATUS_CONNECTED) {
return;
+ }
while (true) {
-
char *r = (char *)req_buf;
int l = req_pos - 1;
if (l > 3 && r[l] == '\n' && r[l - 1] == '\r' && r[l - 2] == '\n' && r[l - 3] == '\r') {
@@ -190,71 +203,105 @@ public:
};
class EditorExportPlatformJavaScript : public EditorExportPlatform {
-
GDCLASS(EditorExportPlatformJavaScript, EditorExportPlatform);
Ref<ImageTexture> logo;
Ref<ImageTexture> run_icon;
Ref<ImageTexture> stop_icon;
- int menu_options;
+ int menu_options = 0;
- void _fix_html(Vector<uint8_t> &p_html, const Ref<EditorExportPreset> &p_preset, const String &p_name, bool p_debug);
-
-private:
Ref<EditorHTTPServer> server;
- bool server_quit;
+ bool server_quit = false;
Mutex server_lock;
- Thread *server_thread;
+ Thread *server_thread = nullptr;
+
+ enum ExportMode {
+ EXPORT_MODE_NORMAL = 0,
+ EXPORT_MODE_THREADS = 1,
+ EXPORT_MODE_GDNATIVE = 2,
+ };
+
+ String _get_template_name(ExportMode p_mode, bool p_debug) const {
+ String name = "webassembly";
+ switch (p_mode) {
+ case EXPORT_MODE_THREADS:
+ name += "_threads";
+ break;
+ case EXPORT_MODE_GDNATIVE:
+ name += "_gdnative";
+ break;
+ default:
+ break;
+ }
+ if (p_debug) {
+ name += "_debug.zip";
+ } else {
+ name += "_release.zip";
+ }
+ return name;
+ }
+
+ void _fix_html(Vector<uint8_t> &p_html, const Ref<EditorExportPreset> &p_preset, const String &p_name, bool p_debug, int p_flags, const Vector<SharedObject> p_shared_objects);
static void _server_thread_poll(void *data);
public:
- virtual void get_preset_features(const Ref<EditorExportPreset> &p_preset, List<String> *r_features);
+ virtual void get_preset_features(const Ref<EditorExportPreset> &p_preset, List<String> *r_features) override;
- virtual void get_export_options(List<ExportOption> *r_options);
+ virtual void get_export_options(List<ExportOption> *r_options) override;
- virtual String get_name() const;
- virtual String get_os_name() const;
- virtual Ref<Texture2D> get_logo() const;
+ virtual String get_name() const override;
+ virtual String get_os_name() const override;
+ virtual Ref<Texture2D> get_logo() const override;
- virtual bool can_export(const Ref<EditorExportPreset> &p_preset, String &r_error, bool &r_missing_templates) const;
- virtual List<String> get_binary_extensions(const Ref<EditorExportPreset> &p_preset) const;
- virtual Error export_project(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path, int p_flags = 0);
+ virtual bool can_export(const Ref<EditorExportPreset> &p_preset, String &r_error, bool &r_missing_templates) const override;
+ virtual List<String> get_binary_extensions(const Ref<EditorExportPreset> &p_preset) const override;
+ virtual Error export_project(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path, int p_flags = 0) override;
- virtual bool poll_export();
- virtual int get_options_count() const;
- virtual String get_option_label(int p_index) const { return p_index ? TTR("Stop HTTP Server") : TTR("Run in Browser"); }
- virtual String get_option_tooltip(int p_index) const { return p_index ? TTR("Stop HTTP Server") : TTR("Run exported HTML in the system's default browser."); }
- virtual Ref<ImageTexture> get_option_icon(int p_index) const;
- virtual Error run(const Ref<EditorExportPreset> &p_preset, int p_option, int p_debug_flags);
- virtual Ref<Texture2D> get_run_icon() const;
-
- virtual void get_platform_features(List<String> *r_features) {
+ virtual bool poll_export() override;
+ virtual int get_options_count() const override;
+ virtual String get_option_label(int p_index) const override { return p_index ? TTR("Stop HTTP Server") : TTR("Run in Browser"); }
+ virtual String get_option_tooltip(int p_index) const override { return p_index ? TTR("Stop HTTP Server") : TTR("Run exported HTML in the system's default browser."); }
+ virtual Ref<ImageTexture> get_option_icon(int p_index) const override;
+ virtual Error run(const Ref<EditorExportPreset> &p_preset, int p_option, int p_debug_flags) override;
+ virtual Ref<Texture2D> get_run_icon() const override;
+ virtual void get_platform_features(List<String> *r_features) override {
r_features->push_back("web");
r_features->push_back(get_os_name());
}
- virtual void resolve_platform_feature_priorities(const Ref<EditorExportPreset> &p_preset, Set<String> &p_features) {
+ virtual void resolve_platform_feature_priorities(const Ref<EditorExportPreset> &p_preset, Set<String> &p_features) override {
}
+ String get_debug_protocol() const override { return "ws://"; }
+
EditorExportPlatformJavaScript();
~EditorExportPlatformJavaScript();
};
-void EditorExportPlatformJavaScript::_fix_html(Vector<uint8_t> &p_html, const Ref<EditorExportPreset> &p_preset, const String &p_name, bool p_debug) {
-
+void EditorExportPlatformJavaScript::_fix_html(Vector<uint8_t> &p_html, const Ref<EditorExportPreset> &p_preset, const String &p_name, bool p_debug, int p_flags, const Vector<SharedObject> p_shared_objects) {
String str_template = String::utf8(reinterpret_cast<const char *>(p_html.ptr()), p_html.size());
String str_export;
Vector<String> lines = str_template.split("\n");
+ Vector<String> flags;
+ String flags_json;
+ gen_export_flags(flags, p_flags);
+ flags_json = JSON::print(flags);
+ String libs;
+ for (int i = 0; i < p_shared_objects.size(); i++) {
+ libs += "\"" + p_shared_objects[i].path.get_file() + "\",";
+ }
for (int i = 0; i < lines.size(); i++) {
-
String current_line = lines[i];
current_line = current_line.replace("$GODOT_BASENAME", p_name);
current_line = current_line.replace("$GODOT_PROJECT_NAME", ProjectSettings::get_singleton()->get_setting("application/config/name"));
current_line = current_line.replace("$GODOT_HEAD_INCLUDE", p_preset->get("html/head_include"));
+ current_line = current_line.replace("$GODOT_FULL_WINDOW", p_preset->get("html/full_window_size") ? "true" : "false");
+ current_line = current_line.replace("$GODOT_GDNATIVE_LIBS", libs);
current_line = current_line.replace("$GODOT_DEBUG_ENABLED", p_debug ? "true" : "false");
+ current_line = current_line.replace("$GODOT_ARGS", flags_json);
str_export += current_line + "\n";
}
@@ -266,7 +313,6 @@ void EditorExportPlatformJavaScript::_fix_html(Vector<uint8_t> &p_html, const Re
}
void EditorExportPlatformJavaScript::get_preset_features(const Ref<EditorExportPreset> &p_preset, List<String> *r_features) {
-
if (p_preset->get("vram_texture_compression/for_desktop")) {
r_features->push_back("s3tc");
}
@@ -280,42 +326,47 @@ void EditorExportPlatformJavaScript::get_preset_features(const Ref<EditorExportP
r_features->push_back("etc2");
}
}
+ ExportMode mode = (ExportMode)(int)p_preset->get("variant/export_type");
+ if (mode == EXPORT_MODE_THREADS) {
+ r_features->push_back("threads");
+ } else if (mode == EXPORT_MODE_GDNATIVE) {
+ r_features->push_back("wasm32");
+ }
}
void EditorExportPlatformJavaScript::get_export_options(List<ExportOption> *r_options) {
+ r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "custom_template/debug", PROPERTY_HINT_GLOBAL_FILE, "*.zip"), ""));
+ r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "custom_template/release", PROPERTY_HINT_GLOBAL_FILE, "*.zip"), ""));
+ r_options->push_back(ExportOption(PropertyInfo(Variant::INT, "variant/export_type", PROPERTY_HINT_ENUM, "Regular,Threads,GDNative"), 0)); // Export type.
r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "vram_texture_compression/for_desktop"), true)); // S3TC
r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "vram_texture_compression/for_mobile"), false)); // ETC or ETC2, depending on renderer
+
r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "html/custom_html_shell", PROPERTY_HINT_FILE, "*.html"), ""));
r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "html/head_include", PROPERTY_HINT_MULTILINE_TEXT), ""));
- r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "custom_template/release", PROPERTY_HINT_GLOBAL_FILE, "*.zip"), ""));
- r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "custom_template/debug", PROPERTY_HINT_GLOBAL_FILE, "*.zip"), ""));
+ r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "html/full_window_size"), true));
}
String EditorExportPlatformJavaScript::get_name() const {
-
return "HTML5";
}
String EditorExportPlatformJavaScript::get_os_name() const {
-
return "HTML5";
}
Ref<Texture2D> EditorExportPlatformJavaScript::get_logo() const {
-
return logo;
}
bool EditorExportPlatformJavaScript::can_export(const Ref<EditorExportPreset> &p_preset, String &r_error, bool &r_missing_templates) const {
-
String err;
bool valid = false;
+ ExportMode mode = (ExportMode)(int)p_preset->get("variant/export_type");
// Look for export templates (first official, and if defined custom templates).
-
- bool dvalid = exists_export_template(EXPORT_TEMPLATE_WEBASSEMBLY_DEBUG, &err);
- bool rvalid = exists_export_template(EXPORT_TEMPLATE_WEBASSEMBLY_RELEASE, &err);
+ bool dvalid = exists_export_template(_get_template_name(mode, true), &err);
+ bool rvalid = exists_export_template(_get_template_name(mode, false), &err);
if (p_preset->get("custom_template/debug") != "") {
dvalid = FileAccess::exists(p_preset->get("custom_template/debug"));
@@ -343,14 +394,14 @@ bool EditorExportPlatformJavaScript::can_export(const Ref<EditorExportPreset> &p
}
}
- if (!err.empty())
+ if (!err.is_empty()) {
r_error = err;
+ }
return valid;
}
List<String> EditorExportPlatformJavaScript::get_binary_extensions(const Ref<EditorExportPreset> &p_preset) const {
-
List<String> list;
list.push_back("html");
return list;
@@ -368,11 +419,8 @@ Error EditorExportPlatformJavaScript::export_project(const Ref<EditorExportPrese
template_path = template_path.strip_edges();
if (template_path == String()) {
-
- if (p_debug)
- template_path = find_export_template(EXPORT_TEMPLATE_WEBASSEMBLY_DEBUG);
- else
- template_path = find_export_template(EXPORT_TEMPLATE_WEBASSEMBLY_RELEASE);
+ ExportMode mode = (ExportMode)(int)p_preset->get("variant/export_type");
+ template_path = find_export_template(_get_template_name(mode, p_debug));
}
if (!DirAccess::exists(p_path.get_base_dir())) {
@@ -384,19 +432,30 @@ Error EditorExportPlatformJavaScript::export_project(const Ref<EditorExportPrese
return ERR_FILE_NOT_FOUND;
}
+ Vector<SharedObject> shared_objects;
String pck_path = p_path.get_basename() + ".pck";
- Error error = save_pack(p_preset, pck_path);
+ Error error = save_pack(p_preset, pck_path, &shared_objects);
if (error != OK) {
EditorNode::get_singleton()->show_warning(TTR("Could not write file:") + "\n" + pck_path);
return error;
}
+ DirAccess *da = DirAccess::create(DirAccess::ACCESS_FILESYSTEM);
+ for (int i = 0; i < shared_objects.size(); i++) {
+ String dst = p_path.get_base_dir().plus_file(shared_objects[i].path.get_file());
+ error = da->copy(shared_objects[i].path, dst);
+ if (error != OK) {
+ EditorNode::get_singleton()->show_warning(TTR("Could not write file:") + "\n" + shared_objects[i].path.get_file());
+ memdelete(da);
+ return error;
+ }
+ }
+ memdelete(da);
FileAccess *src_f = nullptr;
zlib_filefunc_def io = zipio_create_io_from_file(&src_f);
unzFile pkg = unzOpen2(template_path.utf8().get_data(), &io);
if (!pkg) {
-
EditorNode::get_singleton()->show_warning(TTR("Could not open template for export:") + "\n" + template_path);
return ERR_FILE_NOT_FOUND;
}
@@ -426,22 +485,25 @@ Error EditorExportPlatformJavaScript::export_project(const Ref<EditorExportPrese
//write
if (file == "godot.html") {
-
- if (!custom_html.empty()) {
+ if (!custom_html.is_empty()) {
continue;
}
- _fix_html(data, p_preset, p_path.get_file().get_basename(), p_debug);
+ _fix_html(data, p_preset, p_path.get_file().get_basename(), p_debug, p_flags, shared_objects);
file = p_path.get_file();
} else if (file == "godot.js") {
-
file = p_path.get_file().get_basename() + ".js";
- } else if (file == "godot.worker.js") {
+ } else if (file == "godot.worker.js") {
file = p_path.get_file().get_basename() + ".worker.js";
- } else if (file == "godot.wasm") {
+ } else if (file == "godot.side.wasm") {
+ file = p_path.get_file().get_basename() + ".side.wasm";
+ } else if (file == "godot.audio.worklet.js") {
+ file = p_path.get_file().get_basename() + ".audio.worklet.js";
+
+ } else if (file == "godot.wasm") {
file = p_path.get_file().get_basename() + ".wasm";
}
@@ -458,8 +520,7 @@ Error EditorExportPlatformJavaScript::export_project(const Ref<EditorExportPrese
} while (unzGoToNextFile(pkg) == UNZ_OK);
unzClose(pkg);
- if (!custom_html.empty()) {
-
+ if (!custom_html.is_empty()) {
FileAccess *f = FileAccess::open(custom_html, FileAccess::READ);
if (!f) {
EditorNode::get_singleton()->show_warning(TTR("Could not read custom HTML shell:") + "\n" + custom_html);
@@ -469,7 +530,7 @@ Error EditorExportPlatformJavaScript::export_project(const Ref<EditorExportPrese
buf.resize(f->get_len());
f->get_buffer(buf.ptrw(), buf.size());
memdelete(f);
- _fix_html(buf, p_preset, p_path.get_file().get_basename(), p_debug);
+ _fix_html(buf, p_preset, p_path.get_file().get_basename(), p_debug, p_flags, shared_objects);
f = FileAccess::open(p_path, FileAccess::WRITE);
if (!f) {
@@ -482,7 +543,7 @@ Error EditorExportPlatformJavaScript::export_project(const Ref<EditorExportPrese
Ref<Image> splash;
const String splash_path = String(GLOBAL_GET("application/boot_splash/image")).strip_edges();
- if (!splash_path.empty()) {
+ if (!splash_path.is_empty()) {
splash.instance();
const Error err = splash->load(splash_path);
if (err) {
@@ -503,7 +564,7 @@ Error EditorExportPlatformJavaScript::export_project(const Ref<EditorExportPrese
// This way, the favicon can be displayed immediately when loading the page.
Ref<Image> favicon;
const String favicon_path = String(GLOBAL_GET("application/config/icon")).strip_edges();
- if (!favicon_path.empty()) {
+ if (!favicon_path.is_empty()) {
favicon.instance();
const Error err = favicon->load(favicon_path);
if (err) {
@@ -523,11 +584,9 @@ Error EditorExportPlatformJavaScript::export_project(const Ref<EditorExportPrese
}
bool EditorExportPlatformJavaScript::poll_export() {
-
Ref<EditorExportPreset> preset;
for (int i = 0; i < EditorExport::get_singleton()->get_export_preset_count(); i++) {
-
Ref<EditorExportPreset> ep = EditorExport::get_singleton()->get_export_preset(i);
if (ep->is_runnable() && ep->get_platform() == this) {
preset = ep;
@@ -553,12 +612,10 @@ Ref<ImageTexture> EditorExportPlatformJavaScript::get_option_icon(int p_index) c
}
int EditorExportPlatformJavaScript::get_options_count() const {
-
return menu_options;
}
Error EditorExportPlatformJavaScript::run(const Ref<EditorExportPreset> &p_preset, int p_option, int p_debug_flags) {
-
if (p_option == 1) {
MutexLock lock(server_lock);
server->stop();
@@ -572,8 +629,10 @@ Error EditorExportPlatformJavaScript::run(const Ref<EditorExportPreset> &p_prese
DirAccess::remove_file_or_error(basepath + ".html");
DirAccess::remove_file_or_error(basepath + ".js");
DirAccess::remove_file_or_error(basepath + ".worker.js");
+ DirAccess::remove_file_or_error(basepath + ".audio.worklet.js");
DirAccess::remove_file_or_error(basepath + ".pck");
DirAccess::remove_file_or_error(basepath + ".png");
+ DirAccess::remove_file_or_error(basepath + ".side.wasm");
DirAccess::remove_file_or_error(basepath + ".wasm");
DirAccess::remove_file_or_error(EditorSettings::get_singleton()->get_cache_dir().plus_file("favicon.png"));
return err;
@@ -606,7 +665,6 @@ Error EditorExportPlatformJavaScript::run(const Ref<EditorExportPreset> &p_prese
}
Ref<Texture2D> EditorExportPlatformJavaScript::get_run_icon() const {
-
return run_icon;
}
@@ -622,9 +680,7 @@ void EditorExportPlatformJavaScript::_server_thread_poll(void *data) {
}
EditorExportPlatformJavaScript::EditorExportPlatformJavaScript() {
-
server.instance();
- server_quit = false;
server_thread = Thread::create(_server_thread_poll, this);
Ref<Image> img = memnew(Image(_javascript_logo));
@@ -636,12 +692,11 @@ EditorExportPlatformJavaScript::EditorExportPlatformJavaScript() {
run_icon->create_from_image(img);
Ref<Theme> theme = EditorNode::get_singleton()->get_editor_theme();
- if (theme.is_valid())
+ if (theme.is_valid()) {
stop_icon = theme->get_icon("Stop", "EditorIcons");
- else
+ } else {
stop_icon.instance();
-
- menu_options = 0;
+ }
}
EditorExportPlatformJavaScript::~EditorExportPlatformJavaScript() {
@@ -652,7 +707,6 @@ EditorExportPlatformJavaScript::~EditorExportPlatformJavaScript() {
}
void register_javascript_exporter() {
-
EDITOR_DEF("export/web/http_host", "localhost");
EDITOR_DEF("export/web/http_port", 8060);
EditorSettings::get_singleton()->add_property_hint(PropertyInfo(Variant::INT, "export/web/http_port", PROPERTY_HINT_RANGE, "1,65535,1"));
diff --git a/platform/javascript/export/export.h b/platform/javascript/export/export.h
index 30c5c855d1..e641339f55 100644
--- a/platform/javascript/export/export.h
+++ b/platform/javascript/export/export.h
@@ -5,8 +5,8 @@
/* GODOT ENGINE */
/* https://godotengine.org */
/*************************************************************************/
-/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */
-/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */
+/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
diff --git a/platform/javascript/godot_audio.h b/platform/javascript/godot_audio.h
new file mode 100644
index 0000000000..54fc8fa3b5
--- /dev/null
+++ b/platform/javascript/godot_audio.h
@@ -0,0 +1,63 @@
+/*************************************************************************/
+/* godot_audio.h */
+/*************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/*************************************************************************/
+/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/*************************************************************************/
+
+#ifndef GODOT_AUDIO_H
+#define GODOT_AUDIO_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#include "stddef.h"
+
+extern int godot_audio_is_available();
+extern int godot_audio_init(int p_mix_rate, int p_latency, void (*_state_cb)(int), void (*_latency_cb)(float));
+extern void godot_audio_resume();
+
+extern int godot_audio_capture_start();
+extern void godot_audio_capture_stop();
+
+// Worklet
+typedef int32_t GodotAudioState[4];
+extern void godot_audio_worklet_create(int p_channels);
+extern void godot_audio_worklet_start(float *p_in_buf, int p_in_size, float *p_out_buf, int p_out_size, GodotAudioState p_state);
+extern int godot_audio_worklet_state_add(GodotAudioState p_state, int p_idx, int p_value);
+extern int godot_audio_worklet_state_get(GodotAudioState p_state, int p_idx);
+extern int godot_audio_worklet_state_wait(int32_t *p_state, int p_idx, int32_t p_expected, int p_timeout);
+
+// Script
+extern int godot_audio_script_create(int p_buffer_size, int p_channels);
+extern void godot_audio_script_start(float *p_in_buf, int p_in_size, float *p_out_buf, int p_out_size, void (*p_cb)());
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* GODOT_AUDIO_H */
diff --git a/platform/javascript/godot_js.h b/platform/javascript/godot_js.h
new file mode 100644
index 0000000000..5b98253b08
--- /dev/null
+++ b/platform/javascript/godot_js.h
@@ -0,0 +1,87 @@
+/*************************************************************************/
+/* godot_js.h */
+/*************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/*************************************************************************/
+/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/*************************************************************************/
+
+#ifndef GODOT_JS_H
+#define GODOT_JS_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#include "stddef.h"
+
+// Config
+extern void godot_js_config_locale_get(char *p_ptr, int p_ptr_max);
+extern void godot_js_config_canvas_id_get(char *p_ptr, int p_ptr_max);
+extern int godot_js_config_is_resize_on_start();
+
+// OS
+extern void godot_js_os_finish_async(void (*p_callback)());
+extern void godot_js_os_request_quit_cb(void (*p_callback)());
+extern int godot_js_os_fs_is_persistent();
+extern void godot_js_os_fs_sync(void (*p_callback)());
+extern int godot_js_os_execute(const char *p_json);
+extern void godot_js_os_shell_open(const char *p_uri);
+
+// Display
+extern double godot_js_display_pixel_ratio_get();
+extern void godot_js_display_alert(const char *p_text);
+extern int godot_js_display_touchscreen_is_available();
+extern int godot_js_display_is_swap_ok_cancel();
+
+// Display canvas
+extern void godot_js_display_canvas_focus();
+extern int godot_js_display_canvas_is_focused();
+extern void godot_js_display_canvas_bounding_rect_position_get(int32_t *p_x, int32_t *p_y);
+
+// Display window
+extern void godot_js_display_window_request_fullscreen();
+extern void godot_js_display_window_title_set(const char *p_text);
+extern void godot_js_display_window_icon_set(const uint8_t *p_ptr, int p_len);
+
+// Display clipboard
+extern int godot_js_display_clipboard_set(const char *p_text);
+extern int godot_js_display_clipboard_get(void (*p_callback)(const char *p_text));
+
+// Display cursor
+extern void godot_js_display_cursor_set_shape(const char *p_cursor);
+extern int godot_js_display_cursor_is_hidden();
+extern void godot_js_display_cursor_set_custom_shape(const char *p_shape, const uint8_t *p_ptr, int p_len, int p_hotspot_x, int p_hotspot_y);
+extern void godot_js_display_cursor_set_visible(int p_visible);
+
+// Display listeners
+extern void godot_js_display_notification_cb(void (*p_callback)(int p_notification), int p_enter, int p_exit, int p_in, int p_out);
+extern void godot_js_display_paste_cb(void (*p_callback)(const char *p_text));
+extern void godot_js_display_drop_files_cb(void (*p_callback)(char **p_filev, int p_filec));
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* GODOT_JS_H */
diff --git a/platform/javascript/http_client.h.inc b/platform/javascript/http_client.h.inc
index ac275aadbc..2ef1ad5b83 100644
--- a/platform/javascript/http_client.h.inc
+++ b/platform/javascript/http_client.h.inc
@@ -5,8 +5,8 @@
/* GODOT ENGINE */
/* https://godotengine.org */
/*************************************************************************/
-/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */
-/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */
+/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
@@ -33,21 +33,21 @@
Error prepare_request(Method p_method, const String &p_url, const Vector<String> &p_headers);
int xhr_id;
-int read_limit;
-int response_read_offset;
-Status status;
+int read_limit = 4096;
+int response_read_offset = 0;
+Status status = STATUS_DISCONNECTED;
String host;
-int port;
-bool use_tls;
+int port = -1;
+bool use_tls = false;
String username;
String password;
-int polled_response_code;
+int polled_response_code = 0;
String polled_response_header;
PackedByteArray polled_response;
#ifdef DEBUG_ENABLED
-bool has_polled;
-uint64_t last_polling_frame;
+bool has_polled = false;
+uint64_t last_polling_frame = 0;
#endif
diff --git a/platform/javascript/http_client_javascript.cpp b/platform/javascript/http_client_javascript.cpp
index 863c207896..c8c48dd582 100644
--- a/platform/javascript/http_client_javascript.cpp
+++ b/platform/javascript/http_client_javascript.cpp
@@ -5,8 +5,8 @@
/* GODOT ENGINE */
/* https://godotengine.org */
/*************************************************************************/
-/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */
-/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */
+/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
@@ -29,10 +29,10 @@
/*************************************************************************/
#include "core/io/http_client.h"
+
#include "http_request.h"
Error HTTPClient::connect_to_host(const String &p_host, int p_port, bool p_ssl, bool p_verify_host) {
-
close();
if (p_ssl && !p_verify_host) {
WARN_PRINT("Disabling HTTPClient's host verification is not supported for the HTML5 platform, host will be verified");
@@ -67,29 +67,26 @@ Error HTTPClient::connect_to_host(const String &p_host, int p_port, bool p_ssl,
}
void HTTPClient::set_connection(const Ref<StreamPeer> &p_connection) {
-
ERR_FAIL_MSG("Accessing an HTTPClient's StreamPeer is not supported for the HTML5 platform.");
}
Ref<StreamPeer> HTTPClient::get_connection() const {
-
ERR_FAIL_V_MSG(REF(), "Accessing an HTTPClient's StreamPeer is not supported for the HTML5 platform.");
}
Error HTTPClient::prepare_request(Method p_method, const String &p_url, const Vector<String> &p_headers) {
-
ERR_FAIL_INDEX_V(p_method, METHOD_MAX, ERR_INVALID_PARAMETER);
ERR_FAIL_COND_V_MSG(p_method == METHOD_TRACE || p_method == METHOD_CONNECT, ERR_UNAVAILABLE, "HTTP methods TRACE and CONNECT are not supported for the HTML5 platform.");
ERR_FAIL_COND_V(status != STATUS_CONNECTED, ERR_INVALID_PARAMETER);
- ERR_FAIL_COND_V(host.empty(), ERR_UNCONFIGURED);
+ ERR_FAIL_COND_V(host.is_empty(), ERR_UNCONFIGURED);
ERR_FAIL_COND_V(port < 0, ERR_UNCONFIGURED);
ERR_FAIL_COND_V(!p_url.begins_with("/"), ERR_INVALID_PARAMETER);
String url = (use_tls ? "https://" : "http://") + host + ":" + itos(port) + p_url;
godot_xhr_reset(xhr_id);
godot_xhr_open(xhr_id, _methods[p_method], url.utf8().get_data(),
- username.empty() ? nullptr : username.utf8().get_data(),
- password.empty() ? nullptr : password.utf8().get_data());
+ username.is_empty() ? nullptr : username.utf8().get_data(),
+ password.is_empty() ? nullptr : password.utf8().get_data());
for (int i = 0; i < p_headers.size(); i++) {
int header_separator = p_headers[i].find(": ");
@@ -104,7 +101,6 @@ Error HTTPClient::prepare_request(Method p_method, const String &p_url, const Ve
}
Error HTTPClient::request_raw(Method p_method, const String &p_url, const Vector<String> &p_headers, const Vector<uint8_t> &p_body) {
-
Error err = prepare_request(p_method, p_url, p_headers);
if (err != OK)
return err;
@@ -113,7 +109,6 @@ Error HTTPClient::request_raw(Method p_method, const String &p_url, const Vector
}
Error HTTPClient::request(Method p_method, const String &p_url, const Vector<String> &p_headers, const String &p_body) {
-
Error err = prepare_request(p_method, p_url, p_headers);
if (err != OK)
return err;
@@ -122,7 +117,6 @@ Error HTTPClient::request(Method p_method, const String &p_url, const Vector<Str
}
void HTTPClient::close() {
-
host = "";
port = -1;
use_tls = false;
@@ -134,29 +128,24 @@ void HTTPClient::close() {
}
HTTPClient::Status HTTPClient::get_status() const {
-
return status;
}
bool HTTPClient::has_response() const {
-
- return !polled_response_header.empty();
+ return !polled_response_header.is_empty();
}
bool HTTPClient::is_response_chunked() const {
-
// TODO evaluate using moz-chunked-arraybuffer, fetch & ReadableStream
return false;
}
int HTTPClient::get_response_code() const {
-
return polled_response_code;
}
Error HTTPClient::get_response_headers(List<String> *r_response) {
-
- if (polled_response_header.empty())
+ if (polled_response_header.is_empty())
return ERR_INVALID_PARAMETER;
Vector<String> header_lines = polled_response_header.split("\r\n", false);
@@ -168,12 +157,10 @@ Error HTTPClient::get_response_headers(List<String> *r_response) {
}
int HTTPClient::get_response_body_length() const {
-
return polled_response.size();
}
PackedByteArray HTTPClient::read_response_body_chunk() {
-
ERR_FAIL_COND_V(status != STATUS_BODY, PackedByteArray());
int to_read = MIN(read_limit, polled_response.size() - response_read_offset);
@@ -192,17 +179,14 @@ PackedByteArray HTTPClient::read_response_body_chunk() {
}
void HTTPClient::set_blocking_mode(bool p_enable) {
-
ERR_FAIL_COND_MSG(p_enable, "HTTPClient blocking mode is not supported for the HTML5 platform.");
}
bool HTTPClient::is_blocking_mode_enabled() const {
-
return false;
}
void HTTPClient::set_read_chunk_size(int p_size) {
-
read_limit = p_size;
}
@@ -211,9 +195,7 @@ int HTTPClient::get_read_chunk_size() const {
}
Error HTTPClient::poll() {
-
switch (status) {
-
case STATUS_DISCONNECTED:
return ERR_UNCONFIGURED;
@@ -233,19 +215,18 @@ Error HTTPClient::poll() {
return ERR_CONNECTION_ERROR;
case STATUS_REQUESTING: {
-
#ifdef DEBUG_ENABLED
if (!has_polled) {
has_polled = true;
} else {
// forcing synchronous requests is not possible on the web
- if (last_polling_frame == Engine::get_singleton()->get_idle_frames()) {
+ if (last_polling_frame == Engine::get_singleton()->get_process_frames()) {
WARN_PRINT("HTTPClient polled multiple times in one frame, "
"but request cannot progress more than once per "
"frame on the HTML5 platform.");
}
}
- last_polling_frame = Engine::get_singleton()->get_idle_frames();
+ last_polling_frame = Engine::get_singleton()->get_process_frames();
#endif
polled_response_code = godot_xhr_get_status(xhr_id);
@@ -279,20 +260,9 @@ Error HTTPClient::poll() {
}
HTTPClient::HTTPClient() {
-
xhr_id = godot_xhr_new();
- read_limit = 4096;
- status = STATUS_DISCONNECTED;
- port = -1;
- use_tls = false;
- polled_response_code = 0;
-#ifdef DEBUG_ENABLED
- has_polled = false;
- last_polling_frame = 0;
-#endif
}
HTTPClient::~HTTPClient() {
-
godot_xhr_free(xhr_id);
}
diff --git a/platform/javascript/http_request.h b/platform/javascript/http_request.h
index 54e98c1927..d32b2f265e 100644
--- a/platform/javascript/http_request.h
+++ b/platform/javascript/http_request.h
@@ -5,8 +5,8 @@
/* GODOT ENGINE */
/* https://godotengine.org */
/*************************************************************************/
-/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */
-/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */
+/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
@@ -47,7 +47,7 @@ typedef enum {
extern int godot_xhr_new();
extern void godot_xhr_reset(int p_xhr_id);
-extern bool godot_xhr_free(int p_xhr_id);
+extern void godot_xhr_free(int p_xhr_id);
extern int godot_xhr_open(int p_xhr_id, const char *p_method, const char *p_url, const char *p_user = nullptr, const char *p_password = nullptr);
diff --git a/platform/javascript/javascript_eval.cpp b/platform/javascript/javascript_eval.cpp
index db8050b90e..cb19dd20d4 100644
--- a/platform/javascript/javascript_eval.cpp
+++ b/platform/javascript/javascript_eval.cpp
@@ -5,8 +5,8 @@
/* GODOT ENGINE */
/* https://godotengine.org */
/*************************************************************************/
-/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */
-/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */
+/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
@@ -33,97 +33,30 @@
#include "api/javascript_eval.h"
#include "emscripten.h"
-extern "C" EMSCRIPTEN_KEEPALIVE uint8_t *resize_PackedByteArray_and_open_write(PackedByteArray *p_arr, VectorWriteProxy<uint8_t> *r_write, int p_len) {
+extern "C" {
+union js_eval_ret {
+ uint32_t b;
+ double d;
+ char *s;
+};
- p_arr->resize(p_len);
- *r_write = p_arr->write;
- return p_arr->ptrw();
+extern int godot_js_eval(const char *p_js, int p_use_global_ctx, union js_eval_ret *p_union_ptr, void *p_byte_arr, void *p_byte_arr_write, void *(*p_callback)(void *p_ptr, void *p_ptr2, int p_len));
}
-Variant JavaScript::eval(const String &p_code, bool p_use_global_exec_context) {
-
- union {
- bool b;
- double d;
- char *s;
- } js_data;
+void *resize_PackedByteArray_and_open_write(void *p_arr, void *r_write, int p_len) {
+ PackedByteArray *arr = (PackedByteArray *)p_arr;
+ VectorWriteProxy<uint8_t> *write = (VectorWriteProxy<uint8_t> *)r_write;
+ arr->resize(p_len);
+ *write = arr->write;
+ return arr->ptrw();
+}
+Variant JavaScript::eval(const String &p_code, bool p_use_global_exec_context) {
+ union js_eval_ret js_data;
PackedByteArray arr;
VectorWriteProxy<uint8_t> arr_write;
- /* clang-format off */
- Variant::Type return_type = static_cast<Variant::Type>(EM_ASM_INT({
-
- const CODE = $0;
- const USE_GLOBAL_EXEC_CONTEXT = $1;
- const PTR = $2;
- const BYTEARRAY_PTR = $3;
- const BYTEARRAY_WRITE_PTR = $4;
- var eval_ret;
- try {
- if (USE_GLOBAL_EXEC_CONTEXT) {
- // indirect eval call grants global execution context
- var global_eval = eval;
- eval_ret = global_eval(UTF8ToString(CODE));
- } else {
- eval_ret = eval(UTF8ToString(CODE));
- }
- } catch (e) {
- err(e);
- eval_ret = null;
- }
-
- switch (typeof eval_ret) {
-
- case 'boolean':
- setValue(PTR, eval_ret, 'i32');
- return 1; // BOOL
-
- case 'number':
- setValue(PTR, eval_ret, 'double');
- return 3; // FLOAT
-
- case 'string':
- var array_len = lengthBytesUTF8(eval_ret)+1;
- var array_ptr = _malloc(array_len);
- try {
- if (array_ptr===0) {
- throw new Error('String allocation failed (probably out of memory)');
- }
- setValue(PTR, array_ptr , '*');
- stringToUTF8(eval_ret, array_ptr, array_len);
- return 4; // STRING
- } catch (e) {
- if (array_ptr!==0) {
- _free(array_ptr)
- }
- err(e);
- // fall through
- }
- break;
-
- case 'object':
- if (eval_ret === null) {
- break;
- }
-
- if (ArrayBuffer.isView(eval_ret) && !(eval_ret instanceof Uint8Array)) {
- eval_ret = new Uint8Array(eval_ret.buffer);
- }
- else if (eval_ret instanceof ArrayBuffer) {
- eval_ret = new Uint8Array(eval_ret);
- }
- if (eval_ret instanceof Uint8Array) {
- var bytes_ptr = ccall('resize_PackedByteArray_and_open_write', 'number', ['number', 'number' ,'number'], [BYTEARRAY_PTR, BYTEARRAY_WRITE_PTR, eval_ret.length]);
- HEAPU8.set(eval_ret, bytes_ptr);
- return 20; // PACKED_BYTE_ARRAY
- }
- break;
- }
- return 0; // NIL
-
- }, p_code.utf8().get_data(), p_use_global_exec_context, &js_data, &arr, &arr_write));
- /* clang-format on */
+ Variant::Type return_type = static_cast<Variant::Type>(godot_js_eval(p_code.utf8().get_data(), p_use_global_exec_context, &js_data, &arr, &arr_write, resize_PackedByteArray_and_open_write));
switch (return_type) {
case Variant::BOOL:
@@ -132,9 +65,7 @@ Variant JavaScript::eval(const String &p_code, bool p_use_global_exec_context) {
return js_data.d;
case Variant::STRING: {
String str = String::utf8(js_data.s);
- /* clang-format off */
- EM_ASM_({ _free($0); }, js_data.s);
- /* clang-format on */
+ free(js_data.s); // Must free the string allocated in JS.
return str;
}
case Variant::PACKED_BYTE_ARRAY:
diff --git a/platform/javascript/javascript_main.cpp b/platform/javascript/javascript_main.cpp
index 815bc7e456..0b8af70b13 100644
--- a/platform/javascript/javascript_main.cpp
+++ b/platform/javascript/javascript_main.cpp
@@ -5,8 +5,8 @@
/* GODOT ENGINE */
/* https://godotengine.org */
/*************************************************************************/
-/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */
-/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */
+/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
@@ -30,42 +30,68 @@
#include "core/io/resource_loader.h"
#include "main/main.h"
-#include "os_javascript.h"
+#include "platform/javascript/display_server_javascript.h"
+#include "platform/javascript/os_javascript.h"
#include <emscripten/emscripten.h>
+#include <stdlib.h>
-extern "C" EMSCRIPTEN_KEEPALIVE void main_after_fs_sync(char *p_idbfs_err) {
+#include "godot_js.h"
- String idbfs_err = String::utf8(p_idbfs_err);
- if (!idbfs_err.empty()) {
- print_line("IndexedDB not available: " + idbfs_err);
+static OS_JavaScript *os = nullptr;
+static uint64_t target_ticks = 0;
+
+void exit_callback() {
+ emscripten_cancel_main_loop(); // After this, we can exit!
+ Main::cleanup();
+ int exit_code = OS_JavaScript::get_singleton()->get_exit_code();
+ memdelete(os);
+ os = nullptr;
+ emscripten_force_exit(exit_code); // No matter that we call cancel_main_loop, regular "exit" will not work, forcing.
+}
+
+void cleanup_after_sync() {
+ emscripten_set_main_loop(exit_callback, -1, false);
+}
+
+void main_loop_callback() {
+ uint64_t current_ticks = os->get_ticks_usec();
+
+ bool force_draw = DisplayServerJavaScript::get_singleton()->check_size_force_redraw();
+ if (force_draw) {
+ Main::force_redraw();
+ } else if (current_ticks < target_ticks) {
+ return; // Skip frame.
+ }
+
+ int target_fps = Engine::get_singleton()->get_target_fps();
+ if (target_fps > 0) {
+ target_ticks += (uint64_t)(1000000 / target_fps);
+ }
+ if (os->main_loop_iterate()) {
+ emscripten_cancel_main_loop(); // Cancel current loop and wait for cleanup_after_sync.
+ godot_js_os_finish_async(cleanup_after_sync);
}
- OS_JavaScript *os = OS_JavaScript::get_singleton();
- os->set_idb_available(idbfs_err.empty());
- // Ease up compatibility.
- ResourceLoader::set_abort_on_missing_resources(false);
- Main::start();
- os->run_async();
}
-int main(int argc, char *argv[]) {
+/// When calling main, it is assumed FS is setup and synced.
+extern EMSCRIPTEN_KEEPALIVE int godot_js_main(int argc, char *argv[]) {
+ os = new OS_JavaScript();
- // Sync from persistent state into memory and then
- // run the 'main_after_fs_sync' function.
- /* clang-format off */
- EM_ASM(
- FS.mkdir('/userfs');
- FS.mount(IDBFS, {}, '/userfs');
- FS.syncfs(true, function(err) {
- ccall('main_after_fs_sync', null, ['string'], [err ? err.message : ""])
- });
- );
- /* clang-format on */
+ // We must override main when testing is enabled
+ TEST_MAIN_OVERRIDE
- new OS_JavaScript(argc, argv);
- // TODO: Check error return value.
Main::setup(argv[0], argc - 1, &argv[1]);
+ // Ease up compatibility.
+ ResourceLoader::set_abort_on_missing_resources(false);
+
+ Main::start();
+ os->get_main_loop()->initialize();
+ emscripten_set_main_loop(main_loop_callback, -1, false);
+ // Immediately run the first iteration.
+ // We are inside an animation frame, we want to immediately draw on the newly setup canvas.
+ main_loop_callback();
+
return 0;
- // Continued async in main_after_fs_sync() from the syncfs() callback.
}
diff --git a/platform/javascript/id_handler.js b/platform/javascript/javascript_runtime.cpp
index 67d29075b8..2996e95a95 100644
--- a/platform/javascript/id_handler.js
+++ b/platform/javascript/javascript_runtime.cpp
@@ -1,12 +1,12 @@
/*************************************************************************/
-/* id_handler.js */
+/* javascript_runtime.cpp */
/*************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/*************************************************************************/
-/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */
-/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */
+/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
@@ -28,36 +28,8 @@
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/*************************************************************************/
-var IDHandler = /** @constructor */ function() {
+extern int godot_js_main(int argc, char *argv[]);
- var ids = {};
- var size = 0;
-
- this.has = function(id) {
- return ids.hasOwnProperty(id);
- }
-
- this.add = function(obj) {
- size += 1;
- var id = crypto.getRandomValues(new Int32Array(32))[0];
- ids[id] = obj;
- return id;
- }
-
- this.get = function(id) {
- return ids[id];
- }
-
- this.remove = function(id) {
- size -= 1;
- delete ids[id];
- }
-
- this.size = function() {
- return size;
- }
-
- this.ids = ids;
-};
-
-Module.IDHandler = new IDHandler;
+int main(int argc, char *argv[]) {
+ return godot_js_main(argc, argv);
+}
diff --git a/platform/javascript/js/dynlink.pre.js b/platform/javascript/js/dynlink.pre.js
new file mode 100644
index 0000000000..34bc371ea9
--- /dev/null
+++ b/platform/javascript/js/dynlink.pre.js
@@ -0,0 +1 @@
+Module['dynamicLibraries'] = [Module['thisProgram'] + '.side.wasm'].concat(Module['dynamicLibraries'] ? Module['dynamicLibraries'] : []);
diff --git a/platform/javascript/engine/externs.js b/platform/javascript/js/engine/engine.externs.js
index 1a94dd15ec..1a94dd15ec 100644
--- a/platform/javascript/engine/externs.js
+++ b/platform/javascript/js/engine/engine.externs.js
diff --git a/platform/javascript/js/engine/engine.js b/platform/javascript/js/engine/engine.js
new file mode 100644
index 0000000000..4b8a7dde69
--- /dev/null
+++ b/platform/javascript/js/engine/engine.js
@@ -0,0 +1,292 @@
+const Engine = (function () {
+ const preloader = new Preloader();
+
+ let wasmExt = '.wasm';
+ let unloadAfterInit = true;
+ let loadPath = '';
+ let loadPromise = null;
+ let initPromise = null;
+ let stderr = null;
+ let stdout = null;
+ let progressFunc = null;
+
+ function load(basePath) {
+ if (loadPromise == null) {
+ loadPath = basePath;
+ loadPromise = preloader.loadPromise(basePath + wasmExt);
+ preloader.setProgressFunc(progressFunc);
+ requestAnimationFrame(preloader.animateProgress);
+ }
+ return loadPromise;
+ }
+
+ function unload() {
+ loadPromise = null;
+ }
+
+ /** @constructor */
+ function Engine() { // eslint-disable-line no-shadow
+ this.canvas = null;
+ this.executableName = '';
+ this.rtenv = null;
+ this.customLocale = null;
+ this.resizeCanvasOnStart = false;
+ this.onExecute = null;
+ this.onExit = null;
+ this.persistentPaths = ['/userfs'];
+ this.gdnativeLibs = [];
+ }
+
+ Engine.prototype.init = /** @param {string=} basePath */ function (basePath) {
+ if (initPromise) {
+ return initPromise;
+ }
+ 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);
+ }
+ let config = {};
+ if (typeof stdout === 'function') {
+ config.print = stdout;
+ }
+ if (typeof stderr === 'function') {
+ config.printErr = stderr;
+ }
+ const me = this;
+ initPromise = new Promise(function (resolve, reject) {
+ config['locateFile'] = Utils.createLocateRewrite(loadPath);
+ config['instantiateWasm'] = Utils.createInstantiatePromise(loadPromise);
+ // Emscripten configuration.
+ config['thisProgram'] = me.executableName;
+ config['noExitRuntime'] = true;
+ config['dynamicLibraries'] = me.gdnativeLibs;
+ Godot(config).then(function (module) {
+ module['initFS'](me.persistentPaths).then(function (fs_err) {
+ me.rtenv = module;
+ if (unloadAfterInit) {
+ unload();
+ }
+ resolve();
+ config = null;
+ });
+ });
+ });
+ return initPromise;
+ };
+
+ /** @type {function(string, string):Object} */
+ Engine.prototype.preloadFile = function (file, path) {
+ return preloader.preload(file, path);
+ };
+
+ /** @type {function(...string):Object} */
+ Engine.prototype.start = function () {
+ // Start from arguments.
+ const args = [];
+ for (let i = 0; i < arguments.length; i++) {
+ args.push(arguments[i]);
+ }
+ const me = this;
+ return me.init().then(function () {
+ if (!me.rtenv) {
+ return Promise.reject(new Error('The engine must be initialized before it can be started'));
+ }
+
+ if (!(me.canvas instanceof HTMLCanvasElement)) {
+ me.canvas = Utils.findCanvas();
+ if (!me.canvas) {
+ return Promise.reject(new Error('No canvas found in page'));
+ }
+ }
+
+ // 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'); // eslint-disable-line no-alert
+ ev.preventDefault();
+ }, false);
+
+ // Browser locale, or custom one if defined.
+ let locale = me.customLocale;
+ if (!locale) {
+ locale = navigator.languages ? navigator.languages[0] : navigator.language;
+ locale = locale.split('.')[0];
+ }
+ // Godot configuration.
+ me.rtenv['initConfig']({
+ 'resizeCanvasOnStart': me.resizeCanvasOnStart,
+ 'canvas': me.canvas,
+ 'locale': locale,
+ 'onExecute': function (p_args) {
+ if (me.onExecute) {
+ me.onExecute(p_args);
+ return 0;
+ }
+ return 1;
+ },
+ 'onExit': function (p_code) {
+ me.rtenv['deinitFS']();
+ if (me.onExit) {
+ me.onExit(p_code);
+ }
+ me.rtenv = null;
+ },
+ });
+
+ return new Promise(function (resolve, reject) {
+ preloader.preloadedFiles.forEach(function (file) {
+ me.rtenv['copyToFS'](file.path, file.buffer);
+ });
+ preloader.preloadedFiles.length = 0; // Clear memory
+ me.rtenv['callMain'](args);
+ initPromise = null;
+ resolve();
+ });
+ });
+ };
+
+ Engine.prototype.startGame = function (execName, mainPack, extraArgs) {
+ // Start and init with execName as loadPath if not inited.
+ this.executableName = execName;
+ const me = this;
+ return Promise.all([
+ this.init(execName),
+ this.preloadFile(mainPack, mainPack),
+ ]).then(function () {
+ let args = ['--main-pack', mainPack];
+ if (extraArgs) {
+ args = args.concat(extraArgs);
+ }
+ return me.start.apply(me, args);
+ });
+ };
+
+ Engine.prototype.setWebAssemblyFilenameExtension = function (override) {
+ if (String(override).length === 0) {
+ throw new Error('Invalid WebAssembly filename extension override');
+ }
+ wasmExt = String(override);
+ };
+
+ Engine.prototype.setUnloadAfterInit = function (enabled) {
+ unloadAfterInit = enabled;
+ };
+
+ Engine.prototype.setCanvas = function (canvasElem) {
+ this.canvas = canvasElem;
+ };
+
+ Engine.prototype.setCanvasResizedOnStart = function (enabled) {
+ this.resizeCanvasOnStart = enabled;
+ };
+
+ Engine.prototype.setLocale = function (locale) {
+ this.customLocale = locale;
+ };
+
+ Engine.prototype.setExecutableName = function (newName) {
+ this.executableName = newName;
+ };
+
+ Engine.prototype.setProgressFunc = function (func) {
+ progressFunc = func;
+ };
+
+ Engine.prototype.setStdoutFunc = function (func) {
+ const print = function (text) {
+ let msg = text;
+ if (arguments.length > 1) {
+ msg = Array.prototype.slice.call(arguments).join(' ');
+ }
+ func(msg);
+ };
+ if (this.rtenv) {
+ this.rtenv.print = print;
+ }
+ stdout = print;
+ };
+
+ Engine.prototype.setStderrFunc = function (func) {
+ const printErr = function (text) {
+ let msg = text;
+ if (arguments.length > 1) {
+ msg = Array.prototype.slice.call(arguments).join(' ');
+ }
+ func(msg);
+ };
+ if (this.rtenv) {
+ this.rtenv.printErr = printErr;
+ }
+ stderr = printErr;
+ };
+
+ Engine.prototype.setOnExecute = function (onExecute) {
+ this.onExecute = onExecute;
+ };
+
+ 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');
+ }
+ this.rtenv['copyToFS'](path, buffer);
+ };
+
+ Engine.prototype.setPersistentPaths = function (persistentPaths) {
+ this.persistentPaths = persistentPaths;
+ };
+
+ Engine.prototype.setGDNativeLibraries = function (gdnativeLibs) {
+ this.gdnativeLibs = gdnativeLibs;
+ };
+
+ Engine.prototype.requestQuit = function () {
+ if (this.rtenv) {
+ this.rtenv['request_quit']();
+ }
+ };
+
+ // 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['setOnExecute'] = Engine.prototype.setOnExecute;
+ Engine.prototype['setOnExit'] = Engine.prototype.setOnExit;
+ Engine.prototype['copyToFS'] = Engine.prototype.copyToFS;
+ Engine.prototype['setPersistentPaths'] = Engine.prototype.setPersistentPaths;
+ Engine.prototype['setGDNativeLibraries'] = Engine.prototype.setGDNativeLibraries;
+ Engine.prototype['requestQuit'] = Engine.prototype.requestQuit;
+ return Engine;
+}());
+if (typeof window !== 'undefined') {
+ window['Engine'] = Engine;
+}
diff --git a/platform/javascript/js/engine/preloader.js b/platform/javascript/js/engine/preloader.js
new file mode 100644
index 0000000000..ec34fb93f2
--- /dev/null
+++ b/platform/javascript/js/engine/preloader.js
@@ -0,0 +1,127 @@
+const Preloader = /** @constructor */ function () { // eslint-disable-line no-unused-vars
+ const loadXHR = function (resolve, reject, file, tracker, attempts) {
+ const xhr = new XMLHttpRequest();
+ tracker[file] = {
+ total: 0,
+ loaded: 0,
+ final: false,
+ };
+ xhr.onerror = function () {
+ if (attempts <= 1) {
+ reject(new Error(`Failed loading file '${file}'`));
+ } else {
+ setTimeout(function () {
+ loadXHR(resolve, reject, file, tracker, attempts - 1);
+ }, 1000);
+ }
+ };
+ xhr.onabort = function () {
+ tracker[file].final = true;
+ reject(new Error(`Loading file '${file}' was aborted.`));
+ };
+ xhr.onloadstart = function (ev) {
+ tracker[file].total = ev.total;
+ tracker[file].loaded = ev.loaded;
+ };
+ xhr.onprogress = function (ev) {
+ tracker[file].loaded = ev.loaded;
+ tracker[file].total = ev.total;
+ };
+ xhr.onload = function () {
+ if (xhr.status >= 400) {
+ if (xhr.status < 500 || attempts <= 1) {
+ reject(new Error(`Failed loading file '${file}': ${xhr.statusText}`));
+ xhr.abort();
+ } else {
+ setTimeout(function () {
+ loadXHR(resolve, reject, file, tracker, attempts - 1);
+ }, 1000);
+ }
+ } else {
+ tracker[file].final = true;
+ resolve(xhr);
+ }
+ };
+ // Make request.
+ xhr.open('GET', file);
+ if (!file.endsWith('.js')) {
+ xhr.responseType = 'arraybuffer';
+ }
+ xhr.send();
+ };
+
+ const DOWNLOAD_ATTEMPTS_MAX = 4;
+ const loadingFiles = {};
+ const lastProgress = { loaded: 0, total: 0 };
+ let progressFunc = null;
+
+ const animateProgress = function () {
+ let loaded = 0;
+ let total = 0;
+ let totalIsValid = true;
+ let progressIsFinal = true;
+
+ Object.keys(loadingFiles).forEach(function (file) {
+ const stat = loadingFiles[file];
+ if (!stat.final) {
+ progressIsFinal = false;
+ }
+ if (!totalIsValid || stat.total === 0) {
+ totalIsValid = false;
+ total = 0;
+ } else {
+ total += stat.total;
+ }
+ loaded += stat.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.animateProgress = animateProgress;
+
+ this.setProgressFunc = function (callback) {
+ progressFunc = callback;
+ };
+
+ this.loadPromise = function (file) {
+ return new Promise(function (resolve, reject) {
+ loadXHR(resolve, reject, file, loadingFiles, DOWNLOAD_ATTEMPTS_MAX);
+ });
+ };
+
+ this.preloadedFiles = [];
+ this.preload = function (pathOrBuffer, destPath) {
+ let buffer = null;
+ if (typeof pathOrBuffer === 'string') {
+ const me = this;
+ return this.loadPromise(pathOrBuffer).then(function (xhr) {
+ me.preloadedFiles.push({
+ path: destPath || pathOrBuffer,
+ buffer: xhr.response,
+ });
+ return Promise.resolve();
+ });
+ } else if (pathOrBuffer instanceof ArrayBuffer) {
+ buffer = new Uint8Array(pathOrBuffer);
+ } else if (ArrayBuffer.isView(pathOrBuffer)) {
+ buffer = new Uint8Array(pathOrBuffer.buffer);
+ }
+ if (buffer) {
+ this.preloadedFiles.push({
+ path: destPath,
+ buffer: pathOrBuffer,
+ });
+ return Promise.resolve();
+ }
+ return Promise.reject(new Error('Invalid object for preloading'));
+ };
+};
diff --git a/platform/javascript/js/engine/utils.js b/platform/javascript/js/engine/utils.js
new file mode 100644
index 0000000000..9273bbad42
--- /dev/null
+++ b/platform/javascript/js/engine/utils.js
@@ -0,0 +1,58 @@
+const Utils = { // eslint-disable-line no-unused-vars
+
+ createLocateRewrite: function (execName) {
+ function rw(path) {
+ if (path.endsWith('.worker.js')) {
+ return `${execName}.worker.js`;
+ } else if (path.endsWith('.audio.worklet.js')) {
+ return `${execName}.audio.worklet.js`;
+ } else if (path.endsWith('.js')) {
+ return `${execName}.js`;
+ } else if (path.endsWith('.side.wasm')) {
+ return `${execName}.side.wasm`;
+ } else if (path.endsWith('.wasm')) {
+ return `${execName}.wasm`;
+ }
+ return path;
+ }
+ return rw;
+ },
+
+ createInstantiatePromise: function (wasmLoader) {
+ let loader = wasmLoader;
+ function instantiateWasm(imports, onSuccess) {
+ loader.then(function (xhr) {
+ WebAssembly.instantiate(xhr.response, imports).then(function (result) {
+ onSuccess(result['instance'], result['module']);
+ });
+ });
+ loader = null;
+ return {};
+ }
+
+ return instantiateWasm;
+ },
+
+ findCanvas: function () {
+ const nodes = document.getElementsByTagName('canvas');
+ if (nodes.length && nodes[0] instanceof HTMLCanvasElement) {
+ return nodes[0];
+ }
+ return null;
+ },
+
+ isWebGLAvailable: function (majorVersion = 1) {
+ let testContext = false;
+ try {
+ const testCanvas = document.createElement('canvas');
+ if (majorVersion === 1) {
+ testContext = testCanvas.getContext('webgl') || testCanvas.getContext('experimental-webgl');
+ } else if (majorVersion === 2) {
+ testContext = testCanvas.getContext('webgl2') || testCanvas.getContext('experimental-webgl2');
+ }
+ } catch (e) {
+ // Not available
+ }
+ return !!testContext;
+ },
+};
diff --git a/platform/javascript/js/libs/audio.worklet.js b/platform/javascript/js/libs/audio.worklet.js
new file mode 100644
index 0000000000..6b3f80c6a9
--- /dev/null
+++ b/platform/javascript/js/libs/audio.worklet.js
@@ -0,0 +1,186 @@
+/*************************************************************************/
+/* audio.worklet.js */
+/*************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/*************************************************************************/
+/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/*************************************************************************/
+
+class RingBuffer {
+ constructor(p_buffer, p_state) {
+ this.buffer = p_buffer;
+ this.avail = p_state;
+ this.rpos = 0;
+ this.wpos = 0;
+ }
+
+ data_left() {
+ return Atomics.load(this.avail, 0);
+ }
+
+ space_left() {
+ return this.buffer.length - this.data_left();
+ }
+
+ read(output) {
+ const size = this.buffer.length;
+ let from = 0;
+ let to_write = output.length;
+ if (this.rpos + to_write > size) {
+ const high = size - this.rpos;
+ output.set(this.buffer.subarray(this.rpos, size));
+ from = high;
+ to_write -= high;
+ this.rpos = 0;
+ }
+ output.set(this.buffer.subarray(this.rpos, this.rpos + to_write), from);
+ this.rpos += to_write;
+ Atomics.add(this.avail, 0, -output.length);
+ Atomics.notify(this.avail, 0);
+ }
+
+ write(p_buffer) {
+ const to_write = p_buffer.length;
+ const mw = this.buffer.length - this.wpos;
+ if (mw >= to_write) {
+ this.buffer.set(p_buffer, this.wpos);
+ } else {
+ const high = p_buffer.subarray(0, to_write - mw);
+ const low = p_buffer.subarray(to_write - mw);
+ this.buffer.set(high, this.wpos);
+ this.buffer.set(low);
+ }
+ let diff = to_write;
+ if (this.wpos + diff >= this.buffer.length) {
+ diff -= this.buffer.length;
+ }
+ this.wpos += diff;
+ Atomics.add(this.avail, 0, to_write);
+ Atomics.notify(this.avail, 0);
+ }
+}
+
+class GodotProcessor extends AudioWorkletProcessor {
+ constructor() {
+ super();
+ this.running = true;
+ this.lock = null;
+ this.notifier = null;
+ this.output = null;
+ this.output_buffer = new Float32Array();
+ this.input = null;
+ this.input_buffer = new Float32Array();
+ this.port.onmessage = (event) => {
+ const cmd = event.data['cmd'];
+ const data = event.data['data'];
+ this.parse_message(cmd, data);
+ };
+ }
+
+ process_notify() {
+ Atomics.add(this.notifier, 0, 1);
+ Atomics.notify(this.notifier, 0);
+ }
+
+ parse_message(p_cmd, p_data) {
+ if (p_cmd === 'start' && p_data) {
+ const state = p_data[0];
+ let idx = 0;
+ this.lock = state.subarray(idx, ++idx);
+ this.notifier = state.subarray(idx, ++idx);
+ const avail_in = state.subarray(idx, ++idx);
+ const avail_out = state.subarray(idx, ++idx);
+ this.input = new RingBuffer(p_data[1], avail_in);
+ this.output = new RingBuffer(p_data[2], avail_out);
+ } else if (p_cmd === 'stop') {
+ this.runing = false;
+ this.output = null;
+ this.input = null;
+ }
+ }
+
+ static array_has_data(arr) {
+ return arr.length && arr[0].length && arr[0][0].length;
+ }
+
+ process(inputs, outputs, parameters) {
+ if (!this.running) {
+ return false; // Stop processing.
+ }
+ if (this.output === null) {
+ return true; // Not ready yet, keep processing.
+ }
+ const process_input = GodotProcessor.array_has_data(inputs);
+ if (process_input) {
+ const input = inputs[0];
+ const chunk = input[0].length * input.length;
+ if (this.input_buffer.length !== chunk) {
+ this.input_buffer = new Float32Array(chunk);
+ }
+ if (this.input.space_left() >= chunk) {
+ GodotProcessor.write_input(this.input_buffer, input);
+ this.input.write(this.input_buffer);
+ } else {
+ this.port.postMessage('Input buffer is full! Skipping input frame.');
+ }
+ }
+ const process_output = GodotProcessor.array_has_data(outputs);
+ if (process_output) {
+ const output = outputs[0];
+ const chunk = output[0].length * output.length;
+ if (this.output_buffer.length !== chunk) {
+ this.output_buffer = new Float32Array(chunk);
+ }
+ if (this.output.data_left() >= chunk) {
+ this.output.read(this.output_buffer);
+ GodotProcessor.write_output(output, this.output_buffer);
+ } else {
+ this.port.postMessage('Output buffer has not enough frames! Skipping output frame.');
+ }
+ }
+ this.process_notify();
+ return true;
+ }
+
+ static write_output(dest, source) {
+ const channels = dest.length;
+ for (let ch = 0; ch < channels; ch++) {
+ for (let sample = 0; sample < dest[ch].length; sample++) {
+ dest[ch][sample] = source[sample * channels + ch];
+ }
+ }
+ }
+
+ static write_input(dest, source) {
+ const channels = source.length;
+ for (let ch = 0; ch < channels; ch++) {
+ for (let sample = 0; sample < source[ch].length; sample++) {
+ dest[sample * channels + ch] = source[ch][sample];
+ }
+ }
+ }
+}
+
+registerProcessor('godot-processor', GodotProcessor);
diff --git a/platform/javascript/js/libs/library_godot_audio.js b/platform/javascript/js/libs/library_godot_audio.js
new file mode 100644
index 0000000000..8e385e9176
--- /dev/null
+++ b/platform/javascript/js/libs/library_godot_audio.js
@@ -0,0 +1,362 @@
+/*************************************************************************/
+/* library_godot_audio.js */
+/*************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/*************************************************************************/
+/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/*************************************************************************/
+
+const GodotAudio = {
+ $GodotAudio__deps: ['$GodotRuntime', '$GodotOS'],
+ $GodotAudio: {
+ ctx: null,
+ input: null,
+ driver: null,
+ interval: 0,
+
+ init: function (mix_rate, latency, onstatechange, onlatencyupdate) {
+ const ctx = new (window.AudioContext || window.webkitAudioContext)({
+ sampleRate: mix_rate,
+ // latencyHint: latency / 1000 // Do not specify, leave 'interactive' for good performance.
+ });
+ GodotAudio.ctx = ctx;
+ ctx.onstatechange = function () {
+ let state = 0;
+ switch (ctx.state) {
+ case 'suspended':
+ state = 0;
+ break;
+ case 'running':
+ state = 1;
+ break;
+ case 'closed':
+ state = 2;
+ break;
+
+ // no default
+ }
+ onstatechange(state);
+ };
+ ctx.onstatechange(); // Immeditately notify state.
+ // Update computed latency
+ GodotAudio.interval = setInterval(function () {
+ let computed_latency = 0;
+ if (ctx.baseLatency) {
+ computed_latency += GodotAudio.ctx.baseLatency;
+ }
+ if (ctx.outputLatency) {
+ computed_latency += GodotAudio.ctx.outputLatency;
+ }
+ onlatencyupdate(computed_latency);
+ }, 1000);
+ GodotOS.atexit(GodotAudio.close_async);
+ return ctx.destination.channelCount;
+ },
+
+ create_input: function (callback) {
+ if (GodotAudio.input) {
+ return 0; // Already started.
+ }
+ function gotMediaInput(stream) {
+ try {
+ GodotAudio.input = GodotAudio.ctx.createMediaStreamSource(stream);
+ callback(GodotAudio.input);
+ } catch (e) {
+ GodotRuntime.error('Failed creaating input.', e);
+ }
+ }
+ if (navigator.mediaDevices && navigator.mediaDevices.getUserMedia) {
+ navigator.mediaDevices.getUserMedia({
+ 'audio': true,
+ }).then(gotMediaInput, function (e) {
+ GodotRuntime.error('Error getting user media.', e);
+ });
+ } else {
+ if (!navigator.getUserMedia) {
+ navigator.getUserMedia = navigator.webkitGetUserMedia || navigator.mozGetUserMedia;
+ }
+ if (!navigator.getUserMedia) {
+ GodotRuntime.error('getUserMedia not available.');
+ return 1;
+ }
+ navigator.getUserMedia({
+ 'audio': true,
+ }, gotMediaInput, function (e) {
+ GodotRuntime.print(e);
+ });
+ }
+ return 0;
+ },
+
+ close_async: function (resolve, reject) {
+ const ctx = GodotAudio.ctx;
+ GodotAudio.ctx = null;
+ // Audio was not initialized.
+ if (!ctx) {
+ resolve();
+ return;
+ }
+ // Remove latency callback
+ if (GodotAudio.interval) {
+ clearInterval(GodotAudio.interval);
+ GodotAudio.interval = 0;
+ }
+ // Disconnect input, if it was started.
+ if (GodotAudio.input) {
+ GodotAudio.input.disconnect();
+ GodotAudio.input = null;
+ }
+ // Disconnect output
+ let closed = Promise.resolve();
+ if (GodotAudio.driver) {
+ closed = GodotAudio.driver.close();
+ }
+ closed.then(function () {
+ return ctx.close();
+ }).then(function () {
+ ctx.onstatechange = null;
+ resolve();
+ }).catch(function (e) {
+ ctx.onstatechange = null;
+ GodotRuntime.error('Error closing AudioContext', e);
+ resolve();
+ });
+ },
+ },
+
+ godot_audio_is_available__sig: 'i',
+ godot_audio_is_available__proxy: 'sync',
+ godot_audio_is_available: function () {
+ if (!(window.AudioContext || window.webkitAudioContext)) {
+ return 0;
+ }
+ return 1;
+ },
+
+ godot_audio_init__sig: 'iiiii',
+ godot_audio_init: function (p_mix_rate, p_latency, p_state_change, p_latency_update) {
+ const statechange = GodotRuntime.get_func(p_state_change);
+ const latencyupdate = GodotRuntime.get_func(p_latency_update);
+ return GodotAudio.init(p_mix_rate, p_latency, statechange, latencyupdate);
+ },
+
+ godot_audio_resume__sig: 'v',
+ godot_audio_resume: function () {
+ if (GodotAudio.ctx && GodotAudio.ctx.state !== 'running') {
+ GodotAudio.ctx.resume();
+ }
+ },
+
+ godot_audio_capture_start__proxy: 'sync',
+ godot_audio_capture_start__sig: 'i',
+ godot_audio_capture_start: function () {
+ return GodotAudio.create_input(function (input) {
+ input.connect(GodotAudio.driver.get_node());
+ });
+ },
+
+ godot_audio_capture_stop__proxy: 'sync',
+ godot_audio_capture_stop__sig: 'v',
+ godot_audio_capture_stop: function () {
+ if (GodotAudio.input) {
+ const tracks = GodotAudio.input['mediaStream']['getTracks']();
+ for (let i = 0; i < tracks.length; i++) {
+ tracks[i]['stop']();
+ }
+ GodotAudio.input.disconnect();
+ GodotAudio.input = null;
+ }
+ },
+};
+
+autoAddDeps(GodotAudio, '$GodotAudio');
+mergeInto(LibraryManager.library, GodotAudio);
+
+/**
+ * The AudioWorklet API driver, used when threads are available.
+ */
+const GodotAudioWorklet = {
+ $GodotAudioWorklet__deps: ['$GodotAudio', '$GodotConfig'],
+ $GodotAudioWorklet: {
+ promise: null,
+ worklet: null,
+
+ create: function (channels) {
+ const path = GodotConfig.locate_file('godot.audio.worklet.js');
+ GodotAudioWorklet.promise = GodotAudio.ctx.audioWorklet.addModule(path).then(function () {
+ GodotAudioWorklet.worklet = new AudioWorkletNode(
+ GodotAudio.ctx,
+ 'godot-processor',
+ {
+ 'outputChannelCount': [channels],
+ },
+ );
+ return Promise.resolve();
+ });
+ GodotAudio.driver = GodotAudioWorklet;
+ },
+
+ start: function (in_buf, out_buf, state) {
+ GodotAudioWorklet.promise.then(function () {
+ const node = GodotAudioWorklet.worklet;
+ node.connect(GodotAudio.ctx.destination);
+ node.port.postMessage({
+ 'cmd': 'start',
+ 'data': [state, in_buf, out_buf],
+ });
+ node.port.onmessage = function (event) {
+ GodotRuntime.error(event.data);
+ };
+ });
+ },
+
+ get_node: function () {
+ return GodotAudioWorklet.worklet;
+ },
+
+ close: function () {
+ return new Promise(function (resolve, reject) {
+ GodotAudioWorklet.promise.then(function () {
+ GodotAudioWorklet.worklet.port.postMessage({
+ 'cmd': 'stop',
+ 'data': null,
+ });
+ GodotAudioWorklet.worklet.disconnect();
+ GodotAudioWorklet.worklet = null;
+ GodotAudioWorklet.promise = null;
+ resolve();
+ });
+ });
+ },
+ },
+
+ godot_audio_worklet_create__sig: 'vi',
+ godot_audio_worklet_create: function (channels) {
+ GodotAudioWorklet.create(channels);
+ },
+
+ godot_audio_worklet_start__sig: 'viiiii',
+ godot_audio_worklet_start: function (p_in_buf, p_in_size, p_out_buf, p_out_size, p_state) {
+ const out_buffer = GodotRuntime.heapSub(HEAPF32, p_out_buf, p_out_size);
+ const in_buffer = GodotRuntime.heapSub(HEAPF32, p_in_buf, p_in_size);
+ const state = GodotRuntime.heapSub(HEAP32, p_state, 4);
+ GodotAudioWorklet.start(in_buffer, out_buffer, state);
+ },
+
+ godot_audio_worklet_state_wait__sig: 'iiii',
+ godot_audio_worklet_state_wait: function (p_state, p_idx, p_expected, p_timeout) {
+ Atomics.wait(HEAP32, (p_state >> 2) + p_idx, p_expected, p_timeout);
+ return Atomics.load(HEAP32, (p_state >> 2) + p_idx);
+ },
+
+ godot_audio_worklet_state_add__sig: 'iiii',
+ godot_audio_worklet_state_add: function (p_state, p_idx, p_value) {
+ return Atomics.add(HEAP32, (p_state >> 2) + p_idx, p_value);
+ },
+
+ godot_audio_worklet_state_get__sig: 'iii',
+ godot_audio_worklet_state_get: function (p_state, p_idx) {
+ return Atomics.load(HEAP32, (p_state >> 2) + p_idx);
+ },
+};
+
+autoAddDeps(GodotAudioWorklet, '$GodotAudioWorklet');
+mergeInto(LibraryManager.library, GodotAudioWorklet);
+
+/*
+ * The deprecated ScriptProcessorNode API, used when threads are disabled.
+ */
+const GodotAudioScript = {
+ $GodotAudioScript__deps: ['$GodotAudio'],
+ $GodotAudioScript: {
+ script: null,
+
+ create: function (buffer_length, channel_count) {
+ GodotAudioScript.script = GodotAudio.ctx.createScriptProcessor(buffer_length, 2, channel_count);
+ GodotAudio.driver = GodotAudioScript;
+ return GodotAudioScript.script.bufferSize;
+ },
+
+ start: function (p_in_buf, p_in_size, p_out_buf, p_out_size, onprocess) {
+ GodotAudioScript.script.onaudioprocess = function (event) {
+ // Read input
+ const inb = GodotRuntime.heapSub(HEAPF32, p_in_buf, p_in_size);
+ const input = event.inputBuffer;
+ if (GodotAudio.input) {
+ const inlen = input.getChannelData(0).length;
+ for (let ch = 0; ch < 2; ch++) {
+ const data = input.getChannelData(ch);
+ for (let s = 0; s < inlen; s++) {
+ inb[s * 2 + ch] = data[s];
+ }
+ }
+ }
+
+ // Let Godot process the input/output.
+ onprocess();
+
+ // Write the output.
+ const outb = GodotRuntime.heapSub(HEAPF32, p_out_buf, p_out_size);
+ const output = event.outputBuffer;
+ const channels = output.numberOfChannels;
+ for (let ch = 0; ch < channels; ch++) {
+ const data = output.getChannelData(ch);
+ // Loop through samples and assign computed values.
+ for (let sample = 0; sample < data.length; sample++) {
+ data[sample] = outb[sample * channels + ch];
+ }
+ }
+ };
+ GodotAudioScript.script.connect(GodotAudio.ctx.destination);
+ },
+
+ get_node: function () {
+ return GodotAudioScript.script;
+ },
+
+ close: function () {
+ return new Promise(function (resolve, reject) {
+ GodotAudioScript.script.disconnect();
+ GodotAudioScript.script.onaudioprocess = null;
+ GodotAudioScript.script = null;
+ resolve();
+ });
+ },
+ },
+
+ godot_audio_script_create__sig: 'iii',
+ godot_audio_script_create: function (buffer_length, channel_count) {
+ return GodotAudioScript.create(buffer_length, channel_count);
+ },
+
+ godot_audio_script_start__sig: 'viiiii',
+ godot_audio_script_start: function (p_in_buf, p_in_size, p_out_buf, p_out_size, p_cb) {
+ const onprocess = GodotRuntime.get_func(p_cb);
+ GodotAudioScript.start(p_in_buf, p_in_size, p_out_buf, p_out_size, onprocess);
+ },
+};
+
+autoAddDeps(GodotAudioScript, '$GodotAudioScript');
+mergeInto(LibraryManager.library, GodotAudioScript);
diff --git a/platform/javascript/js/libs/library_godot_display.js b/platform/javascript/js/libs/library_godot_display.js
new file mode 100644
index 0000000000..d1f4d9595b
--- /dev/null
+++ b/platform/javascript/js/libs/library_godot_display.js
@@ -0,0 +1,497 @@
+/*************************************************************************/
+/* library_godot_display.js */
+/*************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/*************************************************************************/
+/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/*************************************************************************/
+
+/*
+ * Display Server listeners.
+ * Keeps track of registered event listeners so it can remove them on shutdown.
+ */
+const GodotDisplayListeners = {
+ $GodotDisplayListeners__deps: ['$GodotOS'],
+ $GodotDisplayListeners__postset: 'GodotOS.atexit(function(resolve, reject) { GodotDisplayListeners.clear(); resolve(); });',
+ $GodotDisplayListeners: {
+ handlers: [],
+
+ has: function (target, event, method, capture) {
+ return GodotDisplayListeners.handlers.findIndex(function (e) {
+ return e.target === target && e.event === event && e.method === method && e.capture === capture;
+ }) !== -1;
+ },
+
+ add: function (target, event, method, capture) {
+ if (GodotDisplayListeners.has(target, event, method, capture)) {
+ return;
+ }
+ function Handler(p_target, p_event, p_method, p_capture) {
+ this.target = p_target;
+ this.event = p_event;
+ this.method = p_method;
+ this.capture = p_capture;
+ }
+ GodotDisplayListeners.handlers.push(new Handler(target, event, method, capture));
+ target.addEventListener(event, method, capture);
+ },
+
+ clear: function () {
+ GodotDisplayListeners.handlers.forEach(function (h) {
+ h.target.removeEventListener(h.event, h.method, h.capture);
+ });
+ GodotDisplayListeners.handlers.length = 0;
+ },
+ },
+};
+mergeInto(LibraryManager.library, GodotDisplayListeners);
+
+/*
+ * Drag and drop handler.
+ * This is pretty big, but basically detect dropped files on GodotConfig.canvas,
+ * process them one by one (recursively for directories), and copies them to
+ * the temporary FS path '/tmp/drop-[random]/' so it can be emitted as a godot
+ * event (that requires a string array of paths).
+ *
+ * NOTE: The temporary files are removed after the callback. This means that
+ * deferred callbacks won't be able to access the files.
+ */
+const GodotDisplayDragDrop = {
+ $GodotDisplayDragDrop__deps: ['$FS', '$GodotFS'],
+ $GodotDisplayDragDrop: {
+ promises: [],
+ pending_files: [],
+
+ add_entry: function (entry) {
+ if (entry.isDirectory) {
+ GodotDisplayDragDrop.add_dir(entry);
+ } else if (entry.isFile) {
+ GodotDisplayDragDrop.add_file(entry);
+ } else {
+ GodotRuntime.error('Unrecognized entry...', entry);
+ }
+ },
+
+ add_dir: function (entry) {
+ GodotDisplayDragDrop.promises.push(new Promise(function (resolve, reject) {
+ const reader = entry.createReader();
+ reader.readEntries(function (entries) {
+ for (let i = 0; i < entries.length; i++) {
+ GodotDisplayDragDrop.add_entry(entries[i]);
+ }
+ resolve();
+ });
+ }));
+ },
+
+ add_file: function (entry) {
+ GodotDisplayDragDrop.promises.push(new Promise(function (resolve, reject) {
+ entry.file(function (file) {
+ const reader = new FileReader();
+ reader.onload = function () {
+ const f = {
+ 'path': file.relativePath || file.webkitRelativePath,
+ 'name': file.name,
+ 'type': file.type,
+ 'size': file.size,
+ 'data': reader.result,
+ };
+ if (!f['path']) {
+ f['path'] = f['name'];
+ }
+ GodotDisplayDragDrop.pending_files.push(f);
+ resolve();
+ };
+ reader.onerror = function () {
+ GodotRuntime.print('Error reading file');
+ reject();
+ };
+ reader.readAsArrayBuffer(file);
+ }, function (err) {
+ GodotRuntime.print('Error!');
+ reject();
+ });
+ }));
+ },
+
+ process: function (resolve, reject) {
+ if (GodotDisplayDragDrop.promises.length === 0) {
+ resolve();
+ return;
+ }
+ GodotDisplayDragDrop.promises.pop().then(function () {
+ setTimeout(function () {
+ GodotDisplayDragDrop.process(resolve, reject);
+ }, 0);
+ });
+ },
+
+ _process_event: function (ev, callback) {
+ ev.preventDefault();
+ if (ev.dataTransfer.items) {
+ // Use DataTransferItemList interface to access the file(s)
+ for (let i = 0; i < ev.dataTransfer.items.length; i++) {
+ const item = ev.dataTransfer.items[i];
+ let entry = null;
+ if ('getAsEntry' in item) {
+ entry = item.getAsEntry();
+ } else if ('webkitGetAsEntry' in item) {
+ entry = item.webkitGetAsEntry();
+ }
+ if (entry) {
+ GodotDisplayDragDrop.add_entry(entry);
+ }
+ }
+ } else {
+ GodotRuntime.error('File upload not supported');
+ }
+ new Promise(GodotDisplayDragDrop.process).then(function () {
+ const DROP = `/tmp/drop-${parseInt(Math.random() * (1 << 30), 10)}/`;
+ const drops = [];
+ const files = [];
+ FS.mkdir(DROP);
+ GodotDisplayDragDrop.pending_files.forEach((elem) => {
+ const path = elem['path'];
+ GodotFS.copy_to_fs(DROP + path, elem['data']);
+ let idx = path.indexOf('/');
+ if (idx === -1) {
+ // Root file
+ drops.push(DROP + path);
+ } else {
+ // Subdir
+ const sub = path.substr(0, idx);
+ idx = sub.indexOf('/');
+ if (idx < 0 && drops.indexOf(DROP + sub) === -1) {
+ drops.push(DROP + sub);
+ }
+ }
+ files.push(DROP + path);
+ });
+ GodotDisplayDragDrop.promises = [];
+ GodotDisplayDragDrop.pending_files = [];
+ callback(drops);
+ const dirs = [DROP.substr(0, DROP.length - 1)];
+ // Remove temporary files
+ files.forEach(function (file) {
+ FS.unlink(file);
+ let dir = file.replace(DROP, '');
+ let idx = dir.lastIndexOf('/');
+ while (idx > 0) {
+ dir = dir.substr(0, idx);
+ if (dirs.indexOf(DROP + dir) === -1) {
+ dirs.push(DROP + dir);
+ }
+ idx = dir.lastIndexOf('/');
+ }
+ });
+ // Remove dirs.
+ dirs.sort(function (a, b) {
+ const al = (a.match(/\//g) || []).length;
+ const bl = (b.match(/\//g) || []).length;
+ if (al > bl) {
+ return -1;
+ } else if (al < bl) {
+ return 1;
+ }
+ return 0;
+ }).forEach(function (dir) {
+ FS.rmdir(dir);
+ });
+ });
+ },
+
+ handler: function (callback) {
+ return function (ev) {
+ GodotDisplayDragDrop._process_event(ev, callback);
+ };
+ },
+ },
+};
+mergeInto(LibraryManager.library, GodotDisplayDragDrop);
+
+/*
+ * Display server cursor helper.
+ * Keeps track of cursor status and custom shapes.
+ */
+const GodotDisplayCursor = {
+ $GodotDisplayCursor__deps: ['$GodotOS', '$GodotConfig'],
+ $GodotDisplayCursor__postset: 'GodotOS.atexit(function(resolve, reject) { GodotDisplayCursor.clear(); resolve(); });',
+ $GodotDisplayCursor: {
+ shape: 'auto',
+ visible: true,
+ cursors: {},
+ set_style: function (style) {
+ GodotConfig.canvas.style.cursor = style;
+ },
+ set_shape: function (shape) {
+ GodotDisplayCursor.shape = shape;
+ let css = shape;
+ if (shape in GodotDisplayCursor.cursors) {
+ const c = GodotDisplayCursor.cursors[shape];
+ css = `url("${c.url}") ${c.x} ${c.y}, auto`;
+ }
+ if (GodotDisplayCursor.visible) {
+ GodotDisplayCursor.set_style(css);
+ }
+ },
+ clear: function () {
+ GodotDisplayCursor.set_style('');
+ GodotDisplayCursor.shape = 'auto';
+ GodotDisplayCursor.visible = true;
+ Object.keys(GodotDisplayCursor.cursors).forEach(function (key) {
+ URL.revokeObjectURL(GodotDisplayCursor.cursors[key]);
+ delete GodotDisplayCursor.cursors[key];
+ });
+ },
+ },
+};
+mergeInto(LibraryManager.library, GodotDisplayCursor);
+
+/**
+ * Display server interface.
+ *
+ * Exposes all the functions needed by DisplayServer implementation.
+ */
+const GodotDisplay = {
+ $GodotDisplay__deps: ['$GodotConfig', '$GodotRuntime', '$GodotDisplayCursor', '$GodotDisplayListeners', '$GodotDisplayDragDrop'],
+ $GodotDisplay: {
+ window_icon: '',
+ },
+
+ godot_js_display_is_swap_ok_cancel__sig: 'i',
+ godot_js_display_is_swap_ok_cancel: function () {
+ const win = (['Windows', 'Win64', 'Win32', 'WinCE']);
+ const plat = navigator.platform || '';
+ if (win.indexOf(plat) !== -1) {
+ return 1;
+ }
+ return 0;
+ },
+
+ godot_js_display_alert__sig: 'vi',
+ godot_js_display_alert: function (p_text) {
+ window.alert(GodotRuntime.parseString(p_text)); // eslint-disable-line no-alert
+ },
+
+ godot_js_display_pixel_ratio_get__sig: 'f',
+ godot_js_display_pixel_ratio_get: function () {
+ return window.devicePixelRatio || 1;
+ },
+
+ /*
+ * Canvas
+ */
+ godot_js_display_canvas_focus__sig: 'v',
+ godot_js_display_canvas_focus: function () {
+ GodotConfig.canvas.focus();
+ },
+
+ godot_js_display_canvas_is_focused__sig: 'i',
+ godot_js_display_canvas_is_focused: function () {
+ return document.activeElement === GodotConfig.canvas;
+ },
+
+ godot_js_display_canvas_bounding_rect_position_get__sig: 'vii',
+ godot_js_display_canvas_bounding_rect_position_get: function (r_x, r_y) {
+ const brect = GodotConfig.canvas.getBoundingClientRect();
+ GodotRuntime.setHeapValue(r_x, brect.x, 'i32');
+ GodotRuntime.setHeapValue(r_y, brect.y, 'i32');
+ },
+
+ /*
+ * Touchscreen
+ */
+ godot_js_display_touchscreen_is_available__sig: 'i',
+ godot_js_display_touchscreen_is_available: function () {
+ return 'ontouchstart' in window;
+ },
+
+ /*
+ * Clipboard
+ */
+ godot_js_display_clipboard_set__sig: 'ii',
+ godot_js_display_clipboard_set: function (p_text) {
+ const text = GodotRuntime.parseString(p_text);
+ if (!navigator.clipboard || !navigator.clipboard.writeText) {
+ return 1;
+ }
+ navigator.clipboard.writeText(text).catch(function (e) {
+ // Setting OS clipboard is only possible from an input callback.
+ GodotRuntime.error('Setting OS clipboard is only possible from an input callback for the HTML5 plafrom. Exception:', e);
+ });
+ return 0;
+ },
+
+ godot_js_display_clipboard_get__sig: 'ii',
+ godot_js_display_clipboard_get: function (callback) {
+ const func = GodotRuntime.get_func(callback);
+ try {
+ navigator.clipboard.readText().then(function (result) {
+ const ptr = GodotRuntime.allocString(result);
+ func(ptr);
+ GodotRuntime.free(ptr);
+ }).catch(function (e) {
+ // Fail graciously.
+ });
+ } catch (e) {
+ // Fail graciously.
+ }
+ },
+
+ /*
+ * Window
+ */
+ godot_js_display_window_request_fullscreen__sig: 'v',
+ godot_js_display_window_request_fullscreen: function () {
+ const canvas = GodotConfig.canvas;
+ (canvas.requestFullscreen || canvas.msRequestFullscreen
+ || canvas.mozRequestFullScreen || canvas.mozRequestFullscreen
+ || canvas.webkitRequestFullscreen
+ ).call(canvas);
+ },
+
+ godot_js_display_window_title_set__sig: 'vi',
+ godot_js_display_window_title_set: function (p_data) {
+ document.title = GodotRuntime.parseString(p_data);
+ },
+
+ godot_js_display_window_icon_set__sig: 'vii',
+ godot_js_display_window_icon_set: function (p_ptr, p_len) {
+ let link = document.getElementById('-gd-engine-icon');
+ if (link === null) {
+ link = document.createElement('link');
+ link.rel = 'icon';
+ link.id = '-gd-engine-icon';
+ document.head.appendChild(link);
+ }
+ const old_icon = GodotDisplay.window_icon;
+ const png = new Blob([GodotRuntime.heapCopy(HEAPU8, p_ptr, p_len)], { type: 'image/png' });
+ GodotDisplay.window_icon = URL.createObjectURL(png);
+ link.href = GodotDisplay.window_icon;
+ if (old_icon) {
+ URL.revokeObjectURL(old_icon);
+ }
+ },
+
+ /*
+ * Cursor
+ */
+ godot_js_display_cursor_set_visible__sig: 'vi',
+ godot_js_display_cursor_set_visible: function (p_visible) {
+ const visible = p_visible !== 0;
+ if (visible === GodotDisplayCursor.visible) {
+ return;
+ }
+ GodotDisplayCursor.visible = visible;
+ if (visible) {
+ GodotDisplayCursor.set_shape(GodotDisplayCursor.shape);
+ } else {
+ GodotDisplayCursor.set_style('none');
+ }
+ },
+
+ godot_js_display_cursor_is_hidden__sig: 'i',
+ godot_js_display_cursor_is_hidden: function () {
+ return !GodotDisplayCursor.visible;
+ },
+
+ godot_js_display_cursor_set_shape__sig: 'vi',
+ godot_js_display_cursor_set_shape: function (p_string) {
+ GodotDisplayCursor.set_shape(GodotRuntime.parseString(p_string));
+ },
+
+ godot_js_display_cursor_set_custom_shape__sig: 'viiiii',
+ godot_js_display_cursor_set_custom_shape: function (p_shape, p_ptr, p_len, p_hotspot_x, p_hotspot_y) {
+ const shape = GodotRuntime.parseString(p_shape);
+ const old_shape = GodotDisplayCursor.cursors[shape];
+ if (p_len > 0) {
+ const png = new Blob([GodotRuntime.heapCopy(HEAPU8, p_ptr, p_len)], { type: 'image/png' });
+ const url = URL.createObjectURL(png);
+ GodotDisplayCursor.cursors[shape] = {
+ url: url,
+ x: p_hotspot_x,
+ y: p_hotspot_y,
+ };
+ } else {
+ delete GodotDisplayCursor.cursors[shape];
+ }
+ if (shape === GodotDisplayCursor.shape) {
+ GodotDisplayCursor.set_shape(GodotDisplayCursor.shape);
+ }
+ if (old_shape) {
+ URL.revokeObjectURL(old_shape.url);
+ }
+ },
+
+ /*
+ * Listeners
+ */
+ godot_js_display_notification_cb__sig: 'viiiii',
+ godot_js_display_notification_cb: function (callback, p_enter, p_exit, p_in, p_out) {
+ const canvas = GodotConfig.canvas;
+ const func = GodotRuntime.get_func(callback);
+ const notif = [p_enter, p_exit, p_in, p_out];
+ ['mouseover', 'mouseleave', 'focus', 'blur'].forEach(function (evt_name, idx) {
+ GodotDisplayListeners.add(canvas, evt_name, function () {
+ func.bind(null, notif[idx]);
+ }, true);
+ });
+ },
+
+ godot_js_display_paste_cb__sig: 'vi',
+ godot_js_display_paste_cb: function (callback) {
+ const func = GodotRuntime.get_func(callback);
+ GodotDisplayListeners.add(window, 'paste', function (evt) {
+ const text = evt.clipboardData.getData('text');
+ const ptr = GodotRuntime.allocString(text);
+ func(ptr);
+ GodotRuntime.free(ptr);
+ }, false);
+ },
+
+ godot_js_display_drop_files_cb__sig: 'vi',
+ godot_js_display_drop_files_cb: function (callback) {
+ const func = GodotRuntime.get_func(callback);
+ const dropFiles = function (files) {
+ const args = files || [];
+ if (!args.length) {
+ return;
+ }
+ const argc = args.length;
+ const argv = GodotRuntime.allocStringArray(args);
+ func(argv, argc);
+ GodotRuntime.freeStringArray(argv, argc);
+ };
+ const canvas = GodotConfig.canvas;
+ GodotDisplayListeners.add(canvas, 'dragover', function (ev) {
+ // Prevent default behavior (which would try to open the file(s))
+ ev.preventDefault();
+ }, false);
+ GodotDisplayListeners.add(canvas, 'drop', GodotDisplayDragDrop.handler(dropFiles));
+ },
+};
+
+autoAddDeps(GodotDisplay, '$GodotDisplay');
+mergeInto(LibraryManager.library, GodotDisplay);
diff --git a/platform/javascript/js/libs/library_godot_editor_tools.js b/platform/javascript/js/libs/library_godot_editor_tools.js
new file mode 100644
index 0000000000..d7f1ad5ea1
--- /dev/null
+++ b/platform/javascript/js/libs/library_godot_editor_tools.js
@@ -0,0 +1,57 @@
+/*************************************************************************/
+/* library_godot_editor_tools.js */
+/*************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/*************************************************************************/
+/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/*************************************************************************/
+
+const GodotEditorTools = {
+ godot_js_editor_download_file__deps: ['$FS'],
+ godot_js_editor_download_file__sig: 'viii',
+ godot_js_editor_download_file: function (p_path, p_name, p_mime) {
+ const path = GodotRuntime.parseString(p_path);
+ const name = GodotRuntime.parseString(p_name);
+ const mime = GodotRuntime.parseString(p_mime);
+ const size = FS.stat(path)['size'];
+ const buf = new Uint8Array(size);
+ const fd = FS.open(path, 'r');
+ FS.read(fd, buf, 0, size);
+ FS.close(fd);
+ FS.unlink(path);
+ const blob = new Blob([buf], { type: mime });
+ const url = window.URL.createObjectURL(blob);
+ const a = document.createElement('a');
+ a.href = url;
+ a.download = name;
+ a.style.display = 'none';
+ document.body.appendChild(a);
+ a.click();
+ a.remove();
+ window.URL.revokeObjectURL(url);
+ },
+};
+
+mergeInto(LibraryManager.library, GodotEditorTools);
diff --git a/platform/javascript/js/libs/library_godot_eval.js b/platform/javascript/js/libs/library_godot_eval.js
new file mode 100644
index 0000000000..9ab392b813
--- /dev/null
+++ b/platform/javascript/js/libs/library_godot_eval.js
@@ -0,0 +1,86 @@
+/*************************************************************************/
+/* library_godot_eval.js */
+/*************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/*************************************************************************/
+/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/*************************************************************************/
+
+const GodotEval = {
+ godot_js_eval__deps: ['$GodotRuntime'],
+ godot_js_eval__sig: 'iiiiiii',
+ godot_js_eval: function (p_js, p_use_global_ctx, p_union_ptr, p_byte_arr, p_byte_arr_write, p_callback) {
+ const js_code = GodotRuntime.parseString(p_js);
+ let eval_ret = null;
+ try {
+ if (p_use_global_ctx) {
+ // indirect eval call grants global execution context
+ const global_eval = eval; // eslint-disable-line no-eval
+ eval_ret = global_eval(js_code);
+ } else {
+ eval_ret = eval(js_code); // eslint-disable-line no-eval
+ }
+ } catch (e) {
+ GodotRuntime.error(e);
+ }
+
+ switch (typeof eval_ret) {
+ case 'boolean':
+ GodotRuntime.setHeapValue(p_union_ptr, eval_ret, 'i32');
+ return 1; // BOOL
+
+ case 'number':
+ GodotRuntime.setHeapValue(p_union_ptr, eval_ret, 'double');
+ return 3; // REAL
+
+ case 'string':
+ GodotRuntime.setHeapValue(p_union_ptr, GodotRuntime.allocString(eval_ret), '*');
+ return 4; // STRING
+
+ case 'object':
+ if (eval_ret === null) {
+ break;
+ }
+
+ if (ArrayBuffer.isView(eval_ret) && !(eval_ret instanceof Uint8Array)) {
+ eval_ret = new Uint8Array(eval_ret.buffer);
+ } else if (eval_ret instanceof ArrayBuffer) {
+ eval_ret = new Uint8Array(eval_ret);
+ }
+ if (eval_ret instanceof Uint8Array) {
+ const func = GodotRuntime.get_func(p_callback);
+ const bytes_ptr = func(p_byte_arr, p_byte_arr_write, eval_ret.length);
+ HEAPU8.set(eval_ret, bytes_ptr);
+ return 20; // POOL_BYTE_ARRAY
+ }
+ break;
+
+ // no default
+ }
+ return 0; // NIL
+ },
+};
+
+mergeInto(LibraryManager.library, GodotEval);
diff --git a/platform/javascript/http_request.js b/platform/javascript/js/libs/library_godot_http_request.js
index f621689f9d..930d3209f8 100644
--- a/platform/javascript/http_request.js
+++ b/platform/javascript/js/libs/library_godot_http_request.js
@@ -5,8 +5,8 @@
/* GODOT ENGINE */
/* https://godotengine.org */
/*************************************************************************/
-/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */
-/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */
+/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
@@ -27,120 +27,134 @@
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/*************************************************************************/
-var GodotHTTPRequest = {
+const GodotHTTPRequest = {
+ $GodotHTTPRequest__deps: ['$GodotRuntime'],
$GodotHTTPRequest: {
-
requests: [],
- getUnusedRequestId: function() {
- var idMax = GodotHTTPRequest.requests.length;
- for (var potentialId = 0; potentialId < idMax; ++potentialId) {
+ getUnusedRequestId: function () {
+ const idMax = GodotHTTPRequest.requests.length;
+ for (let potentialId = 0; potentialId < idMax; ++potentialId) {
if (GodotHTTPRequest.requests[potentialId] instanceof XMLHttpRequest) {
continue;
}
return potentialId;
}
- GodotHTTPRequest.requests.push(null)
+ GodotHTTPRequest.requests.push(null);
return idMax;
},
- setupRequest: function(xhr) {
+ setupRequest: function (xhr) {
xhr.responseType = 'arraybuffer';
},
},
- godot_xhr_new: function() {
- var newId = GodotHTTPRequest.getUnusedRequestId();
- GodotHTTPRequest.requests[newId] = new XMLHttpRequest;
+ godot_xhr_new__sig: 'i',
+ godot_xhr_new: function () {
+ const newId = GodotHTTPRequest.getUnusedRequestId();
+ GodotHTTPRequest.requests[newId] = new XMLHttpRequest();
GodotHTTPRequest.setupRequest(GodotHTTPRequest.requests[newId]);
return newId;
},
- godot_xhr_reset: function(xhrId) {
- GodotHTTPRequest.requests[xhrId] = new XMLHttpRequest;
+ godot_xhr_reset__sig: 'vi',
+ godot_xhr_reset: function (xhrId) {
+ GodotHTTPRequest.requests[xhrId] = new XMLHttpRequest();
GodotHTTPRequest.setupRequest(GodotHTTPRequest.requests[xhrId]);
},
- godot_xhr_free: function(xhrId) {
+ godot_xhr_free__sig: 'vi',
+ godot_xhr_free: function (xhrId) {
GodotHTTPRequest.requests[xhrId].abort();
GodotHTTPRequest.requests[xhrId] = null;
},
- godot_xhr_open: function(xhrId, method, url, user, password) {
- user = user > 0 ? UTF8ToString(user) : null;
- password = password > 0 ? UTF8ToString(password) : null;
- GodotHTTPRequest.requests[xhrId].open(UTF8ToString(method), UTF8ToString(url), true, user, password);
+ godot_xhr_open__sig: 'viiiii',
+ godot_xhr_open: function (xhrId, method, url, p_user, p_password) {
+ const user = p_user > 0 ? GodotRuntime.parseString(p_user) : null;
+ const password = p_password > 0 ? GodotRuntime.parseString(p_password) : null;
+ GodotHTTPRequest.requests[xhrId].open(GodotRuntime.parseString(method), GodotRuntime.parseString(url), true, user, password);
},
- godot_xhr_set_request_header: function(xhrId, header, value) {
- GodotHTTPRequest.requests[xhrId].setRequestHeader(UTF8ToString(header), UTF8ToString(value));
+ godot_xhr_set_request_header__sig: 'viii',
+ godot_xhr_set_request_header: function (xhrId, header, value) {
+ GodotHTTPRequest.requests[xhrId].setRequestHeader(GodotRuntime.parseString(header), GodotRuntime.parseString(value));
},
- godot_xhr_send_null: function(xhrId) {
+ godot_xhr_send_null__sig: 'vi',
+ godot_xhr_send_null: function (xhrId) {
GodotHTTPRequest.requests[xhrId].send();
},
- godot_xhr_send_string: function(xhrId, strPtr) {
+ godot_xhr_send_string__sig: 'vii',
+ godot_xhr_send_string: function (xhrId, strPtr) {
if (!strPtr) {
- err("Failed to send string per XHR: null pointer");
+ GodotRuntime.error('Failed to send string per XHR: null pointer');
return;
}
- GodotHTTPRequest.requests[xhrId].send(UTF8ToString(strPtr));
+ GodotHTTPRequest.requests[xhrId].send(GodotRuntime.parseString(strPtr));
},
- godot_xhr_send_data: function(xhrId, ptr, len) {
+ godot_xhr_send_data__sig: 'viii',
+ godot_xhr_send_data: function (xhrId, ptr, len) {
if (!ptr) {
- err("Failed to send data per XHR: null pointer");
+ GodotRuntime.error('Failed to send data per XHR: null pointer');
return;
}
if (len < 0) {
- err("Failed to send data per XHR: buffer length less than 0");
+ GodotRuntime.error('Failed to send data per XHR: buffer length less than 0');
return;
}
GodotHTTPRequest.requests[xhrId].send(HEAPU8.subarray(ptr, ptr + len));
},
- godot_xhr_abort: function(xhrId) {
+ godot_xhr_abort__sig: 'vi',
+ godot_xhr_abort: function (xhrId) {
GodotHTTPRequest.requests[xhrId].abort();
},
- godot_xhr_get_status: function(xhrId) {
+ godot_xhr_get_status__sig: 'ii',
+ godot_xhr_get_status: function (xhrId) {
return GodotHTTPRequest.requests[xhrId].status;
},
- godot_xhr_get_ready_state: function(xhrId) {
+ godot_xhr_get_ready_state__sig: 'ii',
+ godot_xhr_get_ready_state: function (xhrId) {
return GodotHTTPRequest.requests[xhrId].readyState;
},
- godot_xhr_get_response_headers_length: function(xhrId) {
- var headers = GodotHTTPRequest.requests[xhrId].getAllResponseHeaders();
- return headers === null ? 0 : lengthBytesUTF8(headers);
+ godot_xhr_get_response_headers_length__sig: 'ii',
+ godot_xhr_get_response_headers_length: function (xhrId) {
+ const headers = GodotHTTPRequest.requests[xhrId].getAllResponseHeaders();
+ return headers === null ? 0 : GodotRuntime.strlen(headers);
},
- godot_xhr_get_response_headers: function(xhrId, dst, len) {
- var str = GodotHTTPRequest.requests[xhrId].getAllResponseHeaders();
- if (str === null)
+ godot_xhr_get_response_headers__sig: 'viii',
+ godot_xhr_get_response_headers: function (xhrId, dst, len) {
+ const str = GodotHTTPRequest.requests[xhrId].getAllResponseHeaders();
+ if (str === null) {
return;
- var buf = new Uint8Array(len + 1);
- stringToUTF8Array(str, buf, 0, buf.length);
- buf = buf.subarray(0, -1);
- HEAPU8.set(buf, dst);
+ }
+ GodotRuntime.stringToHeap(str, dst, len);
},
- godot_xhr_get_response_length: function(xhrId) {
- var body = GodotHTTPRequest.requests[xhrId].response;
+ godot_xhr_get_response_length__sig: 'ii',
+ godot_xhr_get_response_length: function (xhrId) {
+ const body = GodotHTTPRequest.requests[xhrId].response;
return body === null ? 0 : body.byteLength;
},
- godot_xhr_get_response: function(xhrId, dst, len) {
- var buf = GodotHTTPRequest.requests[xhrId].response;
- if (buf === null)
+ godot_xhr_get_response__sig: 'viii',
+ godot_xhr_get_response: function (xhrId, dst, len) {
+ let buf = GodotHTTPRequest.requests[xhrId].response;
+ if (buf === null) {
return;
+ }
buf = new Uint8Array(buf).subarray(0, len);
HEAPU8.set(buf, dst);
},
};
-autoAddDeps(GodotHTTPRequest, "$GodotHTTPRequest");
+autoAddDeps(GodotHTTPRequest, '$GodotHTTPRequest');
mergeInto(LibraryManager.library, GodotHTTPRequest);
diff --git a/platform/javascript/js/libs/library_godot_os.js b/platform/javascript/js/libs/library_godot_os.js
new file mode 100644
index 0000000000..9fde4a84e1
--- /dev/null
+++ b/platform/javascript/js/libs/library_godot_os.js
@@ -0,0 +1,288 @@
+/*************************************************************************/
+/* library_godot_os.js */
+/*************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/*************************************************************************/
+/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/*************************************************************************/
+
+const IDHandler = {
+ $IDHandler: {
+ _last_id: 0,
+ _references: {},
+
+ get: function (p_id) {
+ return IDHandler._references[p_id];
+ },
+
+ add: function (p_data) {
+ const id = ++IDHandler._last_id;
+ IDHandler._references[id] = p_data;
+ return id;
+ },
+
+ remove: function (p_id) {
+ delete IDHandler._references[p_id];
+ },
+ },
+};
+
+autoAddDeps(IDHandler, '$IDHandler');
+mergeInto(LibraryManager.library, IDHandler);
+
+const GodotConfig = {
+ $GodotConfig__postset: 'Module["initConfig"] = GodotConfig.init_config;',
+ $GodotConfig__deps: ['$GodotRuntime'],
+ $GodotConfig: {
+ canvas: null,
+ locale: 'en',
+ resize_on_start: false,
+ on_execute: null,
+
+ init_config: function (p_opts) {
+ GodotConfig.resize_on_start = !!p_opts['resizeCanvasOnStart'];
+ GodotConfig.canvas = p_opts['canvas'];
+ GodotConfig.locale = p_opts['locale'] || GodotConfig.locale;
+ GodotConfig.on_execute = p_opts['onExecute'];
+ // This is called by emscripten, even if undocumented.
+ Module['onExit'] = p_opts['onExit']; // eslint-disable-line no-undef
+ },
+
+ locate_file: function (file) {
+ return Module['locateFile'](file); // eslint-disable-line no-undef
+ },
+ },
+
+ godot_js_config_canvas_id_get__sig: 'vii',
+ godot_js_config_canvas_id_get: function (p_ptr, p_ptr_max) {
+ GodotRuntime.stringToHeap(`#${GodotConfig.canvas.id}`, p_ptr, p_ptr_max);
+ },
+
+ godot_js_config_locale_get__sig: 'vii',
+ godot_js_config_locale_get: function (p_ptr, p_ptr_max) {
+ GodotRuntime.stringToHeap(GodotConfig.locale, p_ptr, p_ptr_max);
+ },
+
+ godot_js_config_is_resize_on_start__sig: 'i',
+ godot_js_config_is_resize_on_start: function () {
+ return GodotConfig.resize_on_start ? 1 : 0;
+ },
+};
+
+autoAddDeps(GodotConfig, '$GodotConfig');
+mergeInto(LibraryManager.library, GodotConfig);
+
+const GodotFS = {
+ $GodotFS__deps: ['$FS', '$IDBFS', '$GodotRuntime'],
+ $GodotFS__postset: [
+ 'Module["initFS"] = GodotFS.init;',
+ 'Module["deinitFS"] = GodotFS.deinit;',
+ 'Module["copyToFS"] = GodotFS.copy_to_fs;',
+ ].join(''),
+ $GodotFS: {
+ _idbfs: false,
+ _syncing: false,
+ _mount_points: [],
+
+ is_persistent: function () {
+ return GodotFS._idbfs ? 1 : 0;
+ },
+
+ // Initialize godot file system, setting up persistent paths.
+ // Returns a promise that resolves when the FS is ready.
+ // We keep track of mount_points, so that we can properly close the IDBFS
+ // since emscripten is not doing it by itself. (emscripten GH#12516).
+ init: function (persistentPaths) {
+ GodotFS._idbfs = false;
+ if (!Array.isArray(persistentPaths)) {
+ return Promise.reject(new Error('Persistent paths must be an array'));
+ }
+ if (!persistentPaths.length) {
+ return Promise.resolve();
+ }
+ GodotFS._mount_points = persistentPaths.slice();
+
+ function createRecursive(dir) {
+ try {
+ FS.stat(dir);
+ } catch (e) {
+ if (e.errno !== ERRNO_CODES.ENOENT) {
+ throw e;
+ }
+ FS.mkdirTree(dir);
+ }
+ }
+
+ GodotFS._mount_points.forEach(function (path) {
+ createRecursive(path);
+ FS.mount(IDBFS, {}, path);
+ });
+ return new Promise(function (resolve, reject) {
+ FS.syncfs(true, function (err) {
+ if (err) {
+ GodotFS._mount_points = [];
+ GodotFS._idbfs = false;
+ GodotRuntime.print(`IndexedDB not available: ${err.message}`);
+ } else {
+ GodotFS._idbfs = true;
+ }
+ resolve(err);
+ });
+ });
+ },
+
+ // Deinit godot file system, making sure to unmount file systems, and close IDBFS(s).
+ deinit: function () {
+ GodotFS._mount_points.forEach(function (path) {
+ try {
+ FS.unmount(path);
+ } catch (e) {
+ GodotRuntime.print('Already unmounted', e);
+ }
+ if (GodotFS._idbfs && IDBFS.dbs[path]) {
+ IDBFS.dbs[path].close();
+ delete IDBFS.dbs[path];
+ }
+ });
+ GodotFS._mount_points = [];
+ GodotFS._idbfs = false;
+ GodotFS._syncing = false;
+ },
+
+ sync: function () {
+ if (GodotFS._syncing) {
+ GodotRuntime.error('Already syncing!');
+ return Promise.resolve();
+ }
+ GodotFS._syncing = true;
+ return new Promise(function (resolve, reject) {
+ FS.syncfs(false, function (error) {
+ if (error) {
+ GodotRuntime.error(`Failed to save IDB file system: ${error.message}`);
+ }
+ GodotFS._syncing = false;
+ resolve(error);
+ });
+ });
+ },
+
+ // Copies a buffer to the internal file system. Creating directories recursively.
+ copy_to_fs: function (path, buffer) {
+ const idx = path.lastIndexOf('/');
+ let dir = '/';
+ if (idx > 0) {
+ dir = path.slice(0, idx);
+ }
+ try {
+ FS.stat(dir);
+ } catch (e) {
+ if (e.errno !== ERRNO_CODES.ENOENT) {
+ throw e;
+ }
+ FS.mkdirTree(dir);
+ }
+ FS.writeFile(path, new Uint8Array(buffer));
+ },
+ },
+};
+mergeInto(LibraryManager.library, GodotFS);
+
+const GodotOS = {
+ $GodotOS__deps: ['$GodotFS', '$GodotRuntime'],
+ $GodotOS__postset: [
+ 'Module["request_quit"] = function() { GodotOS.request_quit() };',
+ 'GodotOS._fs_sync_promise = Promise.resolve();',
+ ].join(''),
+ $GodotOS: {
+ request_quit: function () {},
+ _async_cbs: [],
+ _fs_sync_promise: null,
+
+ atexit: function (p_promise_cb) {
+ GodotOS._async_cbs.push(p_promise_cb);
+ },
+
+ finish_async: function (callback) {
+ GodotOS._fs_sync_promise.then(function (err) {
+ const promises = [];
+ GodotOS._async_cbs.forEach(function (cb) {
+ promises.push(new Promise(cb));
+ });
+ return Promise.all(promises);
+ }).then(function () {
+ return GodotFS.sync(); // Final FS sync.
+ }).then(function (err) {
+ // Always deferred.
+ setTimeout(function () {
+ callback();
+ }, 0);
+ });
+ },
+ },
+
+ godot_js_os_finish_async__sig: 'vi',
+ godot_js_os_finish_async: function (p_callback) {
+ const func = GodotRuntime.get_func(p_callback);
+ GodotOS.finish_async(func);
+ },
+
+ godot_js_os_request_quit_cb__sig: 'vi',
+ godot_js_os_request_quit_cb: function (p_callback) {
+ GodotOS.request_quit = GodotRuntime.get_func(p_callback);
+ },
+
+ godot_js_os_fs_is_persistent__sig: 'i',
+ godot_js_os_fs_is_persistent: function () {
+ return GodotFS.is_persistent();
+ },
+
+ godot_js_os_fs_sync__sig: 'vi',
+ godot_js_os_fs_sync: function (callback) {
+ const func = GodotRuntime.get_func(callback);
+ GodotOS._fs_sync_promise = GodotFS.sync();
+ GodotOS._fs_sync_promise.then(function (err) {
+ func();
+ });
+ },
+
+ godot_js_os_execute__sig: 'ii',
+ godot_js_os_execute: function (p_json) {
+ const json_args = GodotRuntime.parseString(p_json);
+ const args = JSON.parse(json_args);
+ if (GodotConfig.on_execute) {
+ GodotConfig.on_execute(args);
+ return 0;
+ }
+ return 1;
+ },
+
+ godot_js_os_shell_open__sig: 'vi',
+ godot_js_os_shell_open: function (p_uri) {
+ window.open(GodotRuntime.parseString(p_uri), '_blank');
+ },
+};
+
+autoAddDeps(GodotOS, '$GodotOS');
+mergeInto(LibraryManager.library, GodotOS);
diff --git a/platform/javascript/js/libs/library_godot_runtime.js b/platform/javascript/js/libs/library_godot_runtime.js
new file mode 100644
index 0000000000..7e36ff8ab5
--- /dev/null
+++ b/platform/javascript/js/libs/library_godot_runtime.js
@@ -0,0 +1,120 @@
+/*************************************************************************/
+/* library_godot_runtime.js */
+/*************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/*************************************************************************/
+/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/*************************************************************************/
+
+const GodotRuntime = {
+ $GodotRuntime: {
+ /*
+ * Functions
+ */
+ get_func: function (ptr) {
+ return wasmTable.get(ptr); // eslint-disable-line no-undef
+ },
+
+ /*
+ * Prints
+ */
+ error: function () {
+ err.apply(null, Array.from(arguments)); // eslint-disable-line no-undef
+ },
+
+ print: function () {
+ out.apply(null, Array.from(arguments)); // eslint-disable-line no-undef
+ },
+
+ /*
+ * Memory
+ */
+ malloc: function (p_size) {
+ return _malloc(p_size); // eslint-disable-line no-undef
+ },
+
+ free: function (p_ptr) {
+ _free(p_ptr); // eslint-disable-line no-undef
+ },
+
+ getHeapValue: function (p_ptr, p_type) {
+ return getValue(p_ptr, p_type); // eslint-disable-line no-undef
+ },
+
+ setHeapValue: function (p_ptr, p_value, p_type) {
+ setValue(p_ptr, p_value, p_type); // eslint-disable-line no-undef
+ },
+
+ heapSub: function (p_heap, p_ptr, p_len) {
+ const bytes = p_heap.BYTES_PER_ELEMENT;
+ return p_heap.subarray(p_ptr / bytes, p_ptr / bytes + p_len);
+ },
+
+ heapCopy: function (p_heap, p_ptr, p_len) {
+ const bytes = p_heap.BYTES_PER_ELEMENT;
+ return p_heap.slice(p_ptr / bytes, p_ptr / bytes + p_len);
+ },
+
+ /*
+ * Strings
+ */
+ parseString: function (p_ptr) {
+ return UTF8ToString(p_ptr); // eslint-disable-line no-undef
+ },
+
+ strlen: function (p_str) {
+ return lengthBytesUTF8(p_str); // eslint-disable-line no-undef
+ },
+
+ allocString: function (p_str) {
+ const length = GodotRuntime.strlen(p_str) + 1;
+ const c_str = GodotRuntime.malloc(length);
+ stringToUTF8(p_str, c_str, length); // eslint-disable-line no-undef
+ return c_str;
+ },
+
+ allocStringArray: function (p_strings) {
+ const size = p_strings.length;
+ const c_ptr = GodotRuntime.malloc(size * 4);
+ for (let i = 0; i < size; i++) {
+ HEAP32[(c_ptr >> 2) + i] = GodotRuntime.allocString(p_strings[i]);
+ }
+ return c_ptr;
+ },
+
+ freeStringArray: function (p_ptr, p_len) {
+ for (let i = 0; i < p_len; i++) {
+ GodotRuntime.free(HEAP32[(p_ptr >> 2) + i]);
+ }
+ GodotRuntime.free(p_ptr);
+ },
+
+ stringToHeap: function (p_str, p_ptr, p_len) {
+ return stringToUTF8Array(p_str, HEAP8, p_ptr, p_len); // eslint-disable-line no-undef
+ },
+ },
+};
+autoAddDeps(GodotRuntime, '$GodotRuntime');
+mergeInto(LibraryManager.library, GodotRuntime);
diff --git a/platform/javascript/os_javascript.cpp b/platform/javascript/os_javascript.cpp
index c0230b94fa..b922b2ba91 100644
--- a/platform/javascript/os_javascript.cpp
+++ b/platform/javascript/os_javascript.cpp
@@ -5,8 +5,8 @@
/* GODOT ENGINE */
/* https://godotengine.org */
/*************************************************************************/
-/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */
-/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */
+/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
@@ -30,1266 +30,203 @@
#include "os_javascript.h"
-#include "core/io/file_access_buffered_fa.h"
-//#include "drivers/gles2/rasterizer_gles2.h"
-#include "drivers/dummy/rasterizer_dummy.h"
+#include "core/debugger/engine_debugger.h"
+#include "core/io/json.h"
#include "drivers/unix/dir_access_unix.h"
#include "drivers/unix/file_access_unix.h"
#include "main/main.h"
-#include "servers/rendering/rendering_server_raster.h"
+#include "modules/modules_enabled.gen.h"
+#include "platform/javascript/display_server_javascript.h"
+#ifdef MODULE_WEBSOCKET_ENABLED
+#include "modules/websocket/remote_debugger_peer_websocket.h"
+#endif
+
+#include <dlfcn.h>
#include <emscripten.h>
-#include <png.h>
#include <stdlib.h>
-#include "dom_keys.inc"
-
-#define DOM_BUTTON_LEFT 0
-#define DOM_BUTTON_MIDDLE 1
-#define DOM_BUTTON_RIGHT 2
-#define DOM_BUTTON_XBUTTON1 3
-#define DOM_BUTTON_XBUTTON2 4
-#define GODOT_CANVAS_SELECTOR "#canvas"
-
-// Window (canvas)
-
-static void focus_canvas() {
-
- /* clang-format off */
- EM_ASM(
- Module.canvas.focus();
- );
- /* clang-format on */
-}
-
-static bool is_canvas_focused() {
-
- /* clang-format off */
- return EM_ASM_INT_V(
- return document.activeElement == Module.canvas;
- );
- /* clang-format on */
-}
-
-static Point2 compute_position_in_canvas(int x, int y) {
- int canvas_x = EM_ASM_INT({
- return document.getElementById('canvas').getBoundingClientRect().x;
- });
- int canvas_y = EM_ASM_INT({
- return document.getElementById('canvas').getBoundingClientRect().y;
- });
- int canvas_width;
- int canvas_height;
- emscripten_get_canvas_element_size(GODOT_CANVAS_SELECTOR, &canvas_width, &canvas_height);
-
- double element_width;
- double element_height;
- emscripten_get_element_css_size(GODOT_CANVAS_SELECTOR, &element_width, &element_height);
-
- return Point2((int)(canvas_width / element_width * (x - canvas_x)),
- (int)(canvas_height / element_height * (y - canvas_y)));
-}
-
-static bool cursor_inside_canvas = true;
-
-EM_BOOL OS_JavaScript::fullscreen_change_callback(int p_event_type, const EmscriptenFullscreenChangeEvent *p_event, void *p_user_data) {
-
- OS_JavaScript *os = get_singleton();
- // Empty ID is canvas.
- String target_id = String::utf8(p_event->id);
- if (target_id.empty() || target_id == "canvas") {
- // This event property is the only reliable data on
- // browser fullscreen state.
- os->video_mode.fullscreen = p_event->isFullscreen;
- if (os->video_mode.fullscreen) {
- os->entering_fullscreen = false;
- } else {
- // Restoring maximized window now will cause issues,
- // so delay until main_loop_iterate.
- os->just_exited_fullscreen = true;
- }
- }
- return false;
-}
-
-void OS_JavaScript::set_video_mode(const VideoMode &p_video_mode, int p_screen) {
-
- video_mode = p_video_mode;
-}
-
-OS::VideoMode OS_JavaScript::get_video_mode(int p_screen) const {
-
- return video_mode;
-}
-
-Size2 OS_JavaScript::get_screen_size(int p_screen) const {
-
- EmscriptenFullscreenChangeEvent ev;
- EMSCRIPTEN_RESULT result = emscripten_get_fullscreen_status(&ev);
- ERR_FAIL_COND_V(result != EMSCRIPTEN_RESULT_SUCCESS, Size2());
- return Size2(ev.screenWidth, ev.screenHeight);
-}
-
-void OS_JavaScript::set_window_size(const Size2 p_size) {
-
- windowed_size = p_size;
- if (video_mode.fullscreen) {
- window_maximized = false;
- set_window_fullscreen(false);
- } else {
- if (window_maximized) {
- emscripten_exit_soft_fullscreen();
- window_maximized = false;
- }
- emscripten_set_canvas_element_size(GODOT_CANVAS_SELECTOR, p_size.x, p_size.y);
- }
-}
-
-Size2 OS_JavaScript::get_window_size() const {
-
- int canvas[2];
- emscripten_get_canvas_element_size(GODOT_CANVAS_SELECTOR, canvas, canvas + 1);
- return Size2(canvas[0], canvas[1]);
-}
-
-void OS_JavaScript::set_window_maximized(bool p_enabled) {
-
- if (video_mode.fullscreen) {
- window_maximized = p_enabled;
- set_window_fullscreen(false);
- } else if (!p_enabled) {
- emscripten_exit_soft_fullscreen();
- window_maximized = false;
- } else if (!window_maximized) {
- // Prevent calling emscripten_enter_soft_fullscreen mutltiple times,
- // this would hide page elements permanently.
- EmscriptenFullscreenStrategy strategy;
- strategy.scaleMode = EMSCRIPTEN_FULLSCREEN_SCALE_STRETCH;
- strategy.canvasResolutionScaleMode = EMSCRIPTEN_FULLSCREEN_CANVAS_SCALE_STDDEF;
- strategy.filteringMode = EMSCRIPTEN_FULLSCREEN_FILTERING_DEFAULT;
- strategy.canvasResizedCallback = nullptr;
- emscripten_enter_soft_fullscreen(GODOT_CANVAS_SELECTOR, &strategy);
- window_maximized = p_enabled;
- }
-}
-
-bool OS_JavaScript::is_window_maximized() const {
-
- return window_maximized;
-}
-
-void OS_JavaScript::set_window_fullscreen(bool p_enabled) {
-
- if (p_enabled == video_mode.fullscreen) {
- return;
- }
-
- // Just request changes here, if successful, logic continues in
- // fullscreen_change_callback.
- if (p_enabled) {
- if (window_maximized) {
- // Soft fullsreen during real fullscreen can cause issues, so exit.
- // This must be called before requesting full screen.
- emscripten_exit_soft_fullscreen();
- }
- EmscriptenFullscreenStrategy strategy;
- strategy.scaleMode = EMSCRIPTEN_FULLSCREEN_SCALE_STRETCH;
- strategy.canvasResolutionScaleMode = EMSCRIPTEN_FULLSCREEN_CANVAS_SCALE_STDDEF;
- strategy.filteringMode = EMSCRIPTEN_FULLSCREEN_FILTERING_DEFAULT;
- strategy.canvasResizedCallback = nullptr;
- EMSCRIPTEN_RESULT result = emscripten_request_fullscreen_strategy(GODOT_CANVAS_SELECTOR, false, &strategy);
- ERR_FAIL_COND_MSG(result == EMSCRIPTEN_RESULT_FAILED_NOT_DEFERRED, "Enabling fullscreen is only possible from an input callback for the HTML5 platform.");
- ERR_FAIL_COND_MSG(result != EMSCRIPTEN_RESULT_SUCCESS, "Enabling fullscreen is only possible from an input callback for the HTML5 platform.");
- // Not fullscreen yet, so prevent "windowed" canvas dimensions from
- // being overwritten.
- entering_fullscreen = true;
- } else {
- // No logic allowed here, since exiting w/ ESC key won't use this function.
- ERR_FAIL_COND(emscripten_exit_fullscreen() != EMSCRIPTEN_RESULT_SUCCESS);
- }
-}
-
-bool OS_JavaScript::is_window_fullscreen() const {
-
- return video_mode.fullscreen;
-}
-
-void OS_JavaScript::get_fullscreen_mode_list(List<VideoMode> *p_list, int p_screen) const {
-
- Size2 screen = get_screen_size();
- p_list->push_back(OS::VideoMode(screen.width, screen.height, true));
-}
-
-bool OS_JavaScript::get_window_per_pixel_transparency_enabled() const {
- if (!is_layered_allowed()) {
- return false;
- }
- return transparency_enabled;
-}
-
-void OS_JavaScript::set_window_per_pixel_transparency_enabled(bool p_enabled) {
- if (!is_layered_allowed()) {
- return;
- }
- transparency_enabled = p_enabled;
-}
-
-// Keys
-
-template <typename T>
-static void dom2godot_mod(T *emscripten_event_ptr, Ref<InputEventWithModifiers> godot_event) {
-
- godot_event->set_shift(emscripten_event_ptr->shiftKey);
- godot_event->set_alt(emscripten_event_ptr->altKey);
- godot_event->set_control(emscripten_event_ptr->ctrlKey);
- godot_event->set_metakey(emscripten_event_ptr->metaKey);
-}
-
-static Ref<InputEventKey> setup_key_event(const EmscriptenKeyboardEvent *emscripten_event) {
-
- Ref<InputEventKey> ev;
- ev.instance();
- ev->set_echo(emscripten_event->repeat);
- dom2godot_mod(emscripten_event, ev);
- ev->set_keycode(dom2godot_keycode(emscripten_event->keyCode));
- ev->set_physical_keycode(dom2godot_keycode(emscripten_event->keyCode));
-
- String unicode = String::utf8(emscripten_event->key);
- // Check if empty or multi-character (e.g. `CapsLock`).
- if (unicode.length() != 1) {
- // Might be empty as well, but better than nonsense.
- unicode = String::utf8(emscripten_event->charValue);
- }
- if (unicode.length() == 1) {
- ev->set_unicode(unicode[0]);
- }
-
- return ev;
-}
-
-EM_BOOL OS_JavaScript::keydown_callback(int p_event_type, const EmscriptenKeyboardEvent *p_event, void *p_user_data) {
-
- OS_JavaScript *os = get_singleton();
- Ref<InputEventKey> ev = setup_key_event(p_event);
- ev->set_pressed(true);
- if (ev->get_unicode() == 0 && keycode_has_unicode(ev->get_keycode())) {
- // Defer to keypress event for legacy unicode retrieval.
- os->deferred_key_event = ev;
- // Do not suppress keypress event.
- return false;
- }
- os->input->parse_input_event(ev);
- // Resume audio context after input in case autoplay was denied.
- os->audio_driver_javascript.resume();
- return true;
-}
-
-EM_BOOL OS_JavaScript::keypress_callback(int p_event_type, const EmscriptenKeyboardEvent *p_event, void *p_user_data) {
-
- OS_JavaScript *os = get_singleton();
- os->deferred_key_event->set_unicode(p_event->charCode);
- os->input->parse_input_event(os->deferred_key_event);
- return true;
-}
-
-EM_BOOL OS_JavaScript::keyup_callback(int p_event_type, const EmscriptenKeyboardEvent *p_event, void *p_user_data) {
-
- Ref<InputEventKey> ev = setup_key_event(p_event);
- ev->set_pressed(false);
- get_singleton()->input->parse_input_event(ev);
- return ev->get_keycode() != KEY_UNKNOWN && ev->get_keycode() != 0;
-}
-
-// Mouse
-
-Point2 OS_JavaScript::get_mouse_position() const {
-
- return input->get_mouse_position();
-}
-
-int OS_JavaScript::get_mouse_button_state() const {
-
- return input->get_mouse_button_mask();
-}
-
-EM_BOOL OS_JavaScript::mouse_button_callback(int p_event_type, const EmscriptenMouseEvent *p_event, void *p_user_data) {
-
- OS_JavaScript *os = get_singleton();
-
- Ref<InputEventMouseButton> ev;
- ev.instance();
- ev->set_pressed(p_event_type == EMSCRIPTEN_EVENT_MOUSEDOWN);
- ev->set_position(compute_position_in_canvas(p_event->clientX, p_event->clientY));
- ev->set_global_position(ev->get_position());
- dom2godot_mod(p_event, ev);
-
- switch (p_event->button) {
- case DOM_BUTTON_LEFT: ev->set_button_index(BUTTON_LEFT); break;
- case DOM_BUTTON_MIDDLE: ev->set_button_index(BUTTON_MIDDLE); break;
- case DOM_BUTTON_RIGHT: ev->set_button_index(BUTTON_RIGHT); break;
- case DOM_BUTTON_XBUTTON1: ev->set_button_index(BUTTON_XBUTTON1); break;
- case DOM_BUTTON_XBUTTON2: ev->set_button_index(BUTTON_XBUTTON2); break;
- default: return false;
- }
-
- if (ev->is_pressed()) {
-
- double diff = emscripten_get_now() - os->last_click_ms;
-
- if (ev->get_button_index() == os->last_click_button_index) {
-
- if (diff < 400 && Point2(os->last_click_pos).distance_to(ev->get_position()) < 5) {
-
- os->last_click_ms = 0;
- os->last_click_pos = Point2(-100, -100);
- os->last_click_button_index = -1;
- ev->set_doubleclick(true);
- }
-
- } else {
- os->last_click_button_index = ev->get_button_index();
- }
-
- if (!ev->is_doubleclick()) {
- os->last_click_ms += diff;
- os->last_click_pos = ev->get_position();
- }
- }
-
- int mask = os->input->get_mouse_button_mask();
- int button_flag = 1 << (ev->get_button_index() - 1);
- if (ev->is_pressed()) {
- // Since the event is consumed, focus manually. The containing iframe,
- // if exists, may not have focus yet, so focus even if already focused.
- focus_canvas();
- mask |= button_flag;
- } else if (mask & button_flag) {
- mask &= ~button_flag;
- } else {
- // Received release event, but press was outside the canvas, so ignore.
- return false;
- }
- ev->set_button_mask(mask);
-
- os->input->parse_input_event(ev);
- // Resume audio context after input in case autoplay was denied.
- os->audio_driver_javascript.resume();
- // Prevent multi-click text selection and wheel-click scrolling anchor.
- // Context menu is prevented through contextmenu event.
- return true;
-}
-
-EM_BOOL OS_JavaScript::mousemove_callback(int p_event_type, const EmscriptenMouseEvent *p_event, void *p_user_data) {
-
- OS_JavaScript *os = get_singleton();
-
- int input_mask = os->input->get_mouse_button_mask();
- Point2 pos = compute_position_in_canvas(p_event->clientX, p_event->clientY);
- // For motion outside the canvas, only read mouse movement if dragging
- // started inside the canvas; imitating desktop app behaviour.
- if (!cursor_inside_canvas && !input_mask)
- return false;
-
- Ref<InputEventMouseMotion> ev;
- ev.instance();
- dom2godot_mod(p_event, ev);
- ev->set_button_mask(input_mask);
-
- ev->set_position(pos);
- ev->set_global_position(ev->get_position());
-
- ev->set_relative(Vector2(p_event->movementX, p_event->movementY));
- os->input->set_mouse_position(ev->get_position());
- ev->set_speed(os->input->get_last_mouse_speed());
-
- os->input->parse_input_event(ev);
- // Don't suppress mouseover/-leave events.
- return false;
-}
-
-static const char *godot2dom_cursor(OS::CursorShape p_shape) {
-
- switch (p_shape) {
- case OS::CURSOR_ARROW:
- default:
- return "auto";
- case OS::CURSOR_IBEAM: return "text";
- case OS::CURSOR_POINTING_HAND: return "pointer";
- case OS::CURSOR_CROSS: return "crosshair";
- case OS::CURSOR_WAIT: return "progress";
- case OS::CURSOR_BUSY: return "wait";
- case OS::CURSOR_DRAG: return "grab";
- case OS::CURSOR_CAN_DROP: return "grabbing";
- case OS::CURSOR_FORBIDDEN: return "no-drop";
- case OS::CURSOR_VSIZE: return "ns-resize";
- case OS::CURSOR_HSIZE: return "ew-resize";
- case OS::CURSOR_BDIAGSIZE: return "nesw-resize";
- case OS::CURSOR_FDIAGSIZE: return "nwse-resize";
- case OS::CURSOR_MOVE: return "move";
- case OS::CURSOR_VSPLIT: return "row-resize";
- case OS::CURSOR_HSPLIT: return "col-resize";
- case OS::CURSOR_HELP: return "help";
- }
-}
-
-static void set_css_cursor(const char *p_cursor) {
-
- /* clang-format off */
- EM_ASM_({
- Module.canvas.style.cursor = UTF8ToString($0);
- }, p_cursor);
- /* clang-format on */
-}
-
-static bool is_css_cursor_hidden() {
-
- /* clang-format off */
- return EM_ASM_INT({
- return Module.canvas.style.cursor === 'none';
- });
- /* clang-format on */
-}
-
-void OS_JavaScript::set_cursor_shape(CursorShape p_shape) {
-
- ERR_FAIL_INDEX(p_shape, CURSOR_MAX);
-
- if (get_mouse_mode() == MOUSE_MODE_VISIBLE) {
- if (cursors[p_shape] != "") {
- Vector<String> url = cursors[p_shape].split("?");
- set_css_cursor(("url(\"" + url[0] + "\") " + url[1] + ", auto").utf8());
- } else {
- set_css_cursor(godot2dom_cursor(p_shape));
- }
- }
-
- cursor_shape = p_shape;
-}
-
-void OS_JavaScript::set_custom_mouse_cursor(const RES &p_cursor, CursorShape p_shape, const Vector2 &p_hotspot) {
-
- if (p_cursor.is_valid()) {
-
- Map<CursorShape, Vector<Variant>>::Element *cursor_c = cursors_cache.find(p_shape);
-
- if (cursor_c) {
- if (cursor_c->get()[0] == p_cursor && cursor_c->get()[1] == p_hotspot) {
- set_cursor_shape(p_shape);
- return;
- }
-
- cursors_cache.erase(p_shape);
- }
-
- Ref<Texture2D> texture = p_cursor;
- Ref<AtlasTexture> atlas_texture = p_cursor;
- Ref<Image> image;
- Size2 texture_size;
- Rect2 atlas_rect;
-
- if (texture.is_valid()) {
- image = texture->get_data();
- if (image.is_valid()) {
- image->duplicate();
- }
- }
-
- if (!image.is_valid() && atlas_texture.is_valid()) {
- texture = atlas_texture->get_atlas();
-
- atlas_rect.size.width = texture->get_width();
- atlas_rect.size.height = texture->get_height();
- atlas_rect.position.x = atlas_texture->get_region().position.x;
- atlas_rect.position.y = atlas_texture->get_region().position.y;
-
- texture_size.width = atlas_texture->get_region().size.x;
- texture_size.height = atlas_texture->get_region().size.y;
- } else if (image.is_valid()) {
- texture_size.width = texture->get_width();
- texture_size.height = texture->get_height();
- }
-
- ERR_FAIL_COND(!texture.is_valid());
- ERR_FAIL_COND(p_hotspot.x < 0 || p_hotspot.y < 0);
- ERR_FAIL_COND(texture_size.width > 256 || texture_size.height > 256);
- ERR_FAIL_COND(p_hotspot.x > texture_size.width || p_hotspot.y > texture_size.height);
-
- image = texture->get_data();
-
- ERR_FAIL_COND(!image.is_valid());
-
- image = image->duplicate();
-
- if (atlas_texture.is_valid())
- image->crop_from_point(
- atlas_rect.position.x,
- atlas_rect.position.y,
- texture_size.width,
- texture_size.height);
-
- if (image->get_format() != Image::FORMAT_RGBA8) {
- image->convert(Image::FORMAT_RGBA8);
- }
-
- png_image png_meta;
- memset(&png_meta, 0, sizeof png_meta);
- png_meta.version = PNG_IMAGE_VERSION;
- png_meta.width = texture_size.width;
- png_meta.height = texture_size.height;
- png_meta.format = PNG_FORMAT_RGBA;
-
- PackedByteArray png;
- size_t len;
- PackedByteArray data = image->get_data();
- ERR_FAIL_COND(!png_image_write_get_memory_size(png_meta, len, 0, data.ptr(), 0, nullptr));
-
- png.resize(len);
- ERR_FAIL_COND(!png_image_write_to_memory(&png_meta, png.ptrw(), &len, 0, data.ptr(), 0, nullptr));
-
- char *object_url;
- /* clang-format off */
- EM_ASM({
- var PNG_PTR = $0;
- var PNG_LEN = $1;
- var PTR = $2;
-
- var png = new Blob([HEAPU8.slice(PNG_PTR, PNG_PTR + PNG_LEN)], { type: 'image/png' });
- var url = URL.createObjectURL(png);
- var length_bytes = lengthBytesUTF8(url) + 1;
- var string_on_wasm_heap = _malloc(length_bytes);
- setValue(PTR, string_on_wasm_heap, '*');
- stringToUTF8(url, string_on_wasm_heap, length_bytes);
- }, png.ptr(), len, &object_url);
- /* clang-format on */
-
- String url = String::utf8(object_url) + "?" + itos(p_hotspot.x) + " " + itos(p_hotspot.y);
-
- /* clang-format off */
- EM_ASM({ _free($0); }, object_url);
- /* clang-format on */
-
- if (cursors[p_shape] != "") {
- /* clang-format off */
- EM_ASM({
- URL.revokeObjectURL(UTF8ToString($0).split('?')[0]);
- }, cursors[p_shape].utf8().get_data());
- /* clang-format on */
- cursors[p_shape] = "";
- }
-
- cursors[p_shape] = url;
-
- Vector<Variant> params;
- params.push_back(p_cursor);
- params.push_back(p_hotspot);
- cursors_cache.insert(p_shape, params);
-
- } else if (cursors[p_shape] != "") {
- /* clang-format off */
- EM_ASM({
- URL.revokeObjectURL(UTF8ToString($0).split('?')[0]);
- }, cursors[p_shape].utf8().get_data());
- /* clang-format on */
- cursors[p_shape] = "";
-
- cursors_cache.erase(p_shape);
- }
-
- set_cursor_shape(cursor_shape);
-}
-
-void OS_JavaScript::set_mouse_mode(OS::MouseMode p_mode) {
-
- ERR_FAIL_COND_MSG(p_mode == MOUSE_MODE_CONFINED, "MOUSE_MODE_CONFINED is not supported for the HTML5 platform.");
- if (p_mode == get_mouse_mode())
- return;
-
- if (p_mode == MOUSE_MODE_VISIBLE) {
-
- // set_css_cursor must be called before set_cursor_shape to make the cursor visible
- set_css_cursor(godot2dom_cursor(cursor_shape));
- set_cursor_shape(cursor_shape);
- emscripten_exit_pointerlock();
-
- } else if (p_mode == MOUSE_MODE_HIDDEN) {
-
- set_css_cursor("none");
- emscripten_exit_pointerlock();
-
- } else if (p_mode == MOUSE_MODE_CAPTURED) {
-
- EMSCRIPTEN_RESULT result = emscripten_request_pointerlock("canvas", false);
- ERR_FAIL_COND_MSG(result == EMSCRIPTEN_RESULT_FAILED_NOT_DEFERRED, "MOUSE_MODE_CAPTURED can only be entered from within an appropriate input callback.");
- ERR_FAIL_COND_MSG(result != EMSCRIPTEN_RESULT_SUCCESS, "MOUSE_MODE_CAPTURED can only be entered from within an appropriate input callback.");
- // set_css_cursor must be called before set_cursor_shape to make the cursor visible
- set_css_cursor(godot2dom_cursor(cursor_shape));
- set_cursor_shape(cursor_shape);
- }
-}
-
-OS::MouseMode OS_JavaScript::get_mouse_mode() const {
-
- if (is_css_cursor_hidden())
- return MOUSE_MODE_HIDDEN;
-
- EmscriptenPointerlockChangeEvent ev;
- emscripten_get_pointerlock_status(&ev);
- return (ev.isActive && String::utf8(ev.id) == "canvas") ? MOUSE_MODE_CAPTURED : MOUSE_MODE_VISIBLE;
-}
-
-// Wheel
-
-EM_BOOL OS_JavaScript::wheel_callback(int p_event_type, const EmscriptenWheelEvent *p_event, void *p_user_data) {
-
- ERR_FAIL_COND_V(p_event_type != EMSCRIPTEN_EVENT_WHEEL, false);
- if (!is_canvas_focused()) {
- if (cursor_inside_canvas) {
- focus_canvas();
- } else {
- return false;
- }
- }
-
- InputDefault *input = get_singleton()->input;
- Ref<InputEventMouseButton> ev;
- ev.instance();
- ev->set_position(input->get_mouse_position());
- ev->set_global_position(ev->get_position());
-
- ev->set_shift(input->is_key_pressed(KEY_SHIFT));
- ev->set_alt(input->is_key_pressed(KEY_ALT));
- ev->set_control(input->is_key_pressed(KEY_CONTROL));
- ev->set_metakey(input->is_key_pressed(KEY_META));
-
- if (p_event->deltaY < 0)
- ev->set_button_index(BUTTON_WHEEL_UP);
- else if (p_event->deltaY > 0)
- ev->set_button_index(BUTTON_WHEEL_DOWN);
- else if (p_event->deltaX > 0)
- ev->set_button_index(BUTTON_WHEEL_LEFT);
- else if (p_event->deltaX < 0)
- ev->set_button_index(BUTTON_WHEEL_RIGHT);
- else
- return false;
-
- // Different browsers give wildly different delta values, and we can't
- // interpret deltaMode, so use default value for wheel events' factor.
-
- int button_flag = 1 << (ev->get_button_index() - 1);
-
- ev->set_pressed(true);
- ev->set_button_mask(input->get_mouse_button_mask() | button_flag);
- input->parse_input_event(ev);
-
- ev->set_pressed(false);
- ev->set_button_mask(input->get_mouse_button_mask() & ~button_flag);
- input->parse_input_event(ev);
-
- return true;
-}
-
-// Touch
-
-bool OS_JavaScript::has_touchscreen_ui_hint() const {
-
- /* clang-format off */
- return EM_ASM_INT_V(
- return 'ontouchstart' in window;
- );
- /* clang-format on */
-}
-
-EM_BOOL OS_JavaScript::touch_press_callback(int p_event_type, const EmscriptenTouchEvent *p_event, void *p_user_data) {
-
- OS_JavaScript *os = get_singleton();
- Ref<InputEventScreenTouch> ev;
- ev.instance();
- int lowest_id_index = -1;
- for (int i = 0; i < p_event->numTouches; ++i) {
-
- const EmscriptenTouchPoint &touch = p_event->touches[i];
- if (lowest_id_index == -1 || touch.identifier < p_event->touches[lowest_id_index].identifier)
- lowest_id_index = i;
- if (!touch.isChanged)
- continue;
- ev->set_index(touch.identifier);
- ev->set_position(compute_position_in_canvas(touch.clientX, touch.clientY));
- os->touches[i] = ev->get_position();
- ev->set_pressed(p_event_type == EMSCRIPTEN_EVENT_TOUCHSTART);
-
- os->input->parse_input_event(ev);
- }
- // Resume audio context after input in case autoplay was denied.
- os->audio_driver_javascript.resume();
- return true;
-}
-
-EM_BOOL OS_JavaScript::touchmove_callback(int p_event_type, const EmscriptenTouchEvent *p_event, void *p_user_data) {
-
- OS_JavaScript *os = get_singleton();
- Ref<InputEventScreenDrag> ev;
- ev.instance();
- int lowest_id_index = -1;
- for (int i = 0; i < p_event->numTouches; ++i) {
-
- const EmscriptenTouchPoint &touch = p_event->touches[i];
- if (lowest_id_index == -1 || touch.identifier < p_event->touches[lowest_id_index].identifier)
- lowest_id_index = i;
- if (!touch.isChanged)
- continue;
- ev->set_index(touch.identifier);
- ev->set_position(compute_position_in_canvas(touch.clientX, touch.clientY));
- Point2 &prev = os->touches[i];
- ev->set_relative(ev->get_position() - prev);
- prev = ev->get_position();
-
- os->input->parse_input_event(ev);
- }
- return true;
-}
-
-// Gamepad
-
-EM_BOOL OS_JavaScript::gamepad_change_callback(int p_event_type, const EmscriptenGamepadEvent *p_event, void *p_user_data) {
-
- InputDefault *input = get_singleton()->input;
- if (p_event_type == EMSCRIPTEN_EVENT_GAMEPADCONNECTED) {
-
- String guid = "";
- if (String::utf8(p_event->mapping) == "standard")
- guid = "Default HTML5 Gamepad";
- input->joy_connection_changed(p_event->index, true, String::utf8(p_event->id), guid);
- } else {
- input->joy_connection_changed(p_event->index, false, "");
- }
- return true;
-}
-
-void OS_JavaScript::process_joypads() {
-
- int joypad_count = emscripten_get_num_gamepads();
- for (int joypad = 0; joypad < joypad_count; joypad++) {
- EmscriptenGamepadEvent state;
- EMSCRIPTEN_RESULT query_result = emscripten_get_gamepad_status(joypad, &state);
- // Chromium reserves gamepads slots, so NO_DATA is an expected result.
- ERR_CONTINUE(query_result != EMSCRIPTEN_RESULT_SUCCESS &&
- query_result != EMSCRIPTEN_RESULT_NO_DATA);
- if (query_result == EMSCRIPTEN_RESULT_SUCCESS && state.connected) {
-
- int button_count = MIN(state.numButtons, 18);
- int axis_count = MIN(state.numAxes, 8);
- for (int button = 0; button < button_count; button++) {
-
- float value = state.analogButton[button];
- if (String::utf8(state.mapping) == "standard" && (button == JOY_ANALOG_L2 || button == JOY_ANALOG_R2)) {
- InputDefault::JoyAxis joy_axis;
- joy_axis.min = 0;
- joy_axis.value = value;
- input->joy_axis(joypad, button, joy_axis);
- } else {
- input->joy_button(joypad, button, value);
- }
- }
- for (int axis = 0; axis < axis_count; axis++) {
-
- InputDefault::JoyAxis joy_axis;
- joy_axis.min = -1;
- joy_axis.value = state.axis[axis];
- input->joy_axis(joypad, axis, joy_axis);
- }
- }
- }
-}
-
-bool OS_JavaScript::is_joy_known(int p_device) {
-
- return input->is_joy_mapped(p_device);
-}
-
-String OS_JavaScript::get_joy_guid(int p_device) const {
-
- return input->get_joy_guid_remapped(p_device);
-}
-
-// Video
-
-int OS_JavaScript::get_video_driver_count() const {
-
- return VIDEO_DRIVER_MAX;
-}
-
-const char *OS_JavaScript::get_video_driver_name(int p_driver) const {
-
- switch (p_driver) {
- case VIDEO_DRIVER_GLES2:
- return "GLES2";
- }
- ERR_FAIL_V_MSG(nullptr, "Invalid video driver index: " + itos(p_driver) + ".");
-}
-
-// Audio
-
-int OS_JavaScript::get_audio_driver_count() const {
-
- return 1;
-}
-
-const char *OS_JavaScript::get_audio_driver_name(int p_driver) const {
-
- return "JavaScript";
-}
-
-// Clipboard
-extern "C" EMSCRIPTEN_KEEPALIVE void update_clipboard(const char *p_text) {
- // Only call set_clipboard from OS (sets local clipboard)
- OS::get_singleton()->OS::set_clipboard(p_text);
-}
-
-void OS_JavaScript::set_clipboard(const String &p_text) {
- OS::set_clipboard(p_text);
- /* clang-format off */
- int err = EM_ASM_INT({
- var text = UTF8ToString($0);
- if (!navigator.clipboard || !navigator.clipboard.writeText)
- return 1;
- navigator.clipboard.writeText(text).catch(function(e) {
- // Setting OS clipboard is only possible from an input callback.
- console.error("Setting OS clipboard is only possible from an input callback for the HTML5 plafrom. Exception:", e);
- });
- return 0;
- }, p_text.utf8().get_data());
- /* clang-format on */
- ERR_FAIL_COND_MSG(err, "Clipboard API is not supported.");
-}
-
-String OS_JavaScript::get_clipboard() const {
- /* clang-format off */
- EM_ASM({
- try {
- navigator.clipboard.readText().then(function (result) {
- ccall('update_clipboard', 'void', ['string'], [result]);
- }).catch(function (e) {
- // Fail graciously.
- });
- } catch (e) {
- // Fail graciously.
- }
- });
- /* clang-format on */
- return this->OS::get_clipboard();
-}
+#include "godot_js.h"
// Lifecycle
-int OS_JavaScript::get_current_video_driver() const {
- return video_driver_index;
-}
-
-void OS_JavaScript::initialize_core() {
-
+void OS_JavaScript::initialize() {
OS_Unix::initialize_core();
- FileAccess::make_default<FileAccessBufferedFA<FileAccessUnix>>(FileAccess::ACCESS_RESOURCES);
-}
-
-Error OS_JavaScript::initialize(const VideoMode &p_desired, int p_video_driver, int p_audio_driver) {
-
-#if 0
- EmscriptenWebGLContextAttributes attributes;
- emscripten_webgl_init_context_attributes(&attributes);
- attributes.alpha = GLOBAL_GET("display/window/per_pixel_transparency/allowed");
- attributes.antialias = false;
- ERR_FAIL_INDEX_V(p_video_driver, VIDEO_DRIVER_MAX, ERR_INVALID_PARAMETER);
-
- if (p_desired.layered) {
- set_window_per_pixel_transparency_enabled(true);
- }
+ DisplayServerJavaScript::register_javascript_driver();
- bool gl_initialization_error = false;
-
- if (RasterizerGLES2::is_viable() == OK) {
- attributes.majorVersion = 1;
- RasterizerGLES2::register_config();
- RasterizerGLES2::make_current();
- } else {
- gl_initialization_error = true;
- }
-
- EMSCRIPTEN_WEBGL_CONTEXT_HANDLE ctx = emscripten_webgl_create_context(GODOT_CANVAS_SELECTOR, &attributes);
- if (emscripten_webgl_make_context_current(ctx) != EMSCRIPTEN_RESULT_SUCCESS) {
- gl_initialization_error = true;
- }
-
- if (gl_initialization_error) {
- OS::get_singleton()->alert("Your browser does not seem to support WebGL. Please update your browser version.",
- "Unable to initialize video driver");
- return ERR_UNAVAILABLE;
- }
-
- video_driver_index = p_video_driver;
-
- video_mode = p_desired;
- // fullscreen_change_callback will correct this if the request is successful.
- video_mode.fullscreen = false;
- // Emscripten only attempts fullscreen requests if the user input callback
- // was registered through one its own functions, so request manually for
- // start-up fullscreen.
- if (p_desired.fullscreen) {
- /* clang-format off */
- EM_ASM({
- const canvas = Module.canvas;
- (canvas.requestFullscreen || canvas.msRequestFullscreen ||
- canvas.mozRequestFullScreen || canvas.mozRequestFullscreen ||
- canvas.webkitRequestFullscreen
- ).call(canvas);
- });
- /* clang-format on */
- }
- /* clang-format off */
- if (EM_ASM_INT_V({ return Module.resizeCanvasOnStart })) {
- /* clang-format on */
- set_window_size(Size2(video_mode.width, video_mode.height));
- } else {
- set_window_size(get_window_size());
- }
+#ifdef MODULE_WEBSOCKET_ENABLED
+ EngineDebugger::register_uri_handler("ws://", RemoteDebuggerPeerWebSocket::create);
+ EngineDebugger::register_uri_handler("wss://", RemoteDebuggerPeerWebSocket::create);
#endif
- RasterizerDummy::make_current(); // TODO GLES2 in Godot 4.0... or webgpu?
-
- char locale_ptr[16];
- /* clang-format off */
- EM_ASM_ARGS({
- stringToUTF8(Module.locale, $0, 16);
- }, locale_ptr);
- /* clang-format on */
- setenv("LANG", locale_ptr, true);
-
- AudioDriverManager::initialize(p_audio_driver);
- RenderingServer *rendering_server = memnew(RenderingServerRaster());
- input = memnew(InputDefault);
-
- EMSCRIPTEN_RESULT result;
-#define EM_CHECK(ev) \
- if (result != EMSCRIPTEN_RESULT_SUCCESS) \
- ERR_PRINT("Error while setting " #ev " callback: Code " + itos(result));
-#define SET_EM_CALLBACK(target, ev, cb) \
- result = emscripten_set_##ev##_callback(target, nullptr, true, &cb); \
- EM_CHECK(ev)
-#define SET_EM_CALLBACK_NOTARGET(ev, cb) \
- result = emscripten_set_##ev##_callback(nullptr, true, &cb); \
- EM_CHECK(ev)
- // These callbacks from Emscripten's html5.h suffice to access most
- // JavaScript APIs. For APIs that are not (sufficiently) exposed, EM_ASM
- // is used below.
- SET_EM_CALLBACK(EMSCRIPTEN_EVENT_TARGET_WINDOW, mousemove, mousemove_callback)
- SET_EM_CALLBACK(GODOT_CANVAS_SELECTOR, mousedown, mouse_button_callback)
- SET_EM_CALLBACK(EMSCRIPTEN_EVENT_TARGET_WINDOW, mouseup, mouse_button_callback)
- SET_EM_CALLBACK(GODOT_CANVAS_SELECTOR, wheel, wheel_callback)
- SET_EM_CALLBACK(GODOT_CANVAS_SELECTOR, touchstart, touch_press_callback)
- SET_EM_CALLBACK(GODOT_CANVAS_SELECTOR, touchmove, touchmove_callback)
- SET_EM_CALLBACK(GODOT_CANVAS_SELECTOR, touchend, touch_press_callback)
- SET_EM_CALLBACK(GODOT_CANVAS_SELECTOR, touchcancel, touch_press_callback)
- SET_EM_CALLBACK(GODOT_CANVAS_SELECTOR, keydown, keydown_callback)
- SET_EM_CALLBACK(GODOT_CANVAS_SELECTOR, keypress, keypress_callback)
- SET_EM_CALLBACK(GODOT_CANVAS_SELECTOR, keyup, keyup_callback)
- SET_EM_CALLBACK(EMSCRIPTEN_EVENT_TARGET_DOCUMENT, fullscreenchange, fullscreen_change_callback)
- SET_EM_CALLBACK_NOTARGET(gamepadconnected, gamepad_change_callback)
- SET_EM_CALLBACK_NOTARGET(gamepaddisconnected, gamepad_change_callback)
-#undef SET_EM_CALLBACK_NOTARGET
-#undef SET_EM_CALLBACK
-#undef EM_CHECK
-
- /* clang-format off */
- EM_ASM_ARGS({
- const send_notification = cwrap('send_notification', null, ['number']);
- const notifications = arguments;
- (['mouseover', 'mouseleave', 'focus', 'blur']).forEach(function(event, index) {
- Module.canvas.addEventListener(event, send_notification.bind(null, notifications[index]));
- });
- // Clipboard
- const update_clipboard = cwrap('update_clipboard', null, ['string']);
- window.addEventListener('paste', function(evt) {
- update_clipboard(evt.clipboardData.getData('text'));
- }, true);
- },
- NOTIFICATION_WM_MOUSE_ENTER,
- NOTIFICATION_WM_MOUSE_EXIT,
- NOTIFICATION_WM_FOCUS_IN,
- NOTIFICATION_WM_FOCUS_OUT
- );
- /* clang-format on */
-
- rendering_server->init();
+}
- return OK;
+void OS_JavaScript::resume_audio() {
+ if (audio_driver_javascript) {
+ audio_driver_javascript->resume();
+ }
}
void OS_JavaScript::set_main_loop(MainLoop *p_main_loop) {
-
main_loop = p_main_loop;
- input->set_main_loop(p_main_loop);
}
MainLoop *OS_JavaScript::get_main_loop() const {
-
return main_loop;
}
-void OS_JavaScript::run_async() {
-
- main_loop->init();
- emscripten_set_main_loop(main_loop_callback, -1, false);
-}
-
-void OS_JavaScript::main_loop_callback() {
-
- get_singleton()->main_loop_iterate();
+void OS_JavaScript::fs_sync_callback() {
+ get_singleton()->idb_is_syncing = false;
}
bool OS_JavaScript::main_loop_iterate() {
-
- if (is_userfs_persistent() && sync_wait_time >= 0) {
- int64_t current_time = get_ticks_msec();
- int64_t elapsed_time = current_time - last_sync_check_time;
- last_sync_check_time = current_time;
-
- sync_wait_time -= elapsed_time;
-
- if (sync_wait_time < 0) {
- /* clang-format off */
- EM_ASM(
- FS.syncfs(function(error) {
- if (error) { err('Failed to save IDB file system: ' + error.message); }
- });
- );
- /* clang-format on */
- }
+ if (is_userfs_persistent() && idb_needs_sync && !idb_is_syncing) {
+ idb_is_syncing = true;
+ idb_needs_sync = false;
+ godot_js_os_fs_sync(&fs_sync_callback);
}
- if (emscripten_sample_gamepad_data() == EMSCRIPTEN_RESULT_SUCCESS)
- process_joypads();
-
- if (just_exited_fullscreen) {
- if (window_maximized) {
- EmscriptenFullscreenStrategy strategy;
- strategy.scaleMode = EMSCRIPTEN_FULLSCREEN_SCALE_STRETCH;
- strategy.canvasResolutionScaleMode = EMSCRIPTEN_FULLSCREEN_CANVAS_SCALE_STDDEF;
- strategy.filteringMode = EMSCRIPTEN_FULLSCREEN_FILTERING_DEFAULT;
- strategy.canvasResizedCallback = nullptr;
- emscripten_enter_soft_fullscreen(GODOT_CANVAS_SELECTOR, &strategy);
- } else {
- emscripten_set_canvas_element_size(GODOT_CANVAS_SELECTOR, windowed_size.width, windowed_size.height);
- }
- just_exited_fullscreen = false;
- }
-
- int canvas[2];
- emscripten_get_canvas_element_size(GODOT_CANVAS_SELECTOR, canvas, canvas + 1);
- video_mode.width = canvas[0];
- video_mode.height = canvas[1];
- if (!window_maximized && !video_mode.fullscreen && !just_exited_fullscreen && !entering_fullscreen) {
- windowed_size.width = canvas[0];
- windowed_size.height = canvas[1];
- }
+ DisplayServer::get_singleton()->process_events();
return Main::iteration();
}
void OS_JavaScript::delete_main_loop() {
-
- memdelete(main_loop);
+ if (main_loop) {
+ memdelete(main_loop);
+ }
+ main_loop = nullptr;
}
void OS_JavaScript::finalize() {
-
- memdelete(input);
+ delete_main_loop();
+ if (audio_driver_javascript) {
+ memdelete(audio_driver_javascript);
+ audio_driver_javascript = nullptr;
+ }
}
// Miscellaneous
-Error OS_JavaScript::execute(const String &p_path, const List<String> &p_arguments, bool p_blocking, ProcessID *r_child_id, String *r_pipe, int *r_exitcode, bool read_stderr, Mutex *p_pipe_mutex) {
+Error OS_JavaScript::execute(const String &p_path, const List<String> &p_arguments, String *r_pipe, int *r_exitcode, bool read_stderr, Mutex *p_pipe_mutex) {
+ return create_process(p_path, p_arguments);
+}
- ERR_FAIL_V_MSG(ERR_UNAVAILABLE, "OS::execute() is not available on the HTML5 platform.");
+Error OS_JavaScript::create_process(const String &p_path, const List<String> &p_arguments, ProcessID *r_child_id) {
+ Array args;
+ for (const List<String>::Element *E = p_arguments.front(); E; E = E->next()) {
+ args.push_back(E->get());
+ }
+ String json_args = JSON::print(args);
+ int failed = godot_js_os_execute(json_args.utf8().get_data());
+ ERR_FAIL_COND_V_MSG(failed, ERR_UNAVAILABLE, "OS::execute() or create_process() must be implemented in JavaScript via 'engine.setOnExecute' if required.");
+ return OK;
}
Error OS_JavaScript::kill(const ProcessID &p_pid) {
-
ERR_FAIL_V_MSG(ERR_UNAVAILABLE, "OS::kill() is not available on the HTML5 platform.");
}
int OS_JavaScript::get_process_id() const {
-
ERR_FAIL_V_MSG(0, "OS::get_process_id() is not available on the HTML5 platform.");
}
-extern "C" EMSCRIPTEN_KEEPALIVE void send_notification(int p_notification) {
-
- if (p_notification == NOTIFICATION_WM_MOUSE_ENTER || p_notification == NOTIFICATION_WM_MOUSE_EXIT) {
- cursor_inside_canvas = p_notification == NOTIFICATION_WM_MOUSE_ENTER;
- }
- OS_JavaScript::get_singleton()->get_main_loop()->notification(p_notification);
-}
-
bool OS_JavaScript::_check_internal_feature_support(const String &p_feature) {
-
- if (p_feature == "HTML5" || p_feature == "web")
+ if (p_feature == "HTML5" || p_feature == "web") {
return true;
+ }
#ifdef JAVASCRIPT_EVAL_ENABLED
- if (p_feature == "JavaScript")
+ if (p_feature == "JavaScript") {
return true;
+ }
#endif
-
- return false;
-}
-
-void OS_JavaScript::alert(const String &p_alert, const String &p_title) {
-
- /* clang-format off */
- EM_ASM_({
- window.alert(UTF8ToString($0));
- }, p_alert.utf8().get_data());
- /* clang-format on */
-}
-
-void OS_JavaScript::set_window_title(const String &p_title) {
-
- /* clang-format off */
- EM_ASM_({
- document.title = UTF8ToString($0);
- }, p_title.utf8().get_data());
- /* clang-format on */
-}
-
-void OS_JavaScript::set_icon(const Ref<Image> &p_icon) {
-
- ERR_FAIL_COND(p_icon.is_null());
- Ref<Image> icon = p_icon;
- if (icon->is_compressed()) {
- icon = icon->duplicate();
- ERR_FAIL_COND(icon->decompress() != OK);
+#ifndef NO_THREADS
+ if (p_feature == "threads") {
+ return true;
}
- if (icon->get_format() != Image::FORMAT_RGBA8) {
- if (icon == p_icon)
- icon = icon->duplicate();
- icon->convert(Image::FORMAT_RGBA8);
+#endif
+#if WASM_GDNATIVE
+ if (p_feature == "wasm32") {
+ return true;
}
+#endif
- png_image png_meta;
- memset(&png_meta, 0, sizeof png_meta);
- png_meta.version = PNG_IMAGE_VERSION;
- png_meta.width = icon->get_width();
- png_meta.height = icon->get_height();
- png_meta.format = PNG_FORMAT_RGBA;
-
- PackedByteArray png;
- size_t len;
- PackedByteArray data = icon->get_data();
- ERR_FAIL_COND(!png_image_write_get_memory_size(png_meta, len, 0, data.ptr(), 0, nullptr));
-
- png.resize(len);
- ERR_FAIL_COND(!png_image_write_to_memory(&png_meta, png.ptrw(), &len, 0, data.ptr(), 0, nullptr));
-
- /* clang-format off */
- EM_ASM_ARGS({
- var PNG_PTR = $0;
- var PNG_LEN = $1;
-
- var png = new Blob([HEAPU8.slice(PNG_PTR, PNG_PTR + PNG_LEN)], { type: "image/png" });
- var url = URL.createObjectURL(png);
- var link = document.getElementById('-gd-engine-icon');
- if (link === null) {
- link = document.createElement('link');
- link.rel = 'icon';
- link.id = '-gd-engine-icon';
- document.head.appendChild(link);
- }
- link.href = url;
- }, png.ptr(), len);
- /* clang-format on */
+ return false;
}
String OS_JavaScript::get_executable_path() const {
-
return OS::get_executable_path();
}
Error OS_JavaScript::shell_open(String p_uri) {
-
// Open URI in a new tab, browser will deal with it by protocol.
- /* clang-format off */
- EM_ASM_({
- window.open(UTF8ToString($0), '_blank');
- }, p_uri.utf8().get_data());
- /* clang-format on */
+ godot_js_os_shell_open(p_uri.utf8().get_data());
return OK;
}
String OS_JavaScript::get_name() const {
-
return "HTML5";
}
-bool OS_JavaScript::can_draw() const {
-
- return true; // Always?
-}
-
String OS_JavaScript::get_user_data_dir() const {
-
return "/userfs";
};
-String OS_JavaScript::get_resource_dir() const {
-
- return "/";
+String OS_JavaScript::get_cache_path() const {
+ return "/home/web_user/.cache";
}
-void OS_JavaScript::file_access_close_callback(const String &p_file, int p_flags) {
-
- OS_JavaScript *os = get_singleton();
- if (os->is_userfs_persistent() && p_file.begins_with("/userfs") && p_flags & FileAccess::WRITE) {
- os->last_sync_check_time = OS::get_singleton()->get_ticks_msec();
- // Wait five seconds in case more files are about to be closed.
- os->sync_wait_time = 5000;
- }
+String OS_JavaScript::get_config_path() const {
+ return "/home/web_user/.config";
}
-void OS_JavaScript::set_idb_available(bool p_idb_available) {
+String OS_JavaScript::get_data_path() const {
+ return "/home/web_user/.local/share";
+}
- idb_available = p_idb_available;
+void OS_JavaScript::file_access_close_callback(const String &p_file, int p_flags) {
+ OS_JavaScript *os = OS_JavaScript::get_singleton();
+ if (!(os->is_userfs_persistent() && (p_flags & FileAccess::WRITE))) {
+ return; // FS persistence is not working or we are not writing.
+ }
+ bool is_file_persistent = p_file.begins_with("/userfs");
+#ifdef TOOLS_ENABLED
+ // Hack for editor persistence (can we track).
+ is_file_persistent = is_file_persistent || p_file.begins_with("/home/web_user/");
+#endif
+ if (is_file_persistent) {
+ os->idb_needs_sync = true;
+ }
}
bool OS_JavaScript::is_userfs_persistent() const {
-
return idb_available;
}
-OS_JavaScript *OS_JavaScript::get_singleton() {
+Error OS_JavaScript::open_dynamic_library(const String p_path, void *&p_library_handle, bool p_also_set_library_path) {
+ String path = p_path.get_file();
+ p_library_handle = dlopen(path.utf8().get_data(), RTLD_NOW);
+ ERR_FAIL_COND_V_MSG(!p_library_handle, ERR_CANT_OPEN, "Can't open dynamic library: " + p_path + ". Error: " + dlerror());
+ return OK;
+}
+OS_JavaScript *OS_JavaScript::get_singleton() {
return static_cast<OS_JavaScript *>(OS::get_singleton());
}
-OS_JavaScript::OS_JavaScript(int p_argc, char *p_argv[]) {
-
- List<String> arguments;
- for (int i = 1; i < p_argc; i++) {
- arguments.push_back(String::utf8(p_argv[i]));
- }
- set_cmdline(p_argv[0], arguments);
-
- last_click_button_index = -1;
- last_click_ms = 0;
- last_click_pos = Point2(-100, -100);
-
- window_maximized = false;
- entering_fullscreen = false;
- just_exited_fullscreen = false;
- transparency_enabled = false;
+void OS_JavaScript::initialize_joypads() {
+}
- main_loop = nullptr;
+OS_JavaScript::OS_JavaScript() {
+ char locale_ptr[16];
+ godot_js_config_locale_get(locale_ptr, 16);
+ setenv("LANG", locale_ptr, true);
- idb_available = false;
- sync_wait_time = -1;
+ if (AudioDriverJavaScript::is_available()) {
+ audio_driver_javascript = memnew(AudioDriverJavaScript);
+ AudioDriverManager::add_driver(audio_driver_javascript);
+ }
- AudioDriverManager::add_driver(&audio_driver_javascript);
+ idb_available = godot_js_os_fs_is_persistent();
Vector<Logger *> loggers;
loggers.push_back(memnew(StdLogger));
diff --git a/platform/javascript/os_javascript.h b/platform/javascript/os_javascript.h
index feb0f0651a..8db62d9d1c 100644
--- a/platform/javascript/os_javascript.h
+++ b/platform/javascript/os_javascript.h
@@ -5,8 +5,8 @@
/* GODOT ENGINE */
/* https://godotengine.org */
/*************************************************************************/
-/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */
-/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */
+/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
@@ -35,135 +35,64 @@
#include "core/input/input.h"
#include "drivers/unix/os_unix.h"
#include "servers/audio_server.h"
-#include "servers/rendering/rasterizer.h"
#include <emscripten/html5.h>
class OS_JavaScript : public OS_Unix {
+ MainLoop *main_loop = nullptr;
+ AudioDriverJavaScript *audio_driver_javascript = nullptr;
- VideoMode video_mode;
- Vector2 windowed_size;
- bool window_maximized;
- bool entering_fullscreen;
- bool just_exited_fullscreen;
- bool transparency_enabled;
-
- InputDefault *input;
- Ref<InputEventKey> deferred_key_event;
- CursorShape cursor_shape;
- String cursors[CURSOR_MAX];
- Map<CursorShape, Vector<Variant>> cursors_cache;
- Point2 touches[32];
-
- Point2i last_click_pos;
- double last_click_ms;
- int last_click_button_index;
-
- MainLoop *main_loop;
- int video_driver_index;
- AudioDriverJavaScript audio_driver_javascript;
-
- bool idb_available;
- int64_t sync_wait_time;
- int64_t last_sync_check_time;
-
- static EM_BOOL fullscreen_change_callback(int p_event_type, const EmscriptenFullscreenChangeEvent *p_event, void *p_user_data);
-
- static EM_BOOL keydown_callback(int p_event_type, const EmscriptenKeyboardEvent *p_event, void *p_user_data);
- static EM_BOOL keypress_callback(int p_event_type, const EmscriptenKeyboardEvent *p_event, void *p_user_data);
- static EM_BOOL keyup_callback(int p_event_type, const EmscriptenKeyboardEvent *p_event, void *p_user_data);
-
- static EM_BOOL mousemove_callback(int p_event_type, const EmscriptenMouseEvent *p_event, void *p_user_data);
- static EM_BOOL mouse_button_callback(int p_event_type, const EmscriptenMouseEvent *p_event, void *p_user_data);
-
- static EM_BOOL wheel_callback(int p_event_type, const EmscriptenWheelEvent *p_event, void *p_user_data);
-
- static EM_BOOL touch_press_callback(int p_event_type, const EmscriptenTouchEvent *p_event, void *p_user_data);
- static EM_BOOL touchmove_callback(int p_event_type, const EmscriptenTouchEvent *p_event, void *p_user_data);
-
- static EM_BOOL gamepad_change_callback(int p_event_type, const EmscriptenGamepadEvent *p_event, void *p_user_data);
- void process_joypads();
+ bool idb_is_syncing = false;
+ bool idb_available = false;
+ bool idb_needs_sync = false;
static void main_loop_callback();
static void file_access_close_callback(const String &p_file, int p_flags);
+ static void fs_sync_callback();
protected:
- virtual int get_current_video_driver() const;
+ void initialize() override;
- virtual void initialize_core();
- virtual Error initialize(const VideoMode &p_desired, int p_video_driver, int p_audio_driver);
+ void set_main_loop(MainLoop *p_main_loop) override;
+ void delete_main_loop() override;
- virtual void set_main_loop(MainLoop *p_main_loop);
- virtual void delete_main_loop();
+ void finalize() override;
- virtual void finalize();
-
- virtual bool _check_internal_feature_support(const String &p_feature);
+ bool _check_internal_feature_support(const String &p_feature) override;
public:
// Override return type to make writing static callbacks less tedious.
static OS_JavaScript *get_singleton();
- virtual void set_video_mode(const VideoMode &p_video_mode, int p_screen = 0);
- virtual VideoMode get_video_mode(int p_screen = 0) const;
- virtual void get_fullscreen_mode_list(List<VideoMode> *p_list, int p_screen = 0) const;
-
- virtual void set_window_size(const Size2);
- virtual Size2 get_window_size() const;
- virtual void set_window_maximized(bool p_enabled);
- virtual bool is_window_maximized() const;
- virtual void set_window_fullscreen(bool p_enabled);
- virtual bool is_window_fullscreen() const;
- virtual Size2 get_screen_size(int p_screen = -1) const;
-
- virtual Point2 get_mouse_position() const;
- virtual int get_mouse_button_state() const;
- virtual void set_cursor_shape(CursorShape p_shape);
- virtual void set_custom_mouse_cursor(const RES &p_cursor, CursorShape p_shape, const Vector2 &p_hotspot);
- virtual void set_mouse_mode(MouseMode p_mode);
- virtual MouseMode get_mouse_mode() const;
-
- virtual bool get_window_per_pixel_transparency_enabled() const;
- virtual void set_window_per_pixel_transparency_enabled(bool p_enabled);
-
- virtual bool has_touchscreen_ui_hint() const;
+ void initialize_joypads() override;
- virtual bool is_joy_known(int p_device);
- virtual String get_joy_guid(int p_device) const;
-
- virtual int get_video_driver_count() const;
- virtual const char *get_video_driver_name(int p_driver) const;
-
- virtual int get_audio_driver_count() const;
- virtual const char *get_audio_driver_name(int p_driver) const;
-
- virtual void set_clipboard(const String &p_text);
- virtual String get_clipboard() const;
-
- virtual MainLoop *get_main_loop() const;
- void run_async();
+ MainLoop *get_main_loop() const override;
bool main_loop_iterate();
- virtual Error execute(const String &p_path, const List<String> &p_arguments, bool p_blocking = true, ProcessID *r_child_id = nullptr, String *r_pipe = nullptr, int *r_exitcode = nullptr, bool read_stderr = false, Mutex *p_pipe_mutex = nullptr);
- virtual Error kill(const ProcessID &p_pid);
- virtual int get_process_id() const;
+ Error execute(const String &p_path, const List<String> &p_arguments, String *r_pipe = nullptr, int *r_exitcode = nullptr, bool read_stderr = false, Mutex *p_pipe_mutex = nullptr) override;
+ Error create_process(const String &p_path, const List<String> &p_arguments, ProcessID *r_child_id = nullptr) override;
+ Error kill(const ProcessID &p_pid) override;
+ int get_process_id() const override;
+
+ String get_executable_path() const override;
+ Error shell_open(String p_uri) override;
+ String get_name() const override;
+ // Override default OS implementation which would block the main thread with delay_usec.
+ // Implemented in javascript_main.cpp loop callback instead.
+ void add_frame_delay(bool p_can_draw) override {}
- virtual void alert(const String &p_alert, const String &p_title = "ALERT!");
- virtual void set_window_title(const String &p_title);
- virtual void set_icon(const Ref<Image> &p_icon);
- String get_executable_path() const;
- virtual Error shell_open(String p_uri);
- virtual String get_name() const;
- virtual bool can_draw() const;
+ String get_cache_path() const override;
+ String get_config_path() const override;
+ String get_data_path() const override;
+ String get_user_data_dir() const override;
- virtual String get_resource_dir() const;
- virtual String get_user_data_dir() const;
+ bool is_userfs_persistent() const override;
+ Error open_dynamic_library(const String p_path, void *&p_library_handle, bool p_also_set_library_path) override;
- void set_idb_available(bool p_idb_available);
- virtual bool is_userfs_persistent() const;
+ void resume_audio();
- OS_JavaScript(int p_argc, char *p_argv[]);
+ OS_JavaScript();
};
#endif
diff --git a/platform/javascript/package-lock.json b/platform/javascript/package-lock.json
new file mode 100644
index 0000000000..8e298a495e
--- /dev/null
+++ b/platform/javascript/package-lock.json
@@ -0,0 +1,1605 @@
+{
+ "name": "godot",
+ "version": "1.0.0",
+ "lockfileVersion": 1,
+ "requires": true,
+ "dependencies": {
+ "@babel/code-frame": {
+ "version": "7.10.4",
+ "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.10.4.tgz",
+ "integrity": "sha512-vG6SvB6oYEhvgisZNFRmRCUkLz11c7rp+tbNTynGqc6mS1d5ATd/sGyV6W0KZZnXRKMTzZDRgQT3Ou9jhpAfUg==",
+ "dev": true,
+ "requires": {
+ "@babel/highlight": "^7.10.4"
+ }
+ },
+ "@babel/helper-validator-identifier": {
+ "version": "7.10.4",
+ "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.10.4.tgz",
+ "integrity": "sha512-3U9y+43hz7ZM+rzG24Qe2mufW5KhvFg/NhnNph+i9mgCtdTCtMJuI1TMkrIUiK7Ix4PYlRF9I5dhqaLYA/ADXw==",
+ "dev": true
+ },
+ "@babel/highlight": {
+ "version": "7.10.4",
+ "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.10.4.tgz",
+ "integrity": "sha512-i6rgnR/YgPEQzZZnbTHHuZdlE8qyoBNalD6F+q4vAFlcMEcqmkoG+mPqJYJCo63qPf74+Y1UZsl3l6f7/RIkmA==",
+ "dev": true,
+ "requires": {
+ "@babel/helper-validator-identifier": "^7.10.4",
+ "chalk": "^2.0.0",
+ "js-tokens": "^4.0.0"
+ },
+ "dependencies": {
+ "chalk": {
+ "version": "2.4.2",
+ "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz",
+ "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==",
+ "dev": true,
+ "requires": {
+ "ansi-styles": "^3.2.1",
+ "escape-string-regexp": "^1.0.5",
+ "supports-color": "^5.3.0"
+ }
+ }
+ }
+ },
+ "@eslint/eslintrc": {
+ "version": "0.1.3",
+ "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-0.1.3.tgz",
+ "integrity": "sha512-4YVwPkANLeNtRjMekzux1ci8hIaH5eGKktGqR0d3LWsKNn5B2X/1Z6Trxy7jQXl9EBGE6Yj02O+t09FMeRllaA==",
+ "dev": true,
+ "requires": {
+ "ajv": "^6.12.4",
+ "debug": "^4.1.1",
+ "espree": "^7.3.0",
+ "globals": "^12.1.0",
+ "ignore": "^4.0.6",
+ "import-fresh": "^3.2.1",
+ "js-yaml": "^3.13.1",
+ "lodash": "^4.17.19",
+ "minimatch": "^3.0.4",
+ "strip-json-comments": "^3.1.1"
+ }
+ },
+ "@types/color-name": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/@types/color-name/-/color-name-1.1.1.tgz",
+ "integrity": "sha512-rr+OQyAjxze7GgWrSaJwydHStIhHq2lvY3BOC2Mj7KnzI7XK0Uw1TOOdI9lDoajEbSWLiYgoo4f1R51erQfhPQ==",
+ "dev": true
+ },
+ "@types/json5": {
+ "version": "0.0.29",
+ "resolved": "https://registry.npmjs.org/@types/json5/-/json5-0.0.29.tgz",
+ "integrity": "sha1-7ihweulOEdK4J7y+UnC86n8+ce4=",
+ "dev": true
+ },
+ "acorn": {
+ "version": "7.4.0",
+ "resolved": "https://registry.npmjs.org/acorn/-/acorn-7.4.0.tgz",
+ "integrity": "sha512-+G7P8jJmCHr+S+cLfQxygbWhXy+8YTVGzAkpEbcLo2mLoL7tij/VG41QSHACSf5QgYRhMZYHuNc6drJaO0Da+w==",
+ "dev": true
+ },
+ "acorn-jsx": {
+ "version": "5.3.1",
+ "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.1.tgz",
+ "integrity": "sha512-K0Ptm/47OKfQRpNQ2J/oIN/3QYiK6FwW+eJbILhsdxh2WTLdl+30o8aGdTbm5JbffpFFAg/g+zi1E+jvJha5ng==",
+ "dev": true
+ },
+ "ajv": {
+ "version": "6.12.5",
+ "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.5.tgz",
+ "integrity": "sha512-lRF8RORchjpKG50/WFf8xmg7sgCLFiYNNnqdKflk63whMQcWR5ngGjiSXkL9bjxy6B2npOK2HSMN49jEBMSkag==",
+ "dev": true,
+ "requires": {
+ "fast-deep-equal": "^3.1.1",
+ "fast-json-stable-stringify": "^2.0.0",
+ "json-schema-traverse": "^0.4.1",
+ "uri-js": "^4.2.2"
+ }
+ },
+ "ansi-colors": {
+ "version": "4.1.1",
+ "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.1.tgz",
+ "integrity": "sha512-JoX0apGbHaUJBNl6yF+p6JAFYZ666/hhCGKN5t9QFjbJQKUU/g8MNbFDbvfrgKXvI1QpZplPOnwIo99lX/AAmA==",
+ "dev": true
+ },
+ "ansi-regex": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.0.tgz",
+ "integrity": "sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg==",
+ "dev": true
+ },
+ "ansi-styles": {
+ "version": "3.2.1",
+ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz",
+ "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==",
+ "dev": true,
+ "requires": {
+ "color-convert": "^1.9.0"
+ }
+ },
+ "argparse": {
+ "version": "1.0.10",
+ "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz",
+ "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==",
+ "dev": true,
+ "requires": {
+ "sprintf-js": "~1.0.2"
+ }
+ },
+ "array-includes": {
+ "version": "3.1.1",
+ "resolved": "https://registry.npmjs.org/array-includes/-/array-includes-3.1.1.tgz",
+ "integrity": "sha512-c2VXaCHl7zPsvpkFsw4nxvFie4fh1ur9bpcgsVkIjqn0H/Xwdg+7fv3n2r/isyS8EBj5b06M9kHyZuIr4El6WQ==",
+ "dev": true,
+ "requires": {
+ "define-properties": "^1.1.3",
+ "es-abstract": "^1.17.0",
+ "is-string": "^1.0.5"
+ },
+ "dependencies": {
+ "es-abstract": {
+ "version": "1.17.6",
+ "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.17.6.tgz",
+ "integrity": "sha512-Fr89bON3WFyUi5EvAeI48QTWX0AyekGgLA8H+c+7fbfCkJwRWRMLd8CQedNEyJuoYYhmtEqY92pgte1FAhBlhw==",
+ "dev": true,
+ "requires": {
+ "es-to-primitive": "^1.2.1",
+ "function-bind": "^1.1.1",
+ "has": "^1.0.3",
+ "has-symbols": "^1.0.1",
+ "is-callable": "^1.2.0",
+ "is-regex": "^1.1.0",
+ "object-inspect": "^1.7.0",
+ "object-keys": "^1.1.1",
+ "object.assign": "^4.1.0",
+ "string.prototype.trimend": "^1.0.1",
+ "string.prototype.trimstart": "^1.0.1"
+ }
+ }
+ }
+ },
+ "array.prototype.flat": {
+ "version": "1.2.3",
+ "resolved": "https://registry.npmjs.org/array.prototype.flat/-/array.prototype.flat-1.2.3.tgz",
+ "integrity": "sha512-gBlRZV0VSmfPIeWfuuy56XZMvbVfbEUnOXUvt3F/eUUUSyzlgLxhEX4YAEpxNAogRGehPSnfXyPtYyKAhkzQhQ==",
+ "dev": true,
+ "requires": {
+ "define-properties": "^1.1.3",
+ "es-abstract": "^1.17.0-next.1"
+ },
+ "dependencies": {
+ "es-abstract": {
+ "version": "1.17.6",
+ "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.17.6.tgz",
+ "integrity": "sha512-Fr89bON3WFyUi5EvAeI48QTWX0AyekGgLA8H+c+7fbfCkJwRWRMLd8CQedNEyJuoYYhmtEqY92pgte1FAhBlhw==",
+ "dev": true,
+ "requires": {
+ "es-to-primitive": "^1.2.1",
+ "function-bind": "^1.1.1",
+ "has": "^1.0.3",
+ "has-symbols": "^1.0.1",
+ "is-callable": "^1.2.0",
+ "is-regex": "^1.1.0",
+ "object-inspect": "^1.7.0",
+ "object-keys": "^1.1.1",
+ "object.assign": "^4.1.0",
+ "string.prototype.trimend": "^1.0.1",
+ "string.prototype.trimstart": "^1.0.1"
+ }
+ }
+ }
+ },
+ "astral-regex": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/astral-regex/-/astral-regex-1.0.0.tgz",
+ "integrity": "sha512-+Ryf6g3BKoRc7jfp7ad8tM4TtMiaWvbF/1/sQcZPkkS7ag3D5nMBCe2UfOTONtAkaG0tO0ij3C5Lwmf1EiyjHg==",
+ "dev": true
+ },
+ "balanced-match": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz",
+ "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=",
+ "dev": true
+ },
+ "brace-expansion": {
+ "version": "1.1.11",
+ "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
+ "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==",
+ "dev": true,
+ "requires": {
+ "balanced-match": "^1.0.0",
+ "concat-map": "0.0.1"
+ }
+ },
+ "callsites": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz",
+ "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==",
+ "dev": true
+ },
+ "chalk": {
+ "version": "4.1.0",
+ "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.0.tgz",
+ "integrity": "sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A==",
+ "dev": true,
+ "requires": {
+ "ansi-styles": "^4.1.0",
+ "supports-color": "^7.1.0"
+ },
+ "dependencies": {
+ "ansi-styles": {
+ "version": "4.2.1",
+ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.2.1.tgz",
+ "integrity": "sha512-9VGjrMsG1vePxcSweQsN20KY/c4zN0h9fLjqAbwbPfahM3t+NL+M9HC8xeXG2I8pX5NoamTGNuomEUFI7fcUjA==",
+ "dev": true,
+ "requires": {
+ "@types/color-name": "^1.1.1",
+ "color-convert": "^2.0.1"
+ }
+ },
+ "color-convert": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
+ "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
+ "dev": true,
+ "requires": {
+ "color-name": "~1.1.4"
+ }
+ },
+ "color-name": {
+ "version": "1.1.4",
+ "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
+ "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
+ "dev": true
+ },
+ "has-flag": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
+ "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==",
+ "dev": true
+ },
+ "supports-color": {
+ "version": "7.2.0",
+ "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
+ "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==",
+ "dev": true,
+ "requires": {
+ "has-flag": "^4.0.0"
+ }
+ }
+ }
+ },
+ "color-convert": {
+ "version": "1.9.3",
+ "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz",
+ "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==",
+ "dev": true,
+ "requires": {
+ "color-name": "1.1.3"
+ }
+ },
+ "color-name": {
+ "version": "1.1.3",
+ "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz",
+ "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=",
+ "dev": true
+ },
+ "concat-map": {
+ "version": "0.0.1",
+ "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
+ "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=",
+ "dev": true
+ },
+ "confusing-browser-globals": {
+ "version": "1.0.9",
+ "resolved": "https://registry.npmjs.org/confusing-browser-globals/-/confusing-browser-globals-1.0.9.tgz",
+ "integrity": "sha512-KbS1Y0jMtyPgIxjO7ZzMAuUpAKMt1SzCL9fsrKsX6b0zJPTaT0SiSPmewwVZg9UAO83HVIlEhZF84LIjZ0lmAw==",
+ "dev": true
+ },
+ "contains-path": {
+ "version": "0.1.0",
+ "resolved": "https://registry.npmjs.org/contains-path/-/contains-path-0.1.0.tgz",
+ "integrity": "sha1-/ozxhP9mcLa67wGp1IYaXL7EEgo=",
+ "dev": true
+ },
+ "cross-spawn": {
+ "version": "7.0.3",
+ "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz",
+ "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==",
+ "dev": true,
+ "requires": {
+ "path-key": "^3.1.0",
+ "shebang-command": "^2.0.0",
+ "which": "^2.0.1"
+ }
+ },
+ "debug": {
+ "version": "4.1.1",
+ "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz",
+ "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==",
+ "dev": true,
+ "requires": {
+ "ms": "^2.1.1"
+ }
+ },
+ "deep-is": {
+ "version": "0.1.3",
+ "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.3.tgz",
+ "integrity": "sha1-s2nW+128E+7PUk+RsHD+7cNXzzQ=",
+ "dev": true
+ },
+ "define-properties": {
+ "version": "1.1.3",
+ "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.3.tgz",
+ "integrity": "sha512-3MqfYKj2lLzdMSf8ZIZE/V+Zuy+BgD6f164e8K2w7dgnpKArBDerGYpM46IYYcjnkdPNMjPk9A6VFB8+3SKlXQ==",
+ "dev": true,
+ "requires": {
+ "object-keys": "^1.0.12"
+ }
+ },
+ "doctrine": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz",
+ "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==",
+ "dev": true,
+ "requires": {
+ "esutils": "^2.0.2"
+ }
+ },
+ "emoji-regex": {
+ "version": "7.0.3",
+ "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-7.0.3.tgz",
+ "integrity": "sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA==",
+ "dev": true
+ },
+ "enquirer": {
+ "version": "2.3.6",
+ "resolved": "https://registry.npmjs.org/enquirer/-/enquirer-2.3.6.tgz",
+ "integrity": "sha512-yjNnPr315/FjS4zIsUxYguYUPP2e1NK4d7E7ZOLiyYCcbFBiTMyID+2wvm2w6+pZ/odMA7cRkjhsPbltwBOrLg==",
+ "dev": true,
+ "requires": {
+ "ansi-colors": "^4.1.1"
+ }
+ },
+ "error-ex": {
+ "version": "1.3.2",
+ "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz",
+ "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==",
+ "dev": true,
+ "requires": {
+ "is-arrayish": "^0.2.1"
+ }
+ },
+ "es-abstract": {
+ "version": "1.18.0-next.0",
+ "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.18.0-next.0.tgz",
+ "integrity": "sha512-elZXTZXKn51hUBdJjSZGYRujuzilgXo8vSPQzjGYXLvSlGiCo8VO8ZGV3kjo9a0WNJJ57hENagwbtlRuHuzkcQ==",
+ "dev": true,
+ "requires": {
+ "es-to-primitive": "^1.2.1",
+ "function-bind": "^1.1.1",
+ "has": "^1.0.3",
+ "has-symbols": "^1.0.1",
+ "is-callable": "^1.2.0",
+ "is-negative-zero": "^2.0.0",
+ "is-regex": "^1.1.1",
+ "object-inspect": "^1.8.0",
+ "object-keys": "^1.1.1",
+ "object.assign": "^4.1.0",
+ "string.prototype.trimend": "^1.0.1",
+ "string.prototype.trimstart": "^1.0.1"
+ }
+ },
+ "es-to-primitive": {
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.2.1.tgz",
+ "integrity": "sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA==",
+ "dev": true,
+ "requires": {
+ "is-callable": "^1.1.4",
+ "is-date-object": "^1.0.1",
+ "is-symbol": "^1.0.2"
+ }
+ },
+ "escape-string-regexp": {
+ "version": "1.0.5",
+ "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz",
+ "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=",
+ "dev": true
+ },
+ "eslint": {
+ "version": "7.9.0",
+ "resolved": "https://registry.npmjs.org/eslint/-/eslint-7.9.0.tgz",
+ "integrity": "sha512-V6QyhX21+uXp4T+3nrNfI3hQNBDa/P8ga7LoQOenwrlEFXrEnUEE+ok1dMtaS3b6rmLXhT1TkTIsG75HMLbknA==",
+ "dev": true,
+ "requires": {
+ "@babel/code-frame": "^7.0.0",
+ "@eslint/eslintrc": "^0.1.3",
+ "ajv": "^6.10.0",
+ "chalk": "^4.0.0",
+ "cross-spawn": "^7.0.2",
+ "debug": "^4.0.1",
+ "doctrine": "^3.0.0",
+ "enquirer": "^2.3.5",
+ "eslint-scope": "^5.1.0",
+ "eslint-utils": "^2.1.0",
+ "eslint-visitor-keys": "^1.3.0",
+ "espree": "^7.3.0",
+ "esquery": "^1.2.0",
+ "esutils": "^2.0.2",
+ "file-entry-cache": "^5.0.1",
+ "functional-red-black-tree": "^1.0.1",
+ "glob-parent": "^5.0.0",
+ "globals": "^12.1.0",
+ "ignore": "^4.0.6",
+ "import-fresh": "^3.0.0",
+ "imurmurhash": "^0.1.4",
+ "is-glob": "^4.0.0",
+ "js-yaml": "^3.13.1",
+ "json-stable-stringify-without-jsonify": "^1.0.1",
+ "levn": "^0.4.1",
+ "lodash": "^4.17.19",
+ "minimatch": "^3.0.4",
+ "natural-compare": "^1.4.0",
+ "optionator": "^0.9.1",
+ "progress": "^2.0.0",
+ "regexpp": "^3.1.0",
+ "semver": "^7.2.1",
+ "strip-ansi": "^6.0.0",
+ "strip-json-comments": "^3.1.0",
+ "table": "^5.2.3",
+ "text-table": "^0.2.0",
+ "v8-compile-cache": "^2.0.3"
+ }
+ },
+ "eslint-config-airbnb-base": {
+ "version": "14.2.0",
+ "resolved": "https://registry.npmjs.org/eslint-config-airbnb-base/-/eslint-config-airbnb-base-14.2.0.tgz",
+ "integrity": "sha512-Snswd5oC6nJaevs3nZoLSTvGJBvzTfnBqOIArkf3cbyTyq9UD79wOk8s+RiL6bhca0p/eRO6veczhf6A/7Jy8Q==",
+ "dev": true,
+ "requires": {
+ "confusing-browser-globals": "^1.0.9",
+ "object.assign": "^4.1.0",
+ "object.entries": "^1.1.2"
+ }
+ },
+ "eslint-import-resolver-node": {
+ "version": "0.3.4",
+ "resolved": "https://registry.npmjs.org/eslint-import-resolver-node/-/eslint-import-resolver-node-0.3.4.tgz",
+ "integrity": "sha512-ogtf+5AB/O+nM6DIeBUNr2fuT7ot9Qg/1harBfBtaP13ekEWFQEEMP94BCB7zaNW3gyY+8SHYF00rnqYwXKWOA==",
+ "dev": true,
+ "requires": {
+ "debug": "^2.6.9",
+ "resolve": "^1.13.1"
+ },
+ "dependencies": {
+ "debug": {
+ "version": "2.6.9",
+ "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
+ "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
+ "dev": true,
+ "requires": {
+ "ms": "2.0.0"
+ }
+ },
+ "ms": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
+ "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=",
+ "dev": true
+ }
+ }
+ },
+ "eslint-module-utils": {
+ "version": "2.6.0",
+ "resolved": "https://registry.npmjs.org/eslint-module-utils/-/eslint-module-utils-2.6.0.tgz",
+ "integrity": "sha512-6j9xxegbqe8/kZY8cYpcp0xhbK0EgJlg3g9mib3/miLaExuuwc3n5UEfSnU6hWMbT0FAYVvDbL9RrRgpUeQIvA==",
+ "dev": true,
+ "requires": {
+ "debug": "^2.6.9",
+ "pkg-dir": "^2.0.0"
+ },
+ "dependencies": {
+ "debug": {
+ "version": "2.6.9",
+ "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
+ "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
+ "dev": true,
+ "requires": {
+ "ms": "2.0.0"
+ }
+ },
+ "ms": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
+ "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=",
+ "dev": true
+ }
+ }
+ },
+ "eslint-plugin-import": {
+ "version": "2.22.0",
+ "resolved": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.22.0.tgz",
+ "integrity": "sha512-66Fpf1Ln6aIS5Gr/55ts19eUuoDhAbZgnr6UxK5hbDx6l/QgQgx61AePq+BV4PP2uXQFClgMVzep5zZ94qqsxg==",
+ "dev": true,
+ "requires": {
+ "array-includes": "^3.1.1",
+ "array.prototype.flat": "^1.2.3",
+ "contains-path": "^0.1.0",
+ "debug": "^2.6.9",
+ "doctrine": "1.5.0",
+ "eslint-import-resolver-node": "^0.3.3",
+ "eslint-module-utils": "^2.6.0",
+ "has": "^1.0.3",
+ "minimatch": "^3.0.4",
+ "object.values": "^1.1.1",
+ "read-pkg-up": "^2.0.0",
+ "resolve": "^1.17.0",
+ "tsconfig-paths": "^3.9.0"
+ },
+ "dependencies": {
+ "debug": {
+ "version": "2.6.9",
+ "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
+ "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
+ "dev": true,
+ "requires": {
+ "ms": "2.0.0"
+ }
+ },
+ "doctrine": {
+ "version": "1.5.0",
+ "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-1.5.0.tgz",
+ "integrity": "sha1-N53Ocw9hZvds76TmcHoVmwLFpvo=",
+ "dev": true,
+ "requires": {
+ "esutils": "^2.0.2",
+ "isarray": "^1.0.0"
+ }
+ },
+ "ms": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
+ "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=",
+ "dev": true
+ }
+ }
+ },
+ "eslint-scope": {
+ "version": "5.1.1",
+ "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz",
+ "integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==",
+ "dev": true,
+ "requires": {
+ "esrecurse": "^4.3.0",
+ "estraverse": "^4.1.1"
+ }
+ },
+ "eslint-utils": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-2.1.0.tgz",
+ "integrity": "sha512-w94dQYoauyvlDc43XnGB8lU3Zt713vNChgt4EWwhXAP2XkBvndfxF0AgIqKOOasjPIPzj9JqgwkwbCYD0/V3Zg==",
+ "dev": true,
+ "requires": {
+ "eslint-visitor-keys": "^1.1.0"
+ }
+ },
+ "eslint-visitor-keys": {
+ "version": "1.3.0",
+ "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-1.3.0.tgz",
+ "integrity": "sha512-6J72N8UNa462wa/KFODt/PJ3IU60SDpC3QXC1Hjc1BXXpfL2C9R5+AU7jhe0F6GREqVMh4Juu+NY7xn+6dipUQ==",
+ "dev": true
+ },
+ "espree": {
+ "version": "7.3.0",
+ "resolved": "https://registry.npmjs.org/espree/-/espree-7.3.0.tgz",
+ "integrity": "sha512-dksIWsvKCixn1yrEXO8UosNSxaDoSYpq9reEjZSbHLpT5hpaCAKTLBwq0RHtLrIr+c0ByiYzWT8KTMRzoRCNlw==",
+ "dev": true,
+ "requires": {
+ "acorn": "^7.4.0",
+ "acorn-jsx": "^5.2.0",
+ "eslint-visitor-keys": "^1.3.0"
+ }
+ },
+ "esprima": {
+ "version": "4.0.1",
+ "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz",
+ "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==",
+ "dev": true
+ },
+ "esquery": {
+ "version": "1.3.1",
+ "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.3.1.tgz",
+ "integrity": "sha512-olpvt9QG0vniUBZspVRN6lwB7hOZoTRtT+jzR+tS4ffYx2mzbw+z0XCOk44aaLYKApNX5nMm+E+P6o25ip/DHQ==",
+ "dev": true,
+ "requires": {
+ "estraverse": "^5.1.0"
+ },
+ "dependencies": {
+ "estraverse": {
+ "version": "5.2.0",
+ "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.2.0.tgz",
+ "integrity": "sha512-BxbNGGNm0RyRYvUdHpIwv9IWzeM9XClbOxwoATuFdOE7ZE6wHL+HQ5T8hoPM+zHvmKzzsEqhgy0GrQ5X13afiQ==",
+ "dev": true
+ }
+ }
+ },
+ "esrecurse": {
+ "version": "4.3.0",
+ "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz",
+ "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==",
+ "dev": true,
+ "requires": {
+ "estraverse": "^5.2.0"
+ },
+ "dependencies": {
+ "estraverse": {
+ "version": "5.2.0",
+ "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.2.0.tgz",
+ "integrity": "sha512-BxbNGGNm0RyRYvUdHpIwv9IWzeM9XClbOxwoATuFdOE7ZE6wHL+HQ5T8hoPM+zHvmKzzsEqhgy0GrQ5X13afiQ==",
+ "dev": true
+ }
+ }
+ },
+ "estraverse": {
+ "version": "4.3.0",
+ "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz",
+ "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==",
+ "dev": true
+ },
+ "esutils": {
+ "version": "2.0.3",
+ "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz",
+ "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==",
+ "dev": true
+ },
+ "fast-deep-equal": {
+ "version": "3.1.3",
+ "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
+ "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==",
+ "dev": true
+ },
+ "fast-json-stable-stringify": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz",
+ "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==",
+ "dev": true
+ },
+ "fast-levenshtein": {
+ "version": "2.0.6",
+ "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz",
+ "integrity": "sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc=",
+ "dev": true
+ },
+ "file-entry-cache": {
+ "version": "5.0.1",
+ "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-5.0.1.tgz",
+ "integrity": "sha512-bCg29ictuBaKUwwArK4ouCaqDgLZcysCFLmM/Yn/FDoqndh/9vNuQfXRDvTuXKLxfD/JtZQGKFT8MGcJBK644g==",
+ "dev": true,
+ "requires": {
+ "flat-cache": "^2.0.1"
+ }
+ },
+ "find-up": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/find-up/-/find-up-2.1.0.tgz",
+ "integrity": "sha1-RdG35QbHF93UgndaK3eSCjwMV6c=",
+ "dev": true,
+ "requires": {
+ "locate-path": "^2.0.0"
+ }
+ },
+ "flat-cache": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-2.0.1.tgz",
+ "integrity": "sha512-LoQe6yDuUMDzQAEH8sgmh4Md6oZnc/7PjtwjNFSzveXqSHt6ka9fPBuso7IGf9Rz4uqnSnWiFH2B/zj24a5ReA==",
+ "dev": true,
+ "requires": {
+ "flatted": "^2.0.0",
+ "rimraf": "2.6.3",
+ "write": "1.0.3"
+ }
+ },
+ "flatted": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/flatted/-/flatted-2.0.2.tgz",
+ "integrity": "sha512-r5wGx7YeOwNWNlCA0wQ86zKyDLMQr+/RB8xy74M4hTphfmjlijTSSXGuH8rnvKZnfT9i+75zmd8jcKdMR4O6jA==",
+ "dev": true
+ },
+ "fs.realpath": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz",
+ "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=",
+ "dev": true
+ },
+ "function-bind": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz",
+ "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==",
+ "dev": true
+ },
+ "functional-red-black-tree": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz",
+ "integrity": "sha1-GwqzvVU7Kg1jmdKcDj6gslIHgyc=",
+ "dev": true
+ },
+ "glob": {
+ "version": "7.1.6",
+ "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz",
+ "integrity": "sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==",
+ "dev": true,
+ "requires": {
+ "fs.realpath": "^1.0.0",
+ "inflight": "^1.0.4",
+ "inherits": "2",
+ "minimatch": "^3.0.4",
+ "once": "^1.3.0",
+ "path-is-absolute": "^1.0.0"
+ }
+ },
+ "glob-parent": {
+ "version": "5.1.1",
+ "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.1.tgz",
+ "integrity": "sha512-FnI+VGOpnlGHWZxthPGR+QhR78fuiK0sNLkHQv+bL9fQi57lNNdquIbna/WrfROrolq8GK5Ek6BiMwqL/voRYQ==",
+ "dev": true,
+ "requires": {
+ "is-glob": "^4.0.1"
+ }
+ },
+ "globals": {
+ "version": "12.4.0",
+ "resolved": "https://registry.npmjs.org/globals/-/globals-12.4.0.tgz",
+ "integrity": "sha512-BWICuzzDvDoH54NHKCseDanAhE3CeDorgDL5MT6LMXXj2WCnd9UC2szdk4AWLfjdgNBCXLUanXYcpBBKOSWGwg==",
+ "dev": true,
+ "requires": {
+ "type-fest": "^0.8.1"
+ }
+ },
+ "graceful-fs": {
+ "version": "4.2.4",
+ "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.4.tgz",
+ "integrity": "sha512-WjKPNJF79dtJAVniUlGGWHYGz2jWxT6VhN/4m1NdkbZ2nOsEF+cI1Edgql5zCRhs/VsQYRvrXctxktVXZUkixw==",
+ "dev": true
+ },
+ "has": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz",
+ "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==",
+ "dev": true,
+ "requires": {
+ "function-bind": "^1.1.1"
+ }
+ },
+ "has-flag": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz",
+ "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=",
+ "dev": true
+ },
+ "has-symbols": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.1.tgz",
+ "integrity": "sha512-PLcsoqu++dmEIZB+6totNFKq/7Do+Z0u4oT0zKOJNl3lYK6vGwwu2hjHs+68OEZbTjiUE9bgOABXbP/GvrS0Kg==",
+ "dev": true
+ },
+ "hosted-git-info": {
+ "version": "2.8.8",
+ "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.8.8.tgz",
+ "integrity": "sha512-f/wzC2QaWBs7t9IYqB4T3sR1xviIViXJRJTWBlx2Gf3g0Xi5vI7Yy4koXQ1c9OYDGHN9sBy1DQ2AB8fqZBWhUg==",
+ "dev": true
+ },
+ "ignore": {
+ "version": "4.0.6",
+ "resolved": "https://registry.npmjs.org/ignore/-/ignore-4.0.6.tgz",
+ "integrity": "sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg==",
+ "dev": true
+ },
+ "import-fresh": {
+ "version": "3.2.1",
+ "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.2.1.tgz",
+ "integrity": "sha512-6e1q1cnWP2RXD9/keSkxHScg508CdXqXWgWBaETNhyuBFz+kUZlKboh+ISK+bU++DmbHimVBrOz/zzPe0sZ3sQ==",
+ "dev": true,
+ "requires": {
+ "parent-module": "^1.0.0",
+ "resolve-from": "^4.0.0"
+ }
+ },
+ "imurmurhash": {
+ "version": "0.1.4",
+ "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz",
+ "integrity": "sha1-khi5srkoojixPcT7a21XbyMUU+o=",
+ "dev": true
+ },
+ "inflight": {
+ "version": "1.0.6",
+ "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz",
+ "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=",
+ "dev": true,
+ "requires": {
+ "once": "^1.3.0",
+ "wrappy": "1"
+ }
+ },
+ "inherits": {
+ "version": "2.0.4",
+ "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
+ "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==",
+ "dev": true
+ },
+ "is-arrayish": {
+ "version": "0.2.1",
+ "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz",
+ "integrity": "sha1-d8mYQFJ6qOyxqLppe4BkWnqSap0=",
+ "dev": true
+ },
+ "is-callable": {
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.1.tgz",
+ "integrity": "sha512-wliAfSzx6V+6WfMOmus1xy0XvSgf/dlStkvTfq7F0g4bOIW0PSUbnyse3NhDwdyYS1ozfUtAAySqTws3z9Eqgg==",
+ "dev": true
+ },
+ "is-date-object": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.2.tgz",
+ "integrity": "sha512-USlDT524woQ08aoZFzh3/Z6ch9Y/EWXEHQ/AaRN0SkKq4t2Jw2R2339tSXmwuVoY7LLlBCbOIlx2myP/L5zk0g==",
+ "dev": true
+ },
+ "is-extglob": {
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz",
+ "integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=",
+ "dev": true
+ },
+ "is-fullwidth-code-point": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz",
+ "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=",
+ "dev": true
+ },
+ "is-glob": {
+ "version": "4.0.1",
+ "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.1.tgz",
+ "integrity": "sha512-5G0tKtBTFImOqDnLB2hG6Bp2qcKEFduo4tZu9MT/H6NQv/ghhy30o55ufafxJ/LdH79LLs2Kfrn85TLKyA7BUg==",
+ "dev": true,
+ "requires": {
+ "is-extglob": "^2.1.1"
+ }
+ },
+ "is-negative-zero": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/is-negative-zero/-/is-negative-zero-2.0.0.tgz",
+ "integrity": "sha1-lVOxIbD6wohp2p7UWeIMdUN4hGE=",
+ "dev": true
+ },
+ "is-regex": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.1.tgz",
+ "integrity": "sha512-1+QkEcxiLlB7VEyFtyBg94e08OAsvq7FUBgApTq/w2ymCLyKJgDPsybBENVtA7XCQEgEXxKPonG+mvYRxh/LIg==",
+ "dev": true,
+ "requires": {
+ "has-symbols": "^1.0.1"
+ }
+ },
+ "is-string": {
+ "version": "1.0.5",
+ "resolved": "https://registry.npmjs.org/is-string/-/is-string-1.0.5.tgz",
+ "integrity": "sha512-buY6VNRjhQMiF1qWDouloZlQbRhDPCebwxSjxMjxgemYT46YMd2NR0/H+fBhEfWX4A/w9TBJ+ol+okqJKFE6vQ==",
+ "dev": true
+ },
+ "is-symbol": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.3.tgz",
+ "integrity": "sha512-OwijhaRSgqvhm/0ZdAcXNZt9lYdKFpcRDT5ULUuYXPoT794UNOdU+gpT6Rzo7b4V2HUl/op6GqY894AZwv9faQ==",
+ "dev": true,
+ "requires": {
+ "has-symbols": "^1.0.1"
+ }
+ },
+ "isarray": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz",
+ "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=",
+ "dev": true
+ },
+ "isexe": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz",
+ "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=",
+ "dev": true
+ },
+ "js-tokens": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
+ "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==",
+ "dev": true
+ },
+ "js-yaml": {
+ "version": "3.14.0",
+ "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.0.tgz",
+ "integrity": "sha512-/4IbIeHcD9VMHFqDR/gQ7EdZdLimOvW2DdcxFjdyyZ9NsbS+ccrXqVWDtab/lRl5AlUqmpBx8EhPaWR+OtY17A==",
+ "dev": true,
+ "requires": {
+ "argparse": "^1.0.7",
+ "esprima": "^4.0.0"
+ }
+ },
+ "json-schema-traverse": {
+ "version": "0.4.1",
+ "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz",
+ "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==",
+ "dev": true
+ },
+ "json-stable-stringify-without-jsonify": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz",
+ "integrity": "sha1-nbe1lJatPzz+8wp1FC0tkwrXJlE=",
+ "dev": true
+ },
+ "json5": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.1.tgz",
+ "integrity": "sha512-aKS4WQjPenRxiQsC93MNfjx+nbF4PAdYzmd/1JIj8HYzqfbu86beTuNgXDzPknWk0n0uARlyewZo4s++ES36Ow==",
+ "dev": true,
+ "requires": {
+ "minimist": "^1.2.0"
+ }
+ },
+ "levn": {
+ "version": "0.4.1",
+ "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz",
+ "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==",
+ "dev": true,
+ "requires": {
+ "prelude-ls": "^1.2.1",
+ "type-check": "~0.4.0"
+ }
+ },
+ "load-json-file": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-2.0.0.tgz",
+ "integrity": "sha1-eUfkIUmvgNaWy/eXvKq8/h/inKg=",
+ "dev": true,
+ "requires": {
+ "graceful-fs": "^4.1.2",
+ "parse-json": "^2.2.0",
+ "pify": "^2.0.0",
+ "strip-bom": "^3.0.0"
+ }
+ },
+ "locate-path": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-2.0.0.tgz",
+ "integrity": "sha1-K1aLJl7slExtnA3pw9u7ygNUzY4=",
+ "dev": true,
+ "requires": {
+ "p-locate": "^2.0.0",
+ "path-exists": "^3.0.0"
+ }
+ },
+ "lodash": {
+ "version": "4.17.20",
+ "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.20.tgz",
+ "integrity": "sha512-PlhdFcillOINfeV7Ni6oF1TAEayyZBoZ8bcshTHqOYJYlrqzRK5hagpagky5o4HfCzzd1TRkXPMFq6cKk9rGmA==",
+ "dev": true
+ },
+ "minimatch": {
+ "version": "3.0.4",
+ "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz",
+ "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==",
+ "dev": true,
+ "requires": {
+ "brace-expansion": "^1.1.7"
+ }
+ },
+ "minimist": {
+ "version": "1.2.5",
+ "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz",
+ "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==",
+ "dev": true
+ },
+ "mkdirp": {
+ "version": "0.5.5",
+ "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.5.tgz",
+ "integrity": "sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ==",
+ "dev": true,
+ "requires": {
+ "minimist": "^1.2.5"
+ }
+ },
+ "ms": {
+ "version": "2.1.2",
+ "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
+ "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==",
+ "dev": true
+ },
+ "natural-compare": {
+ "version": "1.4.0",
+ "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz",
+ "integrity": "sha1-Sr6/7tdUHywnrPspvbvRXI1bpPc=",
+ "dev": true
+ },
+ "normalize-package-data": {
+ "version": "2.5.0",
+ "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.5.0.tgz",
+ "integrity": "sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA==",
+ "dev": true,
+ "requires": {
+ "hosted-git-info": "^2.1.4",
+ "resolve": "^1.10.0",
+ "semver": "2 || 3 || 4 || 5",
+ "validate-npm-package-license": "^3.0.1"
+ },
+ "dependencies": {
+ "semver": {
+ "version": "5.7.1",
+ "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz",
+ "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==",
+ "dev": true
+ }
+ }
+ },
+ "object-inspect": {
+ "version": "1.8.0",
+ "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.8.0.tgz",
+ "integrity": "sha512-jLdtEOB112fORuypAyl/50VRVIBIdVQOSUUGQHzJ4xBSbit81zRarz7GThkEFZy1RceYrWYcPcBFPQwHyAc1gA==",
+ "dev": true
+ },
+ "object-keys": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz",
+ "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==",
+ "dev": true
+ },
+ "object.assign": {
+ "version": "4.1.1",
+ "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.1.tgz",
+ "integrity": "sha512-VT/cxmx5yaoHSOTSyrCygIDFco+RsibY2NM0a4RdEeY/4KgqezwFtK1yr3U67xYhqJSlASm2pKhLVzPj2lr4bA==",
+ "dev": true,
+ "requires": {
+ "define-properties": "^1.1.3",
+ "es-abstract": "^1.18.0-next.0",
+ "has-symbols": "^1.0.1",
+ "object-keys": "^1.1.1"
+ }
+ },
+ "object.entries": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/object.entries/-/object.entries-1.1.2.tgz",
+ "integrity": "sha512-BQdB9qKmb/HyNdMNWVr7O3+z5MUIx3aiegEIJqjMBbBf0YT9RRxTJSim4mzFqtyr7PDAHigq0N9dO0m0tRakQA==",
+ "dev": true,
+ "requires": {
+ "define-properties": "^1.1.3",
+ "es-abstract": "^1.17.5",
+ "has": "^1.0.3"
+ },
+ "dependencies": {
+ "es-abstract": {
+ "version": "1.17.6",
+ "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.17.6.tgz",
+ "integrity": "sha512-Fr89bON3WFyUi5EvAeI48QTWX0AyekGgLA8H+c+7fbfCkJwRWRMLd8CQedNEyJuoYYhmtEqY92pgte1FAhBlhw==",
+ "dev": true,
+ "requires": {
+ "es-to-primitive": "^1.2.1",
+ "function-bind": "^1.1.1",
+ "has": "^1.0.3",
+ "has-symbols": "^1.0.1",
+ "is-callable": "^1.2.0",
+ "is-regex": "^1.1.0",
+ "object-inspect": "^1.7.0",
+ "object-keys": "^1.1.1",
+ "object.assign": "^4.1.0",
+ "string.prototype.trimend": "^1.0.1",
+ "string.prototype.trimstart": "^1.0.1"
+ }
+ }
+ }
+ },
+ "object.values": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/object.values/-/object.values-1.1.1.tgz",
+ "integrity": "sha512-WTa54g2K8iu0kmS/us18jEmdv1a4Wi//BZ/DTVYEcH0XhLM5NYdpDHja3gt57VrZLcNAO2WGA+KpWsDBaHt6eA==",
+ "dev": true,
+ "requires": {
+ "define-properties": "^1.1.3",
+ "es-abstract": "^1.17.0-next.1",
+ "function-bind": "^1.1.1",
+ "has": "^1.0.3"
+ },
+ "dependencies": {
+ "es-abstract": {
+ "version": "1.17.6",
+ "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.17.6.tgz",
+ "integrity": "sha512-Fr89bON3WFyUi5EvAeI48QTWX0AyekGgLA8H+c+7fbfCkJwRWRMLd8CQedNEyJuoYYhmtEqY92pgte1FAhBlhw==",
+ "dev": true,
+ "requires": {
+ "es-to-primitive": "^1.2.1",
+ "function-bind": "^1.1.1",
+ "has": "^1.0.3",
+ "has-symbols": "^1.0.1",
+ "is-callable": "^1.2.0",
+ "is-regex": "^1.1.0",
+ "object-inspect": "^1.7.0",
+ "object-keys": "^1.1.1",
+ "object.assign": "^4.1.0",
+ "string.prototype.trimend": "^1.0.1",
+ "string.prototype.trimstart": "^1.0.1"
+ }
+ }
+ }
+ },
+ "once": {
+ "version": "1.4.0",
+ "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
+ "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=",
+ "dev": true,
+ "requires": {
+ "wrappy": "1"
+ }
+ },
+ "optionator": {
+ "version": "0.9.1",
+ "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.1.tgz",
+ "integrity": "sha512-74RlY5FCnhq4jRxVUPKDaRwrVNXMqsGsiW6AJw4XK8hmtm10wC0ypZBLw5IIp85NZMr91+qd1RvvENwg7jjRFw==",
+ "dev": true,
+ "requires": {
+ "deep-is": "^0.1.3",
+ "fast-levenshtein": "^2.0.6",
+ "levn": "^0.4.1",
+ "prelude-ls": "^1.2.1",
+ "type-check": "^0.4.0",
+ "word-wrap": "^1.2.3"
+ }
+ },
+ "p-limit": {
+ "version": "1.3.0",
+ "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-1.3.0.tgz",
+ "integrity": "sha512-vvcXsLAJ9Dr5rQOPk7toZQZJApBl2K4J6dANSsEuh6QI41JYcsS/qhTGa9ErIUUgK3WNQoJYvylxvjqmiqEA9Q==",
+ "dev": true,
+ "requires": {
+ "p-try": "^1.0.0"
+ }
+ },
+ "p-locate": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-2.0.0.tgz",
+ "integrity": "sha1-IKAQOyIqcMj9OcwuWAaA893l7EM=",
+ "dev": true,
+ "requires": {
+ "p-limit": "^1.1.0"
+ }
+ },
+ "p-try": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/p-try/-/p-try-1.0.0.tgz",
+ "integrity": "sha1-y8ec26+P1CKOE/Yh8rGiN8GyB7M=",
+ "dev": true
+ },
+ "parent-module": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz",
+ "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==",
+ "dev": true,
+ "requires": {
+ "callsites": "^3.0.0"
+ }
+ },
+ "parse-json": {
+ "version": "2.2.0",
+ "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-2.2.0.tgz",
+ "integrity": "sha1-9ID0BDTvgHQfhGkJn43qGPVaTck=",
+ "dev": true,
+ "requires": {
+ "error-ex": "^1.2.0"
+ }
+ },
+ "path-exists": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz",
+ "integrity": "sha1-zg6+ql94yxiSXqfYENe1mwEP1RU=",
+ "dev": true
+ },
+ "path-is-absolute": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz",
+ "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=",
+ "dev": true
+ },
+ "path-key": {
+ "version": "3.1.1",
+ "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz",
+ "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==",
+ "dev": true
+ },
+ "path-parse": {
+ "version": "1.0.6",
+ "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.6.tgz",
+ "integrity": "sha512-GSmOT2EbHrINBf9SR7CDELwlJ8AENk3Qn7OikK4nFYAu3Ote2+JYNVvkpAEQm3/TLNEJFD/xZJjzyxg3KBWOzw==",
+ "dev": true
+ },
+ "path-type": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/path-type/-/path-type-2.0.0.tgz",
+ "integrity": "sha1-8BLMuEFbcJb8LaoQVMPXI4lZTHM=",
+ "dev": true,
+ "requires": {
+ "pify": "^2.0.0"
+ }
+ },
+ "pify": {
+ "version": "2.3.0",
+ "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz",
+ "integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=",
+ "dev": true
+ },
+ "pkg-dir": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-2.0.0.tgz",
+ "integrity": "sha1-9tXREJ4Z1j7fQo4L1X4Sd3YVM0s=",
+ "dev": true,
+ "requires": {
+ "find-up": "^2.1.0"
+ }
+ },
+ "prelude-ls": {
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz",
+ "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==",
+ "dev": true
+ },
+ "progress": {
+ "version": "2.0.3",
+ "resolved": "https://registry.npmjs.org/progress/-/progress-2.0.3.tgz",
+ "integrity": "sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==",
+ "dev": true
+ },
+ "punycode": {
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz",
+ "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==",
+ "dev": true
+ },
+ "read-pkg": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-2.0.0.tgz",
+ "integrity": "sha1-jvHAYjxqbbDcZxPEv6xGMysjaPg=",
+ "dev": true,
+ "requires": {
+ "load-json-file": "^2.0.0",
+ "normalize-package-data": "^2.3.2",
+ "path-type": "^2.0.0"
+ }
+ },
+ "read-pkg-up": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-2.0.0.tgz",
+ "integrity": "sha1-a3KoBImE4MQeeVEP1en6mbO1Sb4=",
+ "dev": true,
+ "requires": {
+ "find-up": "^2.0.0",
+ "read-pkg": "^2.0.0"
+ }
+ },
+ "regexpp": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/regexpp/-/regexpp-3.1.0.tgz",
+ "integrity": "sha512-ZOIzd8yVsQQA7j8GCSlPGXwg5PfmA1mrq0JP4nGhh54LaKN3xdai/vHUDu74pKwV8OxseMS65u2NImosQcSD0Q==",
+ "dev": true
+ },
+ "resolve": {
+ "version": "1.17.0",
+ "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.17.0.tgz",
+ "integrity": "sha512-ic+7JYiV8Vi2yzQGFWOkiZD5Z9z7O2Zhm9XMaTxdJExKasieFCr+yXZ/WmXsckHiKl12ar0y6XiXDx3m4RHn1w==",
+ "dev": true,
+ "requires": {
+ "path-parse": "^1.0.6"
+ }
+ },
+ "resolve-from": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz",
+ "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==",
+ "dev": true
+ },
+ "rimraf": {
+ "version": "2.6.3",
+ "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.6.3.tgz",
+ "integrity": "sha512-mwqeW5XsA2qAejG46gYdENaxXjx9onRNCfn7L0duuP4hCuTIi/QO7PDK07KJfp1d+izWPrzEJDcSqBa0OZQriA==",
+ "dev": true,
+ "requires": {
+ "glob": "^7.1.3"
+ }
+ },
+ "semver": {
+ "version": "7.3.2",
+ "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.2.tgz",
+ "integrity": "sha512-OrOb32TeeambH6UrhtShmF7CRDqhL6/5XpPNp2DuRH6+9QLw/orhp72j87v8Qa1ScDkvrrBNpZcDejAirJmfXQ==",
+ "dev": true
+ },
+ "shebang-command": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz",
+ "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==",
+ "dev": true,
+ "requires": {
+ "shebang-regex": "^3.0.0"
+ }
+ },
+ "shebang-regex": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz",
+ "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==",
+ "dev": true
+ },
+ "slice-ansi": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-2.1.0.tgz",
+ "integrity": "sha512-Qu+VC3EwYLldKa1fCxuuvULvSJOKEgk9pi8dZeCVK7TqBfUNTH4sFkk4joj8afVSfAYgJoSOetjx9QWOJ5mYoQ==",
+ "dev": true,
+ "requires": {
+ "ansi-styles": "^3.2.0",
+ "astral-regex": "^1.0.0",
+ "is-fullwidth-code-point": "^2.0.0"
+ }
+ },
+ "spdx-correct": {
+ "version": "3.1.1",
+ "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.1.1.tgz",
+ "integrity": "sha512-cOYcUWwhCuHCXi49RhFRCyJEK3iPj1Ziz9DpViV3tbZOwXD49QzIN3MpOLJNxh2qwq2lJJZaKMVw9qNi4jTC0w==",
+ "dev": true,
+ "requires": {
+ "spdx-expression-parse": "^3.0.0",
+ "spdx-license-ids": "^3.0.0"
+ }
+ },
+ "spdx-exceptions": {
+ "version": "2.3.0",
+ "resolved": "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-2.3.0.tgz",
+ "integrity": "sha512-/tTrYOC7PPI1nUAgx34hUpqXuyJG+DTHJTnIULG4rDygi4xu/tfgmq1e1cIRwRzwZgo4NLySi+ricLkZkw4i5A==",
+ "dev": true
+ },
+ "spdx-expression-parse": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-3.0.1.tgz",
+ "integrity": "sha512-cbqHunsQWnJNE6KhVSMsMeH5H/L9EpymbzqTQ3uLwNCLZ1Q481oWaofqH7nO6V07xlXwY6PhQdQ2IedWx/ZK4Q==",
+ "dev": true,
+ "requires": {
+ "spdx-exceptions": "^2.1.0",
+ "spdx-license-ids": "^3.0.0"
+ }
+ },
+ "spdx-license-ids": {
+ "version": "3.0.6",
+ "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.6.tgz",
+ "integrity": "sha512-+orQK83kyMva3WyPf59k1+Y525csj5JejicWut55zeTWANuN17qSiSLUXWtzHeNWORSvT7GLDJ/E/XiIWoXBTw==",
+ "dev": true
+ },
+ "sprintf-js": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz",
+ "integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=",
+ "dev": true
+ },
+ "string-width": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz",
+ "integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==",
+ "dev": true,
+ "requires": {
+ "emoji-regex": "^7.0.1",
+ "is-fullwidth-code-point": "^2.0.0",
+ "strip-ansi": "^5.1.0"
+ },
+ "dependencies": {
+ "ansi-regex": {
+ "version": "4.1.0",
+ "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz",
+ "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==",
+ "dev": true
+ },
+ "strip-ansi": {
+ "version": "5.2.0",
+ "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz",
+ "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==",
+ "dev": true,
+ "requires": {
+ "ansi-regex": "^4.1.0"
+ }
+ }
+ }
+ },
+ "string.prototype.trimend": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.1.tgz",
+ "integrity": "sha512-LRPxFUaTtpqYsTeNKaFOw3R4bxIzWOnbQ837QfBylo8jIxtcbK/A/sMV7Q+OAV/vWo+7s25pOE10KYSjaSO06g==",
+ "dev": true,
+ "requires": {
+ "define-properties": "^1.1.3",
+ "es-abstract": "^1.17.5"
+ },
+ "dependencies": {
+ "es-abstract": {
+ "version": "1.17.6",
+ "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.17.6.tgz",
+ "integrity": "sha512-Fr89bON3WFyUi5EvAeI48QTWX0AyekGgLA8H+c+7fbfCkJwRWRMLd8CQedNEyJuoYYhmtEqY92pgte1FAhBlhw==",
+ "dev": true,
+ "requires": {
+ "es-to-primitive": "^1.2.1",
+ "function-bind": "^1.1.1",
+ "has": "^1.0.3",
+ "has-symbols": "^1.0.1",
+ "is-callable": "^1.2.0",
+ "is-regex": "^1.1.0",
+ "object-inspect": "^1.7.0",
+ "object-keys": "^1.1.1",
+ "object.assign": "^4.1.0",
+ "string.prototype.trimend": "^1.0.1",
+ "string.prototype.trimstart": "^1.0.1"
+ }
+ }
+ }
+ },
+ "string.prototype.trimstart": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.1.tgz",
+ "integrity": "sha512-XxZn+QpvrBI1FOcg6dIpxUPgWCPuNXvMD72aaRaUQv1eD4e/Qy8i/hFTe0BUmD60p/QA6bh1avmuPTfNjqVWRw==",
+ "dev": true,
+ "requires": {
+ "define-properties": "^1.1.3",
+ "es-abstract": "^1.17.5"
+ },
+ "dependencies": {
+ "es-abstract": {
+ "version": "1.17.6",
+ "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.17.6.tgz",
+ "integrity": "sha512-Fr89bON3WFyUi5EvAeI48QTWX0AyekGgLA8H+c+7fbfCkJwRWRMLd8CQedNEyJuoYYhmtEqY92pgte1FAhBlhw==",
+ "dev": true,
+ "requires": {
+ "es-to-primitive": "^1.2.1",
+ "function-bind": "^1.1.1",
+ "has": "^1.0.3",
+ "has-symbols": "^1.0.1",
+ "is-callable": "^1.2.0",
+ "is-regex": "^1.1.0",
+ "object-inspect": "^1.7.0",
+ "object-keys": "^1.1.1",
+ "object.assign": "^4.1.0",
+ "string.prototype.trimend": "^1.0.1",
+ "string.prototype.trimstart": "^1.0.1"
+ }
+ }
+ }
+ },
+ "strip-ansi": {
+ "version": "6.0.0",
+ "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.0.tgz",
+ "integrity": "sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w==",
+ "dev": true,
+ "requires": {
+ "ansi-regex": "^5.0.0"
+ }
+ },
+ "strip-bom": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz",
+ "integrity": "sha1-IzTBjpx1n3vdVv3vfprj1YjmjtM=",
+ "dev": true
+ },
+ "strip-json-comments": {
+ "version": "3.1.1",
+ "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz",
+ "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==",
+ "dev": true
+ },
+ "supports-color": {
+ "version": "5.5.0",
+ "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz",
+ "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==",
+ "dev": true,
+ "requires": {
+ "has-flag": "^3.0.0"
+ }
+ },
+ "table": {
+ "version": "5.4.6",
+ "resolved": "https://registry.npmjs.org/table/-/table-5.4.6.tgz",
+ "integrity": "sha512-wmEc8m4fjnob4gt5riFRtTu/6+4rSe12TpAELNSqHMfF3IqnA+CH37USM6/YR3qRZv7e56kAEAtd6nKZaxe0Ug==",
+ "dev": true,
+ "requires": {
+ "ajv": "^6.10.2",
+ "lodash": "^4.17.14",
+ "slice-ansi": "^2.1.0",
+ "string-width": "^3.0.0"
+ }
+ },
+ "text-table": {
+ "version": "0.2.0",
+ "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz",
+ "integrity": "sha1-f17oI66AUgfACvLfSoTsP8+lcLQ=",
+ "dev": true
+ },
+ "tsconfig-paths": {
+ "version": "3.9.0",
+ "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-3.9.0.tgz",
+ "integrity": "sha512-dRcuzokWhajtZWkQsDVKbWyY+jgcLC5sqJhg2PSgf4ZkH2aHPvaOY8YWGhmjb68b5qqTfasSsDO9k7RUiEmZAw==",
+ "dev": true,
+ "requires": {
+ "@types/json5": "^0.0.29",
+ "json5": "^1.0.1",
+ "minimist": "^1.2.0",
+ "strip-bom": "^3.0.0"
+ }
+ },
+ "type-check": {
+ "version": "0.4.0",
+ "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz",
+ "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==",
+ "dev": true,
+ "requires": {
+ "prelude-ls": "^1.2.1"
+ }
+ },
+ "type-fest": {
+ "version": "0.8.1",
+ "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.8.1.tgz",
+ "integrity": "sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA==",
+ "dev": true
+ },
+ "uri-js": {
+ "version": "4.4.0",
+ "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.0.tgz",
+ "integrity": "sha512-B0yRTzYdUCCn9n+F4+Gh4yIDtMQcaJsmYBDsTSG8g/OejKBodLQ2IHfN3bM7jUsRXndopT7OIXWdYqc1fjmV6g==",
+ "dev": true,
+ "requires": {
+ "punycode": "^2.1.0"
+ }
+ },
+ "v8-compile-cache": {
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/v8-compile-cache/-/v8-compile-cache-2.1.1.tgz",
+ "integrity": "sha512-8OQ9CL+VWyt3JStj7HX7/ciTL2V3Rl1Wf5OL+SNTm0yK1KvtReVulksyeRnCANHHuUxHlQig+JJDlUhBt1NQDQ==",
+ "dev": true
+ },
+ "validate-npm-package-license": {
+ "version": "3.0.4",
+ "resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz",
+ "integrity": "sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==",
+ "dev": true,
+ "requires": {
+ "spdx-correct": "^3.0.0",
+ "spdx-expression-parse": "^3.0.0"
+ }
+ },
+ "which": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",
+ "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==",
+ "dev": true,
+ "requires": {
+ "isexe": "^2.0.0"
+ }
+ },
+ "word-wrap": {
+ "version": "1.2.3",
+ "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.3.tgz",
+ "integrity": "sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ==",
+ "dev": true
+ },
+ "wrappy": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
+ "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=",
+ "dev": true
+ },
+ "write": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/write/-/write-1.0.3.tgz",
+ "integrity": "sha512-/lg70HAjtkUgWPVZhZcm+T4hkL8Zbtp1nFNOn3lRrxnlv50SRBv7cR7RqR+GMsd3hUXy9hWBo4CHTbFTcOYwig==",
+ "dev": true,
+ "requires": {
+ "mkdirp": "^0.5.1"
+ }
+ }
+ }
+}
diff --git a/platform/javascript/package.json b/platform/javascript/package.json
new file mode 100644
index 0000000000..630b584f5b
--- /dev/null
+++ b/platform/javascript/package.json
@@ -0,0 +1,24 @@
+{
+ "name": "godot",
+ "private": true,
+ "version": "1.0.0",
+ "description": "Linting setup for Godot's HTML5 platform code",
+ "scripts": {
+ "test": "echo \"Error: no test specified\" && exit 1",
+ "lint": "npm run lint:engine && npm run lint:libs && npm run lint:modules",
+ "lint:engine": "eslint \"js/engine/*.js\" --no-eslintrc -c .eslintrc.engine.js",
+ "lint:libs": "eslint \"js/libs/*.js\" --no-eslintrc -c .eslintrc.libs.js",
+ "lint:modules": "eslint \"../../modules/**/*.js\" --no-eslintrc -c .eslintrc.libs.js",
+ "format": "npm run format:engine && npm run format:libs && npm run format:modules",
+ "format:engine": "npm run lint:engine -- --fix",
+ "format:libs": "npm run lint:libs -- --fix",
+ "format:modules": "npm run lint:modules -- --fix"
+ },
+ "author": "Godot Engine contributors",
+ "license": "MIT",
+ "devDependencies": {
+ "eslint": "^7.9.0",
+ "eslint-config-airbnb-base": "^14.2.0",
+ "eslint-plugin-import": "^2.22.0"
+ }
+}
diff --git a/platform/javascript/platform_config.h b/platform/javascript/platform_config.h
index e2200376d3..65df34902e 100644
--- a/platform/javascript/platform_config.h
+++ b/platform/javascript/platform_config.h
@@ -5,8 +5,8 @@
/* GODOT ENGINE */
/* https://godotengine.org */
/*************************************************************************/
-/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */
-/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */
+/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */