DisplayServerJavaScript implementation.

This commit is contained in:
Fabio Alessandrelli 2020-05-01 14:45:45 +02:00
parent 11a81341fa
commit 7411e7fd37
11 changed files with 1538 additions and 1210 deletions

View File

@ -169,8 +169,6 @@ $GODOT_HEAD_INCLUDE
var height = window.innerHeight;
canvas.width = width * scale;
canvas.height = height * scale;
canvas.style.width = width + "px";
canvas.style.height = height + "px";
}
animationCallbacks.push(adjustCanvasDimensions);
adjustCanvasDimensions();

View File

@ -4,6 +4,7 @@ Import("env")
javascript_files = [
"audio_driver_javascript.cpp",
"display_server_javascript.cpp",
"http_client_javascript.cpp",
"javascript_eval.cpp",
"javascript_main.cpp",
@ -46,11 +47,21 @@ 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"]
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")
]
html_file = "#misc/dist/html/full-size.html"
in_files = [
js_wrapped,
build[1],
html_file
]
if env["threads_enabled"]:
in_files.append(build[2])
out_files.append(zip_dir.File("godot.worker.js"))
out_files.append(zip_dir.File(binary_name + ".worker.js"))
zip_files = env.InstallAs(out_files, in_files)
env.Zip(
@ -58,5 +69,5 @@ env.Zip(
zip_files,
ZIPROOT=zip_dir,
ZIPSUFFIX="${PROGSUFFIX}${ZIPSUFFIX}",
ZIPCOMSTR="Archving $SOURCES as $TARGET",
ZIPCOMSTR="Archving $SOURCES as $TARGET"
)

View File

@ -190,19 +190,43 @@ void AudioDriverJavaScript::lock() {
void AudioDriverJavaScript::unlock() {
}
void AudioDriverJavaScript::finish() {
void AudioDriverJavaScript::finish_async() {
// Close the context, add the operation to the async_finish list in module.
int id = _driver_id;
_driver_id = 0;
/* clang-format off */
EM_ASM({
var ref = Module.IDHandler.get($0);
Module.async_finish.push(new Promise(function(accept, reject) {
if (!ref) {
console.log("Ref not found!", $0, Module.IDHandler);
setTimeout(accept, 0);
} else {
const context = ref['context'];
// Disconnect script and input.
ref['script'].disconnect();
if (ref['input'])
ref['input'].disconnect();
ref = null;
context.close().then(function() {
accept();
}).catch(function(e) {
accept();
});
}
}));
Module.IDHandler.remove($0);
}, _driver_id);
}, id);
/* clang-format on */
}
void AudioDriverJavaScript::finish() {
if (internal_buffer) {
memdelete_arr(internal_buffer);
internal_buffer = nullptr;
}
_driver_id = 0;
}
Error AudioDriverJavaScript::capture_start() {

View File

@ -56,6 +56,7 @@ public:
virtual void lock();
virtual void unlock();
virtual void finish();
void finish_async();
virtual Error capture_start();
virtual Error capture_stop();

View File

@ -2,7 +2,6 @@ import os
from emscripten_helpers import parse_config, run_closure_compiler, create_engine_file
def is_active():
return True
@ -17,12 +16,11 @@ def can_build():
def get_opts():
from SCons.Variables import BoolVariable
return [
# 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("use_closure_compiler", "Use closure compiler to minimize JavaScript code", False),
]
@ -57,7 +55,7 @@ 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"])
@ -150,7 +148,7 @@ def configure(env):
env.Append(LIBS=["idbfs.js"])
env.Append(LINKFLAGS=["-s", "BINARYEN=1"])
env.Append(LINKFLAGS=["-s", "MODULARIZE=1", "-s", 'EXPORT_NAME="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
@ -162,8 +160,10 @@ def configure(env):
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, FS for preloading, PATH and ERRNO_CODES for BrowserFS.
env.Append(LINKFLAGS=["-s", "EXTRA_EXPORTED_RUNTIME_METHODS=['callMain', 'FS', 'PATH', 'ERRNO_CODES']"])
# Add code that allow exiting runtime.
env.Append(LINKFLAGS=['-s', 'EXIT_RUNTIME=1'])
env.Append(LINKFLAGS=["-s", "EXIT_RUNTIME=1"])

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,187 @@
/*************************************************************************/
/* display_server_javascript.h */
/*************************************************************************/
/* 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). */
/* */
/* 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 {
//int video_driver_index;
Vector2 windowed_size;
ObjectID window_attached_instance_id = {};
Ref<InputEventKey> deferred_key_event;
CursorShape cursor_shape = CURSOR_ARROW;
String cursors[CURSOR_MAX];
Map<CursorShape, Vector<Variant>> cursors_cache;
Point2 touches[32];
Point2i last_click_pos = Point2(-100, -100); // TODO check this again.
double last_click_ms = 0;
int last_click_button_index = -1;
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);
protected:
virtual int get_current_video_driver() const;
public:
// Override return type to make writing static callbacks less tedious.
static DisplayServerJavaScript *get_singleton();
WindowMode window_mode = WINDOW_MODE_WINDOWED;
String clipboard;
String canvas_id;
Callable window_event_callback;
Callable input_event_callback;
Callable input_text_callback;
// from DisplayServer
virtual void alert(const String &p_alert, const String &p_title = "ALERT!");
virtual bool has_feature(Feature p_feature) const;
virtual String get_name() const;
// cursor
virtual void cursor_set_shape(CursorShape p_shape);
virtual CursorShape cursor_get_shape() const;
virtual void cursor_set_custom_image(const RES &p_cursor, CursorShape p_shape = CURSOR_ARROW, const Vector2 &p_hotspot = Vector2());
// mouse
virtual void mouse_set_mode(MouseMode p_mode);
virtual MouseMode mouse_get_mode() const;
// touch
virtual bool screen_is_touchscreen(int p_screen = SCREEN_OF_MAIN_WINDOW) const;
// clipboard
virtual void clipboard_set(const String &p_text);
virtual String clipboard_get() const;
// screen
virtual int get_screen_count() const;
virtual Point2i screen_get_position(int p_screen = SCREEN_OF_MAIN_WINDOW) const;
virtual Size2i screen_get_size(int p_screen = SCREEN_OF_MAIN_WINDOW) const;
virtual Rect2i screen_get_usable_rect(int p_screen = SCREEN_OF_MAIN_WINDOW) const;
virtual int screen_get_dpi(int p_screen = SCREEN_OF_MAIN_WINDOW) const;
// windows
virtual Vector<DisplayServer::WindowID> get_window_list() const;
virtual WindowID get_window_at_screen_position(const Point2i &p_position) const;
virtual void window_attach_instance_id(ObjectID p_instance, WindowID p_window = MAIN_WINDOW_ID);
virtual ObjectID window_get_attached_instance_id(WindowID p_window = MAIN_WINDOW_ID) const;
virtual void window_set_rect_changed_callback(const Callable &p_callable, WindowID p_window = MAIN_WINDOW_ID);
virtual void window_set_window_event_callback(const Callable &p_callable, WindowID p_window = MAIN_WINDOW_ID);
virtual void window_set_input_event_callback(const Callable &p_callable, WindowID p_window = MAIN_WINDOW_ID);
virtual void window_set_input_text_callback(const Callable &p_callable, WindowID p_window = MAIN_WINDOW_ID);
virtual void window_set_drop_files_callback(const Callable &p_callable, WindowID p_window = MAIN_WINDOW_ID);
virtual void window_set_title(const String &p_title, WindowID p_window = MAIN_WINDOW_ID);
virtual int window_get_current_screen(WindowID p_window = MAIN_WINDOW_ID) const;
virtual void window_set_current_screen(int p_screen, WindowID p_window = MAIN_WINDOW_ID);
virtual Point2i window_get_position(WindowID p_window = MAIN_WINDOW_ID) const;
virtual void window_set_position(const Point2i &p_position, WindowID p_window = MAIN_WINDOW_ID);
virtual void window_set_transient(WindowID p_window, WindowID p_parent);
virtual void window_set_max_size(const Size2i p_size, WindowID p_window = MAIN_WINDOW_ID);
virtual Size2i window_get_max_size(WindowID p_window = MAIN_WINDOW_ID) const;
virtual void window_set_min_size(const Size2i p_size, WindowID p_window = MAIN_WINDOW_ID);
virtual Size2i window_get_min_size(WindowID p_window = MAIN_WINDOW_ID) const;
virtual void window_set_size(const Size2i p_size, WindowID p_window = MAIN_WINDOW_ID);
virtual Size2i window_get_size(WindowID p_window = MAIN_WINDOW_ID) const;
virtual Size2i window_get_real_size(WindowID p_window = MAIN_WINDOW_ID) const; // FIXME: Find clearer name for this.
virtual void window_set_mode(WindowMode p_mode, WindowID p_window = MAIN_WINDOW_ID);
virtual WindowMode window_get_mode(WindowID p_window = MAIN_WINDOW_ID) const;
virtual bool window_is_maximize_allowed(WindowID p_window = MAIN_WINDOW_ID) const;
virtual void window_set_flag(WindowFlags p_flag, bool p_enabled, WindowID p_window = MAIN_WINDOW_ID);
virtual bool window_get_flag(WindowFlags p_flag, WindowID p_window = MAIN_WINDOW_ID) const;
virtual void window_request_attention(WindowID p_window = MAIN_WINDOW_ID);
virtual void window_move_to_foreground(WindowID p_window = MAIN_WINDOW_ID);
virtual bool window_can_draw(WindowID p_window = MAIN_WINDOW_ID) const;
virtual bool can_any_window_draw() const;
// events
virtual void process_events();
// icon
virtual void set_icon(const Ref<Image> &p_icon);
// others
virtual void swap_buffers();
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

View File

@ -31,6 +31,7 @@ Function('return this')()['Engine'] = (function() {
this.rtenv = null;
this.customLocale = null;
this.resizeCanvasOnStart = false;
this.onExecute = null;
this.onExit = null;
};
@ -115,6 +116,7 @@ Function('return this')()['Engine'] = (function() {
me.rtenv['thisProgram'] = me.executableName;
me.rtenv['resizeCanvasOnStart'] = me.resizeCanvasOnStart;
me.rtenv['noExitRuntime'] = true;
me.rtenv['onExecute'] = me.onExecute;
me.rtenv['onExit'] = function(code) {
if (me.onExit)
me.onExit(code);
@ -201,6 +203,12 @@ Function('return this')()['Engine'] = (function() {
stderr = printErr;
};
Engine.prototype.setOnExecute = function(onExecute) {
if (this.rtenv)
this.rtenv.onExecute = onExecute;
this.onExecute = onExecute;
}
Engine.prototype.setOnExit = function(onExit) {
this.onExit = onExit;
}
@ -230,6 +238,7 @@ Function('return this')()['Engine'] = (function() {
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;
return Engine;

View File

@ -34,22 +34,63 @@
#include <emscripten/emscripten.h>
static OS_JavaScript *os = nullptr;
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 main_loop_callback() {
if (os->main_loop_iterate()) {
emscripten_cancel_main_loop(); // Cancel current loop and wait for finalize_async.
EM_ASM({
// This will contain the list of operations that need to complete before cleanup.
Module.async_finish = [];
});
os->get_main_loop()->finish();
os->finalize_async(); // Will add all the async finish functions.
EM_ASM({
Promise.all(Module.async_finish).then(function() {
Module.async_finish = [];
ccall("cleanup_after_sync", null, []);
});
});
}
}
extern "C" EMSCRIPTEN_KEEPALIVE void cleanup_after_sync() {
emscripten_set_main_loop(exit_callback, -1, false);
}
extern "C" EMSCRIPTEN_KEEPALIVE void main_after_fs_sync(char *p_idbfs_err) {
String idbfs_err = String::utf8(p_idbfs_err);
if (!idbfs_err.empty()) {
print_line("IndexedDB not available: " + idbfs_err);
}
OS_JavaScript *os = OS_JavaScript::get_singleton();
os->set_idb_available(idbfs_err.empty());
// TODO: Check error return value.
Main::setup2(); // Manual second phase.
// Ease up compatibility.
ResourceLoader::set_abort_on_missing_resources(false);
Main::start();
os->run_async();
os->get_main_loop()->init();
emscripten_resume_main_loop();
}
int main(int argc, char *argv[]) {
os = new OS_JavaScript();
Main::setup(argv[0], argc - 1, &argv[1], false);
emscripten_set_main_loop(main_loop_callback, -1, false);
emscripten_pause_main_loop(); // Will need to wait for FS sync.
// Sync from persistent state into memory and then
// run the 'main_after_fs_sync' function.
/* clang-format off */
@ -62,10 +103,6 @@ int main(int argc, char *argv[]) {
);
/* clang-format on */
new OS_JavaScript(argc, argv);
// TODO: Check error return value.
Main::setup(argv[0], argc - 1, &argv[1]);
return 0;
// Continued async in main_after_fs_sync() from the syncfs() callback.
}

File diff suppressed because it is too large Load Diff

View File

@ -35,64 +35,25 @@
#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 {
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;
MainLoop *main_loop = nullptr;
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 finalizing = false;
bool idb_available = false;
int64_t sync_wait_time = -1;
int64_t last_sync_check_time = -1;
static void main_loop_callback();
static void file_access_close_callback(const String &p_file, int p_flags);
protected:
virtual int get_current_video_driver() const;
virtual void initialize_core();
virtual Error initialize(const VideoMode &p_desired, int p_video_driver, int p_audio_driver);
virtual void initialize();
virtual void set_main_loop(MainLoop *p_main_loop);
virtual void delete_main_loop();
@ -105,65 +66,38 @@ 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 void initialize_joypads();
virtual bool has_touchscreen_ui_hint() const;
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();
void finalize_async();
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;
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;
virtual String get_resource_dir() const;
virtual String get_cache_path() const;
virtual String get_config_path() const;
virtual String get_data_path() const;
virtual String get_user_data_dir() const;
void set_idb_available(bool p_idb_available);
virtual bool is_userfs_persistent() const;
OS_JavaScript(int p_argc, char *p_argv[]);
void resume_audio();
bool is_finalizing() { return finalizing; }
OS_JavaScript();
};
#endif