-
This commit is contained in:
514
rktwebview/rktwebview.cpp
Normal file
514
rktwebview/rktwebview.cpp
Normal file
@@ -0,0 +1,514 @@
|
||||
#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
|
||||
}
|
||||
Reference in New Issue
Block a user