From 7c61c8308f4d75012afc809474e9d1325df33ee8 Mon Sep 17 00:00:00 2001 From: JackLee <809262979@qq.com> Date: Thu, 20 Mar 2025 21:11:50 +0800 Subject: [PATCH] =?UTF-8?q?=E6=B5=8B=E8=AF=95=E4=BB=A3=E7=A0=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/Cef/CefViewAppBase.cpp | 74 ++++ src/Cef/CefViewAppBase.h | 67 ++++ src/Cef/CefViewBridgeObject.cpp | 518 ++++++++++++++++++++++++++ src/Cef/CefViewBridgeObject.h | 221 +++++++++++ src/Cef/CefViewLog/CefViewCoreLog.cpp | 141 +++++++ src/Cef/CefViewLog/CefViewCoreLog.h | 57 +++ src/Cef/CefViewLog/CefViewDebug.cpp | 64 ++++ src/Cef/CefViewLog/CefViewDebug.h | 21 ++ src/Cef/CefViewOtherApp.cpp | 6 + src/Cef/CefViewOtherApp.h | 22 ++ src/Cef/CefViewRenderApp.cpp | 208 +++++++++++ src/Cef/CefViewRenderApp.h | 180 +++++++++ src/Cef/CefViewWidget.cpp | 31 +- src/Cef/CefViewWidget.h | 9 + src/Cef/MainWindow.cpp | 205 ++++++++-- src/Cef/MainWindow.h | 40 +- src/ctaiHistoryTextEdit.cpp | 3 + src/ctaiHistoryTextEdit.h | 2 + src/main.cpp | 59 +-- 19 files changed, 1875 insertions(+), 53 deletions(-) create mode 100644 src/Cef/CefViewAppBase.cpp create mode 100644 src/Cef/CefViewAppBase.h create mode 100644 src/Cef/CefViewBridgeObject.cpp create mode 100644 src/Cef/CefViewBridgeObject.h create mode 100644 src/Cef/CefViewLog/CefViewCoreLog.cpp create mode 100644 src/Cef/CefViewLog/CefViewCoreLog.h create mode 100644 src/Cef/CefViewLog/CefViewDebug.cpp create mode 100644 src/Cef/CefViewLog/CefViewDebug.h create mode 100644 src/Cef/CefViewOtherApp.cpp create mode 100644 src/Cef/CefViewOtherApp.h create mode 100644 src/Cef/CefViewRenderApp.cpp create mode 100644 src/Cef/CefViewRenderApp.h diff --git a/src/Cef/CefViewAppBase.cpp b/src/Cef/CefViewAppBase.cpp new file mode 100644 index 0000000..38b38e5 --- /dev/null +++ b/src/Cef/CefViewAppBase.cpp @@ -0,0 +1,74 @@ +#include "CefViewAppBase.h" + +#include "CefViewLog/CefViewCoreLog.h" + +#include +#include +// These flags must match the Chromium values. +const char kProcessType[] = "type"; +const char kZygoteProcess[] = "zygote"; +const char kRendererProcess[] = "renderer"; + +CefViewAppBase::CefViewAppBase(const CefString& scheme_name) + : builtin_scheme_name_(scheme_name) +{ +} + +// static +CefViewAppBase::ProcessType +CefViewAppBase::GetProcessType(CefRefPtr command_line) +{ + // The command-line flag won't be specified for the browser process. + if (!command_line->HasSwitch(kProcessType)) + return UnkownProcess; + + auto process_type = command_line->GetSwitchValue(kProcessType); + logI("process type parameter is: %s", process_type.c_str()); + if (process_type == kZygoteProcess) { + // for linux only + return ZygoteProcess; + } else if (process_type == kRendererProcess) { + return RendererProcess; + } + + return OtherProcess; +} + +CefString +CefViewAppBase::GetBridgeObjectName(CefRefPtr command_line) +{ + if (!command_line->HasSwitch(kCefViewBridgeObjectNameKey)) + return ""; + + auto name = command_line->GetSwitchValue(kCefViewBridgeObjectNameKey); + logI("bridge object name: %s", name.c_str()); + return name; +} + +CefString +CefViewAppBase::GetBuiltinSchemeName(CefRefPtr command_line) +{ + if (!command_line->HasSwitch(kCefViewBuiltinSchemeNameKey)) + return ""; + + auto name = command_line->GetSwitchValue(kCefViewBuiltinSchemeNameKey); + logI("built-in scheme name: %s", name.c_str()); + return name; +} + +void +CefViewAppBase::OnRegisterCustomSchemes(CefRawPtr registrar) +{ + if (registrar) { + int options = 0 // + | CEF_SCHEME_OPTION_STANDARD // + | CEF_SCHEME_OPTION_SECURE // + | CEF_SCHEME_OPTION_CORS_ENABLED // + | CEF_SCHEME_OPTION_FETCH_ENABLED // + | 0; + auto scheme = builtin_scheme_name_.empty() ? kCefViewDefaultBuiltinSchemaName : builtin_scheme_name_; + if (!registrar->AddCustomScheme(scheme, options)) { + logE("faield to add built-in scheme: %s", scheme.c_str()); + } + } +} diff --git a/src/Cef/CefViewAppBase.h b/src/Cef/CefViewAppBase.h new file mode 100644 index 0000000..1702ab0 --- /dev/null +++ b/src/Cef/CefViewAppBase.h @@ -0,0 +1,67 @@ +// +// CefWingAppBase.hpp +// CefViewWing +// +// Created by Sheen Tian on 2020/6/17. +// + +#ifndef CefAppBase_h +#define CefAppBase_h + +#pragma region stl_headers +#include +#include +#pragma endregion + +#pragma region cef_headers +#include +#pragma endregion + +class CefViewAppBase : public CefApp +{ + /// + /// + /// + CefString builtin_scheme_name_; + +public: + CefViewAppBase(const CefString& scheme_name); + + enum ProcessType + { + UnkownProcess, + ZygoteProcess, + RendererProcess, + OtherProcess, + }; + + /// + /// Gets the current process type + /// + /// The command line + /// The process type + static ProcessType GetProcessType(CefRefPtr command_line); + + /// + /// Gets the bridge object name from command line + /// + /// The command line + /// The bridge object name + static CefString GetBridgeObjectName(CefRefPtr command_line); + + /// + /// Gets the built-in scheme name + /// + /// The command line + /// The built-in scheme name + static CefString GetBuiltinSchemeName(CefRefPtr command_line); + +private: + /// + /// + /// + /// + virtual void OnRegisterCustomSchemes(CefRawPtr registrar) override; +}; + +#endif diff --git a/src/Cef/CefViewBridgeObject.cpp b/src/Cef/CefViewBridgeObject.cpp new file mode 100644 index 0000000..9882726 --- /dev/null +++ b/src/Cef/CefViewBridgeObject.cpp @@ -0,0 +1,518 @@ +#include "CefViewBridgeObject.h" + +#include + +#include "CefViewLog/CefViewCoreLog.h" + +#include + + +#if CEF_VERSION_MAJOR >= 119 +class CefViewArrayBuffer : public CefV8ArrayBufferReleaseCallback +{ + IMPLEMENT_REFCOUNTING(CefViewArrayBuffer); + DISALLOW_COPY_AND_ASSIGN(CefViewArrayBuffer); + +public: + /// + /// + /// + /// + CefViewArrayBuffer(CefBinaryValue* v) + : m_size(0) + , m_buffer(nullptr) + { + if (v) { + auto l = v->GetSize(); + if (l) { + m_buffer = std::make_unique(l); + v->GetData(m_buffer.get(), l, 0); + m_size = l; + } + } + } + + /// + /// + /// + ~CefViewArrayBuffer() + { + m_buffer.reset(); + m_size = 0; + } + + /// + /// + /// + /// + void* GetBuffer() + { + // get under layer buffer address + return m_buffer.get(); + } + + /// + /// + /// + /// + size_t GetSize() + { + // return size + return m_size; + } + + /// + /// + /// + /// + void ReleaseBuffer(void* buffer) override + { + // release under layer buffer + m_buffer.reset(); + m_size = 0; + } + +private: + size_t m_size; + std::unique_ptr m_buffer; +}; +#endif + +CefViewBridgeObject::V8Handler::V8Handler(CefViewBridgeObject* object) + : object_(object) +{ +} + +bool +CefViewBridgeObject::V8Handler::Execute(const CefString& function, + CefRefPtr object, + const CefV8ValueList& arguments, + CefRefPtr& retval, + CefString& exception) +{ + if (function == kCefViewInvokeMethodFunctionName) + ExecuteNativeMethod(object, arguments, retval, exception); + else if (function == kCefViewAddEventListenerFunctionName) + ExecuteAddEventListener(object, arguments, retval, exception); + else if (function == kCefViewRemoveEventListenerFunctionName) + ExecuteRemoveEventListener(object, arguments, retval, exception); + else if (function == kCefViewReportJSResultFunctionName) + ExecuteReportJSResult(object, arguments, retval, exception); + else + return false; + + return true; +} + +void +CefViewBridgeObject::V8Handler::ExecuteNativeMethod(CefRefPtr object, + const CefV8ValueList& arguments, + CefRefPtr& retval, + CefString& exception) +{ + object_->AsyncExecuteNativeMethod(arguments); + retval = CefV8Value::CreateUndefined(); +} + +void +CefViewBridgeObject::V8Handler::ExecuteAddEventListener(CefRefPtr object, + const CefV8ValueList& arguments, + CefRefPtr& retval, + CefString& exception) +{ + bool bRet = false; + + if (arguments.size() == 2) { + if (arguments[0]->IsString()) { + if (arguments[1]->IsFunction()) { + CefString eventName = arguments[0]->GetStringValue(); + EventListener listener; + listener.callback_ = arguments[1]; + listener.context_ = CefV8Context::GetCurrentContext(); + object_->AddEventListener(eventName, listener); + bRet = true; + } else + exception = "Invalid arguments; argument 2 must be a function"; + } else + exception = "Invalid arguments; argument 1 must be a string"; + } else + exception = "Invalid arguments; expecting 2 arguments"; + + retval = CefV8Value::CreateBool(bRet); +} + +void +CefViewBridgeObject::V8Handler::ExecuteRemoveEventListener(CefRefPtr object, + const CefV8ValueList& arguments, + CefRefPtr& retval, + CefString& exception) +{ + bool bRet = false; + + if (arguments.size() == 2) { + if (arguments[0]->IsString()) { + if (arguments[1]->IsFunction()) { + CefString eventName = arguments[0]->GetStringValue(); + EventListener listener; + listener.callback_ = arguments[1]; + listener.context_ = CefV8Context::GetCurrentContext(); + object_->RemoveEventListener(eventName, listener); + bRet = true; + } else + exception = "Invalid arguments; argument 2 must be a function"; + } else + exception = "Invalid arguments; argument 1 must be a string"; + } else + exception = "Invalid arguments; expecting 2 arguments"; + + retval = CefV8Value::CreateBool(bRet); +} + +void +CefViewBridgeObject::V8Handler::ExecuteReportJSResult(CefRefPtr object, + const CefV8ValueList& arguments, + CefRefPtr& retval, + CefString& exception) +{ + if (arguments.size() == 2) { + if (arguments[0]->IsString()) { + object_->AsyncExecuteReportJSResult(arguments); + } else + exception = "Invalid argument; argument 1 must be a double"; + } else + exception = "Invalid argument; expecting 2 argument"; + + retval = CefV8Value::CreateUndefined(); +} + +////////////////////////////////////////////////////////////////////////// + +CefViewBridgeObject::CefViewBridgeObject(CefRefPtr browser, + CefRefPtr frame, + CefRefPtr global, + const CefString& name) + : name_(name.empty() ? kCefViewDefaultBridgeObjectName : name) + , bridgeObject_(nullptr) + , reportJSResultFunction_(nullptr) + , browser_(browser) + , frame_(frame) + , v8Handler_(new V8Handler(this)) +{ + // create bridge object and mount it on the global context(window) + bridgeObject_ = CefV8Value::CreateObject(nullptr, nullptr); + + // create function "invokeMethod" + CefRefPtr funcInvokeMethod = CefV8Value::CreateFunction(kCefViewInvokeMethodFunctionName, v8Handler_); + // add this function to window object + bridgeObject_->SetValue(kCefViewInvokeMethodFunctionName, + funcInvokeMethod, + static_cast(V8_PROPERTY_ATTRIBUTE_READONLY | + V8_PROPERTY_ATTRIBUTE_DONTENUM | + V8_PROPERTY_ATTRIBUTE_DONTDELETE)); + + // create function addEventListener + CefRefPtr funcAddEventListener = + CefV8Value::CreateFunction(kCefViewAddEventListenerFunctionName, v8Handler_); + // add this function to window object + bridgeObject_->SetValue(kCefViewAddEventListenerFunctionName, + funcAddEventListener, + static_cast(V8_PROPERTY_ATTRIBUTE_READONLY | + V8_PROPERTY_ATTRIBUTE_DONTENUM | + V8_PROPERTY_ATTRIBUTE_DONTDELETE)); + + // create function removeListener + CefRefPtr funcRemoveEventListener = + CefV8Value::CreateFunction(kCefViewRemoveEventListenerFunctionName, v8Handler_); + // add this function to window object + bridgeObject_->SetValue(kCefViewRemoveEventListenerFunctionName, + funcRemoveEventListener, + static_cast(V8_PROPERTY_ATTRIBUTE_READONLY | + V8_PROPERTY_ATTRIBUTE_DONTENUM | + V8_PROPERTY_ATTRIBUTE_DONTDELETE)); + + // mount the client object to the global context(usually the window object) + global->SetValue(name_, + bridgeObject_, + static_cast(V8_PROPERTY_ATTRIBUTE_READONLY | + V8_PROPERTY_ATTRIBUTE_DONTENUM | + V8_PROPERTY_ATTRIBUTE_DONTDELETE)); + frame_->ExecuteJavaScript("console.info('[JSRuntime]:window." + name_.ToString() + " [object] created');", frame_->GetURL(), 0); + + // create "__cefview_report_js_result__" function and mount it on the global context(window) + reportJSResultFunction_ = CefV8Value::CreateFunction(kCefViewReportJSResultFunctionName, v8Handler_); + global->SetValue(kCefViewReportJSResultFunctionName, + reportJSResultFunction_, + static_cast(V8_PROPERTY_ATTRIBUTE_READONLY | + V8_PROPERTY_ATTRIBUTE_DONTENUM | + V8_PROPERTY_ATTRIBUTE_DONTDELETE)); + frame_->ExecuteJavaScript("console.info('[JSRuntime]:window." kCefViewReportJSResultFunctionName + " [function] created');", + frame_->GetURL(), + 0); +} + +CefRefPtr +CefViewBridgeObject::CefValueToV8Value(CefValue* cefValue) +{ + CefRefPtr v8Value = CefV8Value::CreateNull(); + if (!cefValue) { + return v8Value; + } + + auto type = cefValue->GetType(); + switch (type) { + case CefValueType::VTYPE_INVALID: { + v8Value = CefV8Value::CreateUndefined(); + } break; + case CefValueType::VTYPE_NULL: { + v8Value = CefV8Value::CreateNull(); + } break; + case CefValueType::VTYPE_BOOL: { + auto v = cefValue->GetBool(); + v8Value = CefV8Value::CreateBool(v); + } break; + case CefValueType::VTYPE_INT: { + auto v = cefValue->GetInt(); + v8Value = CefV8Value::CreateInt(v); + } break; + case CefValueType::VTYPE_DOUBLE: { + auto v = cefValue->GetDouble(); + v8Value = CefV8Value::CreateDouble(v); + } break; + case CefValueType::VTYPE_STRING: { + auto v = cefValue->GetString(); + v8Value = CefV8Value::CreateString(v); + } break; + case CefValueType::VTYPE_BINARY: { +#if CEF_VERSION_MAJOR >= 119 + auto v = cefValue->GetBinary(); + auto arryBuffer = new CefViewArrayBuffer(v.get()); + v8Value = CefV8Value::CreateArrayBuffer(arryBuffer->GetBuffer(), arryBuffer->GetSize(), arryBuffer); +#else + // currently not supported +#endif + } break; + case CefValueType::VTYPE_DICTIONARY: { + auto cDict = cefValue->GetDictionary(); + CefDictionaryValue::KeyList cKeys; + cDict->GetKeys(cKeys); + v8Value = CefV8Value::CreateObject(nullptr, nullptr); + for (auto& key : cKeys) { + auto cVal = cDict->GetValue(key); + auto v8Val = CefValueToV8Value(cVal.get()); + v8Value->SetValue(key, v8Val.get(), V8_PROPERTY_ATTRIBUTE_NONE); + } + } break; + case CefValueType::VTYPE_LIST: { + auto cList = cefValue->GetList(); + int cCount = static_cast(cList->GetSize()); + v8Value = CefV8Value::CreateArray(static_cast(cCount)); + for (int i = 0; i < cCount; i++) { + auto cVal = cList->GetValue(i); + auto v8Val = CefValueToV8Value(cVal.get()); + v8Value->SetValue(i, v8Val.get()); + } + } break; + default: + break; + } + + return v8Value; +} + +CefRefPtr +CefViewBridgeObject::V8ValueToCefValue(CefV8Value* v8Value) +{ + CefRefPtr cefValue = CefValue::Create(); + if (!v8Value) { + return cefValue; + } + + /** + * The IsDouble, IsInt and IsUint methods return a boolean value indicating whether the CefV8Value instance is an + * target type or can be converted to the target type.If the value can be converted to the target type, the methods + * will attempt to do so and return true. If the value is not the target type or cannot be converted to the target + * type, the method will return false. + * + * For example the code below: + * auto v = CefV8Value::CreateInt(1000); + * auto isDouble = v->IsDouble(); + * logD("isDouble: %d", isDouble); // true + * auto isUnint = v->IsUInt(); + * logD("isUnint: %d", isUnint); // true + * auto isInt = v->IsInt(); + * logD("isInt: %d", isInt); // true + * + * auto v = CefV8Value::CreateDouble(0.1); + * auto isDouble = v->IsDouble(); + * logD("isDouble: %d", isDouble); // true + * auto isUnint = v->IsUInt(); + * logD("isUnint: %d", isUnint); // false + * auto isInt = v->IsInt(); + * logD("isInt: %d", isInt); // false + * + * so we need to keep the testing order, IsInt/IsUint - IsDouble + * since there is no Uint type in JavaScript, we just ignore it. + * Please refer to this test souce: + * https://github.com/svn2github/cef/blob/master/tests/cefclient/binding_test.cpp + */ + if (v8Value->IsNull() || v8Value->IsUndefined()) + cefValue->SetNull(); + else if (v8Value->IsBool()) + cefValue->SetBool(v8Value->GetBoolValue()); + else if (v8Value->IsInt()) + cefValue->SetInt(v8Value->GetIntValue()); + else if (v8Value->IsDouble()) + cefValue->SetDouble(v8Value->GetDoubleValue()); + else if (v8Value->IsString()) + cefValue->SetString(v8Value->GetStringValue()); + else if (v8Value->IsArrayBuffer()) { +#if CEF_VERSION_MAJOR >= 119 + auto size = v8Value->GetArrayBufferByteLength(); + auto data = v8Value->GetArrayBufferData(); + cefValue->SetBinary(CefBinaryValue::Create(data, size)); +#else + // currently not supported +#endif + } else if (v8Value->IsArray()) { + auto s = v8Value->GetArrayLength(); + auto cefList = CefListValue::Create(); + for (int i = 0; i < s; i++) { + auto v8Val = v8Value->GetValue(i); + auto cefVal = V8ValueToCefValue(v8Val.get()); + cefList->SetValue(i, cefVal); + } + cefValue->SetList(cefList); + } else if (v8Value->IsObject()) { + CefDictionaryValue::KeyList keys; + v8Value->GetKeys(keys); + auto cefDict = CefDictionaryValue::Create(); + for (auto& key : keys) { + auto v8Val = v8Value->GetValue(key); + auto cefVal = V8ValueToCefValue(v8Val.get()); + cefDict->SetValue(key, cefVal.get()); + } + cefValue->SetDictionary(cefDict); + } else + cefValue->SetNull(); + + return cefValue; +} + +void +CefViewBridgeObject::AsyncExecuteNativeMethod(const CefV8ValueList& arguments) +{ + CefRefPtr msg = CefProcessMessage::Create(kCefViewClientRenderInvokeMethodMessage); + + //** arguments(CefValueList) + //** +-------+ + //** |0 name | <- the method name + //** |1 arg1 | + //** |2 arg2 | + //** |3 arg3 | + //** |4 arg4 | + //** | ... | + //** | ... | + //** | ... | + //** | ... | + //** +-------+ + CefRefPtr args = msg->GetArgumentList(); + + // push back all the arguments + for (std::size_t i = 0; i < arguments.size(); i++) { + auto cefValue = V8ValueToCefValue(arguments[i].get()); + args->SetValue(i, cefValue); + } + + // send the message + if (browser_) + frame_->SendProcessMessage(PID_BROWSER, msg); +} + +void +CefViewBridgeObject::AsyncExecuteReportJSResult(const CefV8ValueList& arguments) +{ + CefRefPtr msg = CefProcessMessage::Create(kCefViewClientRenderReportJSResultMessage); + + //** arguments(CefValueList) + //** +_------+ + //** |0 arg | <- the context (string) + //** |1 arg | <- the result value + //** +-------+ + CefRefPtr args = msg->GetArgumentList(); + + // push back the result value + for (std::size_t i = 0; i < arguments.size(); i++) { + auto cefValue = V8ValueToCefValue(arguments[i].get()); + args->SetValue(i, cefValue.get()); + } + + // send the message + if (browser_) + frame_->SendProcessMessage(PID_BROWSER, msg); +} + +void +CefViewBridgeObject::AddEventListener(const CefString& name, const EventListener& listener) +{ + auto itListenerList = eventListenerListMap_.find(name); + if (itListenerList == eventListenerListMap_.end()) { + EventListenerList eventListenerList; + eventListenerList.push_back(listener); + eventListenerListMap_[name] = eventListenerList; + } else { + EventListenerList& eventListenerList = itListenerList->second; + // does this listener exist? + bool found = false; + for (auto item : eventListenerList) { + if (item.callback_->IsSame(listener.callback_)) { + found = true; + break; + } + } + + if (!found) + eventListenerList.push_back(listener); + } +} + +void +CefViewBridgeObject::RemoveEventListener(const CefString& name, const EventListener& listener) +{ + auto itListenerList = eventListenerListMap_.find(name); + if (itListenerList != eventListenerListMap_.end()) { + EventListenerList& eventListenerList = itListenerList->second; + for (auto itListener = eventListenerList.begin(); itListener != eventListenerList.end(); itListener++) { + if (itListener->callback_->IsSame(listener.callback_)) { + eventListenerList.erase(itListener); + break; + } + } + } +} + +void +CefViewBridgeObject::ExecuteEventListener(const CefString eventName, CefRefPtr args) +{ + // find the listeners + auto itListenerList = eventListenerListMap_.find(eventName); + if (itListenerList == eventListenerListMap_.end()) { + return; + } + + EventListenerList& eventListenerList = itListenerList->second; + for (auto listener : eventListenerList) { + listener.context_->Enter(); + + // convert argument list from CefValue to CefV8Value + CefV8ValueList v8ArgList; + for (size_t i = 0; i < args->GetSize(); i++) { + auto cefValue = args->GetValue(i); + auto v8Value = CefValueToV8Value(cefValue.get()); + v8ArgList.push_back(v8Value.get()); + } + + listener.callback_->ExecuteFunction(bridgeObject_, v8ArgList); + listener.context_->Exit(); + } +} diff --git a/src/Cef/CefViewBridgeObject.h b/src/Cef/CefViewBridgeObject.h new file mode 100644 index 0000000..66ac759 --- /dev/null +++ b/src/Cef/CefViewBridgeObject.h @@ -0,0 +1,221 @@ +#pragma once +#pragma region stl_headers +#include +#include +#pragma endregion + +#pragma region cef_headers +#include +#pragma endregion + +/// +/// +/// +class CefViewBridgeObject : public CefBaseRefCounted +{ + IMPLEMENT_REFCOUNTING(CefViewBridgeObject); + DISALLOW_COPY_AND_ASSIGN(CefViewBridgeObject); + + /// + /// + /// + typedef struct _EventListener + { + CefRefPtr callback_; + CefRefPtr context_; + } EventListener; + + /// + /// + /// + typedef std::list EventListenerList; + + /// + /// + /// + typedef std::map> EventListenerListMap; + + /// + /// + /// + class V8Handler : public CefV8Handler + { + public: + /// + /// + /// + /// + V8Handler(CefViewBridgeObject* object); + + /// + /// + /// + /// + /// + /// + /// + /// + /// + virtual bool Execute(const CefString& function, + CefRefPtr object, + const CefV8ValueList& arguments, + CefRefPtr& retval, + CefString& exception) override; + + protected: + /// + /// + /// + /// + /// + /// + /// + void ExecuteNativeMethod(CefRefPtr object, + const CefV8ValueList& arguments, + CefRefPtr& retval, + CefString& exception); + + /// + /// + /// + /// + /// + /// + /// + void ExecuteAddEventListener(CefRefPtr object, + const CefV8ValueList& arguments, + CefRefPtr& retval, + CefString& exception); + + /// + /// + /// + /// + /// + /// + /// + void ExecuteRemoveEventListener(CefRefPtr object, + const CefV8ValueList& arguments, + CefRefPtr& retval, + CefString& exception); + + /// + /// + /// + /// + /// + /// + /// + void ExecuteReportJSResult(CefRefPtr object, + const CefV8ValueList& arguments, + CefRefPtr& retval, + CefString& exception); + + private: + /// + /// + /// + CefViewBridgeObject* object_; + + private: + IMPLEMENT_REFCOUNTING(V8Handler); + }; + +public: + /// + /// + /// + /// + /// + /// + /// + CefViewBridgeObject(CefRefPtr browser, + CefRefPtr frame, + CefRefPtr global, + const CefString& name); + + /// + /// + /// + /// + /// + /// + CefRefPtr CefValueToV8Value(CefValue* cefValue); + + /// + /// + /// + /// + /// + CefRefPtr V8ValueToCefValue(CefV8Value* v8Value); + + /// + /// + /// + /// + void AsyncExecuteNativeMethod(const CefV8ValueList& arguments); + + /// + /// + /// + /// + void AsyncExecuteReportJSResult(const CefV8ValueList& arguments); + + /// + /// + /// + /// + /// + /// + void AddEventListener(const CefString& name, const EventListener& listener); + + /// + /// + /// + /// + /// + void RemoveEventListener(const CefString& name, const EventListener& listener); + + /// + /// + /// + /// + /// + void ExecuteEventListener(const CefString eventName, CefRefPtr args); + +private: + /// + /// + /// + CefString name_; + + /// + /// + /// + CefRefPtr bridgeObject_; + + /// + /// + /// + CefRefPtr reportJSResultFunction_; + + /// + /// + /// + CefRefPtr browser_; + + /// + /// + /// + CefRefPtr frame_; + + /// + /// + /// + CefRefPtr v8Handler_; + + /// + /// + /// + CefViewBridgeObject::EventListenerListMap eventListenerListMap_; +}; diff --git a/src/Cef/CefViewLog/CefViewCoreLog.cpp b/src/Cef/CefViewLog/CefViewCoreLog.cpp new file mode 100644 index 0000000..3d2fc02 --- /dev/null +++ b/src/Cef/CefViewLog/CefViewCoreLog.cpp @@ -0,0 +1,141 @@ +// +// CefViewCoreLog.cpp +// CefViewCore +// +// Created by Sheen Tian on 2020/6/19. +// + +#include "CefViewCoreLog.h" + +#include +#include +#include + +typedef enum log_level +{ + ll_debug = 1, + ll_info, + ll_error, + ll_fatal, +} log_level; + +#if defined(__APPLE__) +#include +void +cefView_log(log_level level, const char* message) +{ + switch (level) { + case ll_debug: + os_log_debug(OS_LOG_DEFAULT, "[DEBUG]%s", message); + break; + case ll_info: + os_log_info(OS_LOG_DEFAULT, "[INFO]%s", message); + break; + case ll_error: + os_log_error(OS_LOG_DEFAULT, "[ERROR]%s", message); + break; + case ll_fatal: + os_log_fault(OS_LOG_DEFAULT, "[FATAL]%s", message); + break; + default: + break; + } +} +#elif defined(_WIN32) +#include +void +cefView_log(log_level level, const char* message) +{ + std::string msg; + switch (level) { + case ll_debug: + msg = "[DEBUG]"; + break; + case ll_info: + msg = "[INFO]"; + break; + case ll_error: + msg = "[ERROR]"; + break; + case ll_fatal: + msg = "[FATAL]"; + break; + default: + msg = "[]"; + break; + } + msg += message; + msg += "\r\n"; + OutputDebugStringA(msg.c_str()); +} +#elif defined(__linux__) +#include +void +cefView_log(log_level level, const char* message) +{ + switch (level) { + case ll_debug: + syslog(LOG_USER | LOG_DEBUG, "[DEBUG]%s", message); + break; + case ll_info: + syslog(LOG_USER | LOG_INFO, "[INFO]%s", message); + break; + case ll_error: + syslog(LOG_USER | LOG_ERR, "[ERROR]%s", message); + break; + case ll_fatal: + syslog(LOG_USER | LOG_CRIT, "[FATAL]%s", message); + break; + default: + break; + } +} +#else +#error "unsupported platform" +#endif + +#define LOG_MSG_BUFFER_LIMIT 4096 + +void +log_debug(const char* fmt, ...) +{ + std::vector msg(LOG_MSG_BUFFER_LIMIT, 0); + va_list args; + va_start(args, fmt); + vsnprintf(msg.data(), LOG_MSG_BUFFER_LIMIT, fmt, args); + va_end(args); + cefView_log(ll_debug, msg.data()); +} + +void +log_info(const char* fmt, ...) +{ + std::vector msg(LOG_MSG_BUFFER_LIMIT, 0); + va_list args; + va_start(args, fmt); + vsnprintf(msg.data(), LOG_MSG_BUFFER_LIMIT, fmt, args); + va_end(args); + cefView_log(ll_info, msg.data()); +} + +void +log_error(const char* fmt, ...) +{ + std::vector msg(LOG_MSG_BUFFER_LIMIT, 0); + va_list args; + va_start(args, fmt); + vsnprintf(msg.data(), LOG_MSG_BUFFER_LIMIT, fmt, args); + va_end(args); + cefView_log(ll_error, msg.data()); +} + +void +log_fatal(const char* fmt, ...) +{ + std::vector msg(LOG_MSG_BUFFER_LIMIT, 0); + va_list args; + va_start(args, fmt); + vsnprintf(msg.data(), LOG_MSG_BUFFER_LIMIT, fmt, args); + va_end(args); + cefView_log(ll_fatal, msg.data()); +} diff --git a/src/Cef/CefViewLog/CefViewCoreLog.h b/src/Cef/CefViewLog/CefViewCoreLog.h new file mode 100644 index 0000000..93bee26 --- /dev/null +++ b/src/Cef/CefViewLog/CefViewCoreLog.h @@ -0,0 +1,57 @@ +// +// CefViewCoreLog.h +// CefViewCore +// +// Created by Sheen Tian on 2020/6/19. +// + +#ifndef CefViewCoreLog_h +#define CefViewCoreLog_h +#pragma once +#include + +void +log_debug(const char* fmt, ...); +void +log_info(const char* fmt, ...); +void +log_error(const char* fmt, ...); +void +log_fatal(const char* fmt, ...); + +class ScopeLogger +{ +public: + ScopeLogger(const std::string& fn) + : functionName_(fn) + { + // enter scope + log_debug("+++ %s", functionName_.c_str()); + } + + ~ScopeLogger() + { + // leave scope + log_debug("--- %s", functionName_.c_str()); + } + + std::string functionName_; +}; + +#if (defined(DEBUG) || defined(_DEBUG) || !defined(NDEBUG)) +// DEBUG BUILD +#define logD(format, ...) log_debug(format, ##__VA_ARGS__) +#define logI(format, ...) log_info(format, ##__VA_ARGS__) +#define logE(format, ...) log_error(format, ##__VA_ARGS__) +#define logF(format, ...) log_fatal(format, ##__VA_ARGS__) +#define logScope() ScopeLogger __scope_logger__(__FUNCTION__); +#else +// RELEASE BUILD +#define logD(format, ...) +#define logI(format, ...) log_info(format, ##__VA_ARGS__) +#define logE(format, ...) log_error(format, ##__VA_ARGS__) +#define logF(format, ...) log_fatal(format, ##__VA_ARGS__) +#define logScope() +#endif + +#endif /* CefViewCoreLog_hpp */ diff --git a/src/Cef/CefViewLog/CefViewDebug.cpp b/src/Cef/CefViewLog/CefViewDebug.cpp new file mode 100644 index 0000000..60be81f --- /dev/null +++ b/src/Cef/CefViewLog/CefViewDebug.cpp @@ -0,0 +1,64 @@ +// +// CefViewCoreLog.cpp +// CefViewCore +// +// Created by leashi on 2022/5/10. +// + +#include + +#include "CefViewDebug.h" + +#include + +std::string +toString(CefRefPtr browser) +{ + std::string msg; + + msg += "( "; + msg += "CefBrowser:"; + msg += ", ptr=" + std::to_string((int64_t)browser.get()); +#if CEF_VERSION_MAJOR > 91 + msg += ", IsValid=" + std::to_string(browser->IsValid()); +#endif + msg += ", GetHost=" + std::to_string((int64_t)browser->GetHost().get()); + msg += ", CanGoBack=" + std::to_string(browser->CanGoBack()); + msg += ", CanGoForward=" + std::to_string(browser->CanGoForward()); + msg += ", IsLoading=" + std::to_string(browser->IsLoading()); + msg += ", GetIdentifier=" + std::to_string(browser->GetIdentifier()); + msg += ", IsPopup=" + std::to_string(browser->IsPopup()); + msg += ", HasDocument=" + std::to_string(browser->HasDocument()); + msg += ", GetMainFrame=" + std::to_string((int64_t)browser->GetMainFrame().get()); + msg += ", GetFocusedFrame=" + std::to_string((int64_t)browser->GetFocusedFrame().get()); + msg += ", GetFrameCount=" + std::to_string(browser->GetFrameCount()); + msg += " )"; + + return msg; +} + +std::string +toString(CefRefPtr frame) +{ + std::string msg; + + msg += "( "; + msg += "CefFrame:"; + msg += ", ptr=" + std::to_string((int64_t)frame.get()); + msg += ", IsValid=" + std::to_string(frame->IsValid()); + msg += ", IsMain=" + std::to_string(frame->IsMain()); + msg += ", IsFocused=" + std::to_string(frame->IsFocused()); + msg += ", GetName=" + frame->GetName().ToString(); +#if CEF_VERSION_MAJOR < 122 + msg += ", GetIdentifier=" + std::to_string(frame->GetIdentifier()); +#else + msg += ", GetIdentifier=" + frame->GetIdentifier().ToString(); +#endif + msg += ", GetParent=" + std::to_string((int64_t)frame->GetParent().get()); + msg += ", GetURL=" + frame->GetURL().ToString(); + msg += ", GetBrowser=" + std::to_string((int64_t)frame->GetBrowser().get()); + // msg += ", GetV8Context=" + std::to_string((int64_t)frame->GetV8Context().get()); + msg += " )"; + + return msg; +} diff --git a/src/Cef/CefViewLog/CefViewDebug.h b/src/Cef/CefViewLog/CefViewDebug.h new file mode 100644 index 0000000..7f37ceb --- /dev/null +++ b/src/Cef/CefViewLog/CefViewDebug.h @@ -0,0 +1,21 @@ +// +// CefViewDebug.h +// CefViewCore +// +// Created by leashi on 2022/5/10. +// + +#ifndef CefViewDebug_h +#define CefViewDebug_h + +#pragma once + +#include +#include + + +std::string toString(CefRefPtr browser); +std::string toString(CefRefPtr frame); + + +#endif /* CefViewDebug_h */ diff --git a/src/Cef/CefViewOtherApp.cpp b/src/Cef/CefViewOtherApp.cpp new file mode 100644 index 0000000..740ed18 --- /dev/null +++ b/src/Cef/CefViewOtherApp.cpp @@ -0,0 +1,6 @@ +#include "CefViewOtherApp.h" + +CefViewOtherApp::CefViewOtherApp(const CefString& scheme_name) + : CefViewAppBase(scheme_name) +{ +} diff --git a/src/Cef/CefViewOtherApp.h b/src/Cef/CefViewOtherApp.h new file mode 100644 index 0000000..33a96f0 --- /dev/null +++ b/src/Cef/CefViewOtherApp.h @@ -0,0 +1,22 @@ +// +// CefOtherApp.hpp +// CefViewWing +// +// Created by Sheen Tian on 2020/6/17. +// + +#ifndef CefOtherApp_h +#define CefOtherApp_h +#pragma once + +#include "CefViewAppBase.h" + +class CefViewOtherApp : public CefViewAppBase +{ + IMPLEMENT_REFCOUNTING(CefViewOtherApp); + +public: + CefViewOtherApp(const CefString& scheme_name); +}; + +#endif diff --git a/src/Cef/CefViewRenderApp.cpp b/src/Cef/CefViewRenderApp.cpp new file mode 100644 index 0000000..e7500ec --- /dev/null +++ b/src/Cef/CefViewRenderApp.cpp @@ -0,0 +1,208 @@ +#include "CefViewRenderApp.h" + +#pragma region cef_headers +#include +#include +#include +#pragma endregion + +#include "CefViewLog/CefViewCoreLog.h" + +#include + +CefViewRenderApp::CefViewRenderApp(const CefString& scheme_name, const CefString& bridge_name) + : CefViewAppBase(scheme_name) + , bridge_object_name_(bridge_name) + , last_node_is_editable_(false) +{ +} + +CefViewRenderApp::~CefViewRenderApp() {} + +////////////////////////////////////////////////////////////////////////// +CefRefPtr +CefViewRenderApp::GetRenderProcessHandler() +{ + return this; +} + +void +CefViewRenderApp::OnWebKitInitialized() +{ + CEF_REQUIRE_RENDERER_THREAD(); + + CefMessageRouterConfig config; + config.js_query_function = kCefViewQueryFuntionName; + config.js_cancel_function = kCefViewQueryCancelFunctionName; + message_router_ = CefMessageRouterRendererSide::Create(config); +} + +void +CefViewRenderApp::OnBrowserCreated(CefRefPtr browser, CefRefPtr extra_info) +{ + CEF_REQUIRE_RENDERER_THREAD(); +} + +void +CefViewRenderApp::OnBrowserDestroyed(CefRefPtr browser) +{ + CEF_REQUIRE_RENDERER_THREAD(); +} + +CefRefPtr +CefViewRenderApp::GetLoadHandler() +{ + CEF_REQUIRE_RENDERER_THREAD(); + + return nullptr; +} + +void +CefViewRenderApp::OnContextCreated(CefRefPtr browser, + CefRefPtr frame, + CefRefPtr context) +{ + CEF_REQUIRE_RENDERER_THREAD(); + + // [Javascript Context] + // V8 context for this frame has been initialized already, + // but the script of the page hasn't been executed now + message_router_->OnContextCreated(browser, frame, context); + + // log this event + frame->ExecuteJavaScript("console.info('[JSRuntime]:frame context created')", frame->GetURL(), 0); + + // binding bridge object and functions + auto frameId = frame->GetIdentifier(); + auto it = frame_id_to_bridge_obj_map_.find(frameId); + if (it == frame_id_to_bridge_obj_map_.end()) { + // create and insert the bridge Object into this frame.window object + CefRefPtr objWindow = context->GetGlobal(); + CefRefPtr objClient = new CefViewBridgeObject(browser, frame, objWindow, bridge_object_name_); + if (!objClient) { + log_error("Failed to create the client object"); + return; + } + frame_id_to_bridge_obj_map_[frameId] = objClient; + } +} + +void +CefViewRenderApp::OnContextReleased(CefRefPtr browser, + CefRefPtr frame, + CefRefPtr context) +{ + CEF_REQUIRE_RENDERER_THREAD(); + + message_router_->OnContextReleased(browser, frame, context); + + auto frameId = frame->GetIdentifier(); + auto it = frame_id_to_bridge_obj_map_.find(frameId); + if (it != frame_id_to_bridge_obj_map_.end()) { + frame_id_to_bridge_obj_map_.erase(it); + } +} + +void +CefViewRenderApp::OnUncaughtException(CefRefPtr browser, + CefRefPtr frame, + CefRefPtr context, + CefRefPtr exception, + CefRefPtr stackTrace) +{ + CEF_REQUIRE_RENDERER_THREAD(); +} + +void +CefViewRenderApp::OnFocusedNodeChanged(CefRefPtr browser, + CefRefPtr frame, + CefRefPtr node) +{ + CEF_REQUIRE_RENDERER_THREAD(); + + bool is_editable = (node.get() && node->IsEditable()); + if (is_editable != last_node_is_editable_) { + // Notify the browser of the change in focused element type. + last_node_is_editable_ = is_editable; + CefRefPtr message = CefProcessMessage::Create(kCefViewClientRenderFocusedNodeChangedMessage); + message->GetArgumentList()->SetBool(0, is_editable); + frame->SendProcessMessage(PID_BROWSER, message); + } +} + +bool +CefViewRenderApp::OnProcessMessageReceived(CefRefPtr browser, + CefRefPtr frame, + CefProcessId source_process, + CefRefPtr message) +{ + CEF_REQUIRE_RENDERER_THREAD(); + + bool handled = false; + + if (message_router_->OnProcessMessageReceived(browser, frame, source_process, message)) { + handled = true; + } + + if (OnTriggerEventNotifyMessage(browser, frame, source_process, message)) { + handled = true; + } + + return handled; +} + +bool +CefViewRenderApp::OnTriggerEventNotifyMessage(CefRefPtr browser, + CefRefPtr frame, + CefProcessId source_process, + CefRefPtr message) +{ + if (message->GetName() == kCefViewClientBrowserTriggerEventMessage) { + CefRefPtr args = message->GetArgumentList()->Copy(); + //** arguments(CefValueList) + //** +------------+ + //** | event name | + //** | event arg1 | + //** | event arg2 | + //** | event arg3 | + //** | event arg4 | + //** | ... | + //** | ... | + //** | ... | + //** | ... | + //** +------------+ + if (!args || args->GetSize() <= 0) { + log_error("Invalid message arguments, event name is required"); + return true; + } + + if (CefValueType::VTYPE_STRING != args->GetType(0)) { + log_error("Invalid message arguments, invalid type for event name"); + return true; + } + + auto name = args->GetString(0); + args->Remove(0); + ExecuteEventListener(browser, frame, name, args); + + return true; + } + + return false; +} + +void +CefViewRenderApp::ExecuteEventListener(CefRefPtr browser, + CefRefPtr frame, + const CefString& name, + CefRefPtr args) +{ + if (browser && frame) { + auto frameId = frame->GetIdentifier(); + auto it = frame_id_to_bridge_obj_map_.find(frameId); + if (it != frame_id_to_bridge_obj_map_.end()) { + const CefRefPtr& objClient = it->second; + objClient->ExecuteEventListener(name, args); + } + } +} diff --git a/src/Cef/CefViewRenderApp.h b/src/Cef/CefViewRenderApp.h new file mode 100644 index 0000000..690ec93 --- /dev/null +++ b/src/Cef/CefViewRenderApp.h @@ -0,0 +1,180 @@ +#ifndef CefRenderApp_h +#define CefRenderApp_h +#pragma once + +#pragma region stl_headers +#include +#pragma endregion + +#pragma region cef_headers +#include +#pragma endregion + +#include + +#include "CefViewAppBase.h" +#include "CefViewBridgeObject.h" + +/// +/// +/// +class CefViewRenderApp + : public CefViewAppBase + , public CefRenderProcessHandler +{ + // Include the default reference counting implementation. + IMPLEMENT_REFCOUNTING(CefViewRenderApp); + +private: + CefString bridge_object_name_; + + /// + /// + /// + CefRefPtr message_router_; + + /// + /// + /// + typedef std::unordered_map> FrameID2BridgeObjMap; + FrameID2BridgeObjMap frame_id_to_bridge_obj_map_; + + /// + /// + /// + bool last_node_is_editable_; + +public: + /// + /// + /// + CefViewRenderApp(const CefString& scheme_name, const CefString& bridge_name); + + /// + /// + /// + ~CefViewRenderApp(); + +private: +#pragma region CefApp + /// + /// + /// + /// + virtual CefRefPtr GetRenderProcessHandler() override; + +#pragma endregion + +#pragma region CefRenderProcessHandler + + /// + /// + /// + virtual void OnWebKitInitialized() override; + + /// + /// + /// + /// + /// + virtual void OnBrowserCreated(CefRefPtr browser, CefRefPtr extra_info) override; + + /// + /// + /// + /// + virtual void OnBrowserDestroyed(CefRefPtr browser) override; + + /// + /// + /// + /// + virtual CefRefPtr GetLoadHandler() override; + + /// + /// + /// + /// + /// + /// + virtual void OnContextCreated(CefRefPtr browser, + CefRefPtr frame, + CefRefPtr context) override; + + /// + /// + /// + /// + /// + /// + virtual void OnContextReleased(CefRefPtr browser, + CefRefPtr frame, + CefRefPtr context) override; + + /// + /// + /// + /// + /// + /// + /// + /// + virtual void OnUncaughtException(CefRefPtr browser, + CefRefPtr frame, + CefRefPtr context, + CefRefPtr exception, + CefRefPtr stackTrace) override; + + /// + /// + /// + /// + /// + /// + virtual void OnFocusedNodeChanged(CefRefPtr browser, + CefRefPtr frame, + CefRefPtr node) override; + + /// + /// + /// + /// + /// + /// + /// + /// + virtual bool OnProcessMessageReceived(CefRefPtr browser, + CefRefPtr frame, + CefProcessId source_process, + CefRefPtr message) override; + +#pragma endregion + +private: + /// + /// + /// + /// + /// + /// + /// + /// + bool OnTriggerEventNotifyMessage(CefRefPtr browser, + CefRefPtr frame, + CefProcessId source_process, + CefRefPtr message); + + /// + /// + /// + /// + /// + /// + /// + void ExecuteEventListener(CefRefPtr browser, + CefRefPtr frame, + const CefString& name, + CefRefPtr args); +}; + +#endif diff --git a/src/Cef/CefViewWidget.cpp b/src/Cef/CefViewWidget.cpp index d88e82c..bf1b892 100644 --- a/src/Cef/CefViewWidget.cpp +++ b/src/Cef/CefViewWidget.cpp @@ -19,6 +19,9 @@ CefViewWidget::CefViewWidget(const QString url, const QCefSetting* setting, QWidget* parent /* = 0*/) : QCefView(url, setting, parent) { + // setStyleSheet("background: blue;"); + + connect(this, &CefViewWidget::draggableRegionChanged, this, &CefViewWidget::onDraggableRegionChanged); connect(this, &CefViewWidget::nativeBrowserCreated, this, &CefViewWidget::onNativeBrowserWindowCreated); } @@ -45,6 +48,13 @@ CefViewWidget::onNativeBrowserWindowCreated(QWindow* window) updateMask(); } +void +CefViewWidget::onDraggableRegionChanged(const QRegion& draggableRegion, const QRegion& nonDraggableRegion) +{ + m_draggableRegion = draggableRegion; + m_nonDraggableRegion = nonDraggableRegion; +} + bool CefViewWidget::onNewPopup(const QCefFrameId& sourceFrameId, const QString& targetUrl, @@ -90,10 +100,29 @@ void CefViewWidget::mousePressEvent(QMouseEvent* event) { QCefView::mousePressEvent(event); + +#if defined(Q_OS_WIN) + if (event->buttons().testFlag(Qt::LeftButton) && m_draggableRegion.contains(event->pos())) { + HWND hWnd = ::GetAncestor((HWND)(window()->windowHandle()->winId()), GA_ROOT); + POINT pt; + ::GetCursorPos(&pt); + ::ReleaseCapture(); + ::SendMessage(hWnd, WM_NCLBUTTONDOWN, HTCAPTION, POINTTOPOINTS(pt)); + } +#elif defined(Q_OS_MAC) +#elif defined(Q_OS_LINUX) +#else +#endif } void CefViewWidget::updateMask() { - + // create a rect with rounded corner (50px radius) as mask + QPainterPath path; + path.addRoundedRect(rect(), 50, 50); + QRegion mask = QRegion(path.toFillPolygon().toPolygon()); + + // apply the mask + setMask(mask); } diff --git a/src/Cef/CefViewWidget.h b/src/Cef/CefViewWidget.h index 32cb473..7c25899 100644 --- a/src/Cef/CefViewWidget.h +++ b/src/Cef/CefViewWidget.h @@ -21,6 +21,9 @@ protected slots: void onScreenChanged(QScreen* screen); void onNativeBrowserWindowCreated(QWindow* window); + + void onDraggableRegionChanged(const QRegion& draggableRegion, const QRegion& nonDraggableRegion); + protected: bool onNewPopup(const QCefFrameId& sourceFrameId, const QString& targetUrl, @@ -44,6 +47,12 @@ private: private: QWindow* m_pCefWindow = nullptr; + + int m_iCornerRadius = 50; + + QRegion m_draggableRegion; + + QRegion m_nonDraggableRegion; }; #endif // CUSTOMCEFVIEW_H diff --git a/src/Cef/MainWindow.cpp b/src/Cef/MainWindow.cpp index 3a61cd5..f61893b 100644 --- a/src/Cef/MainWindow.cpp +++ b/src/Cef/MainWindow.cpp @@ -8,22 +8,126 @@ #include #include #include + #include + +#define URL_ROOT "http://QCefViewDoc" +#define LEFT_INDEX_URL URL_ROOT "/left.html" +#define RIGHT_INDEX_URL URL_ROOT "/right.html" + MainWindow::MainWindow(QWidget* parent) - : QWidget(parent /*, Qt::FramelessWindowHint*/) + : QMainWindow(parent /*, Qt::FramelessWindowHint*/) { - m_layout=new QVBoxLayout(); - m_newbrowser=new QPushButton(tr("新浏览器窗口")); - m_exit=new QPushButton(tr("退出")); - m_layout->addWidget(m_newbrowser); - m_layout->addWidget(m_exit); - setLayout(m_layout); - connect(m_newbrowser, &QPushButton::clicked, this, &MainWindow::onBtnNewBrowserClicked); - connect(m_exit, &QPushButton::clicked, qApp, &QCoreApplication::quit); + m_ui.setupUi(this); + +#ifdef Q_OS_MACOS + this->m_ui.nativeContainer->setContentsMargins(0, 28, 0, 0); +#endif + + setupWindow(); + + // setWindowFlags(Qt::FramelessWindowHint); + // setAttribute(Qt::WA_TranslucentBackground); + + connect(m_ui.btn_showDevTools, &QPushButton::clicked, this, &MainWindow::onBtnShowDevToolsClicked); + connect(m_ui.btn_reloadRight, &QPushButton::clicked, this, &MainWindow::onBtnReloadRightViewClicked); + connect(m_ui.btn_recreateRight, &QPushButton::clicked, this, &MainWindow::onBtnRecreateRightViewClicked); + connect(m_ui.btn_changeColor, &QPushButton::clicked, this, &MainWindow::onBtnChangeColorClicked); + connect(m_ui.btn_setFocus, &QPushButton::clicked, this, &MainWindow::onBtnSetFocusClicked); + connect(m_ui.btn_callJSCode, &QPushButton::clicked, this, &MainWindow::onBtnCallJSCodeClicked); + connect(m_ui.btn_newBrowser, &QPushButton::clicked, this, &MainWindow::onBtnNewBrowserClicked); + connect(m_ui.btn_quitApp, &QPushButton::clicked, qApp, &QCoreApplication::quit); + + // build the path to the web resource + QDir dir = QCoreApplication::applicationDirPath(); +#if defined(Q_OS_MACOS) + QString webResourceDir = /*QString("file://") +*/ QDir::toNativeSeparators(dir.filePath("../Resources/webres")); +#else + QString webResourceDir = /*QString("file://") +*/ QDir::toNativeSeparators(dir.filePath("webres")); +#endif + + // add a local folder to URL map (global) + QCefContext::instance()->addLocalFolderResource(webResourceDir, URL_ROOT); + + createLeftCefView(); + createRightCefView(); } MainWindow::~MainWindow() {} +void +MainWindow::createLeftCefView() +{ + if (m_pLeftCefViewWidget) { + m_pLeftCefViewWidget->deleteLater(); + m_pLeftCefViewWidget = nullptr; + } + + QCefSetting setting; + setting.setWindowlessFrameRate(1000); + setting.setHardwareAcceleration(false); + // setting.setBackgroundColor(Qt::magenta); + + m_pLeftCefViewWidget = new CefViewWidget("https://www.testufo.com/", &setting, this); + // connect the invokeMethod to the slot + connect(m_pLeftCefViewWidget, &QCefView::invokeMethod, this, &MainWindow::onInvokeMethod); + + // connect the cefQueryRequest to the slot + connect(m_pLeftCefViewWidget, &QCefView::cefUrlRequest, this, &MainWindow::onQCefUrlRequest); + connect(m_pLeftCefViewWidget, &QCefView::cefQueryRequest, this, &MainWindow::onQCefQueryRequest); + connect(m_pLeftCefViewWidget, &QCefView::reportJavascriptResult, this, &MainWindow::onJavascriptResult); + connect(m_pLeftCefViewWidget, &QCefView::loadStart, this, &MainWindow::onLoadStart); + connect(m_pLeftCefViewWidget, &QCefView::loadEnd, this, &MainWindow::onLoadEnd); + connect(m_pLeftCefViewWidget, &QCefView::loadError, this, &MainWindow::onLoadError); + + m_ui.leftCefViewContainer->layout()->addWidget(m_pLeftCefViewWidget); +} + +void +MainWindow::createRightCefView() +{ + if (m_pRightCefViewWidget) { + m_pRightCefViewWidget->deleteLater(); + m_pRightCefViewWidget = nullptr; + } + + // build settings for per QCefView + QCefSetting setting; +#if CEF_VERSION_MAJOR < 100 + setting.setPlugins(false); +#endif + setting.setWindowlessFrameRate(1000); + setting.setHardwareAcceleration(true); + QColor background(0, 255, 0, 255); + setting.setBackgroundColor(background); + + // create the QCefView widget and add it to the layout container + // m_pRightCefViewWidget = new QCefView(RIGHT_INDEX_URL, &setting, this); + m_pRightCefViewWidget = new QCefView("https://www.testufo.com/", &setting, this); + + // auto vl = new QVBoxLayout(m_pRightCefViewWidget); + // auto btn = new QPushButton("TEST BUTTON OVERLAY", m_pRightCefViewWidget); + //// btn->setFixedSize(320, 240); + // btn->setStyleSheet("background-color: rgba(1, 1, 1, 0);"); + // btn->setAttribute(Qt::WA_TranslucentBackground); + // btn->setWindowFlags(Qt::FramelessWindowHint); + // btn->setAttribute(Qt::WA_NoSystemBackground); + // vl->setAlignment(Qt::AlignVCenter); + + // vl->addWidget(btn); + // m_pRightCefViewWidget->setLayout(vl); + + // all the following values will disable the context menu for both NCW and OSR mode + // m_pRightCefViewWidget->setContextMenuPolicy(Qt::NoContextMenu); + // m_pRightCefViewWidget->setContextMenuPolicy(Qt::ActionsContextMenu); + // m_pRightCefViewWidget->setContextMenuPolicy(Qt::CustomContextMenu); + // m_pRightCefViewWidget->setContextMenuPolicy(Qt::PreventContextMenu); + m_pRightCefViewWidget->setContextMenuPolicy(Qt::DefaultContextMenu); + + // add the QCefView widget to the layout + m_ui.rightCefViewContainer->layout()->addWidget(m_pRightCefViewWidget); +} + void MainWindow::onInvokeMethod(const QCefBrowserId& browserId, const QCefFrameId& frameId, @@ -78,7 +182,12 @@ MainWindow::onQCefQueryRequest(const QCefBrowserId& browserId, const QCefFrameId QString text = QString("Current Thread: QT_UI\r\n" "Query: %1") .arg(query.request()); + QMessageBox::information(this->window(), title, text); + + QString response = query.request().toUpper(); + query.setResponseResult(true, response); + m_pLeftCefViewWidget->responseQCefQuery(query); } void @@ -132,23 +241,77 @@ MainWindow::onLoadError(const QCefBrowserId& browserId, << ", errorCode:" << errorCode; } +void +MainWindow::onBtnShowDevToolsClicked() +{ + if (m_pLeftCefViewWidget) { + m_pLeftCefViewWidget->showDevTools(); + } +} + +void +MainWindow::onBtnReloadRightViewClicked() +{ + if (m_pRightCefViewWidget) { + m_pRightCefViewWidget->navigateToUrl("https://www.google.com"); + } +} + +void +MainWindow::onBtnRecreateRightViewClicked() +{ + createRightCefView(); +} + +void +MainWindow::onBtnChangeColorClicked() +{ + if (m_pLeftCefViewWidget) { + // create a random color + QColor color(QRandomGenerator::global()->generate()); + + // create the cef event and set the arguments + QCefEvent event("colorChange"); + event.arguments().append(QVariant::fromValue(color.name(QColor::HexArgb))); + + // broadcast the event to all frames in all browsers created by this QCefView widget + m_pLeftCefViewWidget->broadcastEvent(event); + } +} + +void +MainWindow::onBtnCallJSCodeClicked() +{ + QString context = "helloQCefView"; + QString code = "alert('hello QCefView'); return {k1: 'str', k2: true, k3: 100};"; + m_pLeftCefViewWidget->executeJavascriptWithResult(QCefView::MainFrameID, code, "", context); +} + +void +MainWindow::onBtnSetFocusClicked() +{ + if (m_pLeftCefViewWidget) { + m_pLeftCefViewWidget->setFocus(); + } +} + void MainWindow::onBtnNewBrowserClicked() { - QWidget* w = new QWidget(); + QMainWindow* w = new QMainWindow(nullptr); w->setAttribute(Qt::WA_DeleteOnClose); - QVBoxLayout* m_layout=new QVBoxLayout(); + QCefSetting settings; - CefViewWidget* view = new CefViewWidget("https://wwww.baidu.com/", &settings, w); - connect(view, &QCefView::invokeMethod, this, &MainWindow::onInvokeMethod); - connect(view, &QCefView::cefUrlRequest, this, &MainWindow::onQCefUrlRequest); - connect(view, &QCefView::cefQueryRequest, this, &MainWindow::onQCefQueryRequest); - connect(view, &QCefView::reportJavascriptResult, this, &MainWindow::onJavascriptResult); - connect(view, &QCefView::loadStart, this, &MainWindow::onLoadStart); - connect(view, &QCefView::loadEnd, this, &MainWindow::onLoadEnd); - connect(view, &QCefView::loadError, this, &MainWindow::onLoadError); - m_layout->addWidget(view); - w->setLayout(m_layout); + QCefView* view = new QCefView("https://cefview.github.io/QCefView/", &settings, w); + + w->setCentralWidget(view); w->resize(1024, 768); w->show(); } + +#ifndef Q_OS_MACOS +void +MainWindow::setupWindow() +{ +} +#endif diff --git a/src/Cef/MainWindow.h b/src/Cef/MainWindow.h index 61c7719..019e16c 100644 --- a/src/Cef/MainWindow.h +++ b/src/Cef/MainWindow.h @@ -2,22 +2,28 @@ #define QCEFVIEWTEST_H #include -#include -#include +#include + +#include "ui_MainWindow.h" + #include "CefViewWidget.h" -#include -class MainWindow : public QWidget + +class MainWindow : public QMainWindow { Q_OBJECT public: MainWindow(QWidget* parent = 0); ~MainWindow(); -private: - QVBoxLayout* m_layout={}; - QPushButton* m_newbrowser={}; - QPushButton* m_exit={}; + +protected: + void createLeftCefView(); + void createRightCefView(); + void setupWindow(); + + // QCefView slots protected slots: + void onInvokeMethod(const QCefBrowserId& browserId, const QCefFrameId& frameId, const QString& method, @@ -47,7 +53,25 @@ protected slots: // ui slots protected slots: + void onBtnShowDevToolsClicked(); + + void onBtnReloadRightViewClicked(); + + void onBtnRecreateRightViewClicked(); + + void onBtnChangeColorClicked(); + + void onBtnSetFocusClicked(); + + void onBtnCallJSCodeClicked(); + void onBtnNewBrowserClicked(); + +private: + Ui::MainWindow m_ui; + + QCefView* m_pLeftCefViewWidget = nullptr; + QCefView* m_pRightCefViewWidget = nullptr; }; #endif // QCEFVIEWTEST_H diff --git a/src/ctaiHistoryTextEdit.cpp b/src/ctaiHistoryTextEdit.cpp index 2fbac46..6606c51 100644 --- a/src/ctaiHistoryTextEdit.cpp +++ b/src/ctaiHistoryTextEdit.cpp @@ -38,6 +38,7 @@ void ctaiHistoryTextEdit::initMsgHistoryLayout() { history_layout = new QVBoxLayout(); // 历史信息QTextEdit + m_mainwindow=new MainWindow(); m_msg_history = new QTextEdit(); m_math_convert = new ctaiMathConvert(); m_msg_history->setUndoRedoEnabled(false); // 关闭撤销历史以节省内存 @@ -47,6 +48,7 @@ void ctaiHistoryTextEdit::initMsgHistoryLayout() m_msg_history->setObjectName("m_msg_history"); m_msg_history->setReadOnly(true); history_layout->addWidget(m_msg_history); + history_layout->addWidget(m_mainwindow); history_layout->addWidget(msg_line); history_layout->setContentsMargins(0, 0, 0, 0); } @@ -336,6 +338,7 @@ void ctaiHistoryTextEdit::addSystemMessage(const model_data &message) // 非流式模式直接设置全部内容 m_current_content = QSL(message.postback_model_data); } + m_msg_history->setMarkdown(m_current_content); } // 增加tokens信息 diff --git a/src/ctaiHistoryTextEdit.h b/src/ctaiHistoryTextEdit.h index 5a398b8..ac64c2e 100644 --- a/src/ctaiHistoryTextEdit.h +++ b/src/ctaiHistoryTextEdit.h @@ -37,6 +37,7 @@ #include "ctai_base.h" #include "ctaiMathConvert.h" #include "ctaiHistoryTools.h" +#include "cef/MainWindow.h" std::mutex m_mutex; class ctaiHistoryTextEdit : public QWidget { @@ -72,6 +73,7 @@ private: void connect_signals(msg_type msg_type_mode); // 连接信号和槽 void set_default_opts(); private: + MainWindow* m_mainwindow; ctaiMathConvert *m_math_convert; QFrame *msg_line; QSpacerItem *sparcer_item = new QSpacerItem(0, 0, QSizePolicy::Expanding, QSizePolicy::Fixed); diff --git a/src/main.cpp b/src/main.cpp index 18d073c..298ada5 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -5,10 +5,43 @@ #include "ctai.h" #include #include +#include "cef/CefWidget.h" +#include "cef/CefViewLog/CefViewCoreLog.h" +#include "cef/CefViewAppBase.h" +#include "cef/CefViewOtherApp.h" +#include "cef/CefViewRenderApp.h" #include "cef/MainWindow.h" +//init cef process +int CefViewMain(int argc, char* argv[]){ + logI("CefViewWing is launching...."); + CefMainArgs main_args(nullptr); + CefRefPtr app; + CefRefPtr command_line = CefCommandLine::CreateCommandLine(); + command_line->InitFromArgv(argc, argv); + // get parameter from command line + auto process_type = CefViewAppBase::GetProcessType(command_line); + auto builtin_scheme_name = CefViewAppBase::GetBuiltinSchemeName(command_line); + auto bridge_object_name = CefViewAppBase::GetBridgeObjectName(command_line); + + if (process_type == CefViewAppBase::RendererProcess || process_type == CefViewAppBase::ZygoteProcess) { + app = new CefViewRenderApp(builtin_scheme_name, bridge_object_name); + } else if (process_type == CefViewAppBase::OtherProcess) { + app = new CefViewOtherApp(builtin_scheme_name); + } else { + logI("Parse process unknown, exit"); + return 1; + } + + // Execute the secondary process. + int rt = CefExecuteProcess(main_args, app, nullptr); + logI("process returned with code: %d", rt); + + return rt; +} int main(int argc, char *argv[]) { + //CefViewMain(argc, const_cast(argv)); QApplication::setHighDpiScaleFactorRoundingPolicy(Qt::HighDpiScaleFactorRoundingPolicy::PassThrough); qputenv("QT_QPA_PLATFORM", "windows:fontengine=freetype"); QApplication a(argc, argv); @@ -30,17 +63,6 @@ int main(int argc, char *argv[]) x.title()->set_type(QD_TYPE::QD_EXIT); #endif x.show(); - //初始化CEF - #if (QT_VERSION <= QT_VERSION_CHECK(6, 0, 0)) - // For off-screen rendering, Qt::AA_EnableHighDpiScaling must be enabled. If not, - // then all devicePixelRatio methods will always return 1.0, - // so CEF will not scale the web content - // NOET: There is bugs in Qt 6.2.4, the HighDpi doesn't work - QApplication::setAttribute(Qt::AA_EnableHighDpiScaling); -#if (QT_VERSION >= QT_VERSION_CHECK(5, 14, 0)) - QApplication::setHighDpiScaleFactorRoundingPolicy(Qt::HighDpiScaleFactorRoundingPolicy::PassThrough); -#endif -#endif // build QCefConfig QCefConfig config; // set user agent @@ -60,26 +82,19 @@ int main(int argc, char *argv[]) // WindowlessRenderingEnabled is set to true by default, // set to false to disable the OSR mode config.setWindowlessRenderingEnabled(true); + config.addCommandLineSwitch("single-process"); // add command line args, you can any cef supported switches or parameters config.addCommandLineSwitch("use-mock-keychain"); - config.addCommandLineSwitch("single-process"); - config.addCommandLineSwitch("enable-gpu"); - config.addCommandLineSwitch("enable-gpu-rasterization"); - config.addCommandLineSwitch("enable-webgl"); - config.addCommandLineSwitch("enable-impl-side-painting"); - config.addCommandLineSwitch("disable-software-rasterizer"); - config.addCommandLineSwitch("multi-threaded-message-loop"); - // config.addCommandLineSwitch("disable-gpu"); // config.addCommandLineSwitch("enable-media-stream"); // config.addCommandLineSwitch("allow-file-access-from-files"); // config.addCommandLineSwitch("disable-spell-checking"); // config.addCommandLineSwitch("disable-site-isolation-trials"); // config.addCommandLineSwitch("enable-aggressive-domstorage-flushing"); - config.addCommandLineSwitchWithValue("renderer-process-limit", "3"); + config.addCommandLineSwitchWithValue("renderer-process-limit", "4"); // allow remote debugging config.addCommandLineSwitchWithValue("remote-allow-origins", "*"); - config.addCommandLineSwitchWithValue("disable-features", "BlinkGenPropertyTrees,TranslateUI,site-per-process"); + // config.addCommandLineSwitchWithValue("disable-features", "BlinkGenPropertyTrees,TranslateUI,site-per-process"); // set cache folder config.setCachePath(QStandardPaths::writableLocation(QStandardPaths::AppLocalDataLocation)); @@ -88,8 +103,6 @@ int main(int argc, char *argv[]) // the lifecycle of cefContext must be the same as QApplication instance QCefContext cefContext(&a, argc, argv, &config); - MainWindow w; - w.show(); return a.exec(); }