550 lines
15 KiB
C++
550 lines
15 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 void *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);
|
|
#ifdef USE_WIN_THREADS
|
|
return 0;
|
|
#endif
|
|
#ifdef USE_PTHREADS
|
|
return NULL;
|
|
#endif
|
|
}
|
|
|
|
#ifdef USE_WIN_THREADS
|
|
static DWORD queueGuardThread(LPVOID args)
|
|
#endif
|
|
#ifdef USE_PTHREADS
|
|
static void *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);
|
|
}
|
|
|
|
#ifdef USE_WIN_THREADS
|
|
return 0;
|
|
#endif
|
|
#ifdef USE_PTHREADS
|
|
return NULL;
|
|
#endif
|
|
}
|
|
|
|
|
|
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 USE_WIN_THREADS
|
|
wv->mutex = CreateMutex(NULL, FALSE, NULL);
|
|
#endif
|
|
#ifdef USE_PTHREADS
|
|
wv->mutex = PTHREAD_MUTEX_INITIALIZER;
|
|
#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_PTHREADS
|
|
wv->webview_thread_id = pthread_create(&wv->webview_thread, NULL, webviewThread, wv);
|
|
wv->queue_guard_thread_id = pthread_create(&wv->queue_guard_thread, NULL, queueGuardThread, wv);
|
|
#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);
|
|
}
|
|
}
|
|
printf("handle_set = %d\n", wv->handle_set);
|
|
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_PTHREADS
|
|
pthread_join(wv->webview_thread, NULL);
|
|
pthread_join(wv->queue_guard_thread, NULL);
|
|
#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 {
|
|
switch(e) {
|
|
case WEBVIEW_ERROR_MISSING_DEPENDENCY: lr = reason_webview_missing_dependency;
|
|
break;
|
|
case WEBVIEW_ERROR_CANCELED: lr = reason_webview_canceled;
|
|
break;
|
|
case WEBVIEW_ERROR_INVALID_STATE: lr = reason_webview_invalid_state;
|
|
break;
|
|
case WEBVIEW_ERROR_INVALID_ARGUMENT: lr = reason_webview_invalid_argument;
|
|
break;
|
|
case WEBVIEW_ERROR_UNSPECIFIED: lr = reason_webview_unspecified;
|
|
break;
|
|
default:
|
|
lr = reason_webview_dispatch_failed;
|
|
}
|
|
r = error;
|
|
}
|
|
|
|
printf("error = %d, reason = %d\n", 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_PTHREADS
|
|
pthread_mutex_lock(&wv->mutex);
|
|
#endif
|
|
}
|
|
|
|
|
|
void mutex_unlock(rkt_webview_t *wv)
|
|
{
|
|
#ifdef USE_WIN_THREADS
|
|
ReleaseMutex(wv->mutex);
|
|
#endif
|
|
#ifdef USE_PTHREADS
|
|
pthread_mutex_unlock(&wv->mutex);
|
|
#endif
|
|
}
|
|
|
|
static void thread_sleep_ms(rkt_webview_t *wv, int ms)
|
|
{
|
|
#ifdef USE_WIN_THREADS
|
|
Sleep(ms);
|
|
#endif
|
|
#ifdef USE_PTHREADS
|
|
usleep(ms * 1000);
|
|
#endif
|
|
}
|