diff --git a/addons/gdcef/demos/HelloCEF/Control.gd b/addons/gdcef/demos/HelloCEF/Control.gd
index 3db9081..a36efa9 100644
--- a/addons/gdcef/demos/HelloCEF/Control.gd
+++ b/addons/gdcef/demos/HelloCEF/Control.gd
@@ -181,16 +181,16 @@ func _ready():
# Left browser is displaying the first webpage with a 3D scene, we are
# enabling webgl. Other default configuration are:
- # {"frame_rate", 30}
- # {"javascript", true}
- # {"javascript_close_windows", false}
- # {"javascript_access_clipboard", false}
- # {"javascript_dom_paste", false}
- # {"image_loading", true}
- # {"databases", true}
- # {"webgl", true}
- var left = $CEF.create_browser(pages[4], $TextRectLeft, {})
- var right = $CEF.create_browser(pages[0], $TextRectRight, {})
+ # {"frame_rate": 30}
+ # {"javascript": true}
+ # {"javascript_close_windows": false}
+ # {"javascript_access_clipboard": false}
+ # {"javascript_dom_paste": false}
+ # {"image_loading": true}
+ # {"databases": true}
+ # {"webgl": true}
+ var left = $CEF.create_browser(pages[4], $TextRectLeft, {"javascript": true, "webgl": true})
+ var right = $CEF.create_browser(pages[0], $TextRectRight, {"javascript": true, "webgl": true})
left.name = "left"
right.name = "right"
diff --git a/addons/gdcef/demos/JS/Control.gd b/addons/gdcef/demos/JS/Control.gd
new file mode 100644
index 0000000..35465f8
--- /dev/null
+++ b/addons/gdcef/demos/JS/Control.gd
@@ -0,0 +1,218 @@
+# ==============================================================================
+# Basic application made in HTML/JS/CSS with interaction with Godot.
+# ==============================================================================
+extends Control
+
+# ==============================================================================
+# CEF variables
+# ==============================================================================
+const BROWSER_NAME = "player_stats"
+@onready var mouse_pressed: bool = false
+
+# ==============================================================================
+# Variables for character stats
+# ==============================================================================
+@onready var player_name: String = "Anonymous"
+@onready var weapon: String = "sword"
+@onready var xp: int = 0
+@onready var level: int = 1
+
+# ==============================================================================
+# Initial character configuration
+# ==============================================================================
+func _ready():
+ initialize_cef()
+# expose_methods()
+ pass
+
+# ==============================================================================
+# Expose methods to JavaScript via CEF
+# ==============================================================================
+#func expose_methods():
+# pass
+
+# ==============================================================================
+# Change character's weapon
+# ==============================================================================
+func change_weapon(new_weapon: String):
+ print("Weapon changed to: ", new_weapon)
+ weapon = new_weapon
+ _update_character_stats()
+ pass
+
+# ==============================================================================
+# Set character's name
+# ==============================================================================
+func set_character_name(new_name: String):
+ print("New name: ", new_name)
+ player_name = new_name
+ _update_character_stats()
+ pass
+
+# ==============================================================================
+# Modify XP (can be positive or negative)
+# ==============================================================================
+func modify_xp(xp_change: int):
+ xp += xp_change
+ _level_up_check()
+ _update_character_stats()
+ pass
+
+# ==============================================================================
+# Check for level up
+# ==============================================================================
+func _level_up_check():
+ var previous_level = level
+ level = 1 + floor(xp / 100) # Simple progression example
+
+ if level > previous_level:
+ print("Level up! New level: ", level)
+ pass
+
+# ==============================================================================
+# Update character statistics
+# ==============================================================================
+func _update_character_stats():
+ var character_info = {
+ "name": player_name,
+ "weapon": weapon,
+ "xp": xp,
+ "level": level
+ }
+ print("Character update: ", character_info)
+ pass
+
+# ==============================================================================
+# Optional method to get complete character state
+# ==============================================================================
+func get_character_state() -> Dictionary:
+ return {
+ "name": player_name,
+ "weapon": weapon,
+ "xp": xp,
+ "level": level
+ }
+
+# ==============================================================================
+# CEF Callback when a page has ended to load with success.
+# ==============================================================================
+func _on_page_loaded(browser):
+ print("The browser " + browser.name + " has loaded " + browser.get_url())
+ $CEF.register_method(self, browser, "change_weapon")
+ $CEF.register_method(self, browser, "set_character_name")
+ $CEF.register_method(self, browser, "modify_xp")
+ pass
+
+# ==============================================================================
+# Callback when a page has ended to load with failure.
+# Display a load error message using a data: URI.
+# ==============================================================================
+func _on_page_failed_loading(_err_code, _err_msg, browser):
+ $AcceptDialog.title = "Alert!"
+ $AcceptDialog.dialog_text = "The browser " + browser.name + " did not load " + browser.get_url()
+ $AcceptDialog.popup_centered(Vector2(0, 0))
+ $AcceptDialog.show()
+ pass
+
+# ==============================================================================
+# Split the browser vertically to display two browsers (aka tabs) rendered in
+# two separate textures.
+# ==============================================================================
+func initialize_cef():
+
+ ### CEF
+
+ if !$CEF.initialize({"incognito": true, "locale": "en-US",
+ "remote_debugging_port": 7777, "remote_allow_origin": "*"}):
+ push_error("Failed initializing CEF")
+ get_tree().quit()
+ else:
+ push_warning("CEF version: " + $CEF.get_full_version())
+ pass
+
+ ### Browser
+
+ var browser = $CEF.create_browser("", $TextureRect, {"javascript": true})
+ browser.name = BROWSER_NAME
+ browser.connect("on_page_loaded", _on_page_loaded)
+ browser.connect("on_page_failed_loading", _on_page_failed_loading)
+ browser.resize($TextureRect.get_size())
+ browser.load_data_uri(_load_html_file(), "text/html")
+ pass
+
+# ==============================================================================
+# Load the HTML file containing the JavaScript code
+# ==============================================================================
+func _load_html_file():
+ var file = FileAccess.open("res://character-management-ui.html", FileAccess.READ)
+ var content = file.get_as_text()
+ file.close()
+ return content
+
+# ==============================================================================
+# Get the browser node interacting with the JavaScript code.
+# ==============================================================================
+func get_browser():
+ var browser = $CEF.get_node(BROWSER_NAME)
+ if browser == null:
+ push_error("Failed getting Godot node '" + name + "'")
+ get_tree().quit()
+ return browser
+
+# ==============================================================================
+# Get mouse events and broadcast them to CEF
+# ==============================================================================
+func _on_TextureRect_gui_input(event: InputEvent):
+ var current_browser = get_browser()
+ if event is InputEventMouseButton:
+ if event.button_index == MOUSE_BUTTON_WHEEL_UP:
+ current_browser.set_mouse_wheel_vertical(2)
+ elif event.button_index == MOUSE_BUTTON_WHEEL_DOWN:
+ current_browser.set_mouse_wheel_vertical(-2)
+ elif event.button_index == MOUSE_BUTTON_LEFT:
+ mouse_pressed = event.pressed
+ if mouse_pressed:
+ current_browser.set_mouse_left_down()
+ else:
+ current_browser.set_mouse_left_up()
+ elif event.button_index == MOUSE_BUTTON_RIGHT:
+ mouse_pressed = event.pressed
+ if mouse_pressed:
+ current_browser.set_mouse_right_down()
+ else:
+ current_browser.set_mouse_right_up()
+ else:
+ mouse_pressed = event.pressed
+ if mouse_pressed:
+ current_browser.set_mouse_middle_down()
+ else:
+ current_browser.set_mouse_middle_up()
+ elif event is InputEventMouseMotion:
+ if mouse_pressed:
+ current_browser.set_mouse_left_down()
+ current_browser.set_mouse_moved(event.position.x, event.position.y)
+ pass
+
+# ==============================================================================
+# Make the CEF browser reacts from keyboard events.
+# ==============================================================================
+func _input(event):
+ if event is InputEventKey:
+ get_browser().set_key_pressed(
+ event.unicode if event.unicode != 0 else event.keycode,
+ event.pressed, event.shift_pressed, event.alt_pressed,
+ event.is_command_or_control_pressed())
+ pass
+
+# ==============================================================================
+# Windows has resized
+# ==============================================================================
+func _on_texture_rect_resized():
+ get_browser().resize($Panel/VBox/TextureRect.get_size())
+ pass
+
+# ==============================================================================
+# CEF is implicitly updated by this function.
+# ==============================================================================
+func _process(_delta):
+ pass
diff --git a/addons/gdcef/demos/JS/Control.tscn b/addons/gdcef/demos/JS/Control.tscn
new file mode 100644
index 0000000..8e6ca1c
--- /dev/null
+++ b/addons/gdcef/demos/JS/Control.tscn
@@ -0,0 +1,28 @@
+[gd_scene load_steps=2 format=3 uid="uid://ckewd5tjvl4rj"]
+
+[ext_resource type="Script" path="res://Control.gd" id="2"]
+
+[node name="Control" type="Control"]
+layout_mode = 3
+anchors_preset = 15
+anchor_right = 1.0
+anchor_bottom = 1.0
+grow_horizontal = 2
+grow_vertical = 2
+script = ExtResource("2")
+
+[node name="TextureRect" type="TextureRect" parent="."]
+layout_mode = 1
+anchors_preset = 15
+anchor_right = 1.0
+anchor_bottom = 1.0
+grow_horizontal = 2
+grow_vertical = 2
+
+[node name="CEF" type="GDCef" parent="."]
+
+[node name="Timer" type="Timer" parent="."]
+wait_time = 10.0
+autostart = true
+
+[connection signal="gui_input" from="TextureRect" to="." method="_on_TextureRect_gui_input"]
diff --git a/addons/gdcef/demos/JS/character-management-ui.html b/addons/gdcef/demos/JS/character-management-ui.html
new file mode 100644
index 0000000..2d2b18d
--- /dev/null
+++ b/addons/gdcef/demos/JS/character-management-ui.html
@@ -0,0 +1,220 @@
+
+
+
+
+
+ Character Management UI
+
+
+
+
+
+
+
+
+
Name of the character
+
+
+
+
+
+
+
+
Weapon Selection
+
+
+
+
+
+
+
+
+
+
+
+
XP Management
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/addons/gdcef/demos/JS/default_env.tres b/addons/gdcef/demos/JS/default_env.tres
new file mode 100644
index 0000000..dfc5189
--- /dev/null
+++ b/addons/gdcef/demos/JS/default_env.tres
@@ -0,0 +1,7 @@
+[gd_resource type="Environment" load_steps=2 format=3 uid="uid://btpjrvhyjvhj2"]
+
+[sub_resource type="Sky" id="1"]
+
+[resource]
+background_mode = 2
+sky = SubResource("1")
diff --git a/addons/gdcef/demos/JS/icon.png b/addons/gdcef/demos/JS/icon.png
new file mode 100644
index 0000000..1acbade
Binary files /dev/null and b/addons/gdcef/demos/JS/icon.png differ
diff --git a/addons/gdcef/demos/JS/project.godot b/addons/gdcef/demos/JS/project.godot
new file mode 100644
index 0000000..322cd70
--- /dev/null
+++ b/addons/gdcef/demos/JS/project.godot
@@ -0,0 +1,25 @@
+; Engine configuration file.
+; It's best edited using the editor UI and not directly,
+; since the parameters that go here are not all obvious.
+;
+; Format:
+; [section] ; section goes between []
+; param=value ; assign values to parameters
+
+config_version=5
+
+[application]
+
+config/name="JS Bindings"
+run/main_scene="res://Control.tscn"
+config/features=PackedStringArray("4.3")
+config/icon="res://icon.png"
+
+[physics]
+
+common/enable_pause_aware_picking=true
+
+[rendering]
+
+renderer/rendering_method="gl_compatibility"
+environment/defaults/default_environment="res://default_env.tres"
diff --git a/addons/gdcef/demos/README.md b/addons/gdcef/demos/README.md
index 5e54933..878ca2a 100644
--- a/addons/gdcef/demos/README.md
+++ b/addons/gdcef/demos/README.md
@@ -33,3 +33,9 @@ A demo showing a 3D GUI with a single CEF browser tab showing a radio website. T
This demo is based on the asset library: https://godotengine.org/asset-library/asset/127
![Screenshot](3D/icon.png)
+
+### Demo 03: CEF browser in 2D with JS bindings
+
+A demo showing a 2D GUI made in HTML/CSS/JavaScript and loaded in a CEF browser. The demo shows how to bind Godot methods to JavaScript functions using JS Binder.
+
+![Screenshot](JS/icon.png)
diff --git a/addons/gdcef/gdcef/SConstruct b/addons/gdcef/gdcef/SConstruct
index b327692..73fd195 100644
--- a/addons/gdcef/gdcef/SConstruct
+++ b/addons/gdcef/gdcef/SConstruct
@@ -232,7 +232,7 @@ env.Append(CPPPATH=['src/'])
env.Append(RPATH=[env['cef_artifacts_folder'][2:-2]])
# Compile the library
-sources = ['src/helper_files.cpp', 'src/browser_io.cpp', 'src/gdcef.cpp', 'src/gdbrowser.cpp', 'src/register_types.cpp']
+sources = ['src/helper_files.cpp', 'src/browser_io.cpp', 'src/gdcef.cpp', 'src/gdbrowser.cpp', 'src/register_types.cpp', 'src/godot_js_binder.cpp']
# sources = Glob('src/*.cpp')
library = env.SharedLibrary(target=target_path + '/' + target_library, source=sources)
Default(library)
diff --git a/addons/gdcef/gdcef/src/gdbrowser.cpp b/addons/gdcef/gdcef/src/gdbrowser.cpp
index 0ec824f..9b999ec 100644
--- a/addons/gdcef/gdcef/src/gdbrowser.cpp
+++ b/addons/gdcef/gdcef/src/gdbrowser.cpp
@@ -37,6 +37,10 @@
# include
#endif
+#ifndef CALL_GODOT_METHOD
+# define CALL_GODOT_METHOD "callGodotMethod"
+#endif
+
//------------------------------------------------------------------------------
// Visit the html content of the current page.
class Visitor: public CefStringVisitor
@@ -886,4 +890,56 @@ void GDBrowserView::onDownloadUpdated(
// Emit signal for Godot script
emit_signal(
"on_download_updated", godot::String(file.c_str()), percentage, this);
+}
+
+//------------------------------------------------------------------------------
+bool GDBrowserView::Impl::OnProcessMessageReceived(
+ CefRefPtr browser,
+ CefRefPtr frame,
+ CefProcessId source_process,
+ CefRefPtr message)
+{
+ if (message->GetName() != CALL_GODOT_METHOD)
+ {
+ return false;
+ }
+
+ CefRefPtr args = message->GetArgumentList();
+ if (args->GetSize() < 1)
+ {
+ return false;
+ }
+
+ // Get method name from first argument
+ CefString method_name = args->GetString(0);
+
+ // Convert CEF arguments to Godot arguments
+ godot::Array godot_args;
+ for (size_t i = 1; i < args->GetSize(); ++i)
+ {
+ switch (args->GetType(i))
+ {
+ case VTYPE_BOOL:
+ godot_args.push_back(args->GetBool(i));
+ break;
+ case VTYPE_INT:
+ godot_args.push_back(args->GetInt(i));
+ break;
+ case VTYPE_DOUBLE:
+ godot_args.push_back(args->GetDouble(i));
+ break;
+ case VTYPE_STRING:
+ godot_args.push_back(
+ godot::String(args->GetString(i).ToString().c_str()));
+ break;
+ default:
+ // For unsupported types, pass as string
+ godot_args.push_back(
+ godot::String(args->GetString(i).ToString().c_str()));
+ }
+ }
+
+ // Call the Godot method
+ m_owner.call_deferred(method_name.ToString().c_str(), godot_args);
+ return true;
}
\ No newline at end of file
diff --git a/addons/gdcef/gdcef/src/gdbrowser.hpp b/addons/gdcef/gdcef/src/gdbrowser.hpp
index 4cb427e..3bfbc8d 100644
--- a/addons/gdcef/gdcef/src/gdbrowser.hpp
+++ b/addons/gdcef/gdcef/src/gdbrowser.hpp
@@ -189,6 +189,12 @@ class GDBrowserView: public godot::Node
return this;
}
+ virtual bool
+ OnProcessMessageReceived(CefRefPtr browser,
+ CefRefPtr frame,
+ CefProcessId source_process,
+ CefRefPtr message) override;
+
private: // CefRenderHandler interfaces
// ---------------------------------------------------------------------
@@ -285,7 +291,7 @@ class GDBrowserView: public godot::Node
{
}
- private: // CefBrowserProcessHandler interfaces
+ private: // CefLifeSpanHandler interfaces
virtual bool OnBeforePopup(CefRefPtr browser,
CefRefPtr frame,
diff --git a/addons/gdcef/gdcef/src/gdcef.cpp b/addons/gdcef/gdcef/src/gdcef.cpp
index 788693b..1ba1e8d 100644
--- a/addons/gdcef/gdcef/src/gdcef.cpp
+++ b/addons/gdcef/gdcef/src/gdcef.cpp
@@ -26,6 +26,7 @@
//------------------------------------------------------------------------------
#include "gdcef.hpp"
#include "gdbrowser.hpp"
+#include "godot_js_binder.hpp"
#include "helper_config.hpp"
#include "helper_files.hpp"
@@ -122,13 +123,15 @@ void GDCef::_bind_methods()
GDCEF_DEBUG("");
using namespace godot;
- ClassDB::bind_method(D_METHOD("initialize"), &GDCef::initialize);
+ ClassDB::bind_method(D_METHOD("initialize", "config"), &GDCef::initialize);
ClassDB::bind_method(D_METHOD("get_full_version"), &GDCef::version);
ClassDB::bind_method(D_METHOD("get_version_part"), &GDCef::versionPart);
ClassDB::bind_method(D_METHOD("create_browser"), &GDCef::createBrowser);
ClassDB::bind_method(D_METHOD("shutdown"), &GDCef::shutdown);
ClassDB::bind_method(D_METHOD("is_alive"), &GDCef::isAlive);
ClassDB::bind_method(D_METHOD("get_error"), &GDCef::getError);
+ ClassDB::bind_method(D_METHOD("register_method"),
+ &GDCef::registerGodotMethod);
}
//------------------------------------------------------------------------------
@@ -581,6 +584,61 @@ GDBrowserView* GDCef::createBrowser(godot::String const& url,
return browser;
}
+//------------------------------------------------------------------------------
+//! \brief Method to register Godot methods in the JavaScript context
+//! \param [in] godot_object The Godot object containing the method to register
+//! \param [in] method_name The name of the method to register
+//------------------------------------------------------------------------------
+void GDCef::registerGodotMethod(godot::Object* godot_object,
+ GDBrowserView* browser,
+ godot::String const& method_name)
+{
+ if (godot_object == nullptr)
+ {
+ GDCEF_ERROR("Invalid Godot object passed to registerGodotMethod");
+ return;
+ }
+
+ if (browser == nullptr)
+ {
+ GDCEF_ERROR("Invalid browser passed to registerGodotMethod");
+ return;
+ }
+
+ // Check that the method exists in the Godot object.
+ if (!godot_object->has_method(method_name))
+ {
+ GDCEF_ERROR("Method " << method_name.utf8().get_data()
+ << " does not exist in the Godot object");
+ return;
+ }
+
+ // Handle private GDScript methods (starting with "_").
+ // Remove the "_" initial for the JavaScript method name
+ godot::String js_method_name = method_name;
+ if (method_name.begins_with("_"))
+ {
+ js_method_name = method_name.substr(1);
+ }
+
+ // Inject the JavaScript code to create the function
+ std::string js_code =
+ "window.godot." + std::string(js_method_name.utf8().get_data()) +
+ " = function() { return window.godot.callGodotMethod('" +
+ std::string(js_method_name.utf8().get_data()) + "', ...arguments); };";
+
+ // Get the active browser from the caller
+ godot::Node* caller = godot::Object::cast_to(godot_object);
+ if (caller == nullptr)
+ {
+ GDCEF_ERROR("Caller object is not a Node");
+ return;
+ }
+
+ // Find the browser in the caller's children
+ browser->executeJavaScript(js_code.c_str());
+}
+
//------------------------------------------------------------------------------
void GDCef::Impl::OnAfterCreated(CefRefPtr /*browser*/)
{
diff --git a/addons/gdcef/gdcef/src/gdcef.hpp b/addons/gdcef/gdcef/src/gdcef.hpp
index 30337d6..6e3cf6a 100644
--- a/addons/gdcef/gdcef/src/gdcef.hpp
+++ b/addons/gdcef/gdcef/src/gdcef.hpp
@@ -108,6 +108,13 @@ class GDCef: public godot::Node
// -------------------------------------------------------------------------
bool isAlive();
+ // -------------------------------------------------------------------------
+ //! \brief Register a Godot method in the JavaScript context
+ // -------------------------------------------------------------------------
+ void registerGodotMethod(godot::Object* godot_object,
+ GDBrowserView* browser,
+ const godot::String& method_name);
+
// -------------------------------------------------------------------------
//! \brief Return the latest error.
// -------------------------------------------------------------------------
@@ -273,14 +280,14 @@ class GDCef: public godot::Node
//! \param[in] texture_rect the texture container in where to paint the CEF
//! output. \param[in] config dictionary of Browser config with default
//! values:
- //! - {"frame_rate", 30}
- //! - {"javascript", STATE_ENABLED}
- //! - {"javascript_close_windows", STATE_DISABLED}
- //! - {"javascript_access_clipboard", STATE_DISABLED}
- //! - {"javascript_dom_paste", STATE_DISABLED}
- //! - {"image_loading", STATE_ENABLED}
- //! - {"databases", STATE_ENABLED}
- //! - {"webgl", STATE_ENABLED}
+ //! - {"frame_rate": 30}
+ //! - {"javascript": STATE_ENABLED}
+ //! - {"javascript_close_windows": STATE_DISABLED}
+ //! - {"javascript_access_clipboard": STATE_DISABLED}
+ //! - {"javascript_dom_paste": STATE_DISABLED}
+ //! - {"image_loading": STATE_ENABLED}
+ //! - {"databases": STATE_ENABLED}
+ //! - {"webgl": STATE_ENABLED}
//! Wherer STATE_DISABLED / STATE_ENABLED == false / true
//! \return the address of the newly created browser (or nullptr in case of
//! error).
diff --git a/addons/gdcef/gdcef/src/godot_js_binder.cpp b/addons/gdcef/gdcef/src/godot_js_binder.cpp
new file mode 100644
index 0000000..5f7900e
--- /dev/null
+++ b/addons/gdcef/gdcef/src/godot_js_binder.cpp
@@ -0,0 +1,174 @@
+//*****************************************************************************
+// MIT License
+//
+// Copyright (c) 2022 Alain Duron
+// Copyright (c) 2022 Quentin Quadrat
+//
+// 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 "godot_js_binder.hpp"
+
+static CefRefPtr GodotToV8(const godot::Variant& godot_value);
+static godot::Variant V8ToGodot(CefRefPtr v8_value);
+
+//------------------------------------------------------------------------------
+bool GodotMethodInvoker::Execute(const CefString& name,
+ CefRefPtr object,
+ const CefV8ValueList& arguments,
+ CefRefPtr& retval,
+ CefString& exception)
+{
+ // Convert arguments JavaScript to Godot arguments
+ godot::Array godot_args;
+ for (const auto& arg : arguments)
+ {
+ godot_args.append(V8ToGodot(arg));
+ }
+
+ // Call the Godot method
+ godot::Variant result = m_godot_object->call(m_method_name, godot_args);
+
+ // Convert Godot result to V8 value
+ retval = GodotToV8(result);
+ return true;
+}
+
+//------------------------------------------------------------------------------
+static CefRefPtr GodotToV8(const godot::Variant& godot_value)
+{
+ switch (godot_value.get_type())
+ {
+ case godot::Variant::BOOL:
+ return CefV8Value::CreateBool(godot_value.operator bool());
+
+ case godot::Variant::INT:
+ return CefV8Value::CreateInt(godot_value.operator int64_t());
+
+ case godot::Variant::FLOAT:
+ return CefV8Value::CreateDouble(godot_value.operator double());
+
+ case godot::Variant::STRING:
+ return CefV8Value::CreateString(CefString(
+ godot_value.operator godot::String().utf8().get_data()));
+
+ case godot::Variant::ARRAY: {
+ godot::Array godot_array = godot_value.operator godot::Array();
+ CefRefPtr js_array =
+ CefV8Value::CreateArray(godot_array.size());
+
+ for (int i = 0; i < godot_array.size(); ++i)
+ {
+ js_array->SetValue(i, GodotToV8(godot_array[i]));
+ }
+ return js_array;
+ }
+
+ case godot::Variant::DICTIONARY: {
+ godot::Dictionary godot_dict =
+ godot_value.operator godot::Dictionary();
+ CefRefPtr js_object =
+ CefV8Value::CreateObject(nullptr, nullptr);
+
+ for (int i = 0; i < godot_dict.size(); ++i)
+ {
+ godot::Variant key = godot_dict.keys()[i];
+ godot::Variant value = godot_dict.values()[i];
+
+ js_object->SetValue(
+ key.operator godot::String().utf8().get_data(),
+ GodotToV8(value),
+ V8_PROPERTY_ATTRIBUTE_NONE);
+ }
+ return js_object;
+ }
+
+ default:
+ return CefV8Value::CreateNull();
+ }
+}
+
+//------------------------------------------------------------------------------
+static godot::Variant V8ToGodot(CefRefPtr v8_value)
+{
+ if (!v8_value.get())
+ {
+ return godot::Variant();
+ }
+
+ if (v8_value->IsNull() || v8_value->IsUndefined())
+ {
+ return godot::Variant();
+ }
+
+ if (v8_value->IsBool())
+ {
+ return godot::Variant(v8_value->GetBoolValue());
+ }
+
+ if (v8_value->IsInt())
+ {
+ return godot::Variant(v8_value->GetIntValue());
+ }
+
+ if (v8_value->IsDouble())
+ {
+ return godot::Variant(v8_value->GetDoubleValue());
+ }
+
+ if (v8_value->IsString())
+ {
+ return godot::Variant(
+ godot::String(v8_value->GetStringValue().ToString().c_str()));
+ }
+
+ if (v8_value->IsArray())
+ {
+ godot::Array godot_array;
+ int length = v8_value->GetArrayLength();
+
+ for (int i = 0; i < length; ++i)
+ {
+ godot_array.append(V8ToGodot(v8_value->GetValue(i)));
+ }
+
+ return godot_array;
+ }
+
+ if (v8_value->IsObject())
+ {
+ godot::Dictionary godot_dict;
+ std::vector keys;
+ v8_value->GetKeys(keys);
+
+ for (const auto& key : keys)
+ {
+ CefRefPtr property = v8_value->GetValue(key);
+ if (property.get())
+ {
+ godot_dict[godot::String(key.ToString().c_str())] =
+ V8ToGodot(property);
+ }
+ }
+
+ return godot_dict;
+ }
+
+ return godot::Variant();
+}
\ No newline at end of file
diff --git a/addons/gdcef/gdcef/src/godot_js_binder.hpp b/addons/gdcef/gdcef/src/godot_js_binder.hpp
new file mode 100644
index 0000000..5e9ada4
--- /dev/null
+++ b/addons/gdcef/gdcef/src/godot_js_binder.hpp
@@ -0,0 +1,64 @@
+//*****************************************************************************
+// MIT License
+//
+// Copyright (c) 2022 Alain Duron
+// Copyright (c) 2022 Quentin Quadrat
+//
+// 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 GDCEF_GODOT_JS_BINDER_HPP
+#define GDCEF_GODOT_JS_BINDER_HPP
+
+// Godot 4
+#include
+#include
+#include
+
+// Chromium Embedded Framework
+#include "cef_v8.h"
+
+// ****************************************************************************
+//! \class GodotMethodInvoker
+//! \brief Class to handle binding between JavaScript and GDScript methods
+// ****************************************************************************
+class GodotMethodInvoker: public CefV8Handler
+{
+public:
+
+ GodotMethodInvoker(godot::Object* obj, const godot::StringName& method)
+ : m_godot_object(obj), m_method_name(method)
+ {
+ }
+
+ bool Execute(const CefString& name,
+ CefRefPtr object,
+ const CefV8ValueList& arguments,
+ CefRefPtr& retval,
+ CefString& exception) override;
+
+ IMPLEMENT_REFCOUNTING(GodotMethodInvoker);
+
+private:
+
+ godot::Object* m_godot_object;
+ godot::StringName m_method_name;
+};
+
+#endif // GDCEF_GODOT_JS_BINDER_HPP
\ No newline at end of file
diff --git a/addons/gdcef/gdcef/src/register_types.cpp b/addons/gdcef/gdcef/src/register_types.cpp
index 0cebe34..00f4d91 100644
--- a/addons/gdcef/gdcef/src/register_types.cpp
+++ b/addons/gdcef/gdcef/src/register_types.cpp
@@ -27,6 +27,7 @@
#include "gdbrowser.hpp"
#include "gdcef.hpp"
+#include "godot_js_binder.hpp"
#include "helper_log.hpp"
#include
#include
diff --git a/addons/gdcef/render_process/src/render_process.cpp b/addons/gdcef/render_process/src/render_process.cpp
index 4792d31..54a9c3a 100644
--- a/addons/gdcef/render_process/src/render_process.cpp
+++ b/addons/gdcef/render_process/src/render_process.cpp
@@ -25,6 +25,7 @@
#include "render_process.hpp"
+//------------------------------------------------------------------------------
#define DEBUG_RENDER_PROCESS(txt) \
{ \
std::stringstream ss; \
@@ -33,6 +34,7 @@
std::cout << ss.str() << std::endl; \
}
+//------------------------------------------------------------------------------
#define DEBUG_BROWSER_PROCESS(txt) \
{ \
std::stringstream ss; \
@@ -41,90 +43,102 @@
std::cout << ss.str() << std::endl; \
}
-//------------------------------------------------------------------------------
-RenderProcess::~RenderProcess()
-{
- DEBUG_RENDER_PROCESS("");
-}
+#ifndef CALL_GODOT_METHOD
+# define CALL_GODOT_METHOD "callGodotMethod"
+#endif
-#if 0
//------------------------------------------------------------------------------
-void RenderProcess::OnContextInitialized()
+bool GodotMethodHandler::Execute(const CefString& name,
+ CefRefPtr object,
+ const CefV8ValueList& arguments,
+ CefRefPtr& retval,
+ CefString& exception)
{
- CEF_REQUIRE_UI_THREAD();
- DEBUG_RENDER_PROCESS("");
+ // Function does not exist.
+ if (name != CALL_GODOT_METHOD)
+ {
+ return false;
+ }
+
+ // No browser created, we cannot call the method.
+ if (m_browser == nullptr)
+ {
+ exception = "Browser pointer at NULL";
+ return true;
+ }
- // Information used when creating the native window.
- CefWindowInfo window_info;
+ // Check that there is at least the method name as argument.
+ if (arguments.size() < 1 || !arguments[0]->IsString())
+ {
+ exception = "First argument must be the method name";
+ return false;
+ }
+
+ // Create and configure the IPC message to the main process.
+ CefRefPtr msg =
+ CefProcessMessage::Create(CALL_GODOT_METHOD);
+ CefRefPtr args = msg->GetArgumentList();
-# if defined(OS_WIN)
- // On Windows we need to specify certain flags that will be passed to
- // CreateWindowEx().
- window_info.SetAsPopup(NULL, "CEF");
-# endif
+ // Add the method name as first argument.
+ args->SetString(0, arguments[0]->GetStringValue());
- // GDCefBrowser implements browser-level callbacks.
- DEBUG_RENDER_PROCESS("Create client handler");
- CefRefPtr handler(new GDCefBrowser());
+ // Add the arguments directly from V8 types.
+ for (size_t i = 1; i < arguments.size(); ++i)
+ {
+ auto arg = arguments[i];
+ if (arg->IsBool())
+ {
+ args->SetBool(i, arg->GetBoolValue());
+ }
+ else if (arg->IsInt())
+ {
+ args->SetInt(i, arg->GetIntValue());
+ }
+ else if (arg->IsDouble())
+ {
+ args->SetDouble(i, arg->GetDoubleValue());
+ }
+ else if (arg->IsString())
+ {
+ args->SetString(i, arg->GetStringValue());
+ }
+ else
+ {
+ // For other types, convert them to string
+ args->SetString(i, arg->GetStringValue());
+ }
+ }
- // Specify CEF browser settings here.
- CefBrowserSettings browser_settings;
+ // Send the message to the main process
+ m_browser->GetMainFrame()->SendProcessMessage(PID_BROWSER, msg);
+ retval = CefV8Value::CreateBool(true);
- // Create the first browser window.
- DEBUG_RENDER_PROCESS("Create the browser");
- CefBrowserHost::CreateBrowser(
- window_info, handler.get(), "", browser_settings, nullptr, nullptr);
+ return true;
}
-#endif
//------------------------------------------------------------------------------
+// TODO Faire OnContextReleased ?
void RenderProcess::OnContextCreated(CefRefPtr browser,
CefRefPtr frame,
CefRefPtr context)
{
- DEBUG_RENDER_PROCESS("");
-}
+ DEBUG_RENDER_PROCESS(browser->GetIdentifier());
-#if 0
-//------------------------------------------------------------------------------
-GDCefBrowser::~GDCefBrowser()
-{
- DEBUG_BROWSER_PROCESS("");
-}
-
-//------------------------------------------------------------------------------
-void GDCefBrowser::OnAfterCreated(CefRefPtr browser)
-{
- CEF_REQUIRE_UI_THREAD();
- DEBUG_BROWSER_PROCESS("");
+ // No handler yet, we need to create it first
+ m_handler = new GodotMethodHandler(browser);
- // Add to the list of existing browsers.
- m_browser_list.push_back(browser);
-}
+ // Create global JavaScript objects and bind methods
+ CefRefPtr global = context->GetGlobal();
-//------------------------------------------------------------------------------
-void GDCefBrowser::OnBeforeClose(CefRefPtr browser)
-{
- CEF_REQUIRE_UI_THREAD();
- DEBUG_BROWSER_PROCESS("");
+ // Create a global Godot bridge object
+ CefRefPtr godotBridge =
+ CefV8Value::CreateObject(nullptr, nullptr);
- // Remove from the list of existing browsers.
- BrowserList::iterator bit = m_browser_list.begin();
- for (; bit != m_browser_list.end(); ++bit)
- {
- if ((*bit)->IsSame(browser))
- {
- m_browser_list.erase(bit);
- break;
- }
- }
+ // Bind methods from Godot to JavaScript
+ godotBridge->SetValue(
+ CALL_GODOT_METHOD,
+ CefV8Value::CreateFunction(CALL_GODOT_METHOD, m_handler),
+ V8_PROPERTY_ATTRIBUTE_NONE);
- if (m_browser_list.empty())
- {
- DEBUG_BROWSER_PROCESS("CefQuitMessageLoop");
- // All browser windows have closed.
- // Quit the application message loop.
- CefQuitMessageLoop();
- }
-}
-#endif
\ No newline at end of file
+ global->SetValue("godot", godotBridge, V8_PROPERTY_ATTRIBUTE_NONE);
+}
\ No newline at end of file
diff --git a/addons/gdcef/render_process/src/render_process.hpp b/addons/gdcef/render_process/src/render_process.hpp
index 423c72b..46c9653 100644
--- a/addons/gdcef/render_process/src/render_process.hpp
+++ b/addons/gdcef/render_process/src/render_process.hpp
@@ -63,94 +63,56 @@
#endif
// *****************************************************************************
-//! \brief Entry point for the render process
+//! \brief JavaScript Method Handler
// *****************************************************************************
-class RenderProcess: public CefApp,
- // public CefBrowserProcessHandler,
- public CefRenderProcessHandler
+class GodotMethodHandler: public CefV8Handler
{
public:
- ~RenderProcess();
+ GodotMethodHandler(CefRefPtr browser) : m_browser(browser) {}
-private: // CefApp methods
+ bool Execute(const CefString& name,
+ CefRefPtr object,
+ const CefV8ValueList& arguments,
+ CefRefPtr& retval,
+ CefString& exception) override;
- // -------------------------------------------------------------------------
- // virtual CefRefPtr
- // GetBrowserProcessHandler() override
- // {
- // return this;
- //}
-
- virtual CefRefPtr
- GetRenderProcessHandler() override
- {
- return this;
- }
-
-private: // CefBrowserProcessHandler methods
-
- // -------------------------------------------------------------------------
- // virtual void OnContextInitialized() override;
-
-private: // CefRenderProcessHandler methods
-
- // -------------------------------------------------------------------------
- virtual void OnContextCreated(CefRefPtr browser,
- CefRefPtr frame,
- CefRefPtr context) override;
+ IMPLEMENT_REFCOUNTING(GodotMethodHandler);
private:
- IMPLEMENT_REFCOUNTING(RenderProcess);
+ CefRefPtr m_browser;
};
-#if 0
// *****************************************************************************
-//! \brief Browser process handler
+//! \brief Entry point for the render process
// *****************************************************************************
-class GDCefBrowser: public CefClient,
- public CefLifeSpanHandler,
- public CefDisplayHandler
+class RenderProcess: public CefApp, public CefRenderProcessHandler
{
public:
- ~GDCefBrowser();
-
-private: // CefDisplayHandler methods
-
- // -------------------------------------------------------------------------
- virtual CefRefPtr GetDisplayHandler() override
- {
- return this;
- }
+ IMPLEMENT_REFCOUNTING(RenderProcess);
-private: // CefLifeSpanHandler methods
+private: // CefApp methods
// -------------------------------------------------------------------------
- virtual CefRefPtr GetLifeSpanHandler() override
+ virtual CefRefPtr
+ GetRenderProcessHandler() override
{
return this;
}
- // -------------------------------------------------------------------------
- virtual void OnAfterCreated(CefRefPtr browser) override;
-
- // -------------------------------------------------------------------------
- virtual void OnBeforeClose(CefRefPtr browser) override;
-
-private:
+private: // CefRenderProcessHandler methods
// -------------------------------------------------------------------------
- // Include the default reference counting implementation.
- IMPLEMENT_REFCOUNTING(GDCefBrowser);
+ virtual void OnContextCreated(CefRefPtr browser,
+ CefRefPtr frame,
+ CefRefPtr context) override;
private:
- using BrowserList = std::list>;
- BrowserList m_browser_list;
+ CefRefPtr m_handler;
};
-#endif
#if !defined(_WIN32)
# if defined(__clang__)