Windows WebView2

This commit is contained in:
2026-07-22 20:58:04 -07:00
parent f3ac7895d0
commit a30a407de6
5 changed files with 667 additions and 81 deletions

View File

@ -19,6 +19,7 @@
#define DIRECTINPUT_VERSION 0x0800
#include <dinput.h>
#include <oleauto.h>
#include <ole2.h>
#include <stdio.h>
#include <wbemidl.h>
@ -1236,7 +1237,9 @@ static bool co_initialized = false;
void kinc_windows_co_initialize(void) {
if (!co_initialized) {
kinc_microsoft_affirm(CoInitializeEx(0, COINIT_MULTITHREADED));
// TODO find way to make multi thread work with webview
kinc_microsoft_affirm(OleInitialize(0));
//kinc_microsoft_affirm(CoInitializeEx(0, COINIT_MULTITHREADED));
co_initialized = true;
}
}

View File

@ -12,7 +12,7 @@ cd LNXRNT
**Windows**
```bash
# Unpack `v8\libraries\win32\release\v8_monolith.7z` using 7-Zip - Extract Here (exceeds 100MB)
Kinc\make -g direct3d11
Kore\make -g direct3d11
# Open generated Visual Studio project at `build\RunT.sln`
# or use command line for solution file like C:\Program Files\Microsoft Visual Studio\2022\Community\MSBuild\Current\Bin\MSBuild.exe" "build\Leenkx-RunT.sln" /p:Configuration=Release /p:Platform=x64 /m
# Build for x64 & release
@ -38,4 +38,16 @@ Kore/make -g opengl
git clone https://github.com/jeroen/apple-libressl-sdk.git
cd apple-libressl-sdk
git checkout fce8ffe30c9939fa1d71076745d9b3c811b8a90e
```
```
**Webview for windodows**
```bash
# Windows needs the WebView2 SDK while mac and linux are OS implementations
curl -L https://www.nuget.org/api/v2/package/Microsoft.Web.WebView2 -o webview2.zip
powershell -Command "Expand-Archive -Path webview2.zip -DestinationPath webview2-tmp -Force"
xcopy /E /I /Y webview2-tmp\build\native\include webview2\include\
mkdir webview2\lib\x64
copy webview2-tmp\build\native\x64\WebView2Loader.dll.lib webview2\lib\x64\WebView2Loader.lib
rmdir /S /Q webview2-tmp
del webview2.zip
```

View File

@ -1292,8 +1292,8 @@ namespace WebSocketWrapper {
if (remainingBytes == 0 && currentFin) {
#ifdef DEBUG_NETWORK
kinc_log(KINC_LOG_LEVEL_INFO, "WebSocket: Large message complete (%zu bytes)", fragmentBuffer.size());
std::vector<uint8_t> payload(fragmentBuffer.begin(), fragmentBuffer.end());
#endif
std::vector<uint8_t> payload(fragmentBuffer.begin(), fragmentBuffer.end());
processFrame(currentOpcode, currentFin, payload);
fragmentBuffer.clear();
wantsHead = true;

View File

@ -1,19 +1,40 @@
#ifdef WITH_WEBVIEW
#ifdef KINC_WINDOWS
#ifdef _WIN32
#include "webview.h"
#include <kinc/log.h>
#include <kinc/window.h>
#include <windows.h>
#include <windowsx.h>
#include <unknwn.h>
#include <comdef.h>
#include <wrl.h>
#include <dcomp.h>
#include <vector>
#include <algorithm>
#include <map>
#include "WebView2.h"
#include "WebView2EnvironmentCreator.h"
#pragma comment(lib, "WebView2Loader.lib")
#pragma comment(lib, "dcomp.lib")
using namespace Microsoft::WRL;
extern "C" struct HWND__ *kinc_windows_window_handle(int window_index);
struct WinWebViewState;
struct DCompShared {
IDCompositionDevice* device;
IDCompositionTarget* target;
IDCompositionVisual* rootVisual;
HWND dcompHwnd;
HWND parentHwnd;
int refCount;
std::vector<WinWebViewState*> webviews;
};
static std::map<HWND, DCompShared*> g_dcompShared;
static std::map<HWND, DCompShared*> g_dcompByHwnd;
struct WinWebViewState {
ICoreWebView2Controller* controller;
@ -23,12 +44,22 @@ struct WinWebViewState {
bool isDetached;
bool destroyed;
bool pending;
bool pendingShow;
DCompShared* dcompShared;
IDCompositionVisual* dcompVisual;
ICoreWebView2CompositionController* compController;
bool useComposition;
EventRegistrationToken messageToken;
EventRegistrationToken navStartingToken;
EventRegistrationToken navCompletedToken;
EventRegistrationToken errorToken;
EventRegistrationToken sourceChangedToken;
EventRegistrationToken contentLoadingToken;
int webviewId;
int x, y, width, height;
bool transparent;
EventRegistrationToken cursorChangedToken;
HCURSOR currentCursor;
std::wstring pendingHTML;
std::wstring pendingURL;
};
@ -72,6 +103,12 @@ static void ensureWebView2Environment() {
if (g_webview2Env || g_envCreating) return;
g_envCreating = true;
LPWSTR versionInfo = nullptr;
HRESULT verHr = GetAvailableCoreWebView2BrowserVersionString(nullptr, &versionInfo);
if (SUCCEEDED(verHr) && versionInfo) {
CoTaskMemFree(versionInfo);
}
HRESULT hr = CreateCoreWebView2EnvironmentWithOptions(
nullptr, nullptr, nullptr,
Callback<ICoreWebView2CreateCoreWebView2EnvironmentCompletedHandler>(
@ -104,85 +141,536 @@ static HRESULT STDMETHODCALLTYPE onMessageReceived(
static HRESULT STDMETHODCALLTYPE onNavCompleted(
ICoreWebView2* sender, ICoreWebView2NavigationCompletedEventArgs* args, DWORD* webviewIdPtr) {
int webviewId = (int)(intptr_t)webviewIdPtr;
BOOL success = FALSE;
if (args) args->get_IsSuccess(&success);
WebViewManager::instance().dispatchLoad(webviewId);
return S_OK;
}
static HRESULT STDMETHODCALLTYPE onSourceChanged(
ICoreWebView2* sender, ICoreWebView2SourceChangedEventArgs* args, DWORD* webviewIdPtr) {
int webviewId = (int)(intptr_t)webviewIdPtr;
LPWSTR uri = nullptr;
sender->get_Source(&uri);
if (uri) CoTaskMemFree(uri);
return S_OK;
}
static HRESULT STDMETHODCALLTYPE onContentLoading(
ICoreWebView2* sender, ICoreWebView2ContentLoadingEventArgs* args, DWORD* webviewIdPtr) {
int webviewId = (int)(intptr_t)webviewIdPtr;
return S_OK;
}
static HRESULT STDMETHODCALLTYPE onProcessFailed(
ICoreWebView2* sender, ICoreWebView2ProcessFailedEventArgs* args, DWORD* webviewIdPtr) {
int webviewId = (int)(intptr_t)webviewIdPtr;
COREWEBVIEW2_PROCESS_FAILED_KIND kind = COREWEBVIEW2_PROCESS_FAILED_KIND_BROWSER_PROCESS_EXITED;
if (args) args->get_ProcessFailedKind(&kind);
WebViewManager::instance().dispatchError(webviewId, "WebView2 process failed");
return S_OK;
}
static void onControllerReady(WinWebViewState* state, HRESULT result, ICoreWebView2Controller* controller) {
if (state->destroyed) {
if (controller) controller->Release();
delete state;
return;
}
if (FAILED(result) || !controller) {
return;
}
state->controller = controller;
state->controller->AddRef();
controller->get_CoreWebView2(&state->webview);
state->webview->AddRef();
RECT bounds;
if (state->isDetached) {
GetClientRect(state->detachedHwnd, &bounds);
} else if (state->useComposition) {
bounds.left = 0;
bounds.top = 0;
bounds.right = state->width;
bounds.bottom = state->height;
} 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();
}
}
HRESULT hr;
hr = state->webview->add_WebMessageReceived(
Callback<ICoreWebView2WebMessageReceivedEventHandler>(
[state](ICoreWebView2* s, ICoreWebView2WebMessageReceivedEventArgs* a) -> HRESULT {
return onMessageReceived(s, a, (DWORD*)(intptr_t)state->webviewId);
}).Get(),
&state->messageToken);
hr = state->webview->add_NavigationStarting(
Callback<ICoreWebView2NavigationStartingEventHandler>(
[state](ICoreWebView2* s, ICoreWebView2NavigationStartingEventArgs* a) -> HRESULT {
LPWSTR uri = nullptr;
a->get_Uri(&uri);
if (uri) CoTaskMemFree(uri);
return S_OK;
}).Get(),
&state->navStartingToken);
hr = state->webview->add_NavigationCompleted(
Callback<ICoreWebView2NavigationCompletedEventHandler>(
[state](ICoreWebView2* s, ICoreWebView2NavigationCompletedEventArgs* a) -> HRESULT {
return onNavCompleted(s, a, (DWORD*)(intptr_t)state->webviewId);
}).Get(),
&state->navCompletedToken);
hr = state->webview->add_SourceChanged(
Callback<ICoreWebView2SourceChangedEventHandler>(
[state](ICoreWebView2* s, ICoreWebView2SourceChangedEventArgs* a) -> HRESULT {
return onSourceChanged(s, a, (DWORD*)(intptr_t)state->webviewId);
}).Get(),
&state->sourceChangedToken);
hr = state->webview->add_ContentLoading(
Callback<ICoreWebView2ContentLoadingEventHandler>(
[state](ICoreWebView2* s, ICoreWebView2ContentLoadingEventArgs* a) -> HRESULT {
return onContentLoading(s, a, (DWORD*)(intptr_t)state->webviewId);
}).Get(),
&state->contentLoadingToken);
hr = 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->pendingShow) {
HRESULT visHr = state->controller->put_IsVisible(TRUE);
state->pendingShow = false;
}
if (!state->pendingHTML.empty()) {
HRESULT navHr = state->webview->NavigateToString(state->pendingHTML.c_str());
state->pendingHTML.clear();
}
{
LPWSTR uri = nullptr;
state->webview->get_Source(&uri);
if (uri) CoTaskMemFree(uri);
}
if (!state->pendingURL.empty()) {
HRESULT navHr = state->webview->Navigate(state->pendingURL.c_str());
state->pendingURL.clear();
}
}
static const wchar_t* g_dcompClassName = L"LNXRNT_DCompChild";
static HWND g_dcompParentHwnd = nullptr;
static WinWebViewState* findWebviewAtPoint(DCompShared* shared, int screenX, int screenY) {
if (!shared) return nullptr;
RECT popupRect;
GetWindowRect(shared->dcompHwnd, &popupRect);
int localX = screenX - popupRect.left;
int localY = screenY - popupRect.top;
for (auto* wv : shared->webviews) {
if (wv->destroyed || !wv->compController) continue;
if (localX >= wv->x && localX < wv->x + wv->width &&
localY >= wv->y && localY < wv->y + wv->height) {
return wv;
}
}
return nullptr;
}
static bool isWebviewInteractive(WinWebViewState* wv) {
if (!wv || !wv->compController) return false;
HCURSOR cursor = nullptr;
HRESULT hr = wv->compController->get_Cursor(&cursor);
if (FAILED(hr) || !cursor) return false;
HCURSOR defaultArrow = LoadCursor(nullptr, IDC_ARROW);
if (cursor == defaultArrow) return false;
return true;
}
static void forwardMouseToWebview(WinWebViewState* wv, UINT msg, WPARAM wParam, LPARAM lParam) {
if (!wv || !wv->compController) return;
int x = GET_X_LPARAM(lParam) - wv->x;
int y = GET_Y_LPARAM(lParam) - wv->y;
POINT pt = {x, y};
COREWEBVIEW2_MOUSE_EVENT_KIND kind;
COREWEBVIEW2_MOUSE_EVENT_VIRTUAL_KEYS vkeys = (COREWEBVIEW2_MOUSE_EVENT_VIRTUAL_KEYS)0;
UINT32 mouseData = 0;
switch (msg) {
case WM_MOUSEMOVE:
kind = COREWEBVIEW2_MOUSE_EVENT_KIND_MOVE;
break;
case WM_LBUTTONDOWN:
kind = COREWEBVIEW2_MOUSE_EVENT_KIND_LEFT_BUTTON_DOWN;
vkeys = (COREWEBVIEW2_MOUSE_EVENT_VIRTUAL_KEYS)(
COREWEBVIEW2_MOUSE_EVENT_VIRTUAL_KEYS_LEFT_BUTTON |
(GET_KEYSTATE_WPARAM(wParam) & MK_SHIFT ? COREWEBVIEW2_MOUSE_EVENT_VIRTUAL_KEYS_SHIFT : 0) |
(GET_KEYSTATE_WPARAM(wParam) & MK_CONTROL ? COREWEBVIEW2_MOUSE_EVENT_VIRTUAL_KEYS_CONTROL : 0));
break;
case WM_LBUTTONUP:
kind = COREWEBVIEW2_MOUSE_EVENT_KIND_LEFT_BUTTON_UP;
vkeys = (COREWEBVIEW2_MOUSE_EVENT_VIRTUAL_KEYS)(
(GET_KEYSTATE_WPARAM(wParam) & MK_SHIFT ? COREWEBVIEW2_MOUSE_EVENT_VIRTUAL_KEYS_SHIFT : 0) |
(GET_KEYSTATE_WPARAM(wParam) & MK_CONTROL ? COREWEBVIEW2_MOUSE_EVENT_VIRTUAL_KEYS_CONTROL : 0));
break;
case WM_RBUTTONDOWN:
kind = COREWEBVIEW2_MOUSE_EVENT_KIND_RIGHT_BUTTON_DOWN;
vkeys = COREWEBVIEW2_MOUSE_EVENT_VIRTUAL_KEYS_RIGHT_BUTTON;
break;
case WM_RBUTTONUP:
kind = COREWEBVIEW2_MOUSE_EVENT_KIND_RIGHT_BUTTON_UP;
break;
case WM_MBUTTONDOWN:
kind = COREWEBVIEW2_MOUSE_EVENT_KIND_MIDDLE_BUTTON_DOWN;
vkeys = COREWEBVIEW2_MOUSE_EVENT_VIRTUAL_KEYS_MIDDLE_BUTTON;
break;
case WM_MBUTTONUP:
kind = COREWEBVIEW2_MOUSE_EVENT_KIND_MIDDLE_BUTTON_UP;
break;
default:
return;
}
wv->compController->SendMouseInput(kind, vkeys, mouseData, pt);
}
static void forwardMouseToParent(DCompShared* shared, HWND popupHwnd, UINT msg, WPARAM wParam, LPARAM lParam) {
if (!shared || !shared->parentHwnd) return;
POINT pt = {GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam)};
MapWindowPoints(popupHwnd, shared->parentHwnd, &pt, 1);
LPARAM newLParam = MAKELPARAM(pt.x, pt.y);
SendMessageW(shared->parentHwnd, msg, wParam, newLParam);
}
static WinWebViewState* g_mouseCaptureWv = nullptr;
static bool g_mouseCaptureIsWebview = false;
static LRESULT CALLBACK dcompWndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) {
if (msg == WM_TIMER && wParam == 1) {
HWND parent = g_dcompParentHwnd;
if (parent && IsWindow(parent)) {
RECT clientRect;
GetClientRect(parent, &clientRect);
POINT pt = {0, 0};
ClientToScreen(parent, &pt);
SetWindowPos(hwnd, HWND_TOP, pt.x, pt.y,
clientRect.right - clientRect.left,
clientRect.bottom - clientRect.top,
SWP_NOACTIVATE | SWP_SHOWWINDOW);
}
return 0;
}
auto it = g_dcompByHwnd.find(hwnd);
DCompShared* shared = (it != g_dcompByHwnd.end()) ? it->second : nullptr;
if (msg == WM_NCHITTEST) {
return HTCLIENT;
}
if (msg == WM_SETCURSOR) {
POINT screenPt;
GetCursorPos(&screenPt);
WinWebViewState* wv = findWebviewAtPoint(shared, screenPt.x, screenPt.y);
if (wv && isWebviewInteractive(wv) && wv->currentCursor) {
SetCursor(wv->currentCursor);
return TRUE;
}
return DefWindowProcW(hwnd, msg, wParam, lParam);
}
if (msg == WM_MOUSEMOVE) {
POINT clientPt = {GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam)};
POINT screenPt = clientPt;
ClientToScreen(hwnd, &screenPt);
WinWebViewState* wv = findWebviewAtPoint(shared, screenPt.x, screenPt.y);
bool interactive = isWebviewInteractive(wv);
if (g_mouseCaptureIsWebview && g_mouseCaptureWv) {
forwardMouseToWebview(g_mouseCaptureWv, msg, wParam, lParam);
return 0;
}
if (wv) {
forwardMouseToWebview(wv, msg, wParam, lParam);
}
if (!interactive) {
forwardMouseToParent(shared, hwnd, msg, wParam, lParam);
}
return 0;
}
if (msg == WM_LBUTTONDOWN || msg == WM_RBUTTONDOWN || msg == WM_MBUTTONDOWN) {
POINT clientPt = {GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam)};
POINT screenPt = clientPt;
ClientToScreen(hwnd, &screenPt);
WinWebViewState* wv = findWebviewAtPoint(shared, screenPt.x, screenPt.y);
bool interactive = isWebviewInteractive(wv);
if (interactive && wv) {
g_mouseCaptureWv = wv;
g_mouseCaptureIsWebview = true;
forwardMouseToWebview(wv, msg, wParam, lParam);
} else {
g_mouseCaptureWv = nullptr;
g_mouseCaptureIsWebview = false;
forwardMouseToParent(shared, hwnd, msg, wParam, lParam);
}
return 0;
}
if (msg == WM_LBUTTONUP || msg == WM_RBUTTONUP || msg == WM_MBUTTONUP) {
if (g_mouseCaptureIsWebview && g_mouseCaptureWv) {
forwardMouseToWebview(g_mouseCaptureWv, msg, wParam, lParam);
g_mouseCaptureWv = nullptr;
g_mouseCaptureIsWebview = false;
} else {
forwardMouseToParent(shared, hwnd, msg, wParam, lParam);
}
return 0;
}
if (msg == WM_MOUSEWHEEL) {
POINT screenPt = {GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam)};
WinWebViewState* wv = findWebviewAtPoint(shared, screenPt.x, screenPt.y);
if (wv && isWebviewInteractive(wv)) {
POINT clientPt = screenPt;
ScreenToClient(hwnd, &clientPt);
int x = clientPt.x - wv->x;
int y = clientPt.y - wv->y;
POINT pt = {x, y};
wv->compController->SendMouseInput(
COREWEBVIEW2_MOUSE_EVENT_KIND_WHEEL,
(COREWEBVIEW2_MOUSE_EVENT_VIRTUAL_KEYS)0,
GET_WHEEL_DELTA_WPARAM(wParam), pt);
} else {
forwardMouseToParent(shared, hwnd, msg, wParam, lParam);
}
return 0;
}
return DefWindowProcW(hwnd, msg, wParam, lParam);
}
static DCompShared* getOrCreateDCompShared(HWND hwnd) {
auto it = g_dcompShared.find(hwnd);
if (it != g_dcompShared.end()) {
it->second->refCount++;
return it->second;
}
auto* shared = new DCompShared();
shared->device = nullptr;
shared->target = nullptr;
shared->rootVisual = nullptr;
shared->dcompHwnd = nullptr;
shared->parentHwnd = hwnd;
shared->refCount = 1;
static bool registered = false;
if (!registered) {
WNDCLASSW wc = {};
wc.lpfnWndProc = dcompWndProc;
wc.hInstance = GetModuleHandle(nullptr);
wc.lpszClassName = g_dcompClassName;
RegisterClassW(&wc);
registered = true;
}
g_dcompParentHwnd = hwnd;
RECT clientRect;
GetClientRect(hwnd, &clientRect);
POINT pt = {0, 0};
ClientToScreen(hwnd, &pt);
int screenX = pt.x;
int screenY = pt.y;
int width = clientRect.right - clientRect.left;
int height = clientRect.bottom - clientRect.top;
shared->dcompHwnd = CreateWindowExW(
WS_EX_NOREDIRECTIONBITMAP | WS_EX_NOACTIVATE,
g_dcompClassName, L"",
WS_POPUP,
screenX, screenY,
width, height,
nullptr, nullptr, GetModuleHandle(nullptr), nullptr);
if (!shared->dcompHwnd) {
delete shared;
return nullptr;
}
g_dcompByHwnd[shared->dcompHwnd] = shared;
SetWindowPos(shared->dcompHwnd, HWND_TOP, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE | SWP_NOACTIVATE | SWP_SHOWWINDOW);
SetTimer(shared->dcompHwnd, 1, 16, nullptr);
HRESULT hr = DCompositionCreateDevice(nullptr, IID_PPV_ARGS(&shared->device));
if (FAILED(hr) || !shared->device) {
DestroyWindow(shared->dcompHwnd);
delete shared;
return nullptr;
}
hr = shared->device->CreateTargetForHwnd(shared->dcompHwnd, TRUE, &shared->target);
if (FAILED(hr) || !shared->target) {
shared->device->Release();
DestroyWindow(shared->dcompHwnd);
delete shared;
return nullptr;
}
hr = shared->device->CreateVisual(&shared->rootVisual);
if (FAILED(hr) || !shared->rootVisual) {
shared->target->Release();
shared->device->Release();
DestroyWindow(shared->dcompHwnd);
delete shared;
return nullptr;
}
shared->target->SetRoot(shared->rootVisual);
shared->device->Commit();
g_dcompShared[hwnd] = shared;
return shared;
}
static void releaseDCompShared(HWND hwnd) {
auto it = g_dcompShared.find(hwnd);
if (it == g_dcompShared.end()) return;
DCompShared* shared = it->second;
shared->refCount--;
if (shared->refCount <= 0) {
if (shared->rootVisual) shared->rootVisual->Release();
if (shared->target) {
shared->target->SetRoot(nullptr);
shared->target->Release();
}
if (shared->device) shared->device->Release();
if (shared->dcompHwnd) {
g_dcompByHwnd.erase(shared->dcompHwnd);
KillTimer(shared->dcompHwnd, 1);
DestroyWindow(shared->dcompHwnd);
}
delete shared;
g_dcompShared.erase(it);
}
}
static void createCompositionController(WinWebViewState* state) {
state->dcompShared = getOrCreateDCompShared(state->parentHwnd);
if (!state->dcompShared) {
return;
}
HRESULT hr = state->dcompShared->device->CreateVisual(&state->dcompVisual);
if (FAILED(hr) || !state->dcompVisual) {
releaseDCompShared(state->parentHwnd);
state->dcompShared = nullptr;
return;
}
state->dcompVisual->SetOffsetX((float)state->x);
state->dcompVisual->SetOffsetY((float)state->y);
ICoreWebView2Environment3* env3;
hr = g_webview2Env->QueryInterface(IID_PPV_ARGS(&env3));
if (FAILED(hr) || !env3) {
state->dcompVisual->Release();
state->dcompVisual = nullptr;
releaseDCompShared(state->parentHwnd);
state->dcompShared = nullptr;
return;
}
env3->CreateCoreWebView2CompositionController(
state->dcompShared->dcompHwnd,
Callback<ICoreWebView2CreateCoreWebView2CompositionControllerCompletedHandler>(
[state](HRESULT result, ICoreWebView2CompositionController* compController) -> HRESULT {
if (state->destroyed) {
if (compController) compController->Release();
delete state;
return S_OK;
}
if (FAILED(result) || !compController) {
return result;
}
state->compController = compController;
state->compController->AddRef();
HRESULT targetHr = compController->put_RootVisualTarget(state->dcompVisual);
state->dcompShared->rootVisual->AddVisual(state->dcompVisual, FALSE, nullptr);
state->dcompShared->device->Commit();
ICoreWebView2Controller* controller = nullptr;
HRESULT qiHr = compController->QueryInterface(IID_PPV_ARGS(&controller));
if (FAILED(qiHr) || !controller) {
compController->Release();
return qiHr;
}
onControllerReady(state, result, controller);
state->dcompShared->webviews.push_back(state);
state->compController->add_CursorChanged(
Callback<ICoreWebView2CursorChangedEventHandler>(
[state](ICoreWebView2CompositionController* sender, IUnknown* args) -> HRESULT {
HCURSOR cursor = nullptr;
HRESULT hr = sender->get_Cursor(&cursor);
if (SUCCEEDED(hr) && cursor) {
state->currentCursor = cursor;
}
return S_OK;
}).Get(),
&state->cursorChangedToken);
controller->Release();
compController->Release();
return S_OK;
}).Get());
env3->Release();
}
static void createController(WinWebViewState* state) {
if (state->useComposition) {
createCompositionController(state);
return;
}
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();
}
onControllerReady(state, result, controller);
return S_OK;
}).Get());
}
@ -197,9 +685,19 @@ int platformCreate(WebViewInstance* wv) {
state->isDetached = (wv->parentWindow < 0);
state->destroyed = false;
state->pending = false;
state->pendingShow = false;
state->dcompShared = nullptr;
state->dcompVisual = nullptr;
state->compController = nullptr;
state->useComposition = (wv->transparent && !state->isDetached);
state->messageToken = {0};
state->navStartingToken = {0};
state->navCompletedToken = {0};
state->errorToken = {0};
state->sourceChangedToken = {0};
state->contentLoadingToken = {0};
state->cursorChangedToken = {0};
state->currentCursor = nullptr;
state->webviewId = wv->id;
state->x = wv->x;
state->y = wv->y;
@ -228,7 +726,6 @@ int platformCreate(WebViewInstance* wv) {
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);
@ -275,15 +772,54 @@ void platformDestroy(WebViewInstance* wv) {
DestroyWindow(state->detachedHwnd);
state->detachedHwnd = nullptr;
}
if (state->dcompShared) {
auto& wvs = state->dcompShared->webviews;
wvs.erase(std::remove(wvs.begin(), wvs.end(), state), wvs.end());
}
if (state->compController) {
if (state->cursorChangedToken.value) state->compController->remove_CursorChanged(state->cursorChangedToken);
state->compController->put_RootVisualTarget(nullptr);
state->compController->Release();
state->compController = nullptr;
}
if (state->dcompShared && state->dcompVisual) {
state->dcompShared->rootVisual->RemoveVisual(state->dcompVisual);
state->dcompShared->device->Commit();
state->dcompVisual->Release();
state->dcompVisual = nullptr;
}
if (state->dcompShared) {
releaseDCompShared(state->parentHwnd);
state->dcompShared = nullptr;
}
return;
}
if (state->webview) {
if (state->messageToken.value) state->webview->remove_WebMessageReceived(state->messageToken);
if (state->navStartingToken.value) state->webview->remove_NavigationStarting(state->navStartingToken);
if (state->navCompletedToken.value) state->webview->remove_NavigationCompleted(state->navCompletedToken);
if (state->errorToken.value) state->webview->remove_ProcessFailed(state->errorToken);
if (state->sourceChangedToken.value) state->webview->remove_SourceChanged(state->sourceChangedToken);
if (state->contentLoadingToken.value) state->webview->remove_ContentLoading(state->contentLoadingToken);
state->webview->Release();
}
if (state->dcompShared) {
auto& wvs = state->dcompShared->webviews;
wvs.erase(std::remove(wvs.begin(), wvs.end(), state), wvs.end());
}
if (state->compController) {
if (state->cursorChangedToken.value) state->compController->remove_CursorChanged(state->cursorChangedToken);
state->compController->put_RootVisualTarget(nullptr);
state->compController->Release();
state->compController = nullptr;
}
if (state->dcompShared && state->dcompVisual) {
state->dcompShared->rootVisual->RemoveVisual(state->dcompVisual);
state->dcompShared->device->Commit();
state->dcompVisual->Release();
state->dcompVisual = nullptr;
}
if (state->controller) {
state->controller->Close();
state->controller->Release();
@ -291,6 +827,10 @@ void platformDestroy(WebViewInstance* wv) {
if (state->detachedHwnd) {
DestroyWindow(state->detachedHwnd);
}
if (state->dcompShared) {
releaseDCompShared(state->parentHwnd);
state->dcompShared = nullptr;
}
delete state;
wv->platformHandle = nullptr;
}
@ -303,7 +843,8 @@ void platformLoadHTML(WebViewInstance* wv, const std::string& html) {
state->pendingURL.clear();
return;
}
state->webview->NavigateToString(utf8ToWide(html).c_str());
std::wstring wHtml = utf8ToWide(html);
HRESULT navHr = state->webview->NavigateToString(wHtml.c_str());
}
void platformLoadURL(WebViewInstance* wv, const std::string& url) {
@ -335,7 +876,11 @@ void platformEvalJSAsync(WebViewInstance* wv, const std::string& js) {
resultStr = "null";
} else {
std::wstring wstr(resultJson);
resultStr = std::string(wstr.begin(), wstr.end());
int len = WideCharToMultiByte(CP_UTF8, 0, wstr.c_str(), -1, nullptr, 0, nullptr, nullptr);
if (len > 0) {
resultStr.resize(len - 1);
WideCharToMultiByte(CP_UTF8, 0, wstr.c_str(), -1, &resultStr[0], len, nullptr, nullptr);
}
}
WebViewManager::instance().dispatchEvalResult(webviewId, resultStr);
return S_OK;
@ -350,6 +895,8 @@ void platformShow(WebViewInstance* wv) {
}
if (state->controller) {
state->controller->put_IsVisible(TRUE);
} else {
state->pendingShow = true;
}
}
@ -378,6 +925,11 @@ void platformResize(WebViewInstance* wv, int width, int height) {
RECT bounds;
if (state->isDetached) {
GetClientRect(state->detachedHwnd, &bounds);
} else if (state->useComposition) {
bounds.left = 0;
bounds.top = 0;
bounds.right = width;
bounds.bottom = height;
} else {
bounds.left = wv->x;
bounds.top = wv->y;
@ -385,6 +937,12 @@ void platformResize(WebViewInstance* wv, int width, int height) {
bounds.bottom = wv->y + height;
}
state->controller->put_Bounds(bounds);
if (state->dcompShared && state->dcompVisual) {
state->dcompVisual->SetOffsetX((float)wv->x);
state->dcompVisual->SetOffsetY((float)wv->y);
state->dcompShared->device->Commit();
}
}
void platformMove(WebViewInstance* wv, int x, int y) {
@ -395,12 +953,25 @@ void platformMove(WebViewInstance* wv, int x, int y) {
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;
if (state->useComposition) {
bounds.left = 0;
bounds.top = 0;
bounds.right = wv->width;
bounds.bottom = wv->height;
} else {
bounds.left = x;
bounds.top = y;
bounds.right = x + wv->width;
bounds.bottom = y + wv->height;
}
state->controller->put_Bounds(bounds);
}
if (state->dcompShared && state->dcompVisual) {
state->dcompVisual->SetOffsetX((float)x);
state->dcompVisual->SetOffsetY((float)y);
state->dcompShared->device->Commit();
}
}
void platformSetTransparent(WebViewInstance* wv, bool transparent) {
@ -588,7 +1159,7 @@ void platformSetContextMenuEnabled(WebViewInstance* wv, bool enabled) {
}
void platformTick() {
// COM event loop is pumped by Kincs loop.
// COM event loop is by Kores loop via PeekMessageW/DispatchMessageW.
}
#endif

View File

@ -10,10 +10,10 @@ let flags = {
with_compute: true,
with_networking: true,
with_viewport: true,
with_webview: false,
with_webview: true,
with_uws: false,
with_ssl: true,
debug_network: true
debug_network: false
};
const system = platform === Platform.Windows ? "win32" :
@ -142,7 +142,7 @@ if (platform === Platform.Windows) {
project.addLib(libdir + 'v8_monolith');
if (flags.with_webview) {
project.addIncludeDir('webview2/include');
project.addLib('WebView2Loader');
project.addLib(root + '/webview2/lib/x64/WebView2Loader');
}
if (!flags.release) {
project.addDefine('_HAS_ITERATOR_DEBUGGING=0');