Files
racket-webview/rtkwebview/rktwebview.cpp
2026-03-02 09:35:14 +01:00

515 lines
14 KiB
C++

#include "rktwebview.h"
#include <webview/webview.h>
#include "json.h"
static void queue_init(queue_t *q);
static void enqueue(queue_t *q, item_t item);
static bool dequeue(queue_t *q, item_t *item);
static int queue_length(queue_t *q);
static void queue_destroy(queue_t *q);
static void free_item(item_t i);
static void mutex_lock(rkt_webview_t *);
static void mutex_unlock(rkt_webview_t *);
static void thread_sleep_ms(rkt_webview_t *, int ms);
static void handle_event(const char *id, const char *data, void *_wv);
static void handle_js_call(const char *id, const char *data, void *_wv);
static void dispatcher(webview_t w, void *args);
static result_t do_dispatch(rkt_webview_t *, item_t item);
#ifdef USE_WIN_THREADS
static DWORD webviewThread(LPVOID args)
#endif
#ifdef USE_PTHREADS
static int webviewThread(void *args)
#endif
{
rkt_webview_t *wv = (rkt_webview_t *) args;
mutex_lock(wv);
webview_t w = webview_create(0, NULL);
wv->webview_handle = w;
wv->handle_set = true;
webview_bind(w, "web_ui_wire_handle_event", handle_event, wv);
webview_bind(w, "rkt_webview_call_js_result", handle_js_call, wv);
mutex_unlock(wv);
webview_run(w);
webview_destroy(w);
mutex_lock(wv);
wv->webview_handle = NULL;
wv->handle_destroyed = true;
mutex_unlock(wv);
return 0;
}
#ifdef USE_WIN_THREADS
static DWORD queueGuardThread(LPVOID args)
#endif
#ifdef USE_PTHREADS
static int queueGuardThread(void *args)
#endif
{
rkt_webview_t *wv = (rkt_webview_t *) args;
auto handle_ok = [](rkt_webview_t *wv) {
mutex_lock(wv);
bool ok = !wv->handle_destroyed;
mutex_unlock(wv);
return ok;
};
while(handle_ok(wv)) {
mutex_lock(wv);
if (wv->queue_callback != NULL) {
while(queue_length(&wv->from_webview) > 0) {
item_t item;
dequeue(&wv->from_webview, &item);
wv->queue_callback(wv->queue_callback_id, item);
}
}
mutex_unlock(wv);
thread_sleep_ms(wv, 10);
}
return 0;
}
RKTWEBVIEW_EXPORT rkt_webview_t *rkt_create_webview()
{
rkt_webview_t *wv = (rkt_webview_t *) malloc(sizeof(rkt_webview_t));
if (wv == NULL) { return NULL; }
#ifdef _WIN32
wv->mutex = CreateMutex(NULL, FALSE, NULL);
#endif
wv->handle_set = false;
wv->handle_destroyed = false;
queue_init(&wv->to_webview);
queue_init(&wv->from_webview);
wv->webview_handle = NULL;
wv->queue_callback = NULL;
wv->js_call_nr = 0;
wv->js_evaluated = new std::map<int, std::string>();
#ifdef USE_WIN_THREADS
wv->webview_thread = CreateThread(
NULL,
0,
webviewThread,
wv,
0,
&wv->webview_thread_id
);
wv->queue_guard_thread = CreateThread(
NULL,
0,
queueGuardThread,
wv,
0,
&wv->queue_guard_thread_id
);
#endif
#ifdef USE_PTHREAD
#endif
bool go_on = true;
while(go_on) {
mutex_lock(wv);
go_on = (wv->handle_set == false);
mutex_unlock(wv);
if (go_on) {
thread_sleep_ms(wv, 10);
}
}
return wv;
}
RKTWEBVIEW_EXPORT result_t rkt_webview_navigate(rkt_webview_t *wv, const char *url)
{
item_t item = { CONTEXT_NAVIGATE, const_cast<char *>(url) };
return do_dispatch(wv, item);
}
RKTWEBVIEW_EXPORT result_t rkt_webview_set_html(rkt_webview_t *wv, const char *html)
{
item_t item = { CONTEXT_SET_HTML, const_cast<char *>(html) };
return do_dispatch(wv, item);
}
RKTWEBVIEW_EXPORT result_t rkt_webview_run_js(rkt_webview_t *wv, const char *js)
{
item_t item = { CONTEXT_EVAL_JS, const_cast<char *>(js) };
return do_dispatch(wv, item);
}
RKTWEBVIEW_EXPORT item_t rkt_webview_call_js(rkt_webview_t *wv, const char *js)
{
mutex_lock(wv);
wv->js_call_nr += 1;
int call_nr = wv->js_call_nr;
mutex_unlock(wv);
char buf[30];
sprintf(buf, "%d", call_nr);
std::string _js = std::string("{ let f = function() { ") + js + " };" +
" let call_nr = " + buf + ";" +
" try { let r = { result: f() };" +
" rkt_webview_call_js_result(call_nr, true, JSON.stringify(r)); " +
" } catch(e) {" +
" rkt_webview_call_js_result(call_nr, false, e.message); " +
" }" +
"}"; "";
item_t item = { CONTEXT_CALL_JS, strdup(_js.c_str()) };
result_t r = do_dispatch(wv, item);
if (r == error) {
item_t item = { CONTEXT_INVALID , NULL };
return item;
}
auto has_result = [](rkt_webview_t *wv, int call_nr) {
mutex_lock(wv);
bool result = false;
if (wv->js_evaluated->find(call_nr) != wv->js_evaluated->end()) {
mutex_unlock(wv);
return true;
} else {
mutex_unlock(wv);
return false;
}
};
while(!has_result(wv, call_nr)) {
thread_sleep_ms(wv, 2);
}
free_item(item);
mutex_lock(wv);
JSON j = JSON::Load(wv->js_evaluated->at(call_nr), [](std::string) { });
bool result_oke = j[1].toBool();
std::string data = j[2].toString();
item_t result_item;
if (result_oke) {
result_item = { CONTEXT_CALL_JS, strdup(data.c_str()) };
} else {
result_item = { CONTEXT_INVALID, strdup(data.c_str()) };
}
wv->js_evaluated->erase(call_nr);
mutex_unlock(wv);
return result_item;
}
RKTWEBVIEW_EXPORT bool rkt_webview_valid(rkt_webview_t *handle)
{
if (handle == NULL) {
return false;
} else {
bool valid;
mutex_lock(handle);
valid = (!handle->handle_destroyed && handle->handle_set);
mutex_unlock(handle);
return valid;
}
}
RKTWEBVIEW_EXPORT result_t rkt_destroy_webview(rkt_webview_t *wv)
{
if (rkt_webview_valid(wv)) {
webview_error_t e = webview_terminate(WEBVIEW_HANDLE(wv));
result_t r = (e >= 0) ? oke : error;
if (r == oke) {
queue_destroy(&wv->to_webview);
queue_destroy(&wv->from_webview);
#ifdef USE_WIN_THREADS
WaitForSingleObject(wv->webview_thread, 2000); // Give up after 2s.
CloseHandle(wv->webview_thread);
WaitForSingleObject(wv->queue_guard_thread, 2000); // Give up after 2s.
CloseHandle(wv->queue_guard_thread);
CloseHandle(wv->mutex);
#endif
#ifdef USE_PTHREAD
#endif
delete wv->js_evaluated;
free(wv);
}
return r;
} else {
return error;
}
}
RKTWEBVIEW_EXPORT int rkt_webview_pending_events(rkt_webview_t *wv)
{
mutex_lock(wv);
int len = queue_length(&wv->from_webview);
mutex_unlock(wv);
return len;
}
RKTWEBVIEW_EXPORT item_t rkt_webview_get_event(rkt_webview_t *wv)
{
item_t i;
mutex_lock(wv);
if (queue_length(&wv->from_webview) > 0) {
dequeue(&wv->from_webview, &i);
} else {
i.context = CONTEXT_INVALID;
i.data = NULL;
}
mutex_unlock(wv);
return i;
}
RKTWEBVIEW_EXPORT void rkt_webview_destroy_item(item_t item)
{
free_item(item);
}
RKTWEBVIEW_EXPORT result_t rkt_webview_devtools(rkt_webview_t *wv)
{
item_t item = { CONTEXT_OPEN_DEVTOOLS, const_cast<char *>("") };
return do_dispatch(wv, item);
}
RKTWEBVIEW_EXPORT reason_t rkt_webview_last_reason(rkt_webview_t *wv)
{
return wv->last_reason;
}
RKTWEBVIEW_EXPORT void rkt_webview_register_queue_callback(rkt_webview_t *wv, int id, void (*cb)(int, item_t))
{
mutex_lock(wv);
wv->queue_callback = cb;
wv->queue_callback_id = id;
mutex_unlock(wv);
}
static void handle_event(const char *id, const char *data, void *_wv)
{
rkt_webview_t *wv = static_cast<rkt_webview_t *>(_wv);
mutex_lock(wv);
JSON json;
json["id"] = id;
json["data"] = data;
std::string s = json.dump();
item_t item;
item.context = CONTEXT_BOUND_EVENT;
item.data = const_cast<char *>(s.c_str());
enqueue(&wv->from_webview, item);
mutex_unlock(wv);
}
static void handle_js_call(const char *id, const char *data, void *_wv)
{
rkt_webview_t *wv = static_cast<rkt_webview_t *>(_wv);
mutex_lock(wv);
std::string d(data);
JSON j = JSON::Load(d, [](std::string err) { });
int call_nr = j[0].toInt();
wv->js_evaluated->insert(std::pair<int, std::string>(call_nr, std::string(data)));
mutex_unlock(wv);
}
void dispatcher(webview_t w, void *args)
{
rkt_webview_t *wv = reinterpret_cast<rkt_webview_t *>(args);
item_t item;
mutex_lock(wv);
if (dequeue(&wv->to_webview, &item)) {
if (item.context == CONTEXT_SET_HTML) {
webview_error_t e = webview_set_html(w, item.data);
wv->wv_res = static_cast<int>(e);
wv->last_result = (e >= 0) ? oke : error;
wv->last_reason = (wv->last_result == oke) ? reason_oke : reason_set_html_failed;
} else if (item.context == CONTEXT_NAVIGATE) {
webview_error_t e = webview_navigate(w, item.data);
wv->wv_res = static_cast<int>(e);
wv->last_result = (e >= 0) ? oke : error;
wv->last_reason = (wv->last_result == oke) ? reason_oke : reason_set_navigate_failed;
} else if (item.context == CONTEXT_EVAL_JS || item.context == CONTEXT_CALL_JS) {
webview_error_t e = webview_eval(w, item.data);
wv->wv_res = static_cast<int>(e);
wv->last_result = (e >= 0) ? oke : error;
wv->last_reason = (wv->last_result == oke) ? reason_oke : reason_eval_js_failed;
} else if (item.context == CONTEXT_OPEN_DEVTOOLS) {
bool handled = false;
#ifdef _WIN32
void *handle = webview_get_native_handle(wv->webview_handle, WEBVIEW_NATIVE_HANDLE_KIND_BROWSER_CONTROLLER);
ICoreWebView2Controller *c = static_cast<ICoreWebView2Controller *>(handle);
ICoreWebView2 *cwv = nullptr;
HRESULT r = c->get_CoreWebView2(&cwv);
if (cwv != nullptr) {
r = cwv->OpenDevToolsWindow();
if (r == S_OK) {
wv->last_result = oke;
wv->last_reason = reason_oke;
} else {
wv->last_result = error;
wv->last_reason = reason_no_devtools_on_platform;
}
} else {
wv->last_result = error;
wv->last_reason = reason_no_devtools_on_platform;
}
#endif
if (!handled) {
wv->last_result = error;
wv->last_reason = reason_no_devtools_on_platform;
}
} else {
wv->last_result = error;
wv->last_reason = reason_no_delegate_for_context;
}
}
mutex_unlock(wv);
free_item(item);
}
result_t do_dispatch(rkt_webview_t *wv, item_t item)
{
webview_t w = WEBVIEW_HANDLE(wv);
mutex_lock(wv);
enqueue(&wv->to_webview, item);
wv->last_reason = reason_no_result_yet;
mutex_unlock(wv);
webview_error_t e = webview_dispatch(w, dispatcher, wv);
reason_t lr;
result_t r;
if (e >= 0) {
bool go_on = true;
while(go_on) {
mutex_lock(wv);
r = wv->last_result;
lr = wv->last_reason;
mutex_unlock(wv);
if (lr != reason_no_result_yet) {
go_on = false;
} else {
thread_sleep_ms(wv, 10);
}
}
return r;
} else {
lr = reason_set_html_failed;
r = error;
}
printf("error = %d, reason = %d", r, lr);
return r;
}
/////////////////////////////////////////////////////////////////////
// Supporting functions
/////////////////////////////////////////////////////////////////////
void queue_init(queue_t *q)
{
q->length = 0;
q->first = NULL;
q->last = NULL;
}
void enqueue(queue_t *q, item_t item)
{
queue_item_t *itm = (queue_item_t *) malloc(sizeof(queue_item_t));
itm->item.context = item.context;
itm->item.data = strdup(item.data);
if (q->first == NULL) {
q->first = itm;
q->last = itm;
itm->prev = NULL;
itm->next = NULL;
q->length = 1;
} else {
itm->prev = q->last;
itm->next = NULL;
q->last->next = itm;
q->last = itm;
q->length += 1;
}
}
bool dequeue(queue_t *q, item_t *item)
{
if (q->length == 0) {
item->context = CONTEXT_INVALID;
item->data = NULL;
return false;
} else {
queue_item_t *itm = q->first;
q->first = q->first->next;
q->length -= 1;
if (q->length == 0) {
q->first = NULL;
q->last = NULL;
}
item->context = itm->item.context;
item->data = itm->item.data;
free(itm);
return true;
}
}
int queue_length(queue_t *q)
{
return q->length;
}
void queue_destroy(queue_t *q)
{
item_t i;
while(dequeue(q, &i)) {
free(i.data);
}
}
void free_item(item_t item)
{
free(item.data);
}
void mutex_lock(rkt_webview_t *wv)
{
#ifdef USE_WIN_THREADS
WaitForSingleObject(wv->mutex, INFINITE);
#endif
#ifdef USE_PTHREAD
#endif
}
void mutex_unlock(rkt_webview_t *wv)
{
#ifdef USE_WIN_THREADS
ReleaseMutex(wv->mutex);
#endif
#ifdef USE_PTHREAD
#endif
}
static void thread_sleep_ms(rkt_webview_t *wv, int ms)
{
#ifdef USE_WIN_THREADS
Sleep(ms);
#endif
#ifdef USE_PTHREAD
#endif
}