#include "rktwebview.h" #include #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(); #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(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(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(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("") }; 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(_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(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(_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(call_nr, std::string(data))); mutex_unlock(wv); } void dispatcher(webview_t w, void *args) { rkt_webview_t *wv = reinterpret_cast(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(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(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(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(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 }