forked from LeenkxTeam/LNXRNT
Webview
This commit is contained in:
Binary file not shown.
@ -46,6 +46,10 @@ extern "C" void websocket_run_groupchat_test(int rate, int duration);
|
||||
|
||||
#endif
|
||||
|
||||
#ifdef WITH_WEBVIEW
|
||||
#include "webview.h"
|
||||
#endif
|
||||
|
||||
//#include "socket_connection_test.cpp"
|
||||
#include <kinc/graphics4/shader.h>
|
||||
#include <kinc/graphics4/vertexbuffer.h>
|
||||
@ -3010,6 +3014,50 @@ namespace {
|
||||
SET_FUNCTION(runt, "windowY", runt_window_y);
|
||||
SET_FUNCTION(runt, "language", runt_language);
|
||||
|
||||
#ifdef WITH_WEBVIEW
|
||||
SET_FUNCTION(runt, "webviewCreate", runt_webview_create);
|
||||
SET_FUNCTION(runt, "webviewLoadHTML", runt_webview_load_html);
|
||||
SET_FUNCTION(runt, "webviewLoadURL", runt_webview_load_url);
|
||||
SET_FUNCTION(runt, "webviewEvalJS", runt_webview_eval_js);
|
||||
SET_FUNCTION(runt, "webviewShow", runt_webview_show);
|
||||
SET_FUNCTION(runt, "webviewHide", runt_webview_hide);
|
||||
SET_FUNCTION(runt, "webviewDestroy", runt_webview_destroy);
|
||||
SET_FUNCTION(runt, "webviewResize", runt_webview_resize);
|
||||
SET_FUNCTION(runt, "webviewMove", runt_webview_move);
|
||||
SET_FUNCTION(runt, "webviewSetBounds", runt_webview_set_bounds);
|
||||
SET_FUNCTION(runt, "webviewGetX", runt_webview_get_x);
|
||||
SET_FUNCTION(runt, "webviewGetY", runt_webview_get_y);
|
||||
SET_FUNCTION(runt, "webviewGetWidth", runt_webview_get_width);
|
||||
SET_FUNCTION(runt, "webviewGetHeight", runt_webview_get_height);
|
||||
SET_FUNCTION(runt, "webviewSetTransparent", runt_webview_set_transparent);
|
||||
SET_FUNCTION(runt, "webviewSetClickThrough", runt_webview_set_click_through);
|
||||
SET_FUNCTION(runt, "webviewSetTitle", runt_webview_set_title);
|
||||
SET_FUNCTION(runt, "webviewSend", runt_webview_send);
|
||||
SET_FUNCTION(runt, "webviewSetOnMessage", runt_webview_set_on_message);
|
||||
SET_FUNCTION(runt, "webviewSetOnLoad", runt_webview_set_on_load);
|
||||
SET_FUNCTION(runt, "webviewSetOnError", runt_webview_set_on_error);
|
||||
SET_FUNCTION(runt, "webviewSetOnClose", runt_webview_set_on_close);
|
||||
SET_FUNCTION(runt, "webviewCount", runt_webview_count);
|
||||
SET_FUNCTION(runt, "webviewIsValid", runt_webview_is_valid);
|
||||
SET_FUNCTION(runt, "webviewSetActiveDOM", runt_webview_set_active_dom);
|
||||
SET_FUNCTION(runt, "webviewGetActiveDOM", runt_webview_get_active_dom);
|
||||
SET_FUNCTION(runt, "webviewEvalJSAsync", runt_webview_eval_js_async);
|
||||
SET_FUNCTION(runt, "webviewGoBack", runt_webview_go_back);
|
||||
SET_FUNCTION(runt, "webviewGoForward", runt_webview_go_forward);
|
||||
SET_FUNCTION(runt, "webviewReload", runt_webview_reload);
|
||||
SET_FUNCTION(runt, "webviewCanGoBack", runt_webview_can_go_back);
|
||||
SET_FUNCTION(runt, "webviewCanGoForward", runt_webview_can_go_forward);
|
||||
SET_FUNCTION(runt, "webviewGetURL", runt_webview_get_url);
|
||||
SET_FUNCTION(runt, "webviewGetPageTitle", runt_webview_get_page_title);
|
||||
SET_FUNCTION(runt, "webviewMinimize", runt_webview_minimize);
|
||||
SET_FUNCTION(runt, "webviewMaximize", runt_webview_maximize);
|
||||
SET_FUNCTION(runt, "webviewRestore", runt_webview_restore);
|
||||
SET_FUNCTION(runt, "webviewSetFullscreen", runt_webview_set_fullscreen);
|
||||
SET_FUNCTION(runt, "webviewIsFullscreen", runt_webview_is_fullscreen);
|
||||
SET_FUNCTION(runt, "webviewEnableDevTools", runt_webview_enable_devtools);
|
||||
SET_FUNCTION(runt, "webviewSetContextMenu", runt_webview_set_context_menu);
|
||||
#endif
|
||||
|
||||
Local<ObjectTemplate> global = ObjectTemplate::New(isolate);
|
||||
global->Set(String::NewFromUtf8(isolate, "RunT").ToLocalChecked(), runt);
|
||||
global->Set(String::NewFromUtf8(isolate, "runt").ToLocalChecked(), runt); // lowercase alias for JavaScript compatibility
|
||||
@ -4072,6 +4120,7 @@ int kickstart(int argc, char **argv) {
|
||||
|
||||
// TODO: find and fix
|
||||
while (update_func.IsEmpty()) {
|
||||
kinc_internal_handle_messages();
|
||||
handle_worker_messages(isolate, global_context);
|
||||
EngineManager::AsyncEngine::instance().process_events();
|
||||
MicrotasksScope::PerformCheckpoint(isolate);
|
||||
|
||||
975
Sources/webview.cpp
Normal file
975
Sources/webview.cpp
Normal file
@ -0,0 +1,975 @@
|
||||
#ifdef WITH_WEBVIEW
|
||||
|
||||
#include "webview.h"
|
||||
#include <kinc/log.h>
|
||||
#include <kinc/window.h>
|
||||
|
||||
WebViewManager& WebViewManager::instance() {
|
||||
static WebViewManager inst;
|
||||
return inst;
|
||||
}
|
||||
|
||||
WebViewManager::~WebViewManager() {
|
||||
for (auto& [id, wv] : webviews) {
|
||||
if (wv->platformHandle) {
|
||||
platformDestroy(wv.get());
|
||||
}
|
||||
}
|
||||
webviews.clear();
|
||||
}
|
||||
|
||||
int WebViewManager::create(v8::Isolate* isolate, v8::Local<v8::Context> context, int parentWindow, int x, int y, int width, int height, bool transparent, const std::string& title) {
|
||||
auto wv = std::make_unique<WebViewInstance>();
|
||||
wv->id = nextId++;
|
||||
wv->parentWindow = parentWindow;
|
||||
wv->x = x;
|
||||
wv->y = y;
|
||||
wv->width = width;
|
||||
wv->height = height;
|
||||
wv->autoResize = (parentWindow >= 0 && (width == -1 || height == -1));
|
||||
wv->transparent = transparent;
|
||||
wv->clickThrough = false;
|
||||
wv->visible = false;
|
||||
wv->title = title;
|
||||
wv->isolate = isolate;
|
||||
wv->context.Reset(isolate, context);
|
||||
|
||||
if (wv->autoResize && parentWindow >= 0) {
|
||||
if (wv->width == -1) wv->width = kinc_window_width(parentWindow);
|
||||
if (wv->height == -1) wv->height = kinc_window_height(parentWindow);
|
||||
}
|
||||
|
||||
int id = wv->id;
|
||||
int result = platformCreate(wv.get());
|
||||
if (result < 0) {
|
||||
kinc_log(KINC_LOG_LEVEL_ERROR, "[WebView] Failed to create WebView (platform returned %d)", result);
|
||||
return -1;
|
||||
}
|
||||
|
||||
webviews[id] = std::move(wv);
|
||||
kinc_log(KINC_LOG_LEVEL_INFO, "[WebView] Created WebView id=%d parent=%d %dx%d", id, parentWindow, width, height);
|
||||
|
||||
if (activeDomId < 0) {
|
||||
setActiveDOM(id, isolate);
|
||||
}
|
||||
|
||||
return id;
|
||||
}
|
||||
|
||||
void WebViewManager::destroy(int id) {
|
||||
auto it = webviews.find(id);
|
||||
if (it == webviews.end()) return;
|
||||
|
||||
if (it->second->platformHandle) {
|
||||
platformDestroy(it->second.get());
|
||||
}
|
||||
webviews.erase(it);
|
||||
kinc_log(KINC_LOG_LEVEL_INFO, "[WebView] Destroyed WebView id=%d", id);
|
||||
}
|
||||
|
||||
bool WebViewManager::isValid(int id) {
|
||||
return webviews.find(id) != webviews.end();
|
||||
}
|
||||
|
||||
int WebViewManager::count() {
|
||||
return (int)webviews.size();
|
||||
}
|
||||
|
||||
void WebViewManager::loadHTML(int id, const std::string& html) {
|
||||
auto it = webviews.find(id);
|
||||
if (it == webviews.end()) return;
|
||||
platformLoadHTML(it->second.get(), html);
|
||||
}
|
||||
|
||||
void WebViewManager::loadURL(int id, const std::string& url) {
|
||||
auto it = webviews.find(id);
|
||||
if (it == webviews.end()) return;
|
||||
platformLoadURL(it->second.get(), url);
|
||||
}
|
||||
|
||||
void WebViewManager::evalJS(int id, const std::string& js) {
|
||||
auto it = webviews.find(id);
|
||||
if (it == webviews.end()) return;
|
||||
platformEvalJS(it->second.get(), js);
|
||||
}
|
||||
|
||||
void WebViewManager::evalJSAsync(int id, v8::Isolate* isolate, v8::Local<v8::Function> callback, const std::string& js) {
|
||||
auto it = webviews.find(id);
|
||||
if (it == webviews.end()) return;
|
||||
it->second->onEvalResult.Reset(isolate, callback);
|
||||
platformEvalJSAsync(it->second.get(), js);
|
||||
}
|
||||
|
||||
void WebViewManager::setActiveDOM(int id, v8::Isolate* isolate) {
|
||||
if (id >= 0 && webviews.find(id) == webviews.end()) return;
|
||||
activeDomId = id;
|
||||
|
||||
if (id < 0) return;
|
||||
|
||||
auto it = webviews.find(id);
|
||||
if (it == webviews.end()) return;
|
||||
auto* wv = it->second.get();
|
||||
|
||||
v8::HandleScope handleScope(isolate);
|
||||
v8::Local<v8::Context> ctx = wv->context.Get(isolate);
|
||||
v8::Context::Scope ctxScope(ctx);
|
||||
|
||||
// This is the proxy interception to support the web apis. TODO replace
|
||||
std::string proxyJs = std::string("(function(){var _id=") + std::to_string(id) + ";" R"JS(
|
||||
var _r=0;
|
||||
function _eval(j){runt.webviewEvalJS(_id,j);}
|
||||
function _evalAsync(j,cb){runt.webviewEvalJSAsync(_id,j,cb);}
|
||||
function _arg(x){
|
||||
if(x!==null&&(typeof x==='object'||typeof x==='function')&&x.__jsExpr)return x.__jsExpr;
|
||||
return JSON.stringify(x);
|
||||
}
|
||||
function _proxy(expr){
|
||||
var fn=function(){};
|
||||
var _loaded=false,_data=null,_fired=false;
|
||||
function _fire(){
|
||||
if(_fired)return;_fired=true;
|
||||
_evalAsync(expr,function(r){_loaded=true;try{_data=JSON.parse(r);}catch(e){_data=r;}});
|
||||
}
|
||||
return new Proxy(fn,{
|
||||
get:function(t,p){
|
||||
if(p==='__jsExpr')return expr;
|
||||
if(p==='loaded'){_fire();return _loaded;}
|
||||
if(p==='data'){_fire();return _data;}
|
||||
if(typeof p==='symbol'||p==='then')return undefined;
|
||||
return _proxy(expr+"."+p);
|
||||
},
|
||||
apply:function(t,thisArg,args){
|
||||
var r='__c'+(++_r);
|
||||
_eval("window['"+r+"']="+expr+"("+args.map(_arg).join(',')+")");
|
||||
return _proxy("window['"+r+"']");
|
||||
},
|
||||
set:function(t,p,v){_eval(expr+"."+p+"="+_arg(v));return true;}
|
||||
});
|
||||
}
|
||||
return [_proxy("window"),_proxy("window.document")];
|
||||
})();
|
||||
)JS";
|
||||
|
||||
v8::Local<v8::String> src = v8::String::NewFromUtf8(isolate, proxyJs.c_str(), v8::NewStringType::kNormal, (int)proxyJs.length()).ToLocalChecked();
|
||||
v8::TryCatch tryCatch(isolate);
|
||||
v8::MaybeLocal<v8::Script> compileResult = v8::Script::Compile(ctx, src);
|
||||
if (compileResult.IsEmpty()) {
|
||||
v8::Local<v8::Message> msg = tryCatch.Message();
|
||||
if (!msg.IsEmpty()) {
|
||||
v8::String::Utf8Value errorStr(isolate, msg->Get());
|
||||
kinc_log(KINC_LOG_LEVEL_ERROR, "WebView proxy compile error: %s", *errorStr);
|
||||
}
|
||||
return;
|
||||
}
|
||||
v8::Local<v8::Script> script = compileResult.ToLocalChecked();
|
||||
v8::MaybeLocal<v8::Value> result = script->Run(ctx);
|
||||
if (tryCatch.HasCaught()) {
|
||||
v8::Local<v8::Message> msg = tryCatch.Message();
|
||||
if (!msg.IsEmpty()) {
|
||||
v8::String::Utf8Value errorStr(isolate, msg->Get());
|
||||
kinc_log(KINC_LOG_LEVEL_ERROR, "Webview proxy run error: %s", *errorStr);
|
||||
} else {
|
||||
kinc_log(KINC_LOG_LEVEL_ERROR, "Webview proxy: no message");
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (!result.IsEmpty()) {
|
||||
v8::Local<v8::Value> val = result.ToLocalChecked();
|
||||
if (val->IsArray()) {
|
||||
v8::Local<v8::Array> arr = val.As<v8::Array>();
|
||||
v8::Local<v8::Object> globalObj = ctx->Global();
|
||||
globalObj->Set(ctx, v8::String::NewFromUtf8(isolate, "window").ToLocalChecked(), arr->Get(ctx, 0).ToLocalChecked()).Check();
|
||||
globalObj->Set(ctx, v8::String::NewFromUtf8(isolate, "document").ToLocalChecked(), arr->Get(ctx, 1).ToLocalChecked()).Check();
|
||||
kinc_log(KINC_LOG_LEVEL_INFO, "DOM proxy injected successfully for webview id=%d", id);
|
||||
} else {
|
||||
kinc_log(KINC_LOG_LEVEL_ERROR, "Proxy returned non array type");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int WebViewManager::getActiveDOM() {
|
||||
return activeDomId;
|
||||
}
|
||||
|
||||
void WebViewManager::show(int id) {
|
||||
auto it = webviews.find(id);
|
||||
if (it == webviews.end()) return;
|
||||
it->second->visible = true;
|
||||
platformShow(it->second.get());
|
||||
}
|
||||
|
||||
void WebViewManager::hide(int id) {
|
||||
auto it = webviews.find(id);
|
||||
if (it == webviews.end()) return;
|
||||
it->second->visible = false;
|
||||
platformHide(it->second.get());
|
||||
}
|
||||
|
||||
void WebViewManager::resize(int id, int width, int height) {
|
||||
auto it = webviews.find(id);
|
||||
if (it == webviews.end()) return;
|
||||
it->second->width = width;
|
||||
it->second->height = height;
|
||||
it->second->autoResize = false;
|
||||
platformResize(it->second.get(), width, height);
|
||||
}
|
||||
|
||||
void WebViewManager::move(int id, int x, int y) {
|
||||
auto it = webviews.find(id);
|
||||
if (it == webviews.end()) return;
|
||||
it->second->x = x;
|
||||
it->second->y = y;
|
||||
platformMove(it->second.get(), x, y);
|
||||
}
|
||||
|
||||
void WebViewManager::setBounds(int id, int x, int y, int width, int height) {
|
||||
auto it = webviews.find(id);
|
||||
if (it == webviews.end()) return;
|
||||
it->second->x = x;
|
||||
it->second->y = y;
|
||||
it->second->width = width;
|
||||
it->second->height = height;
|
||||
it->second->autoResize = false;
|
||||
platformResize(it->second.get(), width, height);
|
||||
platformMove(it->second.get(), x, y);
|
||||
}
|
||||
|
||||
int WebViewManager::getX(int id) {
|
||||
auto it = webviews.find(id);
|
||||
if (it == webviews.end()) return 0;
|
||||
return it->second->x;
|
||||
}
|
||||
|
||||
int WebViewManager::getY(int id) {
|
||||
auto it = webviews.find(id);
|
||||
if (it == webviews.end()) return 0;
|
||||
return it->second->y;
|
||||
}
|
||||
|
||||
int WebViewManager::getWidth(int id) {
|
||||
auto it = webviews.find(id);
|
||||
if (it == webviews.end()) return 0;
|
||||
return it->second->width;
|
||||
}
|
||||
|
||||
int WebViewManager::getHeight(int id) {
|
||||
auto it = webviews.find(id);
|
||||
if (it == webviews.end()) return 0;
|
||||
return it->second->height;
|
||||
}
|
||||
|
||||
void WebViewManager::setTransparent(int id, bool transparent) {
|
||||
auto it = webviews.find(id);
|
||||
if (it == webviews.end()) return;
|
||||
it->second->transparent = transparent;
|
||||
platformSetTransparent(it->second.get(), transparent);
|
||||
}
|
||||
|
||||
void WebViewManager::setClickThrough(int id, bool enabled) {
|
||||
auto it = webviews.find(id);
|
||||
if (it == webviews.end()) return;
|
||||
it->second->clickThrough = enabled;
|
||||
platformSetClickThrough(it->second.get(), enabled);
|
||||
}
|
||||
|
||||
void WebViewManager::setTitle(int id, const std::string& title) {
|
||||
auto it = webviews.find(id);
|
||||
if (it == webviews.end()) return;
|
||||
it->second->title = title;
|
||||
platformSetTitle(it->second.get(), title);
|
||||
}
|
||||
|
||||
void WebViewManager::send(int id, const std::string& message) {
|
||||
auto it = webviews.find(id);
|
||||
if (it == webviews.end()) return;
|
||||
platformSend(it->second.get(), message);
|
||||
}
|
||||
|
||||
void WebViewManager::goBack(int id) {
|
||||
auto it = webviews.find(id);
|
||||
if (it == webviews.end()) return;
|
||||
platformGoBack(it->second.get());
|
||||
}
|
||||
|
||||
void WebViewManager::goForward(int id) {
|
||||
auto it = webviews.find(id);
|
||||
if (it == webviews.end()) return;
|
||||
platformGoForward(it->second.get());
|
||||
}
|
||||
|
||||
void WebViewManager::reload(int id) {
|
||||
auto it = webviews.find(id);
|
||||
if (it == webviews.end()) return;
|
||||
platformReload(it->second.get());
|
||||
}
|
||||
|
||||
bool WebViewManager::canGoBack(int id) {
|
||||
auto it = webviews.find(id);
|
||||
if (it == webviews.end()) return false;
|
||||
return platformCanGoBack(it->second.get());
|
||||
}
|
||||
|
||||
bool WebViewManager::canGoForward(int id) {
|
||||
auto it = webviews.find(id);
|
||||
if (it == webviews.end()) return false;
|
||||
return platformCanGoForward(it->second.get());
|
||||
}
|
||||
|
||||
std::string WebViewManager::getURL(int id) {
|
||||
auto it = webviews.find(id);
|
||||
if (it == webviews.end()) return "";
|
||||
return platformGetURL(it->second.get());
|
||||
}
|
||||
|
||||
std::string WebViewManager::getPageTitle(int id) {
|
||||
auto it = webviews.find(id);
|
||||
if (it == webviews.end()) return "";
|
||||
return platformGetTitle(it->second.get());
|
||||
}
|
||||
|
||||
void WebViewManager::minimize(int id) {
|
||||
auto it = webviews.find(id);
|
||||
if (it == webviews.end()) return;
|
||||
platformMinimize(it->second.get());
|
||||
}
|
||||
|
||||
void WebViewManager::maximize(int id) {
|
||||
auto it = webviews.find(id);
|
||||
if (it == webviews.end()) return;
|
||||
platformMaximize(it->second.get());
|
||||
}
|
||||
|
||||
void WebViewManager::restore(int id) {
|
||||
auto it = webviews.find(id);
|
||||
if (it == webviews.end()) return;
|
||||
platformRestore(it->second.get());
|
||||
}
|
||||
|
||||
void WebViewManager::setFullscreen(int id, bool fullscreen) {
|
||||
auto it = webviews.find(id);
|
||||
if (it == webviews.end()) return;
|
||||
platformSetFullscreen(it->second.get(), fullscreen);
|
||||
}
|
||||
|
||||
bool WebViewManager::isFullscreen(int id) {
|
||||
auto it = webviews.find(id);
|
||||
if (it == webviews.end()) return false;
|
||||
return platformIsFullscreen(it->second.get());
|
||||
}
|
||||
|
||||
void WebViewManager::enableDevTools(int id, bool enabled) {
|
||||
auto it = webviews.find(id);
|
||||
if (it == webviews.end()) return;
|
||||
platformEnableDevTools(it->second.get(), enabled);
|
||||
}
|
||||
|
||||
void WebViewManager::setContextMenuEnabled(int id, bool enabled) {
|
||||
auto it = webviews.find(id);
|
||||
if (it == webviews.end()) return;
|
||||
platformSetContextMenuEnabled(it->second.get(), enabled);
|
||||
}
|
||||
|
||||
void WebViewManager::setOnMessage(int id, v8::Isolate* isolate, v8::Local<v8::Function> cb) {
|
||||
auto it = webviews.find(id);
|
||||
if (it == webviews.end()) return;
|
||||
it->second->onMessage.Reset(isolate, cb);
|
||||
}
|
||||
|
||||
void WebViewManager::setOnLoad(int id, v8::Isolate* isolate, v8::Local<v8::Function> cb) {
|
||||
auto it = webviews.find(id);
|
||||
if (it == webviews.end()) return;
|
||||
it->second->onLoad.Reset(isolate, cb);
|
||||
}
|
||||
|
||||
void WebViewManager::setOnError(int id, v8::Isolate* isolate, v8::Local<v8::Function> cb) {
|
||||
auto it = webviews.find(id);
|
||||
if (it == webviews.end()) return;
|
||||
it->second->onError.Reset(isolate, cb);
|
||||
}
|
||||
|
||||
void WebViewManager::setOnClose(int id, v8::Isolate* isolate, v8::Local<v8::Function> cb) {
|
||||
auto it = webviews.find(id);
|
||||
if (it == webviews.end()) return;
|
||||
it->second->onClose.Reset(isolate, cb);
|
||||
}
|
||||
|
||||
void WebViewManager::dispatchMessage(int id, const std::string& message) {
|
||||
auto it = webviews.find(id);
|
||||
if (it == webviews.end()) return;
|
||||
auto* wv = it->second.get();
|
||||
if (wv->onMessage.IsEmpty()) return;
|
||||
|
||||
v8::Isolate* isolate = wv->isolate;
|
||||
v8::Locker locker(isolate);
|
||||
v8::Isolate::Scope isolateScope(isolate);
|
||||
v8::HandleScope handleScope(isolate);
|
||||
v8::Local<v8::Context> ctx = wv->context.Get(isolate);
|
||||
v8::Context::Scope ctxScope(ctx);
|
||||
|
||||
v8::Local<v8::Function> cb = wv->onMessage.Get(isolate);
|
||||
v8::Local<v8::Value> arg = v8::String::NewFromUtf8(isolate, message.c_str(), v8::NewStringType::kNormal, (int)message.length()).ToLocalChecked();
|
||||
cb->Call(ctx, v8::Undefined(isolate), 1, &arg).IsEmpty();
|
||||
}
|
||||
|
||||
void WebViewManager::dispatchLoad(int id) {
|
||||
auto it = webviews.find(id);
|
||||
if (it == webviews.end()) return;
|
||||
auto* wv = it->second.get();
|
||||
if (wv->onLoad.IsEmpty()) return;
|
||||
|
||||
v8::Isolate* isolate = wv->isolate;
|
||||
v8::Locker locker(isolate);
|
||||
v8::Isolate::Scope isolateScope(isolate);
|
||||
v8::HandleScope handleScope(isolate);
|
||||
v8::Local<v8::Context> ctx = wv->context.Get(isolate);
|
||||
v8::Context::Scope ctxScope(ctx);
|
||||
|
||||
v8::Local<v8::Function> cb = wv->onLoad.Get(isolate);
|
||||
v8::Local<v8::Value> arg = v8::Undefined(isolate);
|
||||
cb->Call(ctx, v8::Undefined(isolate), 1, &arg).IsEmpty();
|
||||
}
|
||||
|
||||
void WebViewManager::dispatchError(int id, const std::string& error) {
|
||||
auto it = webviews.find(id);
|
||||
if (it == webviews.end()) return;
|
||||
auto* wv = it->second.get();
|
||||
if (wv->onError.IsEmpty()) return;
|
||||
|
||||
v8::Isolate* isolate = wv->isolate;
|
||||
v8::Locker locker(isolate);
|
||||
v8::Isolate::Scope isolateScope(isolate);
|
||||
v8::HandleScope handleScope(isolate);
|
||||
v8::Local<v8::Context> ctx = wv->context.Get(isolate);
|
||||
v8::Context::Scope ctxScope(ctx);
|
||||
|
||||
v8::Local<v8::Function> cb = wv->onError.Get(isolate);
|
||||
v8::Local<v8::Value> arg = v8::String::NewFromUtf8(isolate, error.c_str(), v8::NewStringType::kNormal, (int)error.length()).ToLocalChecked();
|
||||
cb->Call(ctx, v8::Undefined(isolate), 1, &arg).IsEmpty();
|
||||
}
|
||||
|
||||
void WebViewManager::dispatchClose(int id) {
|
||||
auto it = webviews.find(id);
|
||||
if (it == webviews.end()) return;
|
||||
auto* wv = it->second.get();
|
||||
if (wv->onClose.IsEmpty()) return;
|
||||
|
||||
v8::Isolate* isolate = wv->isolate;
|
||||
v8::Locker locker(isolate);
|
||||
v8::Isolate::Scope isolateScope(isolate);
|
||||
v8::HandleScope handleScope(isolate);
|
||||
v8::Local<v8::Context> ctx = wv->context.Get(isolate);
|
||||
v8::Context::Scope ctxScope(ctx);
|
||||
|
||||
v8::Local<v8::Function> cb = wv->onClose.Get(isolate);
|
||||
v8::Local<v8::Value> arg = v8::Undefined(isolate);
|
||||
cb->Call(ctx, v8::Undefined(isolate), 1, &arg).IsEmpty();
|
||||
}
|
||||
|
||||
void WebViewManager::dispatchEvalResult(int id, const std::string& result) {
|
||||
auto it = webviews.find(id);
|
||||
if (it == webviews.end()) return;
|
||||
auto* wv = it->second.get();
|
||||
if (wv->onEvalResult.IsEmpty()) return;
|
||||
|
||||
v8::Isolate* isolate = wv->isolate;
|
||||
v8::Locker locker(isolate);
|
||||
v8::Isolate::Scope isolateScope(isolate);
|
||||
v8::HandleScope handleScope(isolate);
|
||||
v8::Local<v8::Context> ctx = wv->context.Get(isolate);
|
||||
v8::Context::Scope ctxScope(ctx);
|
||||
|
||||
v8::Local<v8::Function> cb = wv->onEvalResult.Get(isolate);
|
||||
v8::Local<v8::Value> arg = v8::String::NewFromUtf8(isolate, result.c_str(), v8::NewStringType::kNormal, (int)result.length()).ToLocalChecked();
|
||||
cb->Call(ctx, v8::Undefined(isolate), 1, &arg).IsEmpty();
|
||||
}
|
||||
|
||||
void WebViewManager::tick() {
|
||||
platformTick();
|
||||
}
|
||||
|
||||
void WebViewManager::onWindowResize(int windowId, int width, int height) {
|
||||
for (auto& [id, wv] : webviews) {
|
||||
if (wv->parentWindow == windowId && wv->autoResize) {
|
||||
wv->width = width;
|
||||
wv->height = height;
|
||||
platformResize(wv.get(), width, height);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static int getIntOption(v8::Isolate* isolate, v8::Local<v8::Context> ctx,
|
||||
v8::Local<v8::Object> opts, const char* key, int defaultVal) {
|
||||
v8::Local<v8::Value> val;
|
||||
v8::Local<v8::String> keyStr = v8::String::NewFromUtf8(isolate, key).ToLocalChecked();
|
||||
if (opts->Get(ctx, keyStr).ToLocal(&val) && val->IsNumber()) {
|
||||
return val->Int32Value(ctx).FromJust();
|
||||
}
|
||||
return defaultVal;
|
||||
}
|
||||
|
||||
static bool getBoolOption(v8::Isolate* isolate, v8::Local<v8::Context> ctx,
|
||||
v8::Local<v8::Object> opts, const char* key, bool defaultVal) {
|
||||
v8::Local<v8::Value> val;
|
||||
v8::Local<v8::String> keyStr = v8::String::NewFromUtf8(isolate, key).ToLocalChecked();
|
||||
if (opts->Get(ctx, keyStr).ToLocal(&val) && val->IsBoolean()) {
|
||||
return val->BooleanValue(isolate);
|
||||
}
|
||||
return defaultVal;
|
||||
}
|
||||
|
||||
static std::string getStringOption(v8::Isolate* isolate, v8::Local<v8::Context> ctx,
|
||||
v8::Local<v8::Object> opts, const char* key, const char* defaultVal) {
|
||||
v8::Local<v8::Value> val;
|
||||
v8::Local<v8::String> keyStr = v8::String::NewFromUtf8(isolate, key).ToLocalChecked();
|
||||
if (opts->Get(ctx, keyStr).ToLocal(&val) && val->IsString()) {
|
||||
v8::String::Utf8Value str(isolate, val);
|
||||
return std::string(*str);
|
||||
}
|
||||
return std::string(defaultVal);
|
||||
}
|
||||
|
||||
void runt_webview_create(const v8::FunctionCallbackInfo<v8::Value>& args) {
|
||||
v8::Isolate* isolate = args.GetIsolate();
|
||||
v8::HandleScope scope(isolate);
|
||||
v8::Local<v8::Context> ctx = isolate->GetCurrentContext();
|
||||
|
||||
if (args.Length() < 1 || !args[0]->IsObject()) {
|
||||
isolate->ThrowException(v8::Exception::Error(
|
||||
v8::String::NewFromUtf8(isolate, "Options object required").ToLocalChecked()));
|
||||
return;
|
||||
}
|
||||
|
||||
v8::Local<v8::Object> opts = args[0]->ToObject(ctx).ToLocalChecked();
|
||||
|
||||
int parentWindow = getIntOption(isolate, ctx, opts, "parentWindow", -1);
|
||||
int x = getIntOption(isolate, ctx, opts, "x", 0);
|
||||
int y = getIntOption(isolate, ctx, opts, "y", 0);
|
||||
int width = getIntOption(isolate, ctx, opts, "width", 800);
|
||||
int height = getIntOption(isolate, ctx, opts, "height", 600);
|
||||
bool transparent = getBoolOption(isolate, ctx, opts, "transparent", true);
|
||||
std::string title = getStringOption(isolate, ctx, opts, "title", "WebView");
|
||||
|
||||
int id = WebViewManager::instance().create(isolate, ctx, parentWindow, x, y,
|
||||
width, height, transparent, title);
|
||||
args.GetReturnValue().Set(v8::Number::New(isolate, id));
|
||||
|
||||
if (id < 0) return;
|
||||
|
||||
v8::Local<v8::Value> val;
|
||||
v8::Local<v8::String> htmlKey = v8::String::NewFromUtf8(isolate, "html").ToLocalChecked();
|
||||
if (opts->Get(ctx, htmlKey).ToLocal(&val) && val->IsString()) {
|
||||
v8::String::Utf8Value str(isolate, val);
|
||||
WebViewManager::instance().loadHTML(id, *str);
|
||||
}
|
||||
|
||||
v8::Local<v8::String> urlKey = v8::String::NewFromUtf8(isolate, "url").ToLocalChecked();
|
||||
if (opts->Get(ctx, urlKey).ToLocal(&val) && val->IsString()) {
|
||||
v8::String::Utf8Value str(isolate, val);
|
||||
WebViewManager::instance().loadURL(id, *str);
|
||||
}
|
||||
}
|
||||
|
||||
void runt_webview_load_html(const v8::FunctionCallbackInfo<v8::Value>& args) {
|
||||
v8::Isolate* isolate = args.GetIsolate();
|
||||
v8::HandleScope scope(isolate);
|
||||
|
||||
if (args.Length() < 2 || !args[0]->IsNumber() || !args[1]->IsString()) {
|
||||
isolate->ThrowException(v8::Exception::Error(
|
||||
v8::String::NewFromUtf8(isolate, "ID and HTML string required").ToLocalChecked()));
|
||||
return;
|
||||
}
|
||||
|
||||
int id = args[0]->Int32Value(isolate->GetCurrentContext()).FromJust();
|
||||
v8::String::Utf8Value str(isolate, args[1]);
|
||||
WebViewManager::instance().loadHTML(id, *str);
|
||||
}
|
||||
|
||||
void runt_webview_load_url(const v8::FunctionCallbackInfo<v8::Value>& args) {
|
||||
v8::Isolate* isolate = args.GetIsolate();
|
||||
v8::HandleScope scope(isolate);
|
||||
|
||||
if (args.Length() < 2 || !args[0]->IsNumber() || !args[1]->IsString()) {
|
||||
isolate->ThrowException(v8::Exception::Error(
|
||||
v8::String::NewFromUtf8(isolate, "ID and URL string required").ToLocalChecked()));
|
||||
return;
|
||||
}
|
||||
|
||||
int id = args[0]->Int32Value(isolate->GetCurrentContext()).FromJust();
|
||||
v8::String::Utf8Value str(isolate, args[1]);
|
||||
WebViewManager::instance().loadURL(id, *str);
|
||||
}
|
||||
|
||||
void runt_webview_eval_js(const v8::FunctionCallbackInfo<v8::Value>& args) {
|
||||
v8::Isolate* isolate = args.GetIsolate();
|
||||
v8::HandleScope scope(isolate);
|
||||
|
||||
if (args.Length() < 2 || !args[0]->IsNumber() || !args[1]->IsString()) {
|
||||
isolate->ThrowException(v8::Exception::Error(
|
||||
v8::String::NewFromUtf8(isolate, "ID and JS string required").ToLocalChecked()));
|
||||
return;
|
||||
}
|
||||
|
||||
int id = args[0]->Int32Value(isolate->GetCurrentContext()).FromJust();
|
||||
v8::String::Utf8Value str(isolate, args[1]);
|
||||
WebViewManager::instance().evalJS(id, *str);
|
||||
}
|
||||
|
||||
void runt_webview_show(const v8::FunctionCallbackInfo<v8::Value>& args) {
|
||||
v8::Isolate* isolate = args.GetIsolate();
|
||||
v8::HandleScope scope(isolate);
|
||||
|
||||
if (args.Length() < 1 || !args[0]->IsNumber()) return;
|
||||
int id = args[0]->Int32Value(isolate->GetCurrentContext()).FromJust();
|
||||
WebViewManager::instance().show(id);
|
||||
}
|
||||
|
||||
void runt_webview_hide(const v8::FunctionCallbackInfo<v8::Value>& args) {
|
||||
v8::Isolate* isolate = args.GetIsolate();
|
||||
v8::HandleScope scope(isolate);
|
||||
|
||||
if (args.Length() < 1 || !args[0]->IsNumber()) return;
|
||||
int id = args[0]->Int32Value(isolate->GetCurrentContext()).FromJust();
|
||||
WebViewManager::instance().hide(id);
|
||||
}
|
||||
|
||||
void runt_webview_destroy(const v8::FunctionCallbackInfo<v8::Value>& args) {
|
||||
v8::Isolate* isolate = args.GetIsolate();
|
||||
v8::HandleScope scope(isolate);
|
||||
|
||||
if (args.Length() < 1 || !args[0]->IsNumber()) return;
|
||||
int id = args[0]->Int32Value(isolate->GetCurrentContext()).FromJust();
|
||||
WebViewManager::instance().destroy(id);
|
||||
}
|
||||
|
||||
void runt_webview_resize(const v8::FunctionCallbackInfo<v8::Value>& args) {
|
||||
v8::Isolate* isolate = args.GetIsolate();
|
||||
v8::HandleScope scope(isolate);
|
||||
|
||||
if (args.Length() < 3 || !args[0]->IsNumber() || !args[1]->IsNumber() || !args[2]->IsNumber()) return;
|
||||
int id = args[0]->Int32Value(isolate->GetCurrentContext()).FromJust();
|
||||
int w = args[1]->Int32Value(isolate->GetCurrentContext()).FromJust();
|
||||
int h = args[2]->Int32Value(isolate->GetCurrentContext()).FromJust();
|
||||
WebViewManager::instance().resize(id, w, h);
|
||||
}
|
||||
|
||||
void runt_webview_move(const v8::FunctionCallbackInfo<v8::Value>& args) {
|
||||
v8::Isolate* isolate = args.GetIsolate();
|
||||
v8::HandleScope scope(isolate);
|
||||
|
||||
if (args.Length() < 3 || !args[0]->IsNumber() || !args[1]->IsNumber() || !args[2]->IsNumber()) return;
|
||||
int id = args[0]->Int32Value(isolate->GetCurrentContext()).FromJust();
|
||||
int x = args[1]->Int32Value(isolate->GetCurrentContext()).FromJust();
|
||||
int y = args[2]->Int32Value(isolate->GetCurrentContext()).FromJust();
|
||||
WebViewManager::instance().move(id, x, y);
|
||||
}
|
||||
|
||||
void runt_webview_set_bounds(const v8::FunctionCallbackInfo<v8::Value>& args) {
|
||||
v8::Isolate* isolate = args.GetIsolate();
|
||||
v8::HandleScope scope(isolate);
|
||||
|
||||
if (args.Length() < 5) return;
|
||||
int id = args[0]->Int32Value(isolate->GetCurrentContext()).FromJust();
|
||||
int x = args[1]->Int32Value(isolate->GetCurrentContext()).FromJust();
|
||||
int y = args[2]->Int32Value(isolate->GetCurrentContext()).FromJust();
|
||||
int w = args[3]->Int32Value(isolate->GetCurrentContext()).FromJust();
|
||||
int h = args[4]->Int32Value(isolate->GetCurrentContext()).FromJust();
|
||||
WebViewManager::instance().setBounds(id, x, y, w, h);
|
||||
}
|
||||
|
||||
void runt_webview_get_x(const v8::FunctionCallbackInfo<v8::Value>& args) {
|
||||
v8::Isolate* isolate = args.GetIsolate();
|
||||
v8::HandleScope scope(isolate);
|
||||
if (args.Length() < 1 || !args[0]->IsNumber()) return;
|
||||
int id = args[0]->Int32Value(isolate->GetCurrentContext()).FromJust();
|
||||
args.GetReturnValue().Set(v8::Int32::New(isolate, WebViewManager::instance().getX(id)));
|
||||
}
|
||||
|
||||
void runt_webview_get_y(const v8::FunctionCallbackInfo<v8::Value>& args) {
|
||||
v8::Isolate* isolate = args.GetIsolate();
|
||||
v8::HandleScope scope(isolate);
|
||||
if (args.Length() < 1 || !args[0]->IsNumber()) return;
|
||||
int id = args[0]->Int32Value(isolate->GetCurrentContext()).FromJust();
|
||||
args.GetReturnValue().Set(v8::Int32::New(isolate, WebViewManager::instance().getY(id)));
|
||||
}
|
||||
|
||||
void runt_webview_get_width(const v8::FunctionCallbackInfo<v8::Value>& args) {
|
||||
v8::Isolate* isolate = args.GetIsolate();
|
||||
v8::HandleScope scope(isolate);
|
||||
if (args.Length() < 1 || !args[0]->IsNumber()) return;
|
||||
int id = args[0]->Int32Value(isolate->GetCurrentContext()).FromJust();
|
||||
args.GetReturnValue().Set(v8::Int32::New(isolate, WebViewManager::instance().getWidth(id)));
|
||||
}
|
||||
|
||||
void runt_webview_get_height(const v8::FunctionCallbackInfo<v8::Value>& args) {
|
||||
v8::Isolate* isolate = args.GetIsolate();
|
||||
v8::HandleScope scope(isolate);
|
||||
if (args.Length() < 1 || !args[0]->IsNumber()) return;
|
||||
int id = args[0]->Int32Value(isolate->GetCurrentContext()).FromJust();
|
||||
args.GetReturnValue().Set(v8::Int32::New(isolate, WebViewManager::instance().getHeight(id)));
|
||||
}
|
||||
|
||||
void runt_webview_set_transparent(const v8::FunctionCallbackInfo<v8::Value>& args) {
|
||||
v8::Isolate* isolate = args.GetIsolate();
|
||||
v8::HandleScope scope(isolate);
|
||||
if (args.Length() < 2 || !args[0]->IsNumber() || !args[1]->IsBoolean()) return;
|
||||
int id = args[0]->Int32Value(isolate->GetCurrentContext()).FromJust();
|
||||
bool val = args[1]->BooleanValue(isolate);
|
||||
WebViewManager::instance().setTransparent(id, val);
|
||||
}
|
||||
|
||||
void runt_webview_set_click_through(const v8::FunctionCallbackInfo<v8::Value>& args) {
|
||||
v8::Isolate* isolate = args.GetIsolate();
|
||||
v8::HandleScope scope(isolate);
|
||||
if (args.Length() < 2 || !args[0]->IsNumber() || !args[1]->IsBoolean()) return;
|
||||
int id = args[0]->Int32Value(isolate->GetCurrentContext()).FromJust();
|
||||
bool val = args[1]->BooleanValue(isolate);
|
||||
WebViewManager::instance().setClickThrough(id, val);
|
||||
}
|
||||
|
||||
void runt_webview_set_title(const v8::FunctionCallbackInfo<v8::Value>& args) {
|
||||
v8::Isolate* isolate = args.GetIsolate();
|
||||
v8::HandleScope scope(isolate);
|
||||
if (args.Length() < 2 || !args[0]->IsNumber() || !args[1]->IsString()) return;
|
||||
int id = args[0]->Int32Value(isolate->GetCurrentContext()).FromJust();
|
||||
v8::String::Utf8Value str(isolate, args[1]);
|
||||
WebViewManager::instance().setTitle(id, *str);
|
||||
}
|
||||
|
||||
void runt_webview_send(const v8::FunctionCallbackInfo<v8::Value>& args) {
|
||||
v8::Isolate* isolate = args.GetIsolate();
|
||||
v8::HandleScope scope(isolate);
|
||||
|
||||
if (args.Length() < 2 || !args[0]->IsNumber()) {
|
||||
isolate->ThrowException(v8::Exception::Error(
|
||||
v8::String::NewFromUtf8(isolate, "ID and message required").ToLocalChecked()));
|
||||
return;
|
||||
}
|
||||
|
||||
int id = args[0]->Int32Value(isolate->GetCurrentContext()).FromJust();
|
||||
|
||||
std::string message;
|
||||
if (args[1]->IsString()) {
|
||||
v8::String::Utf8Value str(isolate, args[1]);
|
||||
message = *str;
|
||||
} else {
|
||||
v8::Local<v8::Context> ctx = isolate->GetCurrentContext();
|
||||
v8::Local<v8::String> json = v8::JSON::Stringify(ctx, args[1]).ToLocalChecked();
|
||||
v8::String::Utf8Value str(isolate, json);
|
||||
message = *str;
|
||||
}
|
||||
|
||||
WebViewManager::instance().send(id, message);
|
||||
}
|
||||
|
||||
void runt_webview_set_on_message(const v8::FunctionCallbackInfo<v8::Value>& args) {
|
||||
v8::Isolate* isolate = args.GetIsolate();
|
||||
v8::HandleScope scope(isolate);
|
||||
if (args.Length() < 2 || !args[0]->IsNumber() || !args[1]->IsFunction()) return;
|
||||
int id = args[0]->Int32Value(isolate->GetCurrentContext()).FromJust();
|
||||
WebViewManager::instance().setOnMessage(id, isolate, v8::Local<v8::Function>::Cast(args[1]));
|
||||
}
|
||||
|
||||
void runt_webview_set_on_load(const v8::FunctionCallbackInfo<v8::Value>& args) {
|
||||
v8::Isolate* isolate = args.GetIsolate();
|
||||
v8::HandleScope scope(isolate);
|
||||
if (args.Length() < 2 || !args[0]->IsNumber() || !args[1]->IsFunction()) return;
|
||||
int id = args[0]->Int32Value(isolate->GetCurrentContext()).FromJust();
|
||||
WebViewManager::instance().setOnLoad(id, isolate, v8::Local<v8::Function>::Cast(args[1]));
|
||||
}
|
||||
|
||||
void runt_webview_set_on_error(const v8::FunctionCallbackInfo<v8::Value>& args) {
|
||||
v8::Isolate* isolate = args.GetIsolate();
|
||||
v8::HandleScope scope(isolate);
|
||||
if (args.Length() < 2 || !args[0]->IsNumber() || !args[1]->IsFunction()) return;
|
||||
int id = args[0]->Int32Value(isolate->GetCurrentContext()).FromJust();
|
||||
WebViewManager::instance().setOnError(id, isolate, v8::Local<v8::Function>::Cast(args[1]));
|
||||
}
|
||||
|
||||
void runt_webview_set_on_close(const v8::FunctionCallbackInfo<v8::Value>& args) {
|
||||
v8::Isolate* isolate = args.GetIsolate();
|
||||
v8::HandleScope scope(isolate);
|
||||
if (args.Length() < 2 || !args[0]->IsNumber() || !args[1]->IsFunction()) return;
|
||||
int id = args[0]->Int32Value(isolate->GetCurrentContext()).FromJust();
|
||||
WebViewManager::instance().setOnClose(id, isolate, v8::Local<v8::Function>::Cast(args[1]));
|
||||
}
|
||||
|
||||
void runt_webview_count(const v8::FunctionCallbackInfo<v8::Value>& args) {
|
||||
v8::Isolate* isolate = args.GetIsolate();
|
||||
v8::HandleScope scope(isolate);
|
||||
args.GetReturnValue().Set(v8::Int32::New(isolate, WebViewManager::instance().count()));
|
||||
}
|
||||
|
||||
void runt_webview_is_valid(const v8::FunctionCallbackInfo<v8::Value>& args) {
|
||||
v8::Isolate* isolate = args.GetIsolate();
|
||||
v8::HandleScope scope(isolate);
|
||||
if (args.Length() < 1 || !args[0]->IsNumber()) {
|
||||
args.GetReturnValue().Set(v8::Boolean::New(isolate, false));
|
||||
return;
|
||||
}
|
||||
int id = args[0]->Int32Value(isolate->GetCurrentContext()).FromJust();
|
||||
args.GetReturnValue().Set(v8::Boolean::New(isolate, WebViewManager::instance().isValid(id)));
|
||||
}
|
||||
|
||||
void runt_webview_set_active_dom(const v8::FunctionCallbackInfo<v8::Value>& args) {
|
||||
v8::Isolate* isolate = args.GetIsolate();
|
||||
v8::HandleScope scope(isolate);
|
||||
if (args.Length() < 1 || !args[0]->IsNumber()) return;
|
||||
int id = args[0]->Int32Value(isolate->GetCurrentContext()).FromJust();
|
||||
WebViewManager::instance().setActiveDOM(id, isolate);
|
||||
}
|
||||
|
||||
void runt_webview_get_active_dom(const v8::FunctionCallbackInfo<v8::Value>& args) {
|
||||
v8::Isolate* isolate = args.GetIsolate();
|
||||
v8::HandleScope scope(isolate);
|
||||
args.GetReturnValue().Set(v8::Int32::New(isolate, WebViewManager::instance().getActiveDOM()));
|
||||
}
|
||||
|
||||
void runt_webview_eval_js_async(const v8::FunctionCallbackInfo<v8::Value>& args) {
|
||||
v8::Isolate* isolate = args.GetIsolate();
|
||||
v8::HandleScope scope(isolate);
|
||||
|
||||
if (args.Length() < 3 || !args[0]->IsNumber() || !args[1]->IsString() || !args[2]->IsFunction()) {
|
||||
isolate->ThrowException(v8::Exception::Error(
|
||||
v8::String::NewFromUtf8(isolate, "ID, JS string, and callback required").ToLocalChecked()));
|
||||
return;
|
||||
}
|
||||
|
||||
int id = args[0]->Int32Value(isolate->GetCurrentContext()).FromJust();
|
||||
v8::String::Utf8Value str(isolate, args[1]);
|
||||
v8::Local<v8::Function> cb = v8::Local<v8::Function>::Cast(args[2]);
|
||||
WebViewManager::instance().evalJSAsync(id, isolate, cb, *str);
|
||||
}
|
||||
|
||||
void runt_webview_go_back(const v8::FunctionCallbackInfo<v8::Value>& args) {
|
||||
v8::Isolate* isolate = args.GetIsolate();
|
||||
v8::HandleScope scope(isolate);
|
||||
if (args.Length() < 1 || !args[0]->IsNumber()) return;
|
||||
int id = args[0]->Int32Value(isolate->GetCurrentContext()).FromJust();
|
||||
WebViewManager::instance().goBack(id);
|
||||
}
|
||||
|
||||
void runt_webview_go_forward(const v8::FunctionCallbackInfo<v8::Value>& args) {
|
||||
v8::Isolate* isolate = args.GetIsolate();
|
||||
v8::HandleScope scope(isolate);
|
||||
if (args.Length() < 1 || !args[0]->IsNumber()) return;
|
||||
int id = args[0]->Int32Value(isolate->GetCurrentContext()).FromJust();
|
||||
WebViewManager::instance().goForward(id);
|
||||
}
|
||||
|
||||
void runt_webview_reload(const v8::FunctionCallbackInfo<v8::Value>& args) {
|
||||
v8::Isolate* isolate = args.GetIsolate();
|
||||
v8::HandleScope scope(isolate);
|
||||
if (args.Length() < 1 || !args[0]->IsNumber()) return;
|
||||
int id = args[0]->Int32Value(isolate->GetCurrentContext()).FromJust();
|
||||
WebViewManager::instance().reload(id);
|
||||
}
|
||||
|
||||
void runt_webview_can_go_back(const v8::FunctionCallbackInfo<v8::Value>& args) {
|
||||
v8::Isolate* isolate = args.GetIsolate();
|
||||
v8::HandleScope scope(isolate);
|
||||
if (args.Length() < 1 || !args[0]->IsNumber()) {
|
||||
args.GetReturnValue().Set(v8::Boolean::New(isolate, false));
|
||||
return;
|
||||
}
|
||||
int id = args[0]->Int32Value(isolate->GetCurrentContext()).FromJust();
|
||||
args.GetReturnValue().Set(v8::Boolean::New(isolate, WebViewManager::instance().canGoBack(id)));
|
||||
}
|
||||
|
||||
void runt_webview_can_go_forward(const v8::FunctionCallbackInfo<v8::Value>& args) {
|
||||
v8::Isolate* isolate = args.GetIsolate();
|
||||
v8::HandleScope scope(isolate);
|
||||
if (args.Length() < 1 || !args[0]->IsNumber()) {
|
||||
args.GetReturnValue().Set(v8::Boolean::New(isolate, false));
|
||||
return;
|
||||
}
|
||||
int id = args[0]->Int32Value(isolate->GetCurrentContext()).FromJust();
|
||||
args.GetReturnValue().Set(v8::Boolean::New(isolate, WebViewManager::instance().canGoForward(id)));
|
||||
}
|
||||
|
||||
void runt_webview_get_url(const v8::FunctionCallbackInfo<v8::Value>& args) {
|
||||
v8::Isolate* isolate = args.GetIsolate();
|
||||
v8::HandleScope scope(isolate);
|
||||
if (args.Length() < 1 || !args[0]->IsNumber()) {
|
||||
args.GetReturnValue().Set(v8::String::NewFromUtf8(isolate, "").ToLocalChecked());
|
||||
return;
|
||||
}
|
||||
int id = args[0]->Int32Value(isolate->GetCurrentContext()).FromJust();
|
||||
std::string url = WebViewManager::instance().getURL(id);
|
||||
args.GetReturnValue().Set(v8::String::NewFromUtf8(isolate, url.c_str(), v8::NewStringType::kNormal, (int)url.length()).ToLocalChecked());
|
||||
}
|
||||
|
||||
void runt_webview_get_page_title(const v8::FunctionCallbackInfo<v8::Value>& args) {
|
||||
v8::Isolate* isolate = args.GetIsolate();
|
||||
v8::HandleScope scope(isolate);
|
||||
if (args.Length() < 1 || !args[0]->IsNumber()) {
|
||||
args.GetReturnValue().Set(v8::String::NewFromUtf8(isolate, "").ToLocalChecked());
|
||||
return;
|
||||
}
|
||||
int id = args[0]->Int32Value(isolate->GetCurrentContext()).FromJust();
|
||||
std::string title = WebViewManager::instance().getPageTitle(id);
|
||||
args.GetReturnValue().Set(v8::String::NewFromUtf8(isolate, title.c_str(), v8::NewStringType::kNormal, (int)title.length()).ToLocalChecked());
|
||||
}
|
||||
|
||||
void runt_webview_minimize(const v8::FunctionCallbackInfo<v8::Value>& args) {
|
||||
v8::Isolate* isolate = args.GetIsolate();
|
||||
v8::HandleScope scope(isolate);
|
||||
if (args.Length() < 1 || !args[0]->IsNumber()) return;
|
||||
int id = args[0]->Int32Value(isolate->GetCurrentContext()).FromJust();
|
||||
WebViewManager::instance().minimize(id);
|
||||
}
|
||||
|
||||
void runt_webview_maximize(const v8::FunctionCallbackInfo<v8::Value>& args) {
|
||||
v8::Isolate* isolate = args.GetIsolate();
|
||||
v8::HandleScope scope(isolate);
|
||||
if (args.Length() < 1 || !args[0]->IsNumber()) return;
|
||||
int id = args[0]->Int32Value(isolate->GetCurrentContext()).FromJust();
|
||||
WebViewManager::instance().maximize(id);
|
||||
}
|
||||
|
||||
void runt_webview_restore(const v8::FunctionCallbackInfo<v8::Value>& args) {
|
||||
v8::Isolate* isolate = args.GetIsolate();
|
||||
v8::HandleScope scope(isolate);
|
||||
if (args.Length() < 1 || !args[0]->IsNumber()) return;
|
||||
int id = args[0]->Int32Value(isolate->GetCurrentContext()).FromJust();
|
||||
WebViewManager::instance().restore(id);
|
||||
}
|
||||
|
||||
void runt_webview_set_fullscreen(const v8::FunctionCallbackInfo<v8::Value>& args) {
|
||||
v8::Isolate* isolate = args.GetIsolate();
|
||||
v8::HandleScope scope(isolate);
|
||||
if (args.Length() < 2 || !args[0]->IsNumber() || !args[1]->IsBoolean()) return;
|
||||
int id = args[0]->Int32Value(isolate->GetCurrentContext()).FromJust();
|
||||
bool fs = args[1]->BooleanValue(isolate);
|
||||
WebViewManager::instance().setFullscreen(id, fs);
|
||||
}
|
||||
|
||||
void runt_webview_is_fullscreen(const v8::FunctionCallbackInfo<v8::Value>& args) {
|
||||
v8::Isolate* isolate = args.GetIsolate();
|
||||
v8::HandleScope scope(isolate);
|
||||
if (args.Length() < 1 || !args[0]->IsNumber()) {
|
||||
args.GetReturnValue().Set(v8::Boolean::New(isolate, false));
|
||||
return;
|
||||
}
|
||||
int id = args[0]->Int32Value(isolate->GetCurrentContext()).FromJust();
|
||||
args.GetReturnValue().Set(v8::Boolean::New(isolate, WebViewManager::instance().isFullscreen(id)));
|
||||
}
|
||||
|
||||
void runt_webview_enable_devtools(const v8::FunctionCallbackInfo<v8::Value>& args) {
|
||||
v8::Isolate* isolate = args.GetIsolate();
|
||||
v8::HandleScope scope(isolate);
|
||||
if (args.Length() < 2 || !args[0]->IsNumber() || !args[1]->IsBoolean()) return;
|
||||
int id = args[0]->Int32Value(isolate->GetCurrentContext()).FromJust();
|
||||
bool enabled = args[1]->BooleanValue(isolate);
|
||||
WebViewManager::instance().enableDevTools(id, enabled);
|
||||
}
|
||||
|
||||
void runt_webview_set_context_menu(const v8::FunctionCallbackInfo<v8::Value>& args) {
|
||||
v8::Isolate* isolate = args.GetIsolate();
|
||||
v8::HandleScope scope(isolate);
|
||||
if (args.Length() < 2 || !args[0]->IsNumber() || !args[1]->IsBoolean()) return;
|
||||
int id = args[0]->Int32Value(isolate->GetCurrentContext()).FromJust();
|
||||
bool enabled = args[1]->BooleanValue(isolate);
|
||||
WebViewManager::instance().setContextMenuEnabled(id, enabled);
|
||||
}
|
||||
|
||||
#endif
|
||||
191
Sources/webview.h
Normal file
191
Sources/webview.h
Normal file
@ -0,0 +1,191 @@
|
||||
#pragma once
|
||||
|
||||
#ifdef WITH_WEBVIEW
|
||||
|
||||
#include <map>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <v8.h>
|
||||
|
||||
struct WebViewInstance {
|
||||
int id;
|
||||
int parentWindow; // -1 means detached as its own OS window, >= 0 = child of Kinc window
|
||||
int x, y;
|
||||
int width, height; // -1 means fill parent window for embedded mode only
|
||||
bool autoResize; // true if width or height was -1 at creation
|
||||
bool transparent;
|
||||
bool clickThrough;
|
||||
bool visible;
|
||||
std::string title;
|
||||
|
||||
void* platformHandle;
|
||||
void* platformWindow;
|
||||
|
||||
v8::Isolate* isolate;
|
||||
v8::Global<v8::Context> context;
|
||||
v8::Global<v8::Function> onMessage;
|
||||
v8::Global<v8::Function> onLoad;
|
||||
v8::Global<v8::Function> onError;
|
||||
v8::Global<v8::Function> onClose;
|
||||
v8::Global<v8::Function> onEvalResult;
|
||||
|
||||
WebViewInstance() : id(-1), parentWindow(-1), x(0), y(0),
|
||||
width(800), height(600), autoResize(false), transparent(true),
|
||||
clickThrough(false), visible(false), platformHandle(nullptr),
|
||||
platformWindow(nullptr), isolate(nullptr) {}
|
||||
};
|
||||
|
||||
class WebViewManager {
|
||||
public:
|
||||
static WebViewManager& instance();
|
||||
|
||||
int create(v8::Isolate* isolate, v8::Local<v8::Context> context,
|
||||
int parentWindow, int x, int y, int width, int height,
|
||||
bool transparent, const std::string& title);
|
||||
void destroy(int id);
|
||||
bool isValid(int id);
|
||||
int count();
|
||||
|
||||
void loadHTML(int id, const std::string& html);
|
||||
void loadURL(int id, const std::string& url);
|
||||
void evalJS(int id, const std::string& js);
|
||||
void evalJSAsync(int id, v8::Isolate* isolate, v8::Local<v8::Function> callback, const std::string& js);
|
||||
|
||||
void setActiveDOM(int id, v8::Isolate* isolate);
|
||||
int getActiveDOM();
|
||||
|
||||
void show(int id);
|
||||
void hide(int id);
|
||||
|
||||
void resize(int id, int width, int height);
|
||||
void move(int id, int x, int y);
|
||||
void setBounds(int id, int x, int y, int width, int height);
|
||||
int getX(int id);
|
||||
int getY(int id);
|
||||
int getWidth(int id);
|
||||
int getHeight(int id);
|
||||
|
||||
void setTransparent(int id, bool transparent);
|
||||
void setClickThrough(int id, bool enabled);
|
||||
void setTitle(int id, const std::string& title);
|
||||
|
||||
void send(int id, const std::string& message);
|
||||
|
||||
void goBack(int id);
|
||||
void goForward(int id);
|
||||
void reload(int id);
|
||||
bool canGoBack(int id);
|
||||
bool canGoForward(int id);
|
||||
std::string getURL(int id);
|
||||
std::string getPageTitle(int id);
|
||||
|
||||
void minimize(int id);
|
||||
void maximize(int id);
|
||||
void restore(int id);
|
||||
void setFullscreen(int id, bool fullscreen);
|
||||
bool isFullscreen(int id);
|
||||
|
||||
void enableDevTools(int id, bool enabled);
|
||||
void setContextMenuEnabled(int id, bool enabled);
|
||||
|
||||
void setOnMessage(int id, v8::Isolate* isolate, v8::Local<v8::Function> cb);
|
||||
void setOnLoad(int id, v8::Isolate* isolate, v8::Local<v8::Function> cb);
|
||||
void setOnError(int id, v8::Isolate* isolate, v8::Local<v8::Function> cb);
|
||||
void setOnClose(int id, v8::Isolate* isolate, v8::Local<v8::Function> cb);
|
||||
|
||||
void dispatchMessage(int id, const std::string& message);
|
||||
void dispatchLoad(int id);
|
||||
void dispatchError(int id, const std::string& error);
|
||||
void dispatchClose(int id);
|
||||
void dispatchEvalResult(int id, const std::string& result);
|
||||
|
||||
void tick();
|
||||
|
||||
void onWindowResize(int windowId, int width, int height);
|
||||
|
||||
private:
|
||||
WebViewManager() = default;
|
||||
~WebViewManager();
|
||||
|
||||
std::map<int, std::unique_ptr<WebViewInstance>> webviews;
|
||||
int nextId = 0;
|
||||
int activeDomId = -1;
|
||||
};
|
||||
|
||||
int platformCreate(WebViewInstance* wv);
|
||||
void platformDestroy(WebViewInstance* wv);
|
||||
void platformLoadHTML(WebViewInstance* wv, const std::string& html);
|
||||
void platformLoadURL(WebViewInstance* wv, const std::string& url);
|
||||
void platformEvalJS(WebViewInstance* wv, const std::string& js);
|
||||
void platformEvalJSAsync(WebViewInstance* wv, const std::string& js);
|
||||
void platformShow(WebViewInstance* wv);
|
||||
void platformHide(WebViewInstance* wv);
|
||||
void platformResize(WebViewInstance* wv, int width, int height);
|
||||
void platformMove(WebViewInstance* wv, int x, int y);
|
||||
void platformSetTransparent(WebViewInstance* wv, bool transparent);
|
||||
void platformSetClickThrough(WebViewInstance* wv, bool enabled);
|
||||
void platformSetTitle(WebViewInstance* wv, const std::string& title);
|
||||
void platformSend(WebViewInstance* wv, const std::string& message);
|
||||
|
||||
void platformGoBack(WebViewInstance* wv);
|
||||
void platformGoForward(WebViewInstance* wv);
|
||||
void platformReload(WebViewInstance* wv);
|
||||
bool platformCanGoBack(WebViewInstance* wv);
|
||||
bool platformCanGoForward(WebViewInstance* wv);
|
||||
std::string platformGetURL(WebViewInstance* wv);
|
||||
std::string platformGetTitle(WebViewInstance* wv);
|
||||
|
||||
void platformMinimize(WebViewInstance* wv);
|
||||
void platformMaximize(WebViewInstance* wv);
|
||||
void platformRestore(WebViewInstance* wv);
|
||||
void platformSetFullscreen(WebViewInstance* wv, bool fullscreen);
|
||||
bool platformIsFullscreen(WebViewInstance* wv);
|
||||
|
||||
void platformEnableDevTools(WebViewInstance* wv, bool enabled);
|
||||
void platformSetContextMenuEnabled(WebViewInstance* wv, bool enabled);
|
||||
|
||||
void platformTick();
|
||||
|
||||
void runt_webview_create(const v8::FunctionCallbackInfo<v8::Value>& args);
|
||||
void runt_webview_load_html(const v8::FunctionCallbackInfo<v8::Value>& args);
|
||||
void runt_webview_load_url(const v8::FunctionCallbackInfo<v8::Value>& args);
|
||||
void runt_webview_eval_js(const v8::FunctionCallbackInfo<v8::Value>& args);
|
||||
void runt_webview_show(const v8::FunctionCallbackInfo<v8::Value>& args);
|
||||
void runt_webview_hide(const v8::FunctionCallbackInfo<v8::Value>& args);
|
||||
void runt_webview_destroy(const v8::FunctionCallbackInfo<v8::Value>& args);
|
||||
void runt_webview_resize(const v8::FunctionCallbackInfo<v8::Value>& args);
|
||||
void runt_webview_move(const v8::FunctionCallbackInfo<v8::Value>& args);
|
||||
void runt_webview_set_bounds(const v8::FunctionCallbackInfo<v8::Value>& args);
|
||||
void runt_webview_get_x(const v8::FunctionCallbackInfo<v8::Value>& args);
|
||||
void runt_webview_get_y(const v8::FunctionCallbackInfo<v8::Value>& args);
|
||||
void runt_webview_get_width(const v8::FunctionCallbackInfo<v8::Value>& args);
|
||||
void runt_webview_get_height(const v8::FunctionCallbackInfo<v8::Value>& args);
|
||||
void runt_webview_set_transparent(const v8::FunctionCallbackInfo<v8::Value>& args);
|
||||
void runt_webview_set_click_through(const v8::FunctionCallbackInfo<v8::Value>& args);
|
||||
void runt_webview_set_title(const v8::FunctionCallbackInfo<v8::Value>& args);
|
||||
void runt_webview_send(const v8::FunctionCallbackInfo<v8::Value>& args);
|
||||
void runt_webview_set_on_message(const v8::FunctionCallbackInfo<v8::Value>& args);
|
||||
void runt_webview_set_on_load(const v8::FunctionCallbackInfo<v8::Value>& args);
|
||||
void runt_webview_set_on_error(const v8::FunctionCallbackInfo<v8::Value>& args);
|
||||
void runt_webview_set_on_close(const v8::FunctionCallbackInfo<v8::Value>& args);
|
||||
void runt_webview_count(const v8::FunctionCallbackInfo<v8::Value>& args);
|
||||
void runt_webview_is_valid(const v8::FunctionCallbackInfo<v8::Value>& args);
|
||||
void runt_webview_set_active_dom(const v8::FunctionCallbackInfo<v8::Value>& args);
|
||||
void runt_webview_get_active_dom(const v8::FunctionCallbackInfo<v8::Value>& args);
|
||||
void runt_webview_eval_js_async(const v8::FunctionCallbackInfo<v8::Value>& args);
|
||||
void runt_webview_go_back(const v8::FunctionCallbackInfo<v8::Value>& args);
|
||||
void runt_webview_go_forward(const v8::FunctionCallbackInfo<v8::Value>& args);
|
||||
void runt_webview_reload(const v8::FunctionCallbackInfo<v8::Value>& args);
|
||||
void runt_webview_can_go_back(const v8::FunctionCallbackInfo<v8::Value>& args);
|
||||
void runt_webview_can_go_forward(const v8::FunctionCallbackInfo<v8::Value>& args);
|
||||
void runt_webview_get_url(const v8::FunctionCallbackInfo<v8::Value>& args);
|
||||
void runt_webview_get_page_title(const v8::FunctionCallbackInfo<v8::Value>& args);
|
||||
void runt_webview_minimize(const v8::FunctionCallbackInfo<v8::Value>& args);
|
||||
void runt_webview_maximize(const v8::FunctionCallbackInfo<v8::Value>& args);
|
||||
void runt_webview_restore(const v8::FunctionCallbackInfo<v8::Value>& args);
|
||||
void runt_webview_set_fullscreen(const v8::FunctionCallbackInfo<v8::Value>& args);
|
||||
void runt_webview_is_fullscreen(const v8::FunctionCallbackInfo<v8::Value>& args);
|
||||
void runt_webview_enable_devtools(const v8::FunctionCallbackInfo<v8::Value>& args);
|
||||
void runt_webview_set_context_menu(const v8::FunctionCallbackInfo<v8::Value>& args);
|
||||
|
||||
#endif
|
||||
362
Sources/webview_linux.cpp
Normal file
362
Sources/webview_linux.cpp
Normal file
@ -0,0 +1,362 @@
|
||||
#ifdef WITH_WEBVIEW
|
||||
#ifdef KINC_LINUX
|
||||
|
||||
#include "webview.h"
|
||||
#include <kinc/log.h>
|
||||
#include <kinc/window.h>
|
||||
|
||||
#include <gtk/gtk.h>
|
||||
#include <webkit2/webkit2.h>
|
||||
|
||||
struct LinuxWebViewState {
|
||||
WebKitWebView* webView;
|
||||
GtkWidget* detachedWindow;
|
||||
bool isDetached;
|
||||
gulong closeHandlerId;
|
||||
gulong messageHandlerId;
|
||||
gulong loadHandlerId;
|
||||
int webviewId;
|
||||
};
|
||||
|
||||
static bool g_gtkInitialized = false;
|
||||
|
||||
static void ensureGtkInit() {
|
||||
if (g_gtkInitialized) return;
|
||||
if (!gtk_init_check(nullptr, nullptr)) {
|
||||
kinc_log(KINC_LOG_LEVEL_ERROR, "Failed to initialize webview for GTK");
|
||||
return;
|
||||
}
|
||||
g_gtkInitialized = true;
|
||||
}
|
||||
|
||||
static void onMessageReceived(WebKitUserContentManager* manager,
|
||||
WebKitJavascriptResult* result, gpointer userData) {
|
||||
auto* state = (LinuxWebViewState*)userData;
|
||||
JSCValue* value = webkit_javascript_result_get_js_value(result);
|
||||
if (jsc_value_is_string(value)) {
|
||||
char* str = jsc_value_to_string(value);
|
||||
WebViewManager::instance().dispatchMessage(state->webviewId, std::string(str));
|
||||
g_free(str);
|
||||
} else {
|
||||
char* str = jsc_value_to_string(value);
|
||||
WebViewManager::instance().dispatchMessage(state->webviewId, std::string(str));
|
||||
g_free(str);
|
||||
}
|
||||
}
|
||||
|
||||
static void onLoadChanged(WebKitWebView* webView, WebKitLoadEvent loadEvent,
|
||||
gpointer userData) {
|
||||
auto* state = (LinuxWebViewState*)userData;
|
||||
if (loadEvent == WEBKIT_LOAD_FINISHED) {
|
||||
WebViewManager::instance().dispatchLoad(state->webviewId);
|
||||
}
|
||||
}
|
||||
|
||||
static gboolean onDetachedClose(GtkWidget* widget, GdkEvent* event, gpointer userData) {
|
||||
auto* state = (LinuxWebViewState*)userData;
|
||||
WebViewManager::instance().dispatchClose(state->webviewId);
|
||||
gtk_widget_hide(widget);
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
int platformCreate(WebViewInstance* wv) {
|
||||
ensureGtkInit();
|
||||
if (!g_gtkInitialized) {
|
||||
kinc_log(KINC_LOG_LEVEL_ERROR, "GTK WebView not initialized");
|
||||
return -1;
|
||||
}
|
||||
|
||||
auto* state = new LinuxWebViewState();
|
||||
state->webView = nullptr;
|
||||
state->detachedWindow = nullptr;
|
||||
state->isDetached = (wv->parentWindow < 0);
|
||||
state->webviewId = wv->id;
|
||||
wv->platformHandle = state;
|
||||
|
||||
state->webView = WEBKIT_WEB_VIEW(webkit_web_view_new());
|
||||
|
||||
if (wv->transparent) {
|
||||
GdkRGBA rgba = {0.0, 0.0, 0.0, 0.0};
|
||||
webkit_web_view_set_background_color(state->webView, &rgba);
|
||||
}
|
||||
|
||||
WebKitUserContentManager* contentManager =
|
||||
webkit_web_view_get_user_content_manager(state->webView);
|
||||
webkit_user_content_manager_register_script_message_handler(contentManager, "lnxrnt");
|
||||
state->messageHandlerId = g_signal_connect(contentManager, "script-message-received::lnxrnt",
|
||||
G_CALLBACK(onMessageReceived), state);
|
||||
|
||||
state->loadHandlerId = g_signal_connect(state->webView, "load-changed",
|
||||
G_CALLBACK(onLoadChanged), state);
|
||||
|
||||
if (state->isDetached) {
|
||||
state->detachedWindow = gtk_window_new(GTK_WINDOW_TOPLEVEL);
|
||||
gtk_window_set_title(GTK_WINDOW(state->detachedWindow), wv->title.c_str());
|
||||
gtk_window_set_default_size(GTK_WINDOW(state->detachedWindow), wv->width, wv->height);
|
||||
gtk_window_move(GTK_WINDOW(state->detachedWindow), wv->x, wv->y);
|
||||
|
||||
GtkWidget* scrolledWindow = gtk_scrolled_window_new(nullptr, nullptr);
|
||||
gtk_container_add(GTK_CONTAINER(scrolledWindow), GTK_WIDGET(state->webView));
|
||||
gtk_container_add(GTK_CONTAINER(state->detachedWindow), scrolledWindow);
|
||||
|
||||
state->closeHandlerId = g_signal_connect(state->detachedWindow, "delete-event",
|
||||
G_CALLBACK(onDetachedClose), state);
|
||||
} else {
|
||||
state->detachedWindow = gtk_window_new(GTK_WINDOW_POPUP);
|
||||
gtk_window_set_decorated(GTK_WINDOW(state->detachedWindow), FALSE);
|
||||
gtk_window_set_default_size(GTK_WINDOW(state->detachedWindow), wv->width, wv->height);
|
||||
|
||||
gtk_window_move(GTK_WINDOW(state->detachedWindow), wv->x, wv->y);
|
||||
|
||||
GtkWidget* scrolledWindow = gtk_scrolled_window_new(nullptr, nullptr);
|
||||
gtk_container_add(GTK_CONTAINER(scrolledWindow), GTK_WIDGET(state->webView));
|
||||
gtk_container_add(GTK_CONTAINER(state->detachedWindow), scrolledWindow);
|
||||
}
|
||||
|
||||
return wv->id;
|
||||
}
|
||||
|
||||
void platformDestroy(WebViewInstance* wv) {
|
||||
auto* state = (LinuxWebViewState*)wv->platformHandle;
|
||||
if (!state) return;
|
||||
|
||||
if (state->messageHandlerId) {
|
||||
WebKitUserContentManager* cm = webkit_web_view_get_user_content_manager(state->webView);
|
||||
g_signal_handler_disconnect(cm, state->messageHandlerId);
|
||||
}
|
||||
if (state->loadHandlerId) {
|
||||
g_signal_handler_disconnect(state->webView, state->loadHandlerId);
|
||||
}
|
||||
if (state->closeHandlerId && state->detachedWindow) {
|
||||
g_signal_handler_disconnect(state->detachedWindow, state->closeHandlerId);
|
||||
}
|
||||
if (state->detachedWindow) {
|
||||
gtk_widget_destroy(state->detachedWindow);
|
||||
}
|
||||
delete state;
|
||||
wv->platformHandle = nullptr;
|
||||
}
|
||||
|
||||
void platformLoadHTML(WebViewInstance* wv, const std::string& html) {
|
||||
auto* state = (LinuxWebViewState*)wv->platformHandle;
|
||||
if (!state || !state->webView) return;
|
||||
webkit_web_view_load_html(state->webView, html.c_str(), nullptr);
|
||||
}
|
||||
|
||||
void platformLoadURL(WebViewInstance* wv, const std::string& url) {
|
||||
auto* state = (LinuxWebViewState*)wv->platformHandle;
|
||||
if (!state || !state->webView) return;
|
||||
webkit_web_view_load_uri(state->webView, url.c_str());
|
||||
}
|
||||
|
||||
void platformEvalJS(WebViewInstance* wv, const std::string& js) {
|
||||
auto* state = (LinuxWebViewState*)wv->platformHandle;
|
||||
if (!state || !state->webView) return;
|
||||
webkit_web_view_evaluate_javascript(state->webView, js.c_str(), -1, nullptr, nullptr, nullptr, nullptr, nullptr);
|
||||
}
|
||||
|
||||
static void platformEvalJSAsyncCallback(GObject* source, GAsyncResult* res, gpointer user_data) {
|
||||
int webviewId = (int)(intptr_t)user_data;
|
||||
WebKitJavascriptResult* jsResult = webkit_web_view_evaluate_javascript_finish(WEBKIT_WEB_VIEW(source), res, nullptr);
|
||||
std::string resultStr = "null";
|
||||
if (jsResult) {
|
||||
JSCValue* value = webkit_javascript_result_get_js_value(jsResult);
|
||||
if (value && !jsc_value_is_undefined(value) && !jsc_value_is_null(value)) {
|
||||
JSCValue* jsonStr = jsc_value_to_string(value);
|
||||
gchar* str = (gchar*)jsc_value_to_string(jsonStr);
|
||||
if (str) {
|
||||
resultStr = std::string(str);
|
||||
g_free(str);
|
||||
}
|
||||
g_object_unref(jsonStr);
|
||||
}
|
||||
webkit_javascript_result_unref(jsResult);
|
||||
}
|
||||
WebViewManager::instance().dispatchEvalResult(webviewId, resultStr);
|
||||
}
|
||||
|
||||
void platformEvalJSAsync(WebViewInstance* wv, const std::string& js) {
|
||||
auto* state = (LinuxWebViewState*)wv->platformHandle;
|
||||
if (!state || !state->webView) return;
|
||||
webkit_web_view_evaluate_javascript(state->webView, js.c_str(), -1, nullptr, nullptr,
|
||||
(GAsyncReadyCallback)platformEvalJSAsyncCallback, (gpointer)(intptr_t)wv->id);
|
||||
}
|
||||
|
||||
void platformShow(WebViewInstance* wv) {
|
||||
auto* state = (LinuxWebViewState*)wv->platformHandle;
|
||||
if (!state) return;
|
||||
if (state->detachedWindow) {
|
||||
gtk_widget_show_all(state->detachedWindow);
|
||||
}
|
||||
}
|
||||
|
||||
void platformHide(WebViewInstance* wv) {
|
||||
auto* state = (LinuxWebViewState*)wv->platformHandle;
|
||||
if (!state) return;
|
||||
if (state->detachedWindow) {
|
||||
gtk_widget_hide(state->detachedWindow);
|
||||
}
|
||||
}
|
||||
|
||||
void platformResize(WebViewInstance* wv, int width, int height) {
|
||||
auto* state = (LinuxWebViewState*)wv->platformHandle;
|
||||
if (!state || !state->detachedWindow) return;
|
||||
gtk_window_resize(GTK_WINDOW(state->detachedWindow), width, height);
|
||||
}
|
||||
|
||||
void platformMove(WebViewInstance* wv, int x, int y) {
|
||||
auto* state = (LinuxWebViewState*)wv->platformHandle;
|
||||
if (!state || !state->detachedWindow) return;
|
||||
gtk_window_move(GTK_WINDOW(state->detachedWindow), x, y);
|
||||
}
|
||||
|
||||
void platformSetTransparent(WebViewInstance* wv, bool transparent) {
|
||||
auto* state = (LinuxWebViewState*)wv->platformHandle;
|
||||
if (!state || !state->webView) return;
|
||||
if (transparent) {
|
||||
GdkRGBA rgba = {0.0, 0.0, 0.0, 0.0};
|
||||
webkit_web_view_set_background_color(state->webView, &rgba);
|
||||
} else {
|
||||
GdkRGBA rgba = {1.0, 1.0, 1.0, 1.0};
|
||||
webkit_web_view_set_background_color(state->webView, &rgba);
|
||||
}
|
||||
}
|
||||
|
||||
void platformSetClickThrough(WebViewInstance* wv, bool enabled) {
|
||||
auto* state = (LinuxWebViewState*)wv->platformHandle;
|
||||
if (!state || !state->webView) return;
|
||||
const char* js = enabled
|
||||
? "document.body.style.pointerEvents = 'none';"
|
||||
: "document.body.style.pointerEvents = 'auto';";
|
||||
webkit_web_view_evaluate_javascript(state->webView, js, -1, nullptr, nullptr, nullptr, nullptr, nullptr);
|
||||
}
|
||||
|
||||
void platformSetTitle(WebViewInstance* wv, const std::string& title) {
|
||||
auto* state = (LinuxWebViewState*)wv->platformHandle;
|
||||
if (!state || !state->isDetached || !state->detachedWindow) return;
|
||||
gtk_window_set_title(GTK_WINDOW(state->detachedWindow), title.c_str());
|
||||
}
|
||||
|
||||
void platformSend(WebViewInstance* wv, const std::string& message) {
|
||||
auto* state = (LinuxWebViewState*)wv->platformHandle;
|
||||
if (!state || !state->webView) return;
|
||||
std::string escaped;
|
||||
for (size_t i = 0; i < message.size(); i++) {
|
||||
char c = message[i];
|
||||
switch (c) {
|
||||
case '\\': escaped += "\\\\"; break;
|
||||
case '\'': escaped += "\\'"; break;
|
||||
case '\n': escaped += "\\n"; break;
|
||||
case '\r': escaped += "\\r"; break;
|
||||
default: escaped += c; break;
|
||||
}
|
||||
}
|
||||
std::string js = "if(window.__lnxrntOnMessage)window.__lnxrntOnMessage('" + escaped + "');";
|
||||
webkit_web_view_evaluate_javascript(state->webView, js.c_str(), -1, nullptr, nullptr, nullptr, nullptr, nullptr);
|
||||
}
|
||||
|
||||
void platformGoBack(WebViewInstance* wv) {
|
||||
auto* state = (LinuxWebViewState*)wv->platformHandle;
|
||||
if (!state || !state->webView) return;
|
||||
webkit_web_view_go_back(state->webView);
|
||||
}
|
||||
|
||||
void platformGoForward(WebViewInstance* wv) {
|
||||
auto* state = (LinuxWebViewState*)wv->platformHandle;
|
||||
if (!state || !state->webView) return;
|
||||
webkit_web_view_go_forward(state->webView);
|
||||
}
|
||||
|
||||
void platformReload(WebViewInstance* wv) {
|
||||
auto* state = (LinuxWebViewState*)wv->platformHandle;
|
||||
if (!state || !state->webView) return;
|
||||
webkit_web_view_reload(state->webView);
|
||||
}
|
||||
|
||||
bool platformCanGoBack(WebViewInstance* wv) {
|
||||
auto* state = (LinuxWebViewState*)wv->platformHandle;
|
||||
if (!state || !state->webView) return false;
|
||||
return webkit_web_view_can_go_back(state->webView);
|
||||
}
|
||||
|
||||
bool platformCanGoForward(WebViewInstance* wv) {
|
||||
auto* state = (LinuxWebViewState*)wv->platformHandle;
|
||||
if (!state || !state->webView) return false;
|
||||
return webkit_web_view_can_go_forward(state->webView);
|
||||
}
|
||||
|
||||
std::string platformGetURL(WebViewInstance* wv) {
|
||||
auto* state = (LinuxWebViewState*)wv->platformHandle;
|
||||
if (!state || !state->webView) return "";
|
||||
const gchar* uri = webkit_web_view_get_uri(state->webView);
|
||||
return uri ? std::string(uri) : "";
|
||||
}
|
||||
|
||||
std::string platformGetTitle(WebViewInstance* wv) {
|
||||
auto* state = (LinuxWebViewState*)wv->platformHandle;
|
||||
if (!state || !state->webView) return "";
|
||||
const gchar* title = webkit_web_view_get_title(state->webView);
|
||||
return title ? std::string(title) : "";
|
||||
}
|
||||
|
||||
void platformMinimize(WebViewInstance* wv) {
|
||||
auto* state = (LinuxWebViewState*)wv->platformHandle;
|
||||
if (!state || !state->isDetached || !state->detachedWindow) return;
|
||||
gtk_window_iconify(GTK_WINDOW(state->detachedWindow));
|
||||
}
|
||||
|
||||
void platformMaximize(WebViewInstance* wv) {
|
||||
auto* state = (LinuxWebViewState*)wv->platformHandle;
|
||||
if (!state || !state->isDetached || !state->detachedWindow) return;
|
||||
gtk_window_maximize(GTK_WINDOW(state->detachedWindow));
|
||||
}
|
||||
|
||||
void platformRestore(WebViewInstance* wv) {
|
||||
auto* state = (LinuxWebViewState*)wv->platformHandle;
|
||||
if (!state || !state->isDetached || !state->detachedWindow) return;
|
||||
gtk_window_unmaximize(GTK_WINDOW(state->detachedWindow));
|
||||
gtk_window_deiconify(GTK_WINDOW(state->detachedWindow));
|
||||
}
|
||||
|
||||
void platformSetFullscreen(WebViewInstance* wv, bool fullscreen) {
|
||||
auto* state = (LinuxWebViewState*)wv->platformHandle;
|
||||
if (!state || !state->isDetached || !state->detachedWindow) return;
|
||||
if (fullscreen) {
|
||||
gtk_window_fullscreen(GTK_WINDOW(state->detachedWindow));
|
||||
} else {
|
||||
gtk_window_unfullscreen(GTK_WINDOW(state->detachedWindow));
|
||||
}
|
||||
}
|
||||
|
||||
bool platformIsFullscreen(WebViewInstance* wv) {
|
||||
auto* state = (LinuxWebViewState*)wv->platformHandle;
|
||||
if (!state || !state->isDetached || !state->detachedWindow) return false;
|
||||
GdkWindow* gdkWin = gtk_widget_get_window(state->detachedWindow);
|
||||
if (!gdkWin) return false;
|
||||
GdkWindowState state2 = gdk_window_get_state(gdkWin);
|
||||
return (state2 & GDK_WINDOW_STATE_FULLSCREEN) != 0;
|
||||
}
|
||||
|
||||
void platformEnableDevTools(WebViewInstance* wv, bool enabled) {
|
||||
auto* state = (LinuxWebViewState*)wv->platformHandle;
|
||||
if (!state || !state->webView) return;
|
||||
WebKitSettings* settings = webkit_web_view_get_settings(state->webView);
|
||||
webkit_settings_set_enable_developer_extras(settings, enabled);
|
||||
}
|
||||
|
||||
void platformSetContextMenuEnabled(WebViewInstance* wv, bool enabled) {
|
||||
auto* state = (LinuxWebViewState*)wv->platformHandle;
|
||||
if (!state || !state->webView) return;
|
||||
WebKitSettings* settings = webkit_web_view_get_settings(state->webView);
|
||||
webkit_settings_set_enable_context_menu(settings, enabled);
|
||||
}
|
||||
|
||||
void platformTick() {
|
||||
if (!g_gtkInitialized) return;
|
||||
while (gtk_events_pending()) {
|
||||
gtk_main_iteration_do(FALSE);
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
#endif
|
||||
468
Sources/webview_mac.mm
Normal file
468
Sources/webview_mac.mm
Normal file
@ -0,0 +1,468 @@
|
||||
#ifdef WITH_WEBVIEW
|
||||
|
||||
#include <kinc/global.h>
|
||||
|
||||
#ifdef KINC_MACOS
|
||||
|
||||
#import <WebKit/WebKit.h>
|
||||
#import <AppKit/AppKit.h>
|
||||
#include "webview.h"
|
||||
#include <kinc/log.h>
|
||||
#include <kinc/window.h>
|
||||
|
||||
@interface LNXRNTWebViewDelegate : NSObject <WKNavigationDelegate, WKScriptMessageHandler, NSWindowDelegate, WKUIDelegate> {
|
||||
@public int webviewId;
|
||||
@public bool isDetached;
|
||||
@public NSWindow* detachedWindow;
|
||||
}
|
||||
- (void)userContentController:(WKUserContentController *)userContentController didReceiveScriptMessage:(WKScriptMessage *)message;
|
||||
- (void)webView:(WKWebView *)webView didFinishNavigation:(WKNavigation *)navigation;
|
||||
- (void)webView:(WKWebView *)webView didFailNavigation:(WKNavigation *)navigation withError:(NSError *)error;
|
||||
- (BOOL)windowShouldClose:(NSWindow *)sender;
|
||||
- (void)webView:(WKWebView *)webView runJavaScriptAlertPanelWithMessage:(NSString *)message initiatedByFrame:(WKFrameInfo *)frame completionHandler:(void (^)(void))completionHandler;
|
||||
- (void)webView:(WKWebView *)webView runJavaScriptConfirmPanelWithMessage:(NSString *)message initiatedByFrame:(WKFrameInfo *)frame completionHandler:(void (^)(BOOL result))completionHandler;
|
||||
- (void)webView:(WKWebView *)webView runJavaScriptTextInputPanelWithPrompt:(NSString *)prompt defaultText:(NSString *)defaultText initiatedByFrame:(WKFrameInfo *)frame completionHandler:(void (^)(NSString *result))completionHandler;
|
||||
- (WKWebView *)webView:(WKWebView *)webView createWebViewWithConfiguration:(WKWebViewConfiguration *)configuration forNavigationAction:(WKNavigationAction *)navigationAction windowFeatures:(WKWindowFeatures *)windowFeatures;
|
||||
- (void)webView:(WKWebView *)webView runOpenPanelWithParameters:(WKOpenPanelParameters *)parameters initiatedByFrame:(WKFrameInfo *)frame completionHandler:(void (^)(NSArray<NSURL *> *URLs))completionHandler;
|
||||
@end
|
||||
|
||||
@implementation LNXRNTWebViewDelegate
|
||||
|
||||
- (void)userContentController:(WKUserContentController *)userContentController didReceiveScriptMessage:(WKScriptMessage *)message {
|
||||
NSString *msgBody = [[message body] isKindOfClass:[NSString class]] ? [message body] : [NSString stringWithFormat:@"%@", [message body]];
|
||||
const char *cstr = [msgBody UTF8String];
|
||||
WebViewManager::instance().dispatchMessage(webviewId, std::string(cstr));
|
||||
}
|
||||
|
||||
- (void)webView:(WKWebView *)webView didFinishNavigation:(WKNavigation *)navigation {
|
||||
WebViewManager::instance().dispatchLoad(webviewId);
|
||||
}
|
||||
|
||||
- (void)webView:(WKWebView *)webView didFailNavigation:(WKNavigation *)navigation withError:(NSError *)error {
|
||||
NSString *desc = [error localizedDescription];
|
||||
WebViewManager::instance().dispatchError(webviewId, std::string([desc UTF8String]));
|
||||
}
|
||||
|
||||
- (BOOL)windowShouldClose:(NSWindow *)sender {
|
||||
WebViewManager::instance().dispatchClose(webviewId);
|
||||
[sender orderOut:nil];
|
||||
return NO;
|
||||
}
|
||||
|
||||
- (void)webView:(WKWebView *)webView runJavaScriptAlertPanelWithMessage:(NSString *)message initiatedByFrame:(WKFrameInfo *)frame completionHandler:(void (^)(void))completionHandler {
|
||||
NSAlert *alert = [[NSAlert alloc] init];
|
||||
[alert setMessageText:message];
|
||||
[alert addButtonWithTitle:@"OK"];
|
||||
[alert runModal];
|
||||
completionHandler();
|
||||
}
|
||||
|
||||
- (void)webView:(WKWebView *)webView runJavaScriptConfirmPanelWithMessage:(NSString *)message initiatedByFrame:(WKFrameInfo *)frame completionHandler:(void (^)(BOOL result))completionHandler {
|
||||
NSAlert *alert = [[NSAlert alloc] init];
|
||||
[alert setMessageText:message];
|
||||
[alert addButtonWithTitle:@"OK"];
|
||||
[alert addButtonWithTitle:@"Cancel"];
|
||||
NSModalResponse result = [alert runModal];
|
||||
completionHandler(result == NSAlertFirstButtonReturn);
|
||||
}
|
||||
|
||||
- (void)webView:(WKWebView *)webView runJavaScriptTextInputPanelWithPrompt:(NSString *)prompt defaultText:(NSString *)defaultText initiatedByFrame:(WKFrameInfo *)frame completionHandler:(void (^)(NSString *result))completionHandler {
|
||||
NSAlert *alert = [[NSAlert alloc] init];
|
||||
[alert setMessageText:prompt];
|
||||
[alert addButtonWithTitle:@"OK"];
|
||||
[alert addButtonWithTitle:@"Cancel"];
|
||||
NSTextField *input = [[NSTextField alloc] initWithFrame:NSMakeRect(0, 0, 250, 24)];
|
||||
[input setStringValue:defaultText ? defaultText : @""];
|
||||
[alert setAccessoryView:input];
|
||||
NSModalResponse result = [alert runModal];
|
||||
if (result == NSAlertFirstButtonReturn) {
|
||||
completionHandler([input stringValue]);
|
||||
} else {
|
||||
completionHandler(nil);
|
||||
}
|
||||
}
|
||||
|
||||
- (WKWebView *)webView:(WKWebView *)webView createWebViewWithConfiguration:(WKWebViewConfiguration *)configuration forNavigationAction:(WKNavigationAction *)navigationAction windowFeatures:(WKWindowFeatures *)windowFeatures {
|
||||
if (!navigationAction.targetFrame.isMainFrame) {
|
||||
return nil;
|
||||
}
|
||||
NSRect frame = NSMakeRect(100, 100, 800, 600);
|
||||
NSUInteger styleMask = NSWindowStyleMaskTitled | NSWindowStyleMaskClosable |
|
||||
NSWindowStyleMaskMiniaturizable | NSWindowStyleMaskResizable;
|
||||
NSWindow *newWindow = [[NSWindow alloc]
|
||||
initWithContentRect:frame
|
||||
styleMask:styleMask
|
||||
backing:NSBackingStoreBuffered
|
||||
defer:YES];
|
||||
WKWebView *newWebView = [[WKWebView alloc] initWithFrame:frame configuration:configuration];
|
||||
newWebView.navigationDelegate = self;
|
||||
newWebView.UIDelegate = self;
|
||||
[newWindow setContentView:newWebView];
|
||||
[newWindow makeKeyAndOrderFront:nil];
|
||||
return newWebView;
|
||||
}
|
||||
|
||||
- (void)webView:(WKWebView *)webView runOpenPanelWithParameters:(WKOpenPanelParameters *)parameters initiatedByFrame:(WKFrameInfo *)frame completionHandler:(void (^)(NSArray<NSURL *> *URLs))completionHandler {
|
||||
NSOpenPanel *openPanel = [NSOpenPanel openPanel];
|
||||
openPanel.allowsMultipleSelection = parameters.allowsMultipleSelection;
|
||||
openPanel.canChooseDirectories = parameters.allowsDirectories;
|
||||
if ([openPanel runModal] == NSModalResponseOK) {
|
||||
completionHandler(openPanel.URLs);
|
||||
} else {
|
||||
completionHandler(nil);
|
||||
}
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
struct MacWebViewState {
|
||||
WKWebView* webView;
|
||||
LNXRNTWebViewDelegate* delegate;
|
||||
NSWindow* detachedWindow;
|
||||
bool isDetached;
|
||||
};
|
||||
|
||||
extern "C" NSWindow* kinc_get_mac_window_handle(int window_index);
|
||||
|
||||
int platformCreate(WebViewInstance* wv) {
|
||||
auto* state = new MacWebViewState();
|
||||
state->webView = nullptr;
|
||||
state->delegate = nullptr;
|
||||
state->detachedWindow = nullptr;
|
||||
state->isDetached = (wv->parentWindow < 0);
|
||||
wv->platformHandle = state;
|
||||
|
||||
WKWebViewConfiguration* config = [[WKWebViewConfiguration alloc] init];
|
||||
|
||||
WKUserContentController* contentController = [[WKUserContentController alloc] init];
|
||||
state->delegate = [[LNXRNTWebViewDelegate alloc] init];
|
||||
state->delegate->webviewId = wv->id;
|
||||
state->delegate->isDetached = state->isDetached;
|
||||
[contentController addScriptMessageHandler:state->delegate name:@"lnxrnt"];
|
||||
config.userContentController = contentController;
|
||||
|
||||
CGFloat scale = 1.0;
|
||||
if (!state->isDetached) {
|
||||
NSWindow* kincWindow = kinc_get_mac_window_handle(wv->parentWindow);
|
||||
if (kincWindow) {
|
||||
scale = [kincWindow backingScaleFactor];
|
||||
}
|
||||
}
|
||||
CGFloat ptWidth = wv->width / scale;
|
||||
CGFloat ptHeight = wv->height / scale;
|
||||
NSRect frame = NSMakeRect(wv->x, wv->y, ptWidth, ptHeight);
|
||||
state->webView = [[WKWebView alloc] initWithFrame:frame configuration:config];
|
||||
state->webView.navigationDelegate = state->delegate;
|
||||
state->webView.UIDelegate = state->delegate;
|
||||
|
||||
if (wv->transparent) {
|
||||
[state->webView setValue:@NO forKey:@"opaque"];
|
||||
[state->webView setValue:@NO forKey:@"drawsBackground"];
|
||||
}
|
||||
|
||||
if (state->isDetached) {
|
||||
NSUInteger styleMask = NSWindowStyleMaskTitled | NSWindowStyleMaskClosable |
|
||||
NSWindowStyleMaskMiniaturizable | NSWindowStyleMaskResizable;
|
||||
state->detachedWindow = [[NSWindow alloc]
|
||||
initWithContentRect:frame
|
||||
styleMask:styleMask
|
||||
backing:NSBackingStoreBuffered
|
||||
defer:YES];
|
||||
[state->detachedWindow setTitle:[NSString stringWithUTF8String:wv->title.c_str()]];
|
||||
[state->detachedWindow setContentView:state->webView];
|
||||
[state->detachedWindow setDelegate:state->delegate];
|
||||
state->delegate->detachedWindow = state->detachedWindow;
|
||||
} else {
|
||||
NSWindow* kincWindow = kinc_get_mac_window_handle(wv->parentWindow);
|
||||
if (!kincWindow) {
|
||||
kinc_log(KINC_LOG_LEVEL_ERROR, "Failed to get Kinc NSWindow for webview window %d", wv->parentWindow);
|
||||
[state->webView release];
|
||||
[contentController release];
|
||||
[state->delegate release];
|
||||
delete state;
|
||||
wv->platformHandle = nullptr;
|
||||
return -1;
|
||||
}
|
||||
|
||||
NSView* contentView = [kincWindow contentView];
|
||||
CGFloat flippedY = [contentView frame].size.height - wv->y - ptHeight;
|
||||
[state->webView setFrameOrigin:NSMakePoint(wv->x, flippedY)];
|
||||
[contentView addSubview:state->webView positioned:NSWindowAbove relativeTo:nil];
|
||||
}
|
||||
|
||||
return wv->id;
|
||||
}
|
||||
|
||||
void platformDestroy(WebViewInstance* wv) {
|
||||
auto* state = (MacWebViewState*)wv->platformHandle;
|
||||
if (!state) return;
|
||||
|
||||
if (state->webView) {
|
||||
[state->webView removeFromSuperview];
|
||||
[state->webView release];
|
||||
}
|
||||
if (state->detachedWindow) {
|
||||
[state->detachedWindow close];
|
||||
}
|
||||
if (state->delegate) {
|
||||
[state->delegate release];
|
||||
}
|
||||
delete state;
|
||||
wv->platformHandle = nullptr;
|
||||
}
|
||||
|
||||
void platformLoadHTML(WebViewInstance* wv, const std::string& html) {
|
||||
auto* state = (MacWebViewState*)wv->platformHandle;
|
||||
if (!state || !state->webView) return;
|
||||
NSString* nsHtml = [NSString stringWithUTF8String:html.c_str()];
|
||||
[state->webView loadHTMLString:nsHtml baseURL:nil];
|
||||
}
|
||||
|
||||
void platformLoadURL(WebViewInstance* wv, const std::string& url) {
|
||||
auto* state = (MacWebViewState*)wv->platformHandle;
|
||||
if (!state || !state->webView) return;
|
||||
NSString* nsUrl = [NSString stringWithUTF8String:url.c_str()];
|
||||
NSURL* nsURL = [NSURL URLWithString:nsUrl];
|
||||
[state->webView loadRequest:[NSURLRequest requestWithURL:nsURL]];
|
||||
}
|
||||
|
||||
void platformEvalJS(WebViewInstance* wv, const std::string& js) {
|
||||
auto* state = (MacWebViewState*)wv->platformHandle;
|
||||
if (!state || !state->webView) return;
|
||||
NSString* nsJs = [NSString stringWithUTF8String:js.c_str()];
|
||||
[state->webView evaluateJavaScript:nsJs completionHandler:nil];
|
||||
}
|
||||
|
||||
void platformEvalJSAsync(WebViewInstance* wv, const std::string& js) {
|
||||
auto* state = (MacWebViewState*)wv->platformHandle;
|
||||
if (!state || !state->webView) return;
|
||||
NSString* nsJs = [NSString stringWithUTF8String:js.c_str()];
|
||||
int webviewId = wv->id;
|
||||
[state->webView evaluateJavaScript:nsJs completionHandler:^(id result, NSError* error) {
|
||||
std::string resultStr;
|
||||
if (error) {
|
||||
resultStr = "null";
|
||||
} else if (result == nil) {
|
||||
resultStr = "null";
|
||||
} else if ([result isKindOfClass:[NSString class]]) {
|
||||
resultStr = std::string([[result description] UTF8String]);
|
||||
} else if ([result isKindOfClass:[NSNumber class]]) {
|
||||
resultStr = std::string([[result description] UTF8String]);
|
||||
} else {
|
||||
NSError* jsonErr = nil;
|
||||
NSData* jsonData = [NSJSONSerialization dataWithJSONObject:result options:0 error:&jsonErr];
|
||||
if (jsonData && !jsonErr) {
|
||||
NSString* jsonStr = [[NSString alloc] initWithData:jsonData encoding:NSUTF8StringEncoding];
|
||||
resultStr = std::string([jsonStr UTF8String]);
|
||||
} else {
|
||||
resultStr = std::string([[result description] UTF8String]);
|
||||
}
|
||||
}
|
||||
WebViewManager::instance().dispatchEvalResult(webviewId, resultStr);
|
||||
}];
|
||||
}
|
||||
|
||||
void platformShow(WebViewInstance* wv) {
|
||||
auto* state = (MacWebViewState*)wv->platformHandle;
|
||||
if (!state) return;
|
||||
if (state->isDetached && state->detachedWindow) {
|
||||
[state->detachedWindow makeKeyAndOrderFront:nil];
|
||||
} else if (state->webView) {
|
||||
[state->webView setHidden:NO];
|
||||
}
|
||||
}
|
||||
|
||||
void platformHide(WebViewInstance* wv) {
|
||||
auto* state = (MacWebViewState*)wv->platformHandle;
|
||||
if (!state) return;
|
||||
if (state->isDetached && state->detachedWindow) {
|
||||
[state->detachedWindow orderOut:nil];
|
||||
} else if (state->webView) {
|
||||
[state->webView setHidden:YES];
|
||||
}
|
||||
}
|
||||
|
||||
void platformResize(WebViewInstance* wv, int width, int height) {
|
||||
auto* state = (MacWebViewState*)wv->platformHandle;
|
||||
if (!state || !state->webView) return;
|
||||
|
||||
if (state->isDetached && state->detachedWindow) {
|
||||
NSRect frame = NSMakeRect(wv->x, wv->y, width, height);
|
||||
[state->detachedWindow setFrame:frame display:YES];
|
||||
} else {
|
||||
NSView* superview = [state->webView superview];
|
||||
CGFloat scale = [[superview window] backingScaleFactor];
|
||||
if (scale == 0) scale = 1.0;
|
||||
CGFloat ptW = width / scale;
|
||||
CGFloat ptH = height / scale;
|
||||
if (superview) {
|
||||
CGFloat flippedY = [superview frame].size.height - wv->y - ptH;
|
||||
[state->webView setFrame:NSMakeRect(wv->x, flippedY, ptW, ptH)];
|
||||
} else {
|
||||
[state->webView setFrameSize:NSMakeSize(ptW, ptH)];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void platformMove(WebViewInstance* wv, int x, int y) {
|
||||
auto* state = (MacWebViewState*)wv->platformHandle;
|
||||
if (!state || !state->webView) return;
|
||||
|
||||
if (state->isDetached && state->detachedWindow) {
|
||||
[state->detachedWindow setFrameOrigin:NSMakePoint(x, y)];
|
||||
} else {
|
||||
NSView* superview = [state->webView superview];
|
||||
if (superview) {
|
||||
CGFloat scale = [[superview window] backingScaleFactor];
|
||||
if (scale == 0) scale = 1.0;
|
||||
CGFloat ptH = wv->height / scale;
|
||||
CGFloat flippedY = [superview frame].size.height - y - ptH;
|
||||
[state->webView setFrameOrigin:NSMakePoint(x, flippedY)];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void platformSetTransparent(WebViewInstance* wv, bool transparent) {
|
||||
auto* state = (MacWebViewState*)wv->platformHandle;
|
||||
if (!state || !state->webView) return;
|
||||
[state->webView setValue:(transparent ? @NO : @YES) forKey:@"opaque"];
|
||||
[state->webView setValue:(transparent ? @NO : @YES) forKey:@"drawsBackground"];
|
||||
}
|
||||
|
||||
void platformSetClickThrough(WebViewInstance* wv, bool enabled) {
|
||||
auto* state = (MacWebViewState*)wv->platformHandle;
|
||||
if (!state || !state->webView) return;
|
||||
if (enabled) {
|
||||
[state->webView setAcceptsTouchEvents:NO];
|
||||
[state->webView evaluateJavaScript:
|
||||
@"document.body.style.pointerEvents = 'none';"
|
||||
completionHandler:nil];
|
||||
} else {
|
||||
[state->webView setAcceptsTouchEvents:YES];
|
||||
[state->webView evaluateJavaScript:
|
||||
@"document.body.style.pointerEvents = 'auto';"
|
||||
completionHandler:nil];
|
||||
}
|
||||
}
|
||||
|
||||
void platformSetTitle(WebViewInstance* wv, const std::string& title) {
|
||||
auto* state = (MacWebViewState*)wv->platformHandle;
|
||||
if (!state || !state->isDetached || !state->detachedWindow) return;
|
||||
[state->detachedWindow setTitle:[NSString stringWithUTF8String:title.c_str()]];
|
||||
}
|
||||
|
||||
void platformSend(WebViewInstance* wv, const std::string& message) {
|
||||
auto* state = (MacWebViewState*)wv->platformHandle;
|
||||
if (!state || !state->webView) return;
|
||||
NSString* escaped = [[NSString stringWithUTF8String:message.c_str()]
|
||||
stringByReplacingOccurrencesOfString:@"\\" withString:@"\\\\"];
|
||||
escaped = [escaped stringByReplacingOccurrencesOfString:@"'" withString:@"\\'"];
|
||||
escaped = [escaped stringByReplacingOccurrencesOfString:@"\n" withString:@"\\n"];
|
||||
escaped = [escaped stringByReplacingOccurrencesOfString:@"\r" withString:@"\\r"];
|
||||
NSString* js = [NSString stringWithFormat:
|
||||
@"if(window.__lnxrntOnMessage)window.__lnxrntOnMessage('%@');", escaped];
|
||||
[state->webView evaluateJavaScript:js completionHandler:nil];
|
||||
}
|
||||
|
||||
void platformGoBack(WebViewInstance* wv) {
|
||||
auto* state = (MacWebViewState*)wv->platformHandle;
|
||||
if (!state || !state->webView) return;
|
||||
if ([state->webView canGoBack]) [state->webView goBack];
|
||||
}
|
||||
|
||||
void platformGoForward(WebViewInstance* wv) {
|
||||
auto* state = (MacWebViewState*)wv->platformHandle;
|
||||
if (!state || !state->webView) return;
|
||||
if ([state->webView canGoForward]) [state->webView goForward];
|
||||
}
|
||||
|
||||
void platformReload(WebViewInstance* wv) {
|
||||
auto* state = (MacWebViewState*)wv->platformHandle;
|
||||
if (!state || !state->webView) return;
|
||||
[state->webView reload];
|
||||
}
|
||||
|
||||
bool platformCanGoBack(WebViewInstance* wv) {
|
||||
auto* state = (MacWebViewState*)wv->platformHandle;
|
||||
if (!state || !state->webView) return false;
|
||||
return [state->webView canGoBack];
|
||||
}
|
||||
|
||||
bool platformCanGoForward(WebViewInstance* wv) {
|
||||
auto* state = (MacWebViewState*)wv->platformHandle;
|
||||
if (!state || !state->webView) return false;
|
||||
return [state->webView canGoForward];
|
||||
}
|
||||
|
||||
std::string platformGetURL(WebViewInstance* wv) {
|
||||
auto* state = (MacWebViewState*)wv->platformHandle;
|
||||
if (!state || !state->webView) return "";
|
||||
NSString* url = [state->webView.URL absoluteString];
|
||||
return url ? std::string([url UTF8String]) : "";
|
||||
}
|
||||
|
||||
std::string platformGetTitle(WebViewInstance* wv) {
|
||||
auto* state = (MacWebViewState*)wv->platformHandle;
|
||||
if (!state || !state->webView) return "";
|
||||
NSString* title = [state->webView title];
|
||||
return title ? std::string([title UTF8String]) : "";
|
||||
}
|
||||
|
||||
void platformMinimize(WebViewInstance* wv) {
|
||||
auto* state = (MacWebViewState*)wv->platformHandle;
|
||||
if (!state || !state->isDetached || !state->detachedWindow) return;
|
||||
[state->detachedWindow miniaturize:nil];
|
||||
}
|
||||
|
||||
void platformMaximize(WebViewInstance* wv) {
|
||||
auto* state = (MacWebViewState*)wv->platformHandle;
|
||||
if (!state || !state->isDetached || !state->detachedWindow) return;
|
||||
[state->detachedWindow zoom:nil];
|
||||
}
|
||||
|
||||
void platformRestore(WebViewInstance* wv) {
|
||||
auto* state = (MacWebViewState*)wv->platformHandle;
|
||||
if (!state || !state->isDetached || !state->detachedWindow) return;
|
||||
if ([state->detachedWindow isMiniaturized]) [state->detachedWindow deminiaturize:nil];
|
||||
if ([state->detachedWindow isZoomed]) [state->detachedWindow zoom:nil];
|
||||
}
|
||||
|
||||
void platformSetFullscreen(WebViewInstance* wv, bool fullscreen) {
|
||||
auto* state = (MacWebViewState*)wv->platformHandle;
|
||||
if (!state || !state->isDetached || !state->detachedWindow) return;
|
||||
if (fullscreen) {
|
||||
[state->detachedWindow toggleFullScreen:nil];
|
||||
} else {
|
||||
if (([state->detachedWindow styleMask] & NSWindowStyleMaskFullScreen) != 0) {
|
||||
[state->detachedWindow toggleFullScreen:nil];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool platformIsFullscreen(WebViewInstance* wv) {
|
||||
auto* state = (MacWebViewState*)wv->platformHandle;
|
||||
if (!state || !state->isDetached || !state->detachedWindow) return false;
|
||||
return ([state->detachedWindow styleMask] & NSWindowStyleMaskFullScreen) != 0;
|
||||
}
|
||||
|
||||
void platformEnableDevTools(WebViewInstance* wv, bool enabled) {
|
||||
auto* state = (MacWebViewState*)wv->platformHandle;
|
||||
if (!state || !state->webView) return;
|
||||
[state->webView.configuration.preferences setValue:@(enabled) forKey:@"developerExtrasEnabled"];
|
||||
}
|
||||
|
||||
void platformSetContextMenuEnabled(WebViewInstance* wv, bool enabled) {
|
||||
auto* state = (MacWebViewState*)wv->platformHandle;
|
||||
if (!state || !state->webView) return;
|
||||
if (!enabled) {
|
||||
NSString* js = @"document.addEventListener('contextmenu', function(e){e.preventDefault();}, true);";
|
||||
[state->webView evaluateJavaScript:js completionHandler:nil];
|
||||
}
|
||||
}
|
||||
|
||||
void platformTick() {
|
||||
// WKWebView callbacks are in NSRunLoop already integrated with Kinc loop.
|
||||
}
|
||||
|
||||
#endif
|
||||
#endif
|
||||
595
Sources/webview_win.cpp
Normal file
595
Sources/webview_win.cpp
Normal file
@ -0,0 +1,595 @@
|
||||
#ifdef WITH_WEBVIEW
|
||||
#ifdef KINC_WINDOWS
|
||||
|
||||
#include "webview.h"
|
||||
#include <kinc/log.h>
|
||||
#include <kinc/window.h>
|
||||
#include <windows.h>
|
||||
#include <unknwn.h>
|
||||
#include <comdef.h>
|
||||
#include <vector>
|
||||
#include <algorithm>
|
||||
|
||||
#include "WebView2.h"
|
||||
#include "WebView2EnvironmentCreator.h"
|
||||
|
||||
#pragma comment(lib, "WebView2Loader.lib")
|
||||
|
||||
struct WinWebViewState {
|
||||
ICoreWebView2Controller* controller;
|
||||
ICoreWebView2* webview;
|
||||
HWND parentHwnd;
|
||||
HWND detachedHwnd;
|
||||
bool isDetached;
|
||||
bool destroyed;
|
||||
bool pending;
|
||||
EventRegistrationToken messageToken;
|
||||
EventRegistrationToken navCompletedToken;
|
||||
EventRegistrationToken errorToken;
|
||||
int webviewId;
|
||||
int x, y, width, height;
|
||||
bool transparent;
|
||||
std::wstring pendingHTML;
|
||||
std::wstring pendingURL;
|
||||
};
|
||||
|
||||
static ICoreWebView2Environment* g_webview2Env = nullptr;
|
||||
static bool g_envCreating = false;
|
||||
static std::vector<WebViewInstance*> g_pendingCreates;
|
||||
|
||||
static std::wstring utf8ToWide(const std::string& str) {
|
||||
if (str.empty()) return std::wstring();
|
||||
int len = MultiByteToWideChar(CP_UTF8, 0, str.c_str(), (int)str.length(), nullptr, 0);
|
||||
std::wstring wstr(len, 0);
|
||||
MultiByteToWideChar(CP_UTF8, 0, str.c_str(), (int)str.length(), &wstr[0], len);
|
||||
return wstr;
|
||||
}
|
||||
|
||||
static void createController(WinWebViewState* state);
|
||||
|
||||
static HRESULT STDMETHODCALLTYPE onEnvCreated(HRESULT result, ICoreWebView2Environment* env) {
|
||||
if (FAILED(result) || !env) {
|
||||
kinc_log(KINC_LOG_LEVEL_ERROR, "[WebView] Failed to create WebView2 environment: 0x%08X", result);
|
||||
g_envCreating = false;
|
||||
return result;
|
||||
}
|
||||
g_webview2Env = env;
|
||||
g_webview2Env->AddRef();
|
||||
g_envCreating = false;
|
||||
|
||||
auto pending = std::move(g_pendingCreates);
|
||||
for (auto* wv : pending) {
|
||||
auto* state = (WinWebViewState*)wv->platformHandle;
|
||||
if (!state || state->destroyed) continue;
|
||||
state->pending = false;
|
||||
createController(state);
|
||||
}
|
||||
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
static void ensureWebView2Environment() {
|
||||
if (g_webview2Env || g_envCreating) return;
|
||||
g_envCreating = true;
|
||||
|
||||
HRESULT hr = CreateCoreWebView2EnvironmentWithOptions(
|
||||
nullptr, nullptr, nullptr,
|
||||
Callback<ICoreWebView2CreateCoreWebView2EnvironmentCompletedHandler>(
|
||||
onEnvCreated).Get());
|
||||
|
||||
if (FAILED(hr)) {
|
||||
kinc_log(KINC_LOG_LEVEL_ERROR, "[WebView] CreateCoreWebView2EnvironmentWithOptions failed: 0x%08X", hr);
|
||||
g_envCreating = false;
|
||||
}
|
||||
}
|
||||
|
||||
static HRESULT STDMETHODCALLTYPE onMessageReceived(
|
||||
ICoreWebView2* sender, ICoreWebView2WebMessageReceivedEventArgs* args, DWORD* webviewIdPtr) {
|
||||
|
||||
LPWSTR message = nullptr;
|
||||
args->TryGetWebMessageAsString(&message);
|
||||
if (message) {
|
||||
int len = WideCharToMultiByte(CP_UTF8, 0, message, -1, nullptr, 0, nullptr, nullptr);
|
||||
std::string utf8Message(len - 1, 0);
|
||||
WideCharToMultiByte(CP_UTF8, 0, message, -1, &utf8Message[0], len, nullptr, nullptr);
|
||||
|
||||
int webviewId = (int)(intptr_t)webviewIdPtr;
|
||||
WebViewManager::instance().dispatchMessage(webviewId, utf8Message);
|
||||
|
||||
CoTaskMemFree(message);
|
||||
}
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
static HRESULT STDMETHODCALLTYPE onNavCompleted(
|
||||
ICoreWebView2* sender, ICoreWebView2NavigationCompletedEventArgs* args, DWORD* webviewIdPtr) {
|
||||
int webviewId = (int)(intptr_t)webviewIdPtr;
|
||||
WebViewManager::instance().dispatchLoad(webviewId);
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
static HRESULT STDMETHODCALLTYPE onProcessFailed(
|
||||
ICoreWebView2* sender, ICoreWebView2ProcessFailedEventArgs* args, DWORD* webviewIdPtr) {
|
||||
int webviewId = (int)(intptr_t)webviewIdPtr;
|
||||
WebViewManager::instance().dispatchError(webviewId, "WebView2 process failed");
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
static void createController(WinWebViewState* state) {
|
||||
g_webview2Env->CreateCoreWebView2Controller(
|
||||
state->parentHwnd,
|
||||
Callback<ICoreWebView2CreateCoreWebView2ControllerCompletedHandler>(
|
||||
[state](HRESULT result, ICoreWebView2Controller* controller) -> HRESULT {
|
||||
if (state->destroyed) {
|
||||
if (controller) controller->Release();
|
||||
delete state;
|
||||
return S_OK;
|
||||
}
|
||||
if (FAILED(result) || !controller) {
|
||||
kinc_log(KINC_LOG_LEVEL_ERROR, "CreateController for webview failed: 0x%08X", result);
|
||||
return result;
|
||||
}
|
||||
|
||||
state->controller = controller;
|
||||
controller->get_CoreWebView2(&state->webview);
|
||||
|
||||
RECT bounds;
|
||||
if (state->isDetached) {
|
||||
GetClientRect(state->detachedHwnd, &bounds);
|
||||
} else {
|
||||
bounds.left = state->x;
|
||||
bounds.top = state->y;
|
||||
bounds.right = state->x + state->width;
|
||||
bounds.bottom = state->y + state->height;
|
||||
}
|
||||
controller->put_Bounds(bounds);
|
||||
|
||||
if (state->transparent) {
|
||||
COREWEBVIEW2_COLOR bgColor = {0, 0, 0, 0};
|
||||
ICoreWebView2Controller2* ctrl2;
|
||||
if (SUCCEEDED(controller->QueryInterface(IID_PPV_ARGS(&ctrl2)))) {
|
||||
ctrl2->put_DefaultBackgroundColor(bgColor);
|
||||
ctrl2->Release();
|
||||
}
|
||||
}
|
||||
|
||||
state->webview->add_WebMessageReceived(
|
||||
Callback<ICoreWebView2WebMessageReceivedEventHandler>(
|
||||
[state](ICoreWebView2* s, ICoreWebView2WebMessageReceivedEventArgs* a) -> HRESULT {
|
||||
return onMessageReceived(s, a, (DWORD*)(intptr_t)state->webviewId);
|
||||
}).Get(),
|
||||
&state->messageToken);
|
||||
|
||||
state->webview->add_NavigationCompleted(
|
||||
Callback<ICoreWebView2NavigationCompletedEventHandler>(
|
||||
[state](ICoreWebView2* s, ICoreWebView2NavigationCompletedEventArgs* a) -> HRESULT {
|
||||
return onNavCompleted(s, a, (DWORD*)(intptr_t)state->webviewId);
|
||||
}).Get(),
|
||||
&state->navCompletedToken);
|
||||
|
||||
state->webview->add_ProcessFailed(
|
||||
Callback<ICoreWebView2ProcessFailedEventHandler>(
|
||||
[state](ICoreWebView2* s, ICoreWebView2ProcessFailedEventArgs* a) -> HRESULT {
|
||||
return onProcessFailed(s, a, (DWORD*)(intptr_t)state->webviewId);
|
||||
}).Get(),
|
||||
&state->errorToken);
|
||||
|
||||
if (!state->pendingHTML.empty()) {
|
||||
state->webview->NavigateToString(state->pendingHTML.c_str());
|
||||
state->pendingHTML.clear();
|
||||
}
|
||||
if (!state->pendingURL.empty()) {
|
||||
state->webview->Navigate(state->pendingURL.c_str());
|
||||
state->pendingURL.clear();
|
||||
}
|
||||
|
||||
return S_OK;
|
||||
}).Get());
|
||||
}
|
||||
|
||||
int platformCreate(WebViewInstance* wv) {
|
||||
ensureWebView2Environment();
|
||||
|
||||
auto* state = new WinWebViewState();
|
||||
state->controller = nullptr;
|
||||
state->webview = nullptr;
|
||||
state->detachedHwnd = nullptr;
|
||||
state->isDetached = (wv->parentWindow < 0);
|
||||
state->destroyed = false;
|
||||
state->pending = false;
|
||||
state->messageToken = {0};
|
||||
state->navCompletedToken = {0};
|
||||
state->errorToken = {0};
|
||||
state->webviewId = wv->id;
|
||||
state->x = wv->x;
|
||||
state->y = wv->y;
|
||||
state->width = wv->width;
|
||||
state->height = wv->height;
|
||||
state->transparent = wv->transparent;
|
||||
wv->platformHandle = state;
|
||||
|
||||
if (state->isDetached) {
|
||||
static const wchar_t* className = L"LNXRNT_WebViewWindow";
|
||||
static bool registered = false;
|
||||
if (!registered) {
|
||||
WNDCLASSW wc = {};
|
||||
wc.lpfnWndProc = DefWindowProcW;
|
||||
wc.hInstance = GetModuleHandle(nullptr);
|
||||
wc.lpszClassName = className;
|
||||
RegisterClassW(&wc);
|
||||
registered = true;
|
||||
}
|
||||
|
||||
state->detachedHwnd = CreateWindowExW(
|
||||
WS_EX_APPWINDOW, className,
|
||||
utf8ToWide(wv->title).c_str(),
|
||||
WS_OVERLAPPEDWINDOW,
|
||||
wv->x, wv->y, wv->width, wv->height,
|
||||
nullptr, nullptr, GetModuleHandle(nullptr), nullptr);
|
||||
state->parentHwnd = state->detachedHwnd;
|
||||
} else {
|
||||
extern struct HWND__ *kinc_windows_window_handle(int window_index);
|
||||
state->parentHwnd = kinc_windows_window_handle(wv->parentWindow);
|
||||
if (!state->parentHwnd) {
|
||||
kinc_log(KINC_LOG_LEVEL_ERROR, "[WebView] Failed to get Kinc HWND for window %d", wv->parentWindow);
|
||||
delete state;
|
||||
wv->platformHandle = nullptr;
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
if (!g_webview2Env) {
|
||||
state->pending = true;
|
||||
g_pendingCreates.push_back(wv);
|
||||
kinc_log(KINC_LOG_LEVEL_INFO, "[WebView] Environment not ready, queuing creation id=%d", wv->id);
|
||||
return wv->id;
|
||||
}
|
||||
|
||||
createController(state);
|
||||
|
||||
return wv->id;
|
||||
}
|
||||
|
||||
void platformDestroy(WebViewInstance* wv) {
|
||||
auto* state = (WinWebViewState*)wv->platformHandle;
|
||||
if (!state) return;
|
||||
|
||||
if (state->pending) {
|
||||
g_pendingCreates.erase(
|
||||
std::remove(g_pendingCreates.begin(), g_pendingCreates.end(), wv),
|
||||
g_pendingCreates.end());
|
||||
state->pending = false;
|
||||
if (state->detachedHwnd) {
|
||||
DestroyWindow(state->detachedHwnd);
|
||||
state->detachedHwnd = nullptr;
|
||||
}
|
||||
delete state;
|
||||
wv->platformHandle = nullptr;
|
||||
return;
|
||||
}
|
||||
|
||||
if (!state->controller) {
|
||||
state->destroyed = true;
|
||||
wv->platformHandle = nullptr;
|
||||
if (state->detachedHwnd) {
|
||||
DestroyWindow(state->detachedHwnd);
|
||||
state->detachedHwnd = nullptr;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (state->webview) {
|
||||
if (state->messageToken.value) state->webview->remove_WebMessageReceived(state->messageToken);
|
||||
if (state->navCompletedToken.value) state->webview->remove_NavigationCompleted(state->navCompletedToken);
|
||||
if (state->errorToken.value) state->webview->remove_ProcessFailed(state->errorToken);
|
||||
state->webview->Release();
|
||||
}
|
||||
if (state->controller) {
|
||||
state->controller->Close();
|
||||
state->controller->Release();
|
||||
}
|
||||
if (state->detachedHwnd) {
|
||||
DestroyWindow(state->detachedHwnd);
|
||||
}
|
||||
delete state;
|
||||
wv->platformHandle = nullptr;
|
||||
}
|
||||
|
||||
void platformLoadHTML(WebViewInstance* wv, const std::string& html) {
|
||||
auto* state = (WinWebViewState*)wv->platformHandle;
|
||||
if (!state) return;
|
||||
if (!state->webview) {
|
||||
state->pendingHTML = utf8ToWide(html);
|
||||
state->pendingURL.clear();
|
||||
return;
|
||||
}
|
||||
state->webview->NavigateToString(utf8ToWide(html).c_str());
|
||||
}
|
||||
|
||||
void platformLoadURL(WebViewInstance* wv, const std::string& url) {
|
||||
auto* state = (WinWebViewState*)wv->platformHandle;
|
||||
if (!state) return;
|
||||
if (!state->webview) {
|
||||
state->pendingURL = utf8ToWide(url);
|
||||
state->pendingHTML.clear();
|
||||
return;
|
||||
}
|
||||
state->webview->Navigate(utf8ToWide(url).c_str());
|
||||
}
|
||||
|
||||
void platformEvalJS(WebViewInstance* wv, const std::string& js) {
|
||||
auto* state = (WinWebViewState*)wv->platformHandle;
|
||||
if (!state || !state->webview) return;
|
||||
state->webview->ExecuteScript(utf8ToWide(js).c_str(), nullptr);
|
||||
}
|
||||
|
||||
void platformEvalJSAsync(WebViewInstance* wv, const std::string& js) {
|
||||
auto* state = (WinWebViewState*)wv->platformHandle;
|
||||
if (!state || !state->webview) return;
|
||||
int webviewId = wv->id;
|
||||
state->webview->ExecuteScript(utf8ToWide(js).c_str(),
|
||||
Callback<ICoreWebView2ExecuteScriptCompletedHandler>(
|
||||
[webviewId](HRESULT errorCode, LPCWSTR resultJson) -> HRESULT {
|
||||
std::string resultStr;
|
||||
if (FAILED(errorCode) || resultJson == nullptr) {
|
||||
resultStr = "null";
|
||||
} else {
|
||||
std::wstring wstr(resultJson);
|
||||
resultStr = std::string(wstr.begin(), wstr.end());
|
||||
}
|
||||
WebViewManager::instance().dispatchEvalResult(webviewId, resultStr);
|
||||
return S_OK;
|
||||
}).Get());
|
||||
}
|
||||
|
||||
void platformShow(WebViewInstance* wv) {
|
||||
auto* state = (WinWebViewState*)wv->platformHandle;
|
||||
if (!state) return;
|
||||
if (state->isDetached && state->detachedHwnd) {
|
||||
ShowWindow(state->detachedHwnd, SW_SHOW);
|
||||
}
|
||||
if (state->controller) {
|
||||
state->controller->put_IsVisible(TRUE);
|
||||
}
|
||||
}
|
||||
|
||||
void platformHide(WebViewInstance* wv) {
|
||||
auto* state = (WinWebViewState*)wv->platformHandle;
|
||||
if (!state) return;
|
||||
if (state->isDetached && state->detachedHwnd) {
|
||||
ShowWindow(state->detachedHwnd, SW_HIDE);
|
||||
}
|
||||
if (state->controller) {
|
||||
state->controller->put_IsVisible(FALSE);
|
||||
}
|
||||
}
|
||||
|
||||
void platformResize(WebViewInstance* wv, int width, int height) {
|
||||
auto* state = (WinWebViewState*)wv->platformHandle;
|
||||
if (!state || !state->controller) return;
|
||||
|
||||
if (state->isDetached && state->detachedHwnd) {
|
||||
RECT rect = {0, 0, width, height};
|
||||
AdjustWindowRect(&rect, WS_OVERLAPPEDWINDOW, FALSE);
|
||||
SetWindowPos(state->detachedHwnd, nullptr, 0, 0,
|
||||
rect.right - rect.left, rect.bottom - rect.top, SWP_NOMOVE | SWP_NOZORDER);
|
||||
}
|
||||
|
||||
RECT bounds;
|
||||
if (state->isDetached) {
|
||||
GetClientRect(state->detachedHwnd, &bounds);
|
||||
} else {
|
||||
bounds.left = wv->x;
|
||||
bounds.top = wv->y;
|
||||
bounds.right = wv->x + width;
|
||||
bounds.bottom = wv->y + height;
|
||||
}
|
||||
state->controller->put_Bounds(bounds);
|
||||
}
|
||||
|
||||
void platformMove(WebViewInstance* wv, int x, int y) {
|
||||
auto* state = (WinWebViewState*)wv->platformHandle;
|
||||
if (!state) return;
|
||||
|
||||
if (state->isDetached && state->detachedHwnd) {
|
||||
SetWindowPos(state->detachedHwnd, nullptr, x, y, 0, 0, SWP_NOSIZE | SWP_NOZORDER);
|
||||
} else if (state->controller) {
|
||||
RECT bounds;
|
||||
bounds.left = x;
|
||||
bounds.top = y;
|
||||
bounds.right = x + wv->width;
|
||||
bounds.bottom = y + wv->height;
|
||||
state->controller->put_Bounds(bounds);
|
||||
}
|
||||
}
|
||||
|
||||
void platformSetTransparent(WebViewInstance* wv, bool transparent) {
|
||||
auto* state = (WinWebViewState*)wv->platformHandle;
|
||||
if (!state || !state->controller) return;
|
||||
|
||||
ICoreWebView2Controller2* ctrl2;
|
||||
if (SUCCEEDED(state->controller->QueryInterface(IID_PPV_ARGS(&ctrl2)))) {
|
||||
COREWEBVIEW2_COLOR bgColor;
|
||||
if (transparent) {
|
||||
bgColor = {0, 0, 0, 0};
|
||||
} else {
|
||||
bgColor = {255, 255, 255, 255};
|
||||
}
|
||||
ctrl2->put_DefaultBackgroundColor(bgColor);
|
||||
ctrl2->Release();
|
||||
}
|
||||
}
|
||||
|
||||
void platformSetClickThrough(WebViewInstance* wv, bool enabled) {
|
||||
auto* state = (WinWebViewState*)wv->platformHandle;
|
||||
if (!state || !state->controller) return;
|
||||
|
||||
if (enabled) {
|
||||
HWND childHwnd = FindWindowEx(state->parentHwnd, nullptr, L"WebView2", nullptr);
|
||||
if (childHwnd) {
|
||||
LONG_PTR exStyle = GetWindowLongPtr(childHwnd, GWL_EXSTYLE);
|
||||
SetWindowLongPtr(childHwnd, GWL_EXSTYLE, exStyle | WS_EX_TRANSPARENT);
|
||||
}
|
||||
} else {
|
||||
HWND childHwnd = FindWindowEx(state->parentHwnd, nullptr, L"WebView2", nullptr);
|
||||
if (childHwnd) {
|
||||
LONG_PTR exStyle = GetWindowLongPtr(childHwnd, GWL_EXSTYLE);
|
||||
SetWindowLongPtr(childHwnd, GWL_EXSTYLE, exStyle & ~WS_EX_TRANSPARENT);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void platformSetTitle(WebViewInstance* wv, const std::string& title) {
|
||||
auto* state = (WinWebViewState*)wv->platformHandle;
|
||||
if (!state || !state->isDetached || !state->detachedHwnd) return;
|
||||
SetWindowTextW(state->detachedHwnd, utf8ToWide(title).c_str());
|
||||
}
|
||||
|
||||
void platformSend(WebViewInstance* wv, const std::string& message) {
|
||||
auto* state = (WinWebViewState*)wv->platformHandle;
|
||||
if (!state || !state->webview) return;
|
||||
state->webview->PostWebMessageAsString(utf8ToWide(message).c_str());
|
||||
}
|
||||
|
||||
void platformGoBack(WebViewInstance* wv) {
|
||||
auto* state = (WinWebViewState*)wv->platformHandle;
|
||||
if (!state || !state->webview) return;
|
||||
BOOL canGo = FALSE;
|
||||
state->webview->get_CanGoBack(&canGo);
|
||||
if (canGo) state->webview->GoBack();
|
||||
}
|
||||
|
||||
void platformGoForward(WebViewInstance* wv) {
|
||||
auto* state = (WinWebViewState*)wv->platformHandle;
|
||||
if (!state || !state->webview) return;
|
||||
BOOL canGo = FALSE;
|
||||
state->webview->get_CanGoForward(&canGo);
|
||||
if (canGo) state->webview->GoForward();
|
||||
}
|
||||
|
||||
void platformReload(WebViewInstance* wv) {
|
||||
auto* state = (WinWebViewState*)wv->platformHandle;
|
||||
if (!state || !state->webview) return;
|
||||
state->webview->Reload();
|
||||
}
|
||||
|
||||
bool platformCanGoBack(WebViewInstance* wv) {
|
||||
auto* state = (WinWebViewState*)wv->platformHandle;
|
||||
if (!state || !state->webview) return false;
|
||||
BOOL canGo = FALSE;
|
||||
state->webview->get_CanGoBack(&canGo);
|
||||
return canGo != FALSE;
|
||||
}
|
||||
|
||||
bool platformCanGoForward(WebViewInstance* wv) {
|
||||
auto* state = (WinWebViewState*)wv->platformHandle;
|
||||
if (!state || !state->webview) return false;
|
||||
BOOL canGo = FALSE;
|
||||
state->webview->get_CanGoForward(&canGo);
|
||||
return canGo != FALSE;
|
||||
}
|
||||
|
||||
std::string platformGetURL(WebViewInstance* wv) {
|
||||
auto* state = (WinWebViewState*)wv->platformHandle;
|
||||
if (!state || !state->webview) return "";
|
||||
LPWSTR uri = nullptr;
|
||||
state->webview->get_Source(&uri);
|
||||
std::string result;
|
||||
if (uri) {
|
||||
int len = WideCharToMultiByte(CP_UTF8, 0, uri, -1, nullptr, 0, nullptr, nullptr);
|
||||
result.resize(len - 1);
|
||||
WideCharToMultiByte(CP_UTF8, 0, uri, -1, &result[0], len, nullptr, nullptr);
|
||||
CoTaskMemFree(uri);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
std::string platformGetTitle(WebViewInstance* wv) {
|
||||
auto* state = (WinWebViewState*)wv->platformHandle;
|
||||
if (!state || !state->webview) return "";
|
||||
LPWSTR title = nullptr;
|
||||
state->webview->get_DocumentTitle(&title);
|
||||
std::string result;
|
||||
if (title) {
|
||||
int len = WideCharToMultiByte(CP_UTF8, 0, title, -1, nullptr, 0, nullptr, nullptr);
|
||||
result.resize(len - 1);
|
||||
WideCharToMultiByte(CP_UTF8, 0, title, -1, &result[0], len, nullptr, nullptr);
|
||||
CoTaskMemFree(title);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
void platformMinimize(WebViewInstance* wv) {
|
||||
auto* state = (WinWebViewState*)wv->platformHandle;
|
||||
if (!state || !state->isDetached || !state->detachedHwnd) return;
|
||||
ShowWindow(state->detachedHwnd, SW_MINIMIZE);
|
||||
}
|
||||
|
||||
void platformMaximize(WebViewInstance* wv) {
|
||||
auto* state = (WinWebViewState*)wv->platformHandle;
|
||||
if (!state || !state->isDetached || !state->detachedHwnd) return;
|
||||
ShowWindow(state->detachedHwnd, SW_MAXIMIZE);
|
||||
}
|
||||
|
||||
void platformRestore(WebViewInstance* wv) {
|
||||
auto* state = (WinWebViewState*)wv->platformHandle;
|
||||
if (!state || !state->isDetached || !state->detachedHwnd) return;
|
||||
ShowWindow(state->detachedHwnd, SW_RESTORE);
|
||||
}
|
||||
|
||||
void platformSetFullscreen(WebViewInstance* wv, bool fullscreen) {
|
||||
auto* state = (WinWebViewState*)wv->platformHandle;
|
||||
if (!state || !state->isDetached || !state->detachedHwnd) return;
|
||||
LONG_PTR style = GetWindowLongPtr(state->detachedHwnd, GWL_STYLE);
|
||||
if (fullscreen) {
|
||||
SetWindowLongPtr(state->detachedHwnd, GWL_STYLE, style & ~WS_OVERLAPPEDWINDOW);
|
||||
HMONITOR hmon = MonitorFromWindow(state->detachedHwnd, MONITOR_DEFAULTTONEAREST);
|
||||
MONITORINFO mi = { sizeof(mi) };
|
||||
GetMonitorInfo(hmon, &mi);
|
||||
SetWindowPos(state->detachedHwnd, HWND_TOP, mi.rcMonitor.left, mi.rcMonitor.top,
|
||||
mi.rcMonitor.right - mi.rcMonitor.left, mi.rcMonitor.bottom - mi.rcMonitor.top,
|
||||
SWP_NOOWNERZORDER | SWP_FRAMECHANGED);
|
||||
} else {
|
||||
SetWindowLongPtr(state->detachedHwnd, GWL_STYLE, style | WS_OVERLAPPEDWINDOW);
|
||||
SetWindowPos(state->detachedHwnd, nullptr, 0, 0, 0, 0,
|
||||
SWP_NOMOVE | SWP_NOSIZE | SWP_NOZORDER | SWP_NOOWNERZORDER | SWP_FRAMECHANGED);
|
||||
}
|
||||
}
|
||||
|
||||
bool platformIsFullscreen(WebViewInstance* wv) {
|
||||
auto* state = (WinWebViewState*)wv->platformHandle;
|
||||
if (!state || !state->isDetached || !state->detachedHwnd) return false;
|
||||
LONG_PTR style = GetWindowLongPtr(state->detachedHwnd, GWL_STYLE);
|
||||
return (style & WS_OVERLAPPEDWINDOW) == 0;
|
||||
}
|
||||
|
||||
void platformEnableDevTools(WebViewInstance* wv, bool enabled) {
|
||||
auto* state = (WinWebViewState*)wv->platformHandle;
|
||||
if (!state || !state->webview) return;
|
||||
ICoreWebView2Settings* settings;
|
||||
if (SUCCEEDED(state->webview->get_Settings(&settings))) {
|
||||
ICoreWebView2Settings3* settings3;
|
||||
if (SUCCEEDED(settings->QueryInterface(IID_PPV_ARGS(&settings3)))) {
|
||||
settings3->put_AreDevToolsEnabled(enabled ? TRUE : FALSE);
|
||||
settings3->Release();
|
||||
}
|
||||
settings->Release();
|
||||
}
|
||||
}
|
||||
|
||||
void platformSetContextMenuEnabled(WebViewInstance* wv, bool enabled) {
|
||||
auto* state = (WinWebViewState*)wv->platformHandle;
|
||||
if (!state || !state->webview) return;
|
||||
ICoreWebView2Settings* settings;
|
||||
if (SUCCEEDED(state->webview->get_Settings(&settings))) {
|
||||
settings->put_AreDefaultContextMenusEnabled(enabled ? TRUE : FALSE);
|
||||
settings->Release();
|
||||
}
|
||||
}
|
||||
|
||||
void platformTick() {
|
||||
// COM event loop is pumped by Kincs loop.
|
||||
}
|
||||
|
||||
#endif
|
||||
#endif
|
||||
31
kfile.js
31
kfile.js
@ -10,6 +10,7 @@ let flags = {
|
||||
with_compute: true,
|
||||
with_networking: true,
|
||||
with_viewport: true,
|
||||
with_webview: true,
|
||||
with_uws: false,
|
||||
with_ssl: true,
|
||||
debug_network: true
|
||||
@ -62,6 +63,21 @@ if (flags.with_viewport && (platform === Platform.Windows || platform === Platfo
|
||||
project.addDefine('WITH_VIEWPORT');
|
||||
}
|
||||
|
||||
if (flags.with_webview && (platform === Platform.Windows || platform === Platform.Linux || platform === Platform.OSX)) {
|
||||
project.addDefine('WITH_WEBVIEW');
|
||||
project.addFile('Sources/webview.h');
|
||||
project.addFile('Sources/webview.cpp');
|
||||
if (platform === Platform.Windows) {
|
||||
project.addFile('Sources/webview_win.cpp');
|
||||
}
|
||||
else if (platform === Platform.OSX) {
|
||||
project.addFile('Sources/webview_mac.mm');
|
||||
}
|
||||
else if (platform === Platform.Linux) {
|
||||
project.addFile('Sources/webview_linux.cpp');
|
||||
}
|
||||
}
|
||||
|
||||
project.addFile('Sources/main.cpp');
|
||||
|
||||
|
||||
@ -124,6 +140,10 @@ if (platform === Platform.Windows) {
|
||||
project.addLib('Dwmapi'); // DWMWA_USE_IMMERSIVE_DARK_MODE
|
||||
project.addLib('winmm'); // timeGetTime for V8
|
||||
project.addLib(libdir + 'v8_monolith');
|
||||
if (flags.with_webview) {
|
||||
project.addIncludeDir('webview2/include');
|
||||
project.addLib('WebView2Loader.lib');
|
||||
}
|
||||
if (!flags.release) {
|
||||
project.addDefine('_HAS_ITERATOR_DEBUGGING=0');
|
||||
project.addDefine('_ITERATOR_DEBUG_LEVEL=0');
|
||||
@ -135,14 +155,25 @@ if (platform === Platform.Windows) {
|
||||
}
|
||||
}
|
||||
else if (platform === Platform.Linux) {
|
||||
project.addCppFlag('-fno-rtti');
|
||||
project.addLib('v8_monolith -L' + libdir);
|
||||
project.addDefine("KINC_NO_WAYLAND");
|
||||
if (flags.with_webview) {
|
||||
project.addLib('webkit2gtk-4.1');
|
||||
project.addLib('gtk-3');
|
||||
project.addLib('glib-2.0');
|
||||
}
|
||||
if (flags.with_networking) {
|
||||
project.addLib('pthread');
|
||||
}
|
||||
}
|
||||
else if (platform === Platform.OSX) {
|
||||
project.addCppFlag('-fno-rtti');
|
||||
project.addLib('v8_monolith -L' + libdir);
|
||||
if (flags.with_webview) {
|
||||
project.addLinkerFlag('-framework WebKit');
|
||||
project.addLinkerFlag('-framework AppKit');
|
||||
}
|
||||
if (flags.with_networking) {
|
||||
project.addIncludeDir('apple-libressl-sdk/include');
|
||||
project.addLinkerFlag('-L' + root + '/apple-libressl-sdk/lib');
|
||||
|
||||
Reference in New Issue
Block a user