Merge pull request #56 from SinyimZhi/main

support cookie manager and jsbridge/jschannel
This commit is contained in:
Prome
2023-05-24 12:08:26 +08:00
committed by GitHub
21 changed files with 1167 additions and 9 deletions

View File

@@ -101,3 +101,130 @@ CefRefPtr<CefClient> WebviewApp::GetDefaultClient() {
// Called when a new browser window is created via the Chrome runtime UI.
return WebviewHandler::GetInstance();
}
void WebviewApp::OnWebKitInitialized()
{
//inject js function for jssdk
std::string extensionCode = R"(
var external = {};
var clientSdk = {};
(() => {
clientSdk.jsCmd = (functionName, arg1, arg2, arg3) => {
if (typeof arg1 === 'function') {
native function jsCmd(functionName, arg1);
return jsCmd(functionName, arg1);
}
else if (typeof arg2 === 'function') {
jsonString = arg1;
if (typeof arg1 !== 'string'){
jsonString = JSON.stringify(arg1);
}
native function jsCmd(functionName, jsonString, arg2);
return jsCmd(functionName, jsonString, arg2);
}
else if (typeof arg3 === 'function') {
jsonString = arg1;
if (typeof arg1 !== 'string'){
jsonString = JSON.stringify(arg1);
}
native function jsCmd(functionName, jsonString, arg2, arg3);
return jsCmd(functionName, jsonString, arg2, arg3);
}else {
}
};
external.JavaScriptChannel = (n,e,r) => {
var a;
null == r ? a = '' : (a = '_' + new Date + (1e3 + Math.floor(8999 * Math.random())), window[a] = function (n, e) {
return function () {
try {
e && e.call && e.call(null, arguments[1])
} finally {
delete window[n]
}
}
}(a, r));
try {
external.StartRequest(external.GetNextReqID(), n, a, JSON.stringify(e || {}), '')
} catch (l) {
console.log('messeage send')
}
}
external.StartRequest = (nReqID, strCmd, strCallBack, strArgs, strLog) => {
native function StartRequest();
StartRequest(nReqID, strCmd, strCallBack, strArgs, strLog);
};
external.GetNextReqID = () => {
native function GetNextReqID();
return GetNextReqID();
};
})();
)";
CefRefPtr<CefJSHandler> handler = new CefJSHandler();
if (!m_render_js_bridge.get())
m_render_js_bridge.reset(new CefJSBridge);
handler->AttachJSBridge(m_render_js_bridge);
CefRegisterExtension("v8/extern", extensionCode, handler);
}
void WebviewApp::OnBrowserCreated(CefRefPtr<CefBrowser> browser, CefRefPtr<CefDictionaryValue> extra_info)
{
if (!m_render_js_bridge.get())
m_render_js_bridge.reset(new CefJSBridge);
}
void WebviewApp::OnBrowserDestroyed(CefRefPtr<CefBrowser> browser)
{
}
void WebviewApp::OnContextCreated(CefRefPtr<CefBrowser> browser, CefRefPtr<CefFrame> frame, CefRefPtr<CefV8Context> context)
{
}
void WebviewApp::OnContextReleased(CefRefPtr<CefBrowser> browser, CefRefPtr<CefFrame> frame, CefRefPtr<CefV8Context> context)
{
if (m_render_js_bridge.get())
{
m_render_js_bridge->RemoveCallbackFuncWithFrame(frame);
}
}
void WebviewApp::OnUncaughtException(CefRefPtr<CefBrowser> browser, CefRefPtr<CefFrame> frame, CefRefPtr<CefV8Context> context, CefRefPtr<CefV8Exception> exception, CefRefPtr<CefV8StackTrace> stackTrace)
{
}
void WebviewApp::OnFocusedNodeChanged(CefRefPtr<CefBrowser> browser, CefRefPtr<CefFrame> frame, CefRefPtr<CefDOMNode> node)
{
bool is_editable = (node.get() && node->IsEditable());
if (is_editable != m_last_node_is_editable)
{
// Notify the browser of the change in focused element type.
m_last_node_is_editable = is_editable;
CefRefPtr<CefProcessMessage> message = CefProcessMessage::Create(kFocusedNodeChangedMessage);
message->GetArgumentList()->SetBool(0, is_editable);
frame->SendProcessMessage(PID_BROWSER, message);
}
}
bool WebviewApp::OnProcessMessageReceived(CefRefPtr<CefBrowser> browser, CefRefPtr<CefFrame> frame, CefProcessId source_process, CefRefPtr<CefProcessMessage> message)
{
const CefString& message_name = message->GetName();
if (message_name == kExecuteJsCallbackMessage)
{
int callbackId = message->GetArgumentList()->GetInt(0);
bool error = message->GetArgumentList()->GetBool(1);
CefString result = message->GetArgumentList()->GetString(2);
if (m_render_js_bridge.get())
{
m_render_js_bridge->ExecuteJSCallbackFunc(callbackId, error, result);
}
}
return false;
}

View File

@@ -5,12 +5,12 @@
#ifndef CEF_TESTS_CEFSIMPLE_SIMPLE_APP_H_
#define CEF_TESTS_CEFSIMPLE_SIMPLE_APP_H_
#include "include/cef_app.h"
#include <functional>
#include "webview_handler.h"
#include "webview_js_handler.h"
// Implement application-level callbacks for the browser process.
class WebviewApp : public CefApp, public CefBrowserProcessHandler {
class WebviewApp : public CefApp, public CefBrowserProcessHandler, public CefRenderProcessHandler{
public:
WebviewApp(CefRefPtr<WebviewHandler> handler);
@@ -18,6 +18,11 @@ public:
CefRefPtr<CefBrowserProcessHandler> GetBrowserProcessHandler() override {
return this;
}
CefRefPtr<CefRenderProcessHandler> GetRenderProcessHandler() override {
return this;
}
void OnBeforeCommandLineProcessing(
const CefString& process_type,
CefRefPtr<CefCommandLine> command_line) override {
@@ -27,14 +32,52 @@ public:
command_line->AppendSwitch("use-mock-keychain");
command_line->AppendSwitch("single-process");
#endif
#ifdef _DEBUG
command_line->AppendSwitch("renderer-startup-dialog");
command_line->AppendSwitch("no-sandbox");
#endif
}
// CefBrowserProcessHandler methods:
void OnContextInitialized() override;
CefRefPtr<CefClient> GetDefaultClient() override;
// CefRenderProcessHandler methods.
void OnWebKitInitialized() override;
void OnBrowserCreated(
CefRefPtr<CefBrowser> browser,
CefRefPtr<CefDictionaryValue> extra_info) override;
void OnBrowserDestroyed(CefRefPtr<CefBrowser> browser) override;
void OnContextCreated(
CefRefPtr<CefBrowser> browser,
CefRefPtr<CefFrame> frame,
CefRefPtr<CefV8Context> context) override;
void OnContextReleased(
CefRefPtr<CefBrowser> browser,
CefRefPtr<CefFrame> frame,
CefRefPtr<CefV8Context> context) override;
void OnUncaughtException(
CefRefPtr<CefBrowser> browser,
CefRefPtr<CefFrame> frame,
CefRefPtr<CefV8Context> context,
CefRefPtr<CefV8Exception> exception,
CefRefPtr<CefV8StackTrace> stackTrace) override;
void OnFocusedNodeChanged(
CefRefPtr<CefBrowser> browser,
CefRefPtr<CefFrame> frame,
CefRefPtr<CefDOMNode> node) override;
bool OnProcessMessageReceived(
CefRefPtr<CefBrowser> browser,
CefRefPtr<CefFrame> frame,
CefProcessId source_process,
CefRefPtr<CefProcessMessage> message) override;
private:
CefRefPtr<WebviewHandler> m_handler;
std::shared_ptr<CefJSBridge> m_render_js_bridge;
bool m_last_node_is_editable = false;
// Include the default reference counting implementation.
IMPLEMENT_REFCOUNTING(WebviewApp);
};

View File

@@ -0,0 +1,38 @@
#include "webview_cookieVisitor.h"
WebviewCookieVisitor::WebviewCookieVisitor()
{
}
WebviewCookieVisitor::~WebviewCookieVisitor()
{
}
bool WebviewCookieVisitor::Visit(const CefCookie &cookie, int count, int total, bool &deleteCookie)
{
{
std::unique_lock<std::mutex> lock(m_mutexCookieVector);
if (count == 0)
{
m_vecAllCookies.clear();
}
m_vecAllCookies.emplace_back(cookie);
}
return count != total;
}
std::map<std::string, std::map<std::string, std::string>> WebviewCookieVisitor::getVisitedCookies()
{
std::map<std::string, std::map<std::string, std::string>> ret;
for (auto &cookie : m_vecAllCookies)
{
;
std::string domain = CefString(cookie.domain.str).ToString();
std::string name = CefString(cookie.name.str).ToString();
std::string value = CefString(cookie.value.str).ToString();
ret[domain][name] = value;
}
return ret;
}

View File

@@ -0,0 +1,28 @@
#ifndef WEBVIEW_CEF_COOKIE_VISITOR_H_
#define WEBVIEW_CEF_COOKIE_VISITOR_H_
#include "include/cef_base.h"
#include "include/cef_cookie.h"
#include <mutex>
#include <map>
class WebviewCookieVisitor : public CefCookieVisitor
{
public:
WebviewCookieVisitor();
~WebviewCookieVisitor();
//CefCookieVisitor
bool Visit(const CefCookie& cookie, int count, int total, bool& deleteCookie);
std::map<std::string, std::map<std::string, std::string>> getVisitedCookies();
// Include the default reference counting implementation.
IMPLEMENT_REFCOUNTING(WebviewCookieVisitor);
private:
std::vector<CefCookie> m_vecAllCookies;
std::mutex m_mutexCookieVector;
};
#endif // WEBVIEW_CEF_COOKIE_VISITOR_H_

View File

@@ -16,6 +16,8 @@
#include "include/wrapper/cef_closure_task.h"
#include "include/wrapper/cef_helpers.h"
#include "webview_js_handler.h"
namespace {
WebviewHandler* g_instance = nullptr;
@@ -43,6 +45,28 @@ WebviewHandler* WebviewHandler::GetInstance() {
return g_instance;
}
bool WebviewHandler::OnProcessMessageReceived(
CefRefPtr<CefBrowser> browser, CefRefPtr<CefFrame> frame,
CefProcessId source_process, CefRefPtr<CefProcessMessage> message)
{
std::string message_name = message->GetName();
if(message_name == kJSCallCppFunctionMessage)
{
CefString fun_name = message->GetArgumentList()->GetString(0);
CefString param = message->GetArgumentList()->GetString(1);
int js_callback_id = message->GetArgumentList()->GetInt(2);
if (fun_name.empty() || !(browser.get())) {
return false;
}
onJavaScriptChannelMessage(
fun_name,param,std::to_string(js_callback_id),std::to_string(frame->GetIdentifier()));
}
return false;
}
void WebviewHandler::OnTitleChange(CefRefPtr<CefBrowser> browser,
const CefString& title) {
//todo: title change
@@ -291,6 +315,138 @@ void WebviewHandler::openDevTools() {
}
}
void WebviewHandler::setCookie(const std::string& domain, const std::string& key, const std::string& value){
CefRefPtr<CefCookieManager> manager = CefCookieManager::GetGlobalManager(nullptr);
if(manager){
CefCookie cookie;
CefString(&cookie.path).FromASCII("/");
CefString(&cookie.name).FromString(key.c_str());
CefString(&cookie.value).FromString(value.c_str());
if (!domain.empty()) {
CefString(&cookie.domain).FromString(domain.c_str());
}
cookie.httponly = true;
cookie.secure = false;
std::string httpDomain = "https://" + domain + "/cookiestorage";
manager->SetCookie(httpDomain, cookie, nullptr);
}
}
void WebviewHandler::deleteCookie(const std::string& domain, const std::string& key)
{
CefRefPtr<CefCookieManager> manager = CefCookieManager::GetGlobalManager(nullptr);
if (manager) {
std::string httpDomain = "https://" + domain + "/cookiestorage";
manager->DeleteCookies(httpDomain, key, nullptr);
}
}
bool WebviewHandler::visitAllCookies(){
CefRefPtr<CefCookieManager> manager = CefCookieManager::GetGlobalManager(nullptr);
if (!manager)
{
return false;
}
if(!m_CookieVisitor.get())
{
m_CookieVisitor = new WebviewCookieVisitor();
if (!m_CookieVisitor.get())
{
return false;
}
}
if (manager->VisitAllCookies(m_CookieVisitor))
{
if (onAllCookieVisitedCb) {
onAllCookieVisitedCb(m_CookieVisitor->getVisitedCookies());
return true;
}
}
return false;
}
bool WebviewHandler::visitUrlCookies(const std::string& domain, const bool& isHttpOnly){
CefRefPtr<CefCookieManager> manager = CefCookieManager::GetGlobalManager(nullptr);
if (!manager)
{
return false;
}
if(!m_CookieVisitor.get())
{
m_CookieVisitor = new WebviewCookieVisitor();
if (!m_CookieVisitor.get())
{
return false;
}
}
std::string httpDomain = "https://" + domain + "/cookiestorage";
if (manager->VisitUrlCookies(httpDomain, isHttpOnly, m_CookieVisitor))
{
if (onUrlCookieVisitedCb) {
onUrlCookieVisitedCb(m_CookieVisitor->getVisitedCookies());
return true;
}
}
return false;
}
bool WebviewHandler::setJavaScriptChannels(const std::vector<std::string> channels)
{
std::string extensionCode = "";
for(auto& channel : channels)
{
extensionCode += channel;
extensionCode += " = (e,r) => {external.JavaScriptChannel('";
extensionCode += channel;
extensionCode += "',e,r)};";
}
return executeJavaScript(extensionCode);
}
bool WebviewHandler::sendJavaScriptChannelCallBack(const bool error, const std::string result, const std::string callbackId, const std::string frameId)
{
CefRefPtr<CefProcessMessage> message = CefProcessMessage::Create(kExecuteJsCallbackMessage);
CefRefPtr<CefListValue> args = message->GetArgumentList();
args->SetInt(0, atoi(callbackId.c_str()));
args->SetBool(1, error);
args->SetString(2, result);
BrowserList::iterator bit = browser_list_.begin();
for (; bit != browser_list_.end(); ++bit) {
CefRefPtr<CefFrame> frame = (*bit)->GetMainFrame();
if (frame->GetIdentifier() == atoll(frameId.c_str()))
{
frame->SendProcessMessage(PID_RENDERER, message);
return true;
}
}
return false;
}
bool WebviewHandler::executeJavaScript(const std::string code)
{
if(!code.empty())
{
BrowserList::iterator bit = browser_list_.begin();
for (; bit != browser_list_.end(); ++bit) {
if ((*bit).get()) {
CefRefPtr<CefFrame> frame = (*bit)->GetMainFrame();
if (frame) {
frame->ExecuteJavaScript(code, frame->GetURL(), 0);
return true;
}
}
}
}
return false;
}
void WebviewHandler::GetViewRect(CefRefPtr<CefBrowser> browser, CefRect &rect) {
CEF_REQUIRE_UI_THREAD();

View File

@@ -10,6 +10,8 @@
#include <functional>
#include <list>
#include "webview_cookieVisitor.h"
class WebviewHandler : public CefClient,
public CefDisplayHandler,
public CefLifeSpanHandler,
@@ -19,6 +21,9 @@ public:
std::function<void(const void*, int32_t width, int32_t height)> onPaintCallback;
std::function<void(std::string url)> onUrlChangedCb;
std::function<void(std::string title)> onTitleChangedCb;
std::function<void(std::map<std::string, std::map<std::string, std::string>>)> onAllCookieVisitedCb;
std::function<void(std::map<std::string, std::map<std::string, std::string>>)> onUrlCookieVisitedCb;
std::function<void(std::string channelName, std::string message, std::string js_callback_id, std::string frameId)> onJavaScriptChannelMessage;
explicit WebviewHandler();
~WebviewHandler();
@@ -35,6 +40,13 @@ public:
}
virtual CefRefPtr<CefLoadHandler> GetLoadHandler() override { return this; }
virtual CefRefPtr<CefRenderHandler> GetRenderHandler() override { return this; }
bool OnProcessMessageReceived(
CefRefPtr<CefBrowser> browser,
CefRefPtr<CefFrame> frame,
CefProcessId source_process,
CefRefPtr<CefProcessMessage> message) override;
// CefDisplayHandler methods:
virtual void OnTitleChange(CefRefPtr<CefBrowser> browser,
@@ -94,6 +106,15 @@ public:
void reload();
void openDevTools();
void setCookie(const std::string& domain, const std::string& key, const std::string& value);
void deleteCookie(const std::string& domain, const std::string& key);
bool visitAllCookies();
bool visitUrlCookies(const std::string& domain, const bool& isHttpOnly);
bool setJavaScriptChannels(const std::vector<std::string> channels);
bool sendJavaScriptChannelCallBack(const bool error, const std::string result, const std::string callbackId, const std::string frameId);
bool executeJavaScript(const std::string code);
private:
uint32_t width = 1;
uint32_t height = 1;
@@ -106,6 +127,8 @@ private:
// Include the default reference counting implementation.
IMPLEMENT_REFCOUNTING(WebviewHandler);
CefRefPtr<WebviewCookieVisitor> m_CookieVisitor;
};
#endif // CEF_TESTS_CEFSIMPLE_SIMPLE_HANDLER_H_

View File

@@ -0,0 +1,265 @@
#include "webview_js_handler.h"
#include <atomic>
std::atomic_long s_nReqID {1001};
bool CefJSHandler::Execute(const CefString& name,
CefRefPtr<CefV8Value> object,
const CefV8ValueList& arguments,
CefRefPtr<CefV8Value>& retval,
CefString& exception)
{
if (name == "jsCmd")
{
if (arguments.size() < 2) {
exception = "Invalid arguments.";
return true;
}
//the first param is function name,the last param is callback function,and allow most 2 custom params between them.
CefString function_name = arguments[0]->GetStringValue();
CefString params = "";
CefRefPtr<CefV8Value> callback;
CefRefPtr<CefV8Value> rawdata;
if (arguments[0]->IsString() && arguments[1]->IsFunction())
{
callback = arguments[1];
}
else if (arguments[0]->IsString() && arguments[1]->IsString() && arguments[2]->IsFunction())
{
params = arguments[1]->GetStringValue();
callback = arguments[2];
}
else if (arguments[0]->IsString() && arguments[1]->IsString() && arguments[3]->IsFunction())
{
params = arguments[1]->GetStringValue();
rawdata = arguments[2];
callback = arguments[3];
}
else
{
exception = "Invalid arguments.";
return true;
}
//call c++ funtion
if (!js_bridge_->CallCppFunction(function_name, params, callback, rawdata))
{
std::ostringstream strStream;
strStream << "Failed to call function: " << function_name.c_str() << ".";
strStream.flush();
exception = strStream.str();
}
}
else if (name == "StartRequest")
{
if (arguments.size() < 5) {
exception = "Invalid arguments.";
return true;
}
// args[0]
int reqId = (int)arguments[0]->GetIntValue();
// args[1]
CefString strCmd = arguments[1]->GetStringValue();
//// args[2]
CefString strCallback = arguments[2]->GetStringValue();
//// args[3]
CefString strArgs = arguments[3]->GetStringValue();
// call c++ function
if (!js_bridge_->StartRequest(reqId, strCmd, strCallback, strArgs))
{
std::ostringstream strStream;
strStream << "Failed to call function: " << strCmd.c_str() << ".";
strStream.flush();
exception = strStream.str();
}
}
else if (name == "GetNextReqID")
{
int reqID = CefJSBridge::GetNextReqID();
retval = CefV8Value::CreateInt(reqID);
}
else {
exception = "NativeHost no this fun.";
}
return true;
}
bool CefJSBridge::StartRequest(int reqId,
const CefString& strCmd,
const CefString& strCallback,
const CefString& strArgs)
{
if (reqId > 0) {
reqId *= -1;
}
auto it = startRequest_callback_.find(reqId);
if (it == startRequest_callback_.cend())
{
CefRefPtr<CefV8Context> context = CefV8Context::GetCurrentContext();
if (context)
{
CefRefPtr<CefFrame> frame = context->GetFrame();
if (frame)
{
CefRefPtr<CefProcessMessage> message = CefProcessMessage::Create(kJSCallCppFunctionMessage);
message->GetArgumentList()->SetString(0, strCmd);
message->GetArgumentList()->SetString(1, strArgs);
message->GetArgumentList()->SetInt(2, reqId);
startRequest_callback_.emplace(reqId, std::make_pair(frame, strCallback));
frame->SendProcessMessage(PID_BROWSER, message);
return true;
}
}
}
return false;
}
int CefJSBridge::GetNextReqID()
{
long nRet = ++s_nReqID;
if (nRet < 0)
{
nRet = 0;
}
while (nRet == 0)
{
nRet = ++s_nReqID;
}
return nRet;
}
bool CefJSBridge::CallCppFunction(const CefString& function_name,
const CefString& params,
CefRefPtr<CefV8Value> callback,
CefRefPtr<CefV8Value> rawdata)
{
auto it = render_callback_.find(js_callback_id_);
if (it == render_callback_.cend())
{
CefRefPtr<CefV8Context> context = CefV8Context::GetCurrentContext();
if (context)
{
CefRefPtr<CefFrame> frame = context->GetFrame();
if (frame)
{
CefRefPtr<CefProcessMessage> message = CefProcessMessage::Create(kJSCallCppFunctionMessage);
message->GetArgumentList()->SetString(0, function_name);
message->GetArgumentList()->SetString(1, params);
message->GetArgumentList()->SetInt(2, js_callback_id_);
render_callback_.emplace(js_callback_id_++, std::make_pair(context, std::make_pair(callback, rawdata)));
frame->SendProcessMessage(PID_BROWSER, message);
return true;
}
}
}
return false;
}
void CefJSBridge::RemoveCallbackFuncWithFrame(CefRefPtr<CefFrame> frame)
{
if (!render_callback_.empty()) {
for (auto it = render_callback_.begin(); it != render_callback_.end();) {
if (it->second.first->IsSame(frame->GetV8Context())) {
it = render_callback_.erase(it);
}
else {
++it;
}
}
}
if (!startRequest_callback_.empty()) {
for (auto it = startRequest_callback_.begin(); it != startRequest_callback_.end();) {
if (it->second.first->GetIdentifier() == frame->GetIdentifier()) {
it = startRequest_callback_.erase(it);
}
else {
++it;
}
}
}
}
bool CefJSBridge::ExecuteJSCallbackFunc(int callbackId, bool error, const CefString& result)
{
if (callbackId < 0)
{
auto it = startRequest_callback_.find(callbackId);
if (it != startRequest_callback_.cend())
{
auto frame = it->second.first;
CefString callback = it->second.second;
if (callback != "" && frame.get())
{
std::ostringstream strStream;
strStream <<"window['" << callback.ToString() << "'](" << callbackId * -1 << ", " << result.ToString() << ");";
strStream.flush();
CefString strCode = strStream.str();
frame->ExecuteJavaScript(strCode, frame->GetURL(), 0);
startRequest_callback_.erase(callbackId);
return true;
}
else
{
return false;
}
}
}
else
{
auto it = render_callback_.find(callbackId);
if (it != render_callback_.cend())
{
auto context = it->second.first;
auto callback = it->second.second.first;
auto rawdata = it->second.second.second;
if (context.get() && callback.get())
{
context->Enter();
CefV8ValueList arguments;
//the first param marks whether the function execution result was successful
arguments.push_back(CefV8Value::CreateBool(error));
// the second prarm take the return data
arguments.push_back(CefV8Value::CreateString(result));
if (rawdata.get()) {
arguments.push_back(rawdata);
}
// call js function
CefRefPtr<CefV8Value> retval = callback->ExecuteFunction(nullptr, arguments);
context->Exit();
render_callback_.erase(callbackId);
return true;
}
else
{
return false;
}
}
}
return false;
}

View File

@@ -0,0 +1,50 @@
#pragma once
#ifndef WEBVIEW_CEF_JS_HANDLER_H_
#define WEBVIEW_CEF_JS_HANDLER_H_
#include "include/cef_base.h"
#include "include/cef_app.h"
#include <functional>
#include <memory>
static const char kJSCallCppFunctionMessage[] = "JSCallCppFunction"; //js call c++ message
static const char kExecuteJsCallbackMessage[] = "ExecuteJsCallback"; //c++ call js message
static const char kFocusedNodeChangedMessage[] = "FocusedNodeChanged"; //elements that capture focus in web pages changed message
class CefJSBridge
{
typedef std::map<int/* js_callback_id*/, std::pair<CefRefPtr<CefV8Context>/* context*/, std::pair<CefRefPtr<CefV8Value>/* callback*/, CefRefPtr<CefV8Value>/* rawdata*/>>> RenderCallbackMap;
typedef std::map<int/* reqId*/, std::pair<CefRefPtr<CefFrame>/* frame*/, CefString /* callback*/>> StartRequestCallbackMap;
public:
CefJSBridge() {};
~CefJSBridge() {};
public:
static int GetNextReqID();
bool StartRequest(int reqId, const CefString& strCmd, const CefString& strCallback, const CefString& strArgs);
bool CallCppFunction(const CefString& function_name, const CefString& params, CefRefPtr<CefV8Value> callback, CefRefPtr<CefV8Value> rawdata);
void RemoveCallbackFuncWithFrame(CefRefPtr<CefFrame> frame);
bool ExecuteJSCallbackFunc(int js_callback_id, bool has_error, const CefString& json_result);
private:
uint32 js_callback_id_ = 0;
RenderCallbackMap render_callback_;
StartRequestCallbackMap startRequest_callback_;
};
class CefJSHandler : public CefV8Handler
{
public:
CefJSHandler() {}
virtual bool Execute(const CefString& name,
CefRefPtr<CefV8Value> object,
const CefV8ValueList& arguments,
CefRefPtr<CefV8Value>& retval,
CefString& exception) override;
void AttachJSBridge(std::shared_ptr<CefJSBridge> js_bridge) { js_bridge_ = js_bridge; }
IMPLEMENT_REFCOUNTING(CefJSHandler);
private:
std::shared_ptr<CefJSBridge> js_bridge_;
};
#endif // WEBVIEW_CEF_JS_HANDLER_H_

View File

@@ -41,6 +41,24 @@ class _MyAppState extends State<MyApp> {
_textController.text = url;
},
));
// ignore: prefer_collection_literals
final Set<JavascriptChannel> jsChannels = [
JavascriptChannel(
name: 'Print',
onMessageReceived: (JavascriptMessage message) {
print(message.message);
_controller.sendJavaScriptChannelCallBack(
false,
"{'code':'200','message':'print succeed!'}",
message.callbackId,
message.frameId);
}),
].toSet();
//normal JavaScriptChannels
await _controller.setJavaScriptChannels(jsChannels);
//also you can build your own jssdk by execute JavaScript code to CEF
await _controller.executeJavaScript("function abc(e){console.log(e)}");
// If the widget was removed from the tree while the asynchronous platform
// message was in flight, we want to discard the reply rather than calling
// setState to update our non-existent appearance.

View File

@@ -6,6 +6,7 @@ import 'package:flutter/services.dart';
import 'package:flutter/widgets.dart';
import 'webview_events_listener.dart';
import 'webview_javascript.dart';
const MethodChannel _pluginChannel = MethodChannel("webview_cef");
@@ -15,6 +16,9 @@ class WebViewController extends ValueNotifier<bool> {
bool _isDisposed = false;
WebviewEventsListener? _listener;
final Map<String, JavascriptChannel> _javascriptChannels =
<String, JavascriptChannel>{};
Future<void> get ready => _creatingCompleter.future;
WebViewController() : super(false);
@@ -46,6 +50,19 @@ class WebViewController extends ValueNotifier<bool> {
case "titleChanged":
_listener?.onTitleChanged?.call(call.arguments);
return;
case "allCookiesVisited":
_listener?.onAllCookiesVisited?.call(Map.from(call.arguments));
return;
case "urlCookiesVisited":
_listener?.onUrlCookiesVisited?.call(Map.from(call.arguments));
return;
case 'javascriptChannelMessage':
_handleJavascriptChannelMessage(
call.arguments['channel'],
call.arguments['message'],
call.arguments['callbackId'],
call.arguments['frameId']);
break;
default:
}
}
@@ -59,6 +76,7 @@ class WebViewController extends ValueNotifier<bool> {
await _creatingCompleter.future;
if (!_isDisposed) {
_isDisposed = true;
_javascriptChannels.clear();
await _pluginChannel.invokeMethod('dispose', _textureId);
}
super.dispose();
@@ -106,6 +124,71 @@ class WebViewController extends ValueNotifier<bool> {
return _pluginChannel.invokeMethod('openDevTools');
}
Future<void> setCookie(String domain, String key, String val) async {
if (_isDisposed) {
return;
}
assert(value);
return _pluginChannel.invokeMethod('setCookie', [domain, key, val]);
}
Future<void> deleteCookie(String domain, String key) async {
if (_isDisposed) {
return;
}
assert(value);
return _pluginChannel.invokeMethod('deleteCookie', [domain, key]);
}
Future<void> visitAllCookies() async {
if (_isDisposed) {
return;
}
assert(value);
return _pluginChannel.invokeMethod('visitAllCookies');
}
Future<void> visitUrlCookies(String domain, bool isHttpOnly) async {
if (_isDisposed) {
return;
}
assert(value);
return _pluginChannel.invokeMethod('visitUrlCookies', [domain, isHttpOnly]);
}
Future<void> setJavaScriptChannels(Set<JavascriptChannel> channels) async {
if (_isDisposed) {
return;
}
assert(value);
_assertJavascriptChannelNamesAreUnique(channels);
channels.forEach((channel) {
_javascriptChannels[channel.name] = channel;
});
return _pluginChannel.invokeMethod('setJavaScriptChannels',
[_extractJavascriptChannelNames(channels).toList()]);
}
Future<void> sendJavaScriptChannelCallBack(
bool error, String result, String callbackId, String frameId) async {
if (_isDisposed) {
return;
}
assert(value);
return _pluginChannel.invokeMethod(
'sendJavaScriptChannelCallBack', [error, result, callbackId, frameId]);
}
Future<void> executeJavaScript(String code) async {
if (_isDisposed) {
return;
}
assert(value);
return _pluginChannel.invokeMethod('executeJavaScript', [code]);
}
/// Moves the virtual cursor to [position].
Future<void> _cursorMove(Offset position) async {
if (_isDisposed) {
@@ -162,6 +245,30 @@ class WebViewController extends ValueNotifier<bool> {
return _pluginChannel
.invokeMethod('setSize', [dpi, size.width, size.height]);
}
Set<String> _extractJavascriptChannelNames(Set<JavascriptChannel> channels) {
final Set<String> channelNames =
channels.map((JavascriptChannel channel) => channel.name).toSet();
return channelNames;
}
void _handleJavascriptChannelMessage(final String channelName,
final String message, final String callbackId, final String frameId) {
if (_javascriptChannels.containsKey(channelName)) {
_javascriptChannels[channelName]!
.onMessageReceived(JavascriptMessage(message, callbackId, frameId));
} else {
print('Channel "$channelName" is not exstis');
}
}
void _assertJavascriptChannelNamesAreUnique(
final Set<JavascriptChannel>? channels) {
if (channels == null || channels.isEmpty) {
return;
}
assert(_extractJavascriptChannelNames(channels).length == channels.length);
}
}
class WebView extends StatefulWidget {

View File

@@ -1,9 +1,17 @@
typedef TitleChangeCb = void Function(String title);
typedef UrlChangeCb = void Function(String url);
typedef AllCookieVisitedCb = void Function(Map<String, dynamic> cookies);
typedef UrlCookieVisitedCb = void Function(Map<String, dynamic> cookies);
class WebviewEventsListener {
TitleChangeCb? onTitleChanged;
UrlChangeCb? onUrlChanged;
AllCookieVisitedCb? onAllCookiesVisited;
UrlCookieVisitedCb? onUrlCookiesVisited;
WebviewEventsListener({this.onTitleChanged, this.onUrlChanged});
WebviewEventsListener(
{this.onTitleChanged,
this.onUrlChanged,
this.onAllCookiesVisited,
this.onUrlCookiesVisited});
}

View File

@@ -0,0 +1,49 @@
/// A message that was sent by JavaScript code running in a [WebView].
class JavascriptMessage {
/// Constructs a JavaScript message object.
///
/// The `message` parameter must not be null.
const JavascriptMessage(this.message, this.callbackId, this.frameId)
: assert(message != null && frameId != null);
/// The contents of the message that was sent by the JavaScript code.
final String message;
// The callbackId of the JavaScript code
final String callbackId;
// the frameId of the webview frame
final String frameId;
}
final RegExp _validChannelNames = RegExp('^[a-zA-Z_][a-zA-Z0-9]*\$');
/// A named channel for receiving messaged from JavaScript code running inside a web view.
class JavascriptChannel {
/// Constructs a Javascript channel.
///
/// The parameters `name` and `onMessageReceived` must not be null.
JavascriptChannel({
required this.name,
required this.onMessageReceived,
}) : assert(_validChannelNames.hasMatch(name));
/// The channel's name.
///
/// Passing this channel object as part of a [WebView.javascriptChannels] adds a channel object to
/// the Javascript window object's property named `name`.
///
/// The name must start with a letter or underscore(_), followed by any combination of those
/// characters plus digits.
///
/// Note that any JavaScript existing `window` property with this name will be overriden.
///
/// See also [WebView.javascriptChannels] for more details on the channel registration mechanism.
final String name;
/// A callback that's invoked when a message is received through the channel.
final JavascriptMessageHandler onMessageReceived;
}
/// Callback type for handling messages sent from Javascript running in a web view.
typedef void JavascriptMessageHandler(JavascriptMessage message);

View File

@@ -1,2 +1,3 @@
export 'src/webview.dart';
export 'src/webview_events_listener.dart';
export 'src/webview_javascript.dart';

View File

@@ -41,6 +41,20 @@ extern int64_t textureId;
+ (void) setMethodChannel: (FlutterMethodChannel*)channel;
+ (void) setCookie: (NSString *)domain key:(NSString *) key value:(NSString *)value;
+ (void) deleteCookie: (NSString *)domain key:(NSString *) key;
+ (void) visitAllCookies;
+ (void) visitUrlCookies: (NSString *)domain isHttpOnly:(bool)isHttpOnly;
+ (void) setJavaScriptChannels: (NSArray *)channels;
+ (void) sendJavaScriptChannelCallBack: (bool)error result:(NSString *)result callbackId:(NSString *)callbackId frameId:(NSString *)frameId;
+ (void) executeJavaScript: (NSString *)code;
@end
#endif /* CefWrapper_h */

View File

@@ -11,6 +11,8 @@
#import "include/cef_app.h"
#import "../../common/webview_app.h"
#import "../../common/webview_handler.h"
#import "../../common/webview_cookieVisitor.h"
#import "../../common/webview_js_handler.h"
#include <thread>
@@ -93,7 +95,52 @@ FlutterMethodChannel* f_channel;
handler.get()->onTitleChangedCb = [](std::string title) {
[f_channel invokeMethod:@"titleChanged" arguments:[NSString stringWithCString:title.c_str() encoding:NSUTF8StringEncoding]];
};
//allcookie visited cb
handler.get()->onAllCookieVisitedCb = [](std::map<std::string, std::map<std::string, std::string>> cookies) {
NSMutableDictionary * dict = [NSMutableDictionary dictionary];
for(auto &cookie : cookies)
{
NSString * domain = [NSString stringWithCString:cookie.first.c_str() encoding:NSUTF8StringEncoding];
NSMutableDictionary * tempdict = [NSMutableDictionary dictionary];
for(auto &c : cookie.second)
{
NSString * key = [NSString stringWithCString:c.first.c_str() encoding:NSUTF8StringEncoding];
NSString * val = [NSString stringWithCString:c.second.c_str() encoding:NSUTF8StringEncoding];
tempdict[key] = val;
}
dict[domain] = tempdict;
}
[f_channel invokeMethod:@"allCookiesVisited" arguments:dict];
};
//urlcookie visited cb
handler.get()->onUrlCookieVisitedCb = [](std::map<std::string, std::map<std::string, std::string>> cookies) {
NSMutableDictionary * dict = [NSMutableDictionary dictionary];
for(auto &cookie : cookies)
{
NSString * domain = [NSString stringWithCString:cookie.first.c_str() encoding:NSUTF8StringEncoding];
NSMutableDictionary * tempdict = [NSMutableDictionary dictionary];
for(auto &c : cookie.second)
{
NSString * key = [NSString stringWithCString:c.first.c_str() encoding:NSUTF8StringEncoding];
NSString * val = [NSString stringWithCString:c.second.c_str() encoding:NSUTF8StringEncoding];
tempdict[key] = val;
}
dict[domain] = tempdict;
}
[f_channel invokeMethod:@"urlCookiesVisited" arguments:dict];
};
//JavaScriptChannel called
handler.get()->onJavaScriptChannelMessage = [](std::string channelName, std::string message, std::string callbackId, std::string frameId) {
NSMutableDictionary * dict = [NSMutableDictionary dictionary];
dict[@"channel"] = [NSString stringWithCString:channelName.c_str() encoding:NSUTF8StringEncoding];
dict[@"message"] = [NSString stringWithCString:message.c_str() encoding:NSUTF8StringEncoding];
dict[@"callbackId"] = [NSString stringWithCString:callbackId.c_str() encoding:NSUTF8StringEncoding];
dict[@"frameId"] = [NSString stringWithCString:frameId.c_str() encoding:NSUTF8StringEncoding];
[f_channel invokeMethod:@"javascriptChannelMessage" arguments:dict];
};
CefSettings settings;
settings.windowless_rendering_enabled = true;
settings.external_message_pump = true;
@@ -248,4 +295,38 @@ FlutterMethodChannel* f_channel;
f_channel = channel;
}
+ (void)setCookie: (NSString *)domain key:(NSString *) key value:(NSString *)value {
handler.get()->setCookie(std::string([domain cStringUsingEncoding:NSUTF8StringEncoding]), std::string([key cStringUsingEncoding:NSUTF8StringEncoding]), std::string([value cStringUsingEncoding:NSUTF8StringEncoding]));
}
+ (void)deleteCookie: (NSString *)domain key:(NSString *) key {
handler.get()->deleteCookie(std::string([domain cStringUsingEncoding:NSUTF8StringEncoding]), std::string([key cStringUsingEncoding:NSUTF8StringEncoding]));
}
+ (void)visitAllCookies {
handler.get()->visitAllCookies();
}
+ (void)visitUrlCookies: (NSString *)domain isHttpOnly:(bool)isHttpOnly {
handler.get()->visitUrlCookies(std::string([domain cStringUsingEncoding:NSUTF8StringEncoding]), isHttpOnly);
}
+ (void) setJavaScriptChannels: (NSArray *)channels {
std::vector<std::string> stdChannels;
NSEnumerator * enumerator = [channels objectEnumerator];
NSString * value;
while (value = [enumerator nextObject]) {
stdChannels.push_back(std::string([value cStringUsingEncoding:NSUTF8StringEncoding]));
}
handler.get()->setJavaScriptChannels(stdChannels);
}
+ (void) sendJavaScriptChannelCallBack: (bool)error result:(NSString *)result callbackId:(NSString *)callbackId frameId:(NSString *)frameId {
handler.get()->sendJavaScriptChannelCallBack(error, std::string([result cStringUsingEncoding:NSUTF8StringEncoding]),
std::string([callbackId cStringUsingEncoding:NSUTF8StringEncoding]), std::string([frameId cStringUsingEncoding:NSUTF8StringEncoding]));
}
+ (void) executeJavaScript: (NSString *)code {
handler.get()->executeJavaScript(std::string([code cStringUsingEncoding:NSUTF8StringEncoding]));
}
@end

View File

@@ -95,6 +95,53 @@
[CefWrapper openDevTools];
result(nil);
}
else if([@"setCookie" isEqualToString:call.method]){
NSArray<NSString *> *_arg = call.arguments;
NSString * domain = [_arg objectAtIndex:0];
NSString * key = [_arg objectAtIndex:1];
NSString * value = [_arg objectAtIndex:2];
[CefWrapper setCookie:domain key:key value:value];
result(nil);
}
else if ([@"deleteCookie" isEqualToString:call.method]) {
NSArray<NSString *> *_arg = call.arguments;
NSString * domain = [_arg objectAtIndex:0];
NSString * key = [_arg objectAtIndex:1];
[CefWrapper deleteCookie:domain key:key];
result(nil);
}
else if ([@"visitAllCookies" isEqualToString:call.method]) {
[CefWrapper visitAllCookies];
result(nil);
}
else if ([@"visitUrlCookies" isEqualToString:call.method]) {
NSArray<NSString *> *_arg = call.arguments;
NSString * domain = [_arg objectAtIndex:0];
NSString * isHttpOnly = [_arg objectAtIndex:1];
[CefWrapper visitUrlCookies:domain isHttpOnly:[isHttpOnly boolValue]];
result(nil);
}
else if ([@"setJavaScriptChannels" isEqualToString:call.method]) {
NSArray<NSArray *> *_arg = call.arguments;
NSArray * channels = [_arg objectAtIndex:0];
[CefWrapper setJavaScriptChannels:channels];
result(nil);
}
else if ([@"sendJavaScriptChannelCallBack" isEqualToString:call.method]) {
NSArray<NSString *> *_arg = call.arguments;
NSString * error = [_arg objectAtIndex:0];
NSString * ret = [_arg objectAtIndex:1];
NSString * callbackId = [_arg objectAtIndex:2];
NSString * frameId = [_arg objectAtIndex:3];
[CefWrapper sendJavaScriptChannelCallBack:[error boolValue] result:ret callbackId:callbackId frameId:frameId];
result(nil);
}
else if ([@"executeJavaScript" isEqualToString:call.method]) {
NSArray<NSString *> *_arg = call.arguments;
NSString * code = [_arg objectAtIndex:0];
[CefWrapper executeJavaScript:code];
result(nil);
}
else {
result(FlutterMethodNotImplemented);
}

View File

@@ -4,5 +4,7 @@
#include "../../common/webview_app.cc"
#include "../../common/webview_handler.cc"
#include "../../common/webview_cookieVisitor.cc"
#include "../../common/webview_js_handler.cc"
#endif

View File

@@ -27,6 +27,10 @@ list(APPEND PLUGIN_SOURCES
"${CMAKE_CURRENT_LIST_DIR}/../common/webview_app.h"
"${CMAKE_CURRENT_LIST_DIR}/../common/webview_handler.cc"
"${CMAKE_CURRENT_LIST_DIR}/../common/webview_handler.h"
"${CMAKE_CURRENT_LIST_DIR}/../common/webview_js_handler.cc"
"${CMAKE_CURRENT_LIST_DIR}/../common/webview_js_handler.h"
"${CMAKE_CURRENT_LIST_DIR}/../common/webview_cookieVisitor.cc"
"${CMAKE_CURRENT_LIST_DIR}/../common/webview_cookieVisitor.h"
)
# Define the plugin library target. Its name must not be changed (see comment

View File

@@ -12,11 +12,9 @@
#include <memory>
#include <thread>
#include<iostream>
#include <iostream>
#include <mutex>
#include "webview_app.h"
namespace webview_cef {
bool init = false;
int64_t texture_id;
@@ -38,6 +36,7 @@ namespace webview_cef {
CefRefPtr<WebviewHandler> handler(new WebviewHandler());
CefRefPtr<WebviewApp> app(new WebviewApp(handler));
CefMainArgs mainArgs;
std::unique_ptr<
flutter::MethodChannel<flutter::EncodableValue>,
std::default_delete<flutter::MethodChannel<flutter::EncodableValue>>>
@@ -98,6 +97,43 @@ namespace webview_cef {
channel->InvokeMethod("titleChanged", std::make_unique<flutter::EncodableValue>(title));
};
handler.get()->onAllCookieVisitedCb = [](std::map<std::string, std::map<std::string, std::string>> cookies) {
flutter::EncodableMap retMap;
for (auto& cookie : cookies)
{
flutter::EncodableMap tempMap;
for (auto& c : cookie.second)
{
tempMap[flutter::EncodableValue(c.first)] = flutter::EncodableValue(c.second);
}
retMap[flutter::EncodableValue(cookie.first)] = flutter::EncodableValue(tempMap);
}
channel->InvokeMethod("allCookiesVisited", std::make_unique<flutter::EncodableValue>(retMap));
};
handler.get()->onUrlCookieVisitedCb = [](std::map<std::string, std::map<std::string, std::string>> cookies) {
flutter::EncodableMap retMap;
for (auto& cookie : cookies)
{
flutter::EncodableMap tempMap;
for (auto& c : cookie.second)
{
tempMap[flutter::EncodableValue(c.first)] = flutter::EncodableValue(c.second);
}
retMap[flutter::EncodableValue(cookie.first)] = flutter::EncodableValue(tempMap);
}
channel->InvokeMethod("urlCookiesVisited", std::make_unique<flutter::EncodableValue>(retMap));
};
handler.get()->onJavaScriptChannelMessage = [](std::string channelName, std::string message, std::string callbackId, std::string frameId) {
flutter::EncodableMap retMap;
retMap[flutter::EncodableValue("channel")] = flutter::EncodableValue(channelName);
retMap[flutter::EncodableValue("message")] = flutter::EncodableValue(message);
retMap[flutter::EncodableValue("callbackId")] = flutter::EncodableValue(callbackId);
retMap[flutter::EncodableValue("frameId")] = flutter::EncodableValue(frameId);
channel->InvokeMethod("javascriptChannelMessage", std::make_unique<flutter::EncodableValue>(retMap));
};
CefSettings cefs;
cefs.windowless_rendering_enabled = true;
CefInitialize(mainArgs, cefs, app.get(), nullptr);
@@ -233,6 +269,63 @@ namespace webview_cef {
handler.get()->openDevTools();
result->Success();
}
else if(method_call.method_name().compare("setCookie") == 0){
const flutter::EncodableList* list =
std::get_if<flutter::EncodableList>(method_call.arguments());
const auto domain = *std::get_if<std::string>(&(*list)[0]);
const auto key = *std::get_if<std::string>(&(*list)[1]);
const auto value = *std::get_if<std::string>(&(*list)[2]);
handler.get()->setCookie(domain, key, value);
result->Success();
}
else if (method_call.method_name().compare("deleteCookie") == 0) {
const flutter::EncodableList* list =
std::get_if<flutter::EncodableList>(method_call.arguments());
const auto domain = *std::get_if<std::string>(&(*list)[0]);
const auto key = *std::get_if<std::string>(&(*list)[1]);
handler.get()->deleteCookie(domain, key);
result->Success();
}
else if (method_call.method_name().compare("visitAllCookies") == 0) {
handler.get()->visitAllCookies();
result->Success();
}
else if (method_call.method_name().compare("visitUrlCookies") == 0) {
const flutter::EncodableList* list =
std::get_if<flutter::EncodableList>(method_call.arguments());
const auto domain = *std::get_if<std::string>(&(*list)[0]);
const auto isHttpOnly = *std::get_if<bool>(&(*list)[1]);
handler.get()->visitUrlCookies(domain, isHttpOnly);
result->Success();
}
else if(method_call.method_name().compare("setJavaScriptChannels") == 0){
const flutter::EncodableList* list =
std::get_if<flutter::EncodableList>(method_call.arguments());
const auto jsChannels = *std::get_if<std::vector<flutter::EncodableValue>>(&(*list)[0]);
std::vector<std::string> channels;
for (auto& jsChannel : jsChannels) {
channels.push_back(*std::get_if<std::string>(&(jsChannel)));
}
handler.get()->setJavaScriptChannels(channels);
result->Success();
}
else if (method_call.method_name().compare("sendJavaScriptChannelCallBack") == 0) {
const flutter::EncodableList* list =
std::get_if<flutter::EncodableList>(method_call.arguments());
const auto error = *std::get_if<bool>(&(*list)[0]);
const auto ret = *std::get_if<std::string>(&(*list)[1]);
const auto callbackId = *std::get_if<std::string>(&(*list)[2]);
const auto frameId = *std::get_if<std::string>(&(*list)[3]);
handler.get()->sendJavaScriptChannelCallBack(error, ret,callbackId,frameId);
result->Success();
}
else if(method_call.method_name().compare("executeJavaScript") == 0){
const flutter::EncodableList* list =
std::get_if<flutter::EncodableList>(method_call.arguments());
const auto code = *std::get_if<std::string>(&(*list)[0]);
handler.get()->executeJavaScript(code);
result->Success();
}
else {
result->NotImplemented();
}

View File

@@ -6,10 +6,13 @@
#include <memory>
#include "include/cef_app.h"
#include "webview_app.h"
namespace webview_cef {
//Use WebviewApp for both CefExecuteProcess and CefInitialize.
extern CefRefPtr<WebviewApp> app;
class WebviewCefPlugin : public flutter::Plugin {
public:
static void RegisterWithRegistrar(flutter::PluginRegistrarWindows *registrar);

View File

@@ -3,7 +3,7 @@
#include <flutter/plugin_registrar_windows.h>
#include "webview_cef_plugin.h"
#include "include/cef_app.h"
//#include "include/cef_app.h"
void WebviewCefPluginCApiRegisterWithRegistrar(
FlutterDesktopPluginRegistrarRef registrar) {
@@ -14,7 +14,8 @@ void WebviewCefPluginCApiRegisterWithRegistrar(
FLUTTER_PLUGIN_EXPORT void initCEFProcesses() {
CefMainArgs mainArgs;
CefExecuteProcess(mainArgs, nullptr, nullptr);
//Post class WebviewApp can run the override functions which support JSBridge/JSSDK
CefExecuteProcess(mainArgs, webview_cef::app, nullptr);
}
bool IsKeyDown(WPARAM wparam) {