stremio-community-v5/src/webview/webview.cpp
Zarg 7add3f622b Added WebMods feature
- Added WebMods feature, allows loading custom js and css
2025-10-08 07:25:29 +02:00

718 lines
No EOL
27 KiB
C++

#include "webview.h"
#include <string>
#include <thread>
#include <cmath>
#include <fstream>
#include <iostream>
#include <Shlwapi.h>
#include <wrl.h>
#include "../core/globals.h"
#include "../utils/crashlog.h"
#include "../utils/helpers.h"
#include "../ui/mainwindow.h"
#include "../utils/extensions.h"
static const wchar_t* EXEC_SHELL_SCRIPT = LR"JS_CODE(
try {
console.log('Shell JS injected');
if (window.self === window.top && !window.qt) {
window.qt = {
webChannelTransport: {
send: window.chrome.webview.postMessage,
onmessage: (ev) => {
// Will be overwritten by ShellTransport
console.log('Received message from WebView2:', ev);
}
}
};
window.chrome.webview.addEventListener('message', (ev) => {
window.qt.webChannelTransport.onmessage(ev);
});
window.onload = () => {
try {
initShellComm();
} catch (e) {
const errorMessage = {
type: 6,
object: "transport",
method: "handleInboundJSON",
id: 888,
args: [
"app-error",
[ "shellComm" ]
]
};
window.chrome.webview.postMessage(JSON.stringify(errorMessage));
}
};
}
} catch(e) {
console.error("Error exec initShellComm:", e);
const errorMessage = {
type: 6,
object: "transport",
method: "handleInboundJSON",
id: 888,
args: [
"app-error",
[ "shellComm" ]
]
};
if(window.chrome && window.chrome.webview && window.chrome.webview.postMessage) {
window.chrome.webview.postMessage(JSON.stringify(errorMessage));
}
};
)JS_CODE";
static const wchar_t* INJECTED_KEYDOWN_SCRIPT = LR"JS(
(function() {
window.addEventListener('keydown', function(event) {
if (event.code === 'F5') {
event.preventDefault();
const ctrlPressed = event.ctrlKey || event.metaKey;
const msg = {
type: 6,
object: "transport",
method: "handleInboundJSON",
id: 999,
args: [
"refresh",
[ ctrlPressed ? "all" : "no" ]
]
};
window.chrome.webview.postMessage(JSON.stringify(msg));
}
});
})();
)JS";
static const wchar_t* INJECTED_BUTTON_SCRIPT = LR"JS(
(function() {
// Create the button element
var btn = document.createElement('button');
btn.id = 'goBackStremioBtn';
// Style the button:
btn.style.position = 'fixed';
btn.style.bottom = '15px';
btn.style.right = '15px';
btn.style.zIndex = '9999';
btn.style.backgroundColor = '#121024'; // Purple
btn.style.color = 'white';
btn.style.border = 'none';
btn.style.borderRadius = '30px';
btn.style.padding = '12px 20px';
btn.style.fontSize = '16px';
btn.style.fontWeight = 'bold';
btn.style.display = 'flex';
btn.style.alignItems = 'center';
btn.style.boxShadow = '0 4px 6px rgba(0,0,0,0.1)';
btn.style.cursor = 'pointer';
btn.style.transition = 'background-color 0.3s ease';
// Hover effect:
btn.addEventListener('mouseenter', function() {
btn.style.backgroundColor = '#211e39'; // Gray
});
btn.addEventListener('mouseleave', function() {
btn.style.backgroundColor = '#121024';
});
// Create an image element for the logo
var img = document.createElement('img');
img.src = 'https://stremio.zarg.me/images/stremio_symbol.png';
img.alt = 'Logo';
img.style.height = '24px';
img.style.width = '24px';
img.style.marginRight = '8px';
img.addEventListener('error', function() {
img.style.display = 'none';
});
// Create a text element
var txt = document.createElement('span');
txt.textContent = 'Back to Stremio';
// Append the logo and text to the button
btn.appendChild(img);
btn.appendChild(txt);
// On click go home
btn.addEventListener('click', function() {
const payload = {
type: 6,
object: "transport",
method: "handleInboundJSON",
id: 666,
args: [
"navigate",
[ "home" ]
]
};
window.chrome.webview.postMessage(JSON.stringify(payload));
});
// Append the button to the document body
document.body.appendChild(btn);
})();
)JS";
void WaitAndRefreshIfNeeded()
{
std::thread([](){
const int maxAttempts = 10;
const int initialWaitTime = 5;
const int maxWaitTime = 60;
std::cout << "[WEBVIEW]: Web Page could not be reached, retrying..." << std::endl;
for(int attempt=0; attempt<maxAttempts; ++attempt)
{
int waitTime = (int)(initialWaitTime * pow(1.25, attempt));
if(waitTime>maxWaitTime) waitTime = maxWaitTime;
std::this_thread::sleep_for(std::chrono::seconds(waitTime));
if(g_isAppReady){
std::cout << "[WEBVIEW]: Web Page ready!" << std::endl;
g_waitStarted.store(false);
return;
}
std::cout << "[WEBVIEW]: Refreshing attempt " << (attempt+1) << std::endl;
refreshWeb(false);
}
if(!g_isAppReady) {
AppendToCrashLog("[WEBVIEW]: Could not load after attempts");
MessageBoxW(nullptr,
L"Web page could not be loaded after multiple attempts. Make sure the Web UI is reachable.",
L"WebView2 Page load fail",
MB_ICONERROR | MB_OK
);
PostQuitMessage(1);
exit(1);
}
}).detach();
}
void InitWebView2(HWND hWnd)
{
std::cout << "[WEBVIEW]: Starting webview..." << std::endl;
// Setup environment
Microsoft::WRL::ComPtr<ICoreWebView2EnvironmentOptions> options = Microsoft::WRL::Make<CoreWebView2EnvironmentOptions>();
if(options){
options->put_AdditionalBrowserArguments(
L"--autoplay-policy=no-user-gesture-required --disable-features=msWebOOUI,msPdfOOUI,msSmartScreenProtection"
);
Microsoft::WRL::ComPtr<ICoreWebView2EnvironmentOptions6> options6;
if(SUCCEEDED(options.As(&options6))) {
options6->put_AreBrowserExtensionsEnabled(TRUE);
}
Microsoft::WRL::ComPtr<ICoreWebView2EnvironmentOptions5> options5;
if(SUCCEEDED(options.As(&options5))) {
options5->put_EnableTrackingPrevention(TRUE);
}
}
// Check for local Edge runtime in "portable_config/EdgeWebView"
std::wstring exeDir;
{
wchar_t buf[MAX_PATH];
GetModuleFileNameW(nullptr, buf, MAX_PATH);
exeDir = buf;
size_t pos = exeDir.find_last_of(L"\\/");
if(pos!=std::wstring::npos) exeDir.erase(pos);
}
std::wstring browserDir = exeDir + L"\\portable_config\\EdgeWebView";
const wchar_t* browserExecutableFolder = nullptr;
if(DirectoryExists(browserDir)) {
browserExecutableFolder = browserDir.c_str();
std::wcout << L"[WEBVIEW]: Using local WebView2: " << browserDir << std::endl;
}
HRESULT hr = CreateCoreWebView2EnvironmentWithOptions(
browserExecutableFolder, nullptr, options.Get(),
Microsoft::WRL::Callback<ICoreWebView2CreateCoreWebView2EnvironmentCompletedHandler>(
[hWnd](HRESULT res, ICoreWebView2Environment* env)->HRESULT
{
if(!env) return E_FAIL;
env->CreateCoreWebView2Controller(
hWnd,
Microsoft::WRL::Callback<ICoreWebView2CreateCoreWebView2ControllerCompletedHandler>(
[hWnd](HRESULT result, ICoreWebView2Controller* rawController)->HRESULT
{
if (FAILED(result) || !rawController) return E_FAIL;
std::cout << "[WEBVIEW]: Initializing WebView..." << std::endl;
wil::com_ptr<ICoreWebView2Controller> m_webviewController = rawController;
if (!m_webviewController) return E_FAIL;
g_webviewController = m_webviewController.try_query<ICoreWebView2Controller4>();
if (!g_webviewController) return E_FAIL;
wil::com_ptr<ICoreWebView2> coreWebView;
g_webviewController->get_CoreWebView2(&coreWebView);
g_webview = coreWebView.try_query<ICoreWebView2_21>();
if (!g_webview) return E_FAIL;
wil::com_ptr<ICoreWebView2Profile> webView2Profile;
g_webview->get_Profile(&webView2Profile);
g_webviewProfile = webView2Profile.try_query<ICoreWebView2Profile8>();
if (!g_webviewProfile) return E_FAIL;
wil::com_ptr<ICoreWebView2Settings> webView2Settings;
g_webview->get_Settings(&webView2Settings);
auto settings = webView2Settings.try_query<ICoreWebView2Settings8>();
if (!settings) return E_FAIL;
if(settings) {
#ifndef DEBUG_LOG
settings->put_AreDevToolsEnabled(FALSE);
#endif
settings->put_IsStatusBarEnabled(FALSE);
settings->put_AreBrowserAcceleratorKeysEnabled(FALSE);
std::wstring customUA = std::wstring(L"StremioShell/") + Utf8ToWstring(APP_VERSION);
settings->put_UserAgent(customUA.c_str());
if(!g_allowZoom) {
settings->put_IsZoomControlEnabled(FALSE);
settings->put_IsPinchZoomEnabled(FALSE);
}
}
// Set background color
COREWEBVIEW2_COLOR col={0,0,0,0};
g_webviewController->put_DefaultBackgroundColor(col);
RECT rc; GetClientRect(hWnd,&rc);
g_webviewController->put_Bounds(rc);
g_webview->AddScriptToExecuteOnDocumentCreated(EXEC_SHELL_SCRIPT,nullptr);
g_webview->AddScriptToExecuteOnDocumentCreated(INJECTED_KEYDOWN_SCRIPT,nullptr);
SetupWebMods();
SetupExtensions();
SetupWebMessageHandler();
std::thread([](){
std::wcout << L"[WEBVIEW]: Checking web ui endpoints..." << std::endl;
std::wstring foundUrl = GetFirstReachableUrl();
std::wstring* pResult = new std::wstring(foundUrl);
g_webuiUrl = foundUrl;
PostMessage(g_hWnd, WM_REACHABILITY_DONE, (WPARAM)pResult, 0);
FetchAndParseWhitelist();
}).detach();
return S_OK;
}).Get()
);
return S_OK;
}).Get()
);
if(FAILED(hr)) {
std::wstring msg = L"[WEBVIEW]: CreateCoreWebView2EnvironmentWithOptions failed => " + std::to_wstring(hr);
AppendToCrashLog(msg);
MessageBoxW(nullptr, msg.c_str(), L"WebView2 Initialization Error", MB_ICONERROR | MB_OK);
PostQuitMessage(1);
exit(1);
}
}
static void SetupWebMessageHandler()
{
if(!g_webview) return;
EventRegistrationToken navToken;
g_webview->add_NavigationCompleted(
Microsoft::WRL::Callback<ICoreWebView2NavigationCompletedEventHandler>(
[](ICoreWebView2* sender, ICoreWebView2NavigationCompletedEventArgs* args)->HRESULT
{
BOOL isSuccess;
args->get_IsSuccess(&isSuccess);
// Retrieve the final URL
wil::unique_cotaskmem_string rawUri;
sender->get_Source(&rawUri);
std::wstring finalUri = rawUri ? rawUri.get() : L"";
std::wcout << L"[WEBVIEW]: Navigation try to " << finalUri << std::endl;
// Add back to stremio button if not on stremio
if (finalUri.find(g_webuiUrl) == std::wstring::npos) {
sender->ExecuteScript(INJECTED_BUTTON_SCRIPT, nullptr);
}
if(isSuccess) {
std::cout<<"[WEBVIEW]: Navigation Complete - Success\n";
sender->ExecuteScript(EXEC_SHELL_SCRIPT, nullptr);
// Flush the script queue.
if (!g_scriptQueue.empty()) {
for (const auto &script : g_scriptQueue) {
sender->ExecuteScript(script.c_str(), nullptr);
}
g_scriptQueue.clear();
}
} else {
std::cout<<"[WEBVIEW]: Navigation failed\n";
if(g_hSplash && !g_waitStarted.exchange(true)) {
WaitAndRefreshIfNeeded();
}
HandleExtensions(finalUri);
}
return S_OK;
}).Get(),
&navToken
);
EventRegistrationToken contentToken;
g_webview->add_ContentLoading(
Microsoft::WRL::Callback<ICoreWebView2ContentLoadingEventHandler>(
[](ICoreWebView2* sender, ICoreWebView2ContentLoadingEventArgs* args) -> HRESULT {
std::cout<<"[WEBVIEW]: Content loaded\n";
sender->ExecuteScript(EXEC_SHELL_SCRIPT, nullptr);
return S_OK;
}
).Get(),
&contentToken
);
EventRegistrationToken domToken;
g_webview->add_DOMContentLoaded(
Microsoft::WRL::Callback<ICoreWebView2DOMContentLoadedEventHandler>(
[](ICoreWebView2* sender, ICoreWebView2DOMContentLoadedEventArgs* args)->HRESULT
{
sender->ExecuteScript(EXEC_SHELL_SCRIPT, nullptr);
return S_OK;
}).Get(),
&domToken
);
EventRegistrationToken contextMenuToken;
g_webview->add_ContextMenuRequested(
Microsoft::WRL::Callback<ICoreWebView2ContextMenuRequestedEventHandler>(
[](ICoreWebView2* sender, ICoreWebView2ContextMenuRequestedEventArgs* args) -> HRESULT {
// Existing variable declarations
wil::com_ptr<ICoreWebView2ContextMenuItemCollection> items;
HRESULT hr = args->get_MenuItems(&items);
if (FAILED(hr) || !items) return hr;
#ifdef DEBUG_LOG
return S_OK; //DEV TOOLS DEBUG ONLY
#endif
// Get current URL
wil::unique_cotaskmem_string currentUri;
sender->get_Source(&currentUri);
std::wstring uri(currentUri.get());
bool isExtensionUrl = uri.starts_with(L"chrome-extension://");
// Get context menu target
wil::com_ptr<ICoreWebView2ContextMenuTarget> target;
hr = args->get_ContextMenuTarget(&target);
BOOL isEditable = FALSE;
if (SUCCEEDED(hr) && target) {
target->get_IsEditable(&isEditable);
}
UINT count = 0;
items->get_Count(&count);
if (!isEditable) {
// Allow only Back command (ID 33000) for extension URLs
std::set<INT32> allowedCommands = isExtensionUrl ?
std::set<INT32>{33000} :
std::set<INT32>{};
for (UINT i = 0; i < count;) {
wil::com_ptr<ICoreWebView2ContextMenuItem> item;
hr = items->GetValueAtIndex(i, &item);
if (FAILED(hr)) {
i++;
continue;
}
INT32 commandId;
item->get_CommandId(&commandId);
if (allowedCommands.find(commandId) == allowedCommands.end()) {
items->RemoveValueAtIndex(i);
items->get_Count(&count);
} else {
i++;
}
}
return S_OK;
}
// Define allowed command IDs for filtering
std::set<INT32> allowedCommandIds = {
50151, // Cut
50150, // Copy
50152, // Paste
50157, // Paste as plain text
50156 // Select all
};
for (UINT i = 0; i < count;) {
wil::com_ptr<ICoreWebView2ContextMenuItem> item;
hr = items->GetValueAtIndex(i, &item);
if (FAILED(hr)) {
i++;
continue;
}
INT32 commandId;
item->get_CommandId(&commandId);
if (allowedCommandIds.find(commandId) == allowedCommandIds.end()) {
items->RemoveValueAtIndex(i);
items->get_Count(&count);
} else {
i++;
}
}
return S_OK;
}).Get(),
&contextMenuToken
);
EventRegistrationToken msgToken;
g_webview->add_WebMessageReceived(
Microsoft::WRL::Callback<ICoreWebView2WebMessageReceivedEventHandler>(
[](ICoreWebView2* /*sender*/, ICoreWebView2WebMessageReceivedEventArgs* args)->HRESULT
{
wil::unique_cotaskmem_string msgRaw;
args->TryGetWebMessageAsString(&msgRaw);
if(!msgRaw) return S_OK;
std::wstring wstr(msgRaw.get());
std::string str = WStringToUtf8(wstr);
HandleInboundJSON(str);
return S_OK;
}).Get(),
&msgToken
);
EventRegistrationToken newWindowToken;
g_webview->add_NewWindowRequested(
Microsoft::WRL::Callback<ICoreWebView2NewWindowRequestedEventHandler>(
[](ICoreWebView2* /*sender*/, ICoreWebView2NewWindowRequestedEventArgs* args) -> HRESULT
{
// Mark the event as handled to prevent default behavior
args->put_Handled(TRUE);
wil::unique_cotaskmem_string uri;
if (SUCCEEDED(args->get_Uri(&uri)) && uri)
{
std::wstring wuri(uri.get());
// Check if the URI is a local file (starts with "file://")
if (wuri.rfind(L"file://", 0) == 0)
{
std::wstring filePath = wuri.substr(8);
std::string utf8FilePath = WStringToUtf8(filePath);
std::string decodedFilePathUtf8 = decodeURIComponent(utf8FilePath);
std::string baseName = std::filesystem::path(decodedFilePathUtf8).filename().string();
if (isSubtitle(filePath)) {
std::vector<std::string> subaddArgs = {"sub-add",decodedFilePathUtf8, "select", baseName + " External", "Other Tracks"};
HandleEvent("mpv-command", subaddArgs);
json j;
j["type"] = "SubtitleDropped";
j["path"] = utf8FilePath;
SendToJS("SubtitleDropped", j);
return S_OK;
}
json j;
j["type"] = "FileDropped";
j["path"] = decodedFilePathUtf8;
SendToJS("FileDropped", j);
return S_OK;
}
if (URLContainsAny(wuri)) {
g_webview->Navigate(wuri.c_str());
return S_OK;
}
// For non-file URIs, open externally
ShellExecuteW(nullptr, L"open", uri.get(), nullptr, nullptr, SW_SHOWNORMAL);
}
return S_OK;
}
).Get(),
&newWindowToken
);
// For redirects like window.location.replace
EventRegistrationToken navstartedToken;
g_webview->add_NavigationStarting(
Microsoft::WRL::Callback<ICoreWebView2NavigationStartingEventHandler>(
[](ICoreWebView2* sender, ICoreWebView2NavigationStartingEventArgs* args) -> HRESULT
{
wil::unique_cotaskmem_string uri;
args->get_Uri(&uri);
std::wstring destination(uri.get());
if (!URLContainsAny(destination)) {
args->put_Cancel(TRUE);
ShellExecuteW(nullptr, L"open", uri.get(), nullptr, nullptr, SW_SHOWNORMAL);
}
return S_OK;
}
).Get(),
&navstartedToken
);
// FullScreen
EventRegistrationToken cfeToken;
g_webview->add_ContainsFullScreenElementChanged(
Microsoft::WRL::Callback<ICoreWebView2ContainsFullScreenElementChangedEventHandler>(
[](ICoreWebView2* sender, IUnknown* /*args*/) -> HRESULT
{
// FullScreen Toggle Handle
BOOL inFull = FALSE;
sender->get_ContainsFullScreenElement(&inFull);
ToggleFullScreen(g_hWnd, inFull != FALSE);
return S_OK;
}
).Get(),
&cfeToken
);
}
static void SetupExtensions()
{
if(!g_webview || !g_webviewProfile) return;
// e.g. from "portable_config/extensions"
std::wstring exeDir;
{
wchar_t buf[MAX_PATH];
GetModuleFileNameW(nullptr, buf, MAX_PATH);
exeDir = buf;
size_t pos = exeDir.find_last_of(L"\\/");
if(pos!=std::wstring::npos) exeDir.erase(pos);
}
std::wstring extensionsRoot = exeDir + L"\\portable_config\\extensions";
try {
for(const auto& entry : std::filesystem::directory_iterator(extensionsRoot)) {
if(entry.is_directory()) {
std::wstring folderName = entry.path().filename().wstring();
HRESULT hr = g_webviewProfile->AddBrowserExtension(
entry.path().wstring().c_str(),
Microsoft::WRL::Callback<ICoreWebView2ProfileAddBrowserExtensionCompletedHandler>(
[folderName](HRESULT result, ICoreWebView2BrowserExtension* extension)->HRESULT
{
if (SUCCEEDED(result) && extension)
{
wil::unique_cotaskmem_string extId;
HRESULT hrId = extension->get_Id(&extId);
if (SUCCEEDED(hrId) && extId)
{
// Store extension ID in the global map
g_extensionMap[folderName] = extId.get();
std::wcout << L"[EXTENSIONS]: " << folderName
<< L" => " << extId.get() << std::endl;
}
std::wcout << L"[EXTENSIONS]: Added extension " << folderName << std::endl;
} else {
std::wstring err = L"[EXTENSIONS]: Failed to add extension => " + std::to_wstring(result);
AppendToCrashLog(err);
}
return S_OK;
}).Get()
);
if(FAILED(hr)) {
std::wstring err = L"[EXTENSIONS]: AddBrowserExtension failed => " + std::to_wstring(hr);
AppendToCrashLog(err);
}
}
}
} catch(...) {
std::cout<<"[EXTENSIONS]: No extensions folder or iteration failed.\n";
}
}
static void SetupWebMods()
{
if (!g_webview) return;
wchar_t buf[MAX_PATH];
GetModuleFileNameW(nullptr, buf, MAX_PATH);
std::wstring exeDir = buf;
size_t pos = exeDir.find_last_of(L"\\/");
if (pos != std::wstring::npos) exeDir.erase(pos);
const std::filesystem::path root = std::filesystem::path(exeDir) / L"portable_config" / L"webmods";
if (!std::filesystem::exists(root) || !std::filesystem::is_directory(root)) {
std::wcout << L"[WEBMODS] Folder not found: " << root.wstring() << std::endl;
return;
}
std::vector<std::filesystem::path> cssFiles, jsFiles;
for (const auto& e : std::filesystem::recursive_directory_iterator(root)) {
if (!e.is_regular_file()) continue;
auto ext = e.path().extension().wstring();
std::transform(ext.begin(), ext.end(), ext.begin(), ::towlower);
if (ext == L".map" || ext == L".bak" || ext == L".tmp") continue;
if (ext == L".css") cssFiles.push_back(e.path());
else if (ext == L".js") jsFiles.push_back(e.path());
}
auto relStr = [&](const std::filesystem::path& p){
try { return std::filesystem::relative(p, root).wstring(); }
catch(...) { return p.wstring(); }
};
auto sorter = [&](const std::filesystem::path& a, const std::filesystem::path& b){
auto ra = relStr(a), rb = relStr(b);
return _wcsicmp(ra.c_str(), rb.c_str()) < 0;
};
std::sort(cssFiles.begin(), cssFiles.end(), sorter);
std::sort(jsFiles.begin(), jsFiles.end(), sorter);
auto makeId = [&](const std::filesystem::path& p){
std::wstring id = relStr(p);
for (auto& ch : id) if (!iswalnum(ch)) ch = L'_';
return id;
};
for (const auto& p : cssFiles) {
std::string content;
if (!ReadFileUtf8(p.wstring(), content)) continue;
const std::wstring id = makeId(p);
const std::wstring script = MakeInjectCssScript(id, content);
g_webview->AddScriptToExecuteOnDocumentCreated(script.c_str(), nullptr);
std::wcout << L"[WEBMODS] CSS: " << relStr(p) << std::endl;
}
for (const auto& p : jsFiles) {
std::string content;
if (!ReadFileUtf8(p.wstring(), content)) continue;
const std::wstring id = makeId(p);
const std::wstring script = MakeInjectJsScript(id, content);
g_webview->AddScriptToExecuteOnDocumentCreated(script.c_str(), nullptr);
std::wcout << L"[WEBMODS] JS: " << relStr(p) << std::endl;
}
}
void refreshWeb(const bool refreshAll) {
if (g_webviewProfile && refreshAll)
{
HRESULT hr = g_webviewProfile->ClearBrowsingData(
COREWEBVIEW2_BROWSING_DATA_KINDS_DISK_CACHE |
COREWEBVIEW2_BROWSING_DATA_KINDS_CACHE_STORAGE |
COREWEBVIEW2_BROWSING_DATA_KINDS_SERVICE_WORKERS |
COREWEBVIEW2_BROWSING_DATA_KINDS_FILE_SYSTEMS |
COREWEBVIEW2_BROWSING_DATA_KINDS_WEB_SQL |
COREWEBVIEW2_BROWSING_DATA_KINDS_INDEXED_DB,
Microsoft::WRL::Callback<ICoreWebView2ClearBrowsingDataCompletedHandler>(
[](HRESULT result) -> HRESULT {
std::cout << "[BROWSER]: Cleared browser cache successfully" << std::endl;
return S_OK;
}
).Get()
);
if (FAILED(hr)) {
std::cout << "[BROWSER]: Could not clear browser cache" << std::endl;
}
}
if (g_webview) {
g_webview->Reload();
}
}