Files
UnrealEngine/Engine/Source/Runtime/WebBrowser/Private/CEF/CEFJSScripting.cpp
2025-05-18 13:04:45 +08:00

368 lines
12 KiB
C++

// Copyright Epic Games, Inc. All Rights Reserved.
#include "CEF/CEFJSScripting.h"
#if WITH_CEF3
#include "WebJSScripting.h"
#include "WebJSFunction.h"
#include "CEFWebBrowserWindow.h"
#include "CEFJSStructSerializerBackend.h"
#include "CEFJSStructDeserializerBackend.h"
#include "StructSerializer.h"
#include "StructDeserializer.h"
// Internal utility function(s)
namespace
{
template<typename DestContainerType, typename SrcContainerType, typename DestKeyType, typename SrcKeyType>
bool CopyContainerValue(DestContainerType DestContainer, SrcContainerType SrcContainer, DestKeyType DestKey, SrcKeyType SrcKey )
{
switch (SrcContainer->GetType(SrcKey))
{
case VTYPE_NULL:
return DestContainer->SetNull(DestKey);
case VTYPE_BOOL:
return DestContainer->SetBool(DestKey, SrcContainer->GetBool(SrcKey));
case VTYPE_INT:
return DestContainer->SetInt(DestKey, SrcContainer->GetInt(SrcKey));
case VTYPE_DOUBLE:
return DestContainer->SetDouble(DestKey, SrcContainer->GetDouble(SrcKey));
case VTYPE_STRING:
return DestContainer->SetString(DestKey, SrcContainer->GetString(SrcKey));
case VTYPE_BINARY:
return DestContainer->SetBinary(DestKey, SrcContainer->GetBinary(SrcKey));
case VTYPE_DICTIONARY:
return DestContainer->SetDictionary(DestKey, SrcContainer->GetDictionary(SrcKey));
case VTYPE_LIST:
return DestContainer->SetList(DestKey, SrcContainer->GetList(SrcKey));
case VTYPE_INVALID:
default:
return false;
}
}
}
CefRefPtr<CefDictionaryValue> FCEFJSScripting::ConvertStruct(UStruct* TypeInfo, const void* StructPtr)
{
FCEFJSStructSerializerBackend Backend (SharedThis(this));
FStructSerializer::Serialize(StructPtr, *TypeInfo, Backend);
CefRefPtr<CefDictionaryValue> Result = CefDictionaryValue::Create();
Result->SetString("$type", "struct");
Result->SetString("$ue4Type", TCHAR_TO_WCHAR(*GetBindingName(TypeInfo)));
Result->SetDictionary("$value", Backend.GetResult());
return Result;
}
CefRefPtr<CefDictionaryValue> FCEFJSScripting::ConvertObject(UObject* Object)
{
CefRefPtr<CefDictionaryValue> Result = CefDictionaryValue::Create();
RetainBinding(Object);
UClass* Class = Object->GetClass();
CefRefPtr<CefListValue> MethodNames = CefListValue::Create();
int32 MethodIndex = 0;
for (TFieldIterator<UFunction> FunctionIt(Class, EFieldIteratorFlags::IncludeSuper); FunctionIt; ++FunctionIt)
{
UFunction* Function = *FunctionIt;
MethodNames->SetString(MethodIndex++, TCHAR_TO_WCHAR(*GetBindingName(Function)));
}
Result->SetString("$type", "uobject");
Result->SetString("$id", TCHAR_TO_WCHAR(*PtrToGuid(Object).ToString(EGuidFormats::Digits)));
Result->SetList("$methods", MethodNames);
return Result;
}
bool FCEFJSScripting::OnProcessMessageReceived(CefRefPtr<CefBrowser> Browser, CefProcessId SourceProcess, CefRefPtr<CefProcessMessage> Message)
{
bool Result = false;
FString MessageName = WCHAR_TO_TCHAR(Message->GetName().ToWString().c_str());
if (MessageName == TEXT("UE::ExecuteUObjectMethod"))
{
Result = HandleExecuteUObjectMethodMessage(Message->GetArgumentList());
}
else if (MessageName == TEXT("UE::ReleaseUObject"))
{
Result = HandleReleaseUObjectMessage(Message->GetArgumentList());
}
return Result;
}
void FCEFJSScripting::SendProcessMessage(CefRefPtr<CefProcessMessage> Message)
{
if (IsValid() && InternalCefBrowser->GetMainFrame())
{
InternalCefBrowser->GetMainFrame()->SendProcessMessage(PID_RENDERER, Message);
}
}
CefRefPtr<CefDictionaryValue> FCEFJSScripting::GetPermanentBindings()
{
CefRefPtr<CefDictionaryValue> Result = CefDictionaryValue::Create();
TMap<FString, UObject*> CachedPermanentUObjectsByName = PermanentUObjectsByName;
for(auto& Entry : CachedPermanentUObjectsByName)
{
Result->SetDictionary(TCHAR_TO_WCHAR(*Entry.Key), ConvertObject(Entry.Value));
}
return Result;
}
void FCEFJSScripting::BindUObject(const FString& Name, UObject* Object, bool bIsPermanent)
{
const FString ExposedName = GetBindingName(Name, Object);
CefRefPtr<CefDictionaryValue> Converted = ConvertObject(Object);
if (bIsPermanent)
{
// Each object can only have one permanent binding
if (BoundObjects[Object].bIsPermanent)
{
return;
}
// Existing permanent objects must be removed first
if (PermanentUObjectsByName.Contains(ExposedName))
{
return;
}
BoundObjects[Object]={true, -1};
PermanentUObjectsByName.Add(ExposedName, Object);
}
CefRefPtr<CefProcessMessage> SetValueMessage = CefProcessMessage::Create(TCHAR_TO_WCHAR(TEXT("UE::SetValue")));
CefRefPtr<CefListValue>MessageArguments = SetValueMessage->GetArgumentList();
CefRefPtr<CefDictionaryValue> Value = CefDictionaryValue::Create();
Value->SetString("name", TCHAR_TO_WCHAR(*ExposedName));
Value->SetDictionary("value", Converted);
Value->SetBool("permanent", bIsPermanent);
MessageArguments->SetDictionary(0, Value);
SendProcessMessage(SetValueMessage);
}
void FCEFJSScripting::UnbindUObject(const FString& Name, UObject* Object, bool bIsPermanent)
{
const FString ExposedName = GetBindingName(Name, Object);
if (bIsPermanent)
{
// If overriding an existing permanent object, make it non-permanent
if (PermanentUObjectsByName.Contains(ExposedName) && (Object == nullptr || PermanentUObjectsByName[ExposedName] == Object))
{
Object = PermanentUObjectsByName.FindAndRemoveChecked(ExposedName);
BoundObjects.Remove(Object);
return;
}
else
{
return;
}
}
CefRefPtr<CefProcessMessage> DeleteValueMessage = CefProcessMessage::Create(TCHAR_TO_WCHAR(TEXT("UE::DeleteValue")));
CefRefPtr<CefListValue>MessageArguments = DeleteValueMessage->GetArgumentList();
CefRefPtr<CefDictionaryValue> Info = CefDictionaryValue::Create();
Info->SetString("name", TCHAR_TO_WCHAR(*ExposedName));
Info->SetString("id", TCHAR_TO_WCHAR(*PtrToGuid(Object).ToString(EGuidFormats::Digits)));
Info->SetBool("permanent", bIsPermanent);
MessageArguments->SetDictionary(0, Info);
SendProcessMessage(DeleteValueMessage);
}
bool FCEFJSScripting::HandleReleaseUObjectMessage(CefRefPtr<CefListValue> MessageArguments)
{
FGuid ObjectKey;
// Message arguments are Name, Value, bGlobal
if (MessageArguments->GetSize() != 1 || MessageArguments->GetType(0) != VTYPE_STRING)
{
// Wrong message argument types or count
return false;
}
if (!FGuid::Parse(FString(WCHAR_TO_TCHAR(MessageArguments->GetString(0).ToWString().c_str())), ObjectKey))
{
// Invalid GUID
return false;
}
UObject* Object = GuidToPtr(ObjectKey);
if ( Object == nullptr )
{
// Invalid object
return false;
}
ReleaseBinding(Object);
return true;
}
bool FCEFJSScripting::HandleExecuteUObjectMethodMessage(CefRefPtr<CefListValue> MessageArguments)
{
FGuid ObjectKey;
// Message arguments are Name, Value, bGlobal
if (MessageArguments->GetSize() != 4
|| MessageArguments->GetType(0) != VTYPE_STRING
|| MessageArguments->GetType(1) != VTYPE_STRING
|| MessageArguments->GetType(2) != VTYPE_STRING
|| MessageArguments->GetType(3) != VTYPE_LIST
)
{
// Wrong message argument types or count
return false;
}
if (!FGuid::Parse(FString(WCHAR_TO_TCHAR(MessageArguments->GetString(0).ToWString().c_str())), ObjectKey))
{
// Invalid GUID
return false;
}
// Get the promise callback and use that to report any results from executing this function.
FGuid ResultCallbackId;
if (!FGuid::Parse(FString(WCHAR_TO_TCHAR(MessageArguments->GetString(2).ToWString().c_str())), ResultCallbackId))
{
// Invalid GUID
return false;
}
UObject* Object = GuidToPtr(ObjectKey);
if (Object == nullptr)
{
// Unknown uobject id
InvokeJSErrorResult(ResultCallbackId, TEXT("Unknown UObject ID"));
return true;
}
FName MethodName = WCHAR_TO_TCHAR(MessageArguments->GetString(1).ToWString().c_str());
UFunction* Function = Object->FindFunction(MethodName);
if (!Function)
{
InvokeJSErrorResult(ResultCallbackId, TEXT("Unknown UObject Function"));
return true;
}
// Coerce arguments to function arguments.
uint16 ParamsSize = Function->ParmsSize;
uint8* Params = nullptr;
FProperty* ReturnParam = nullptr;
FProperty* PromiseParam = nullptr;
if (ParamsSize > 0)
{
// Convert cef argument list to a dictionary, so we can use FStructDeserializer to convert it for us
CefRefPtr<CefDictionaryValue> NamedArgs = CefDictionaryValue::Create();
int32 CurrentArg = 0;
CefRefPtr<CefListValue> CefArgs = MessageArguments->GetList(3);
for ( TFieldIterator<FProperty> It(Function); It; ++It )
{
FProperty* Param = *It;
if (Param->PropertyFlags & CPF_Parm)
{
if (Param->PropertyFlags & CPF_ReturnParm)
{
ReturnParam = Param;
}
else
{
FStructProperty *StructProperty = CastField<FStructProperty>(Param);
if (StructProperty && StructProperty->Struct->IsChildOf(FWebJSResponse::StaticStruct()))
{
PromiseParam = Param;
}
else
{
CopyContainerValue(NamedArgs, CefArgs, CefString(TCHAR_TO_WCHAR(*GetBindingName(Param))), CurrentArg);
CurrentArg++;
}
}
}
}
// UFunction is a subclass of UStruct, so we can treat the arguments as a struct for deserialization
check(nullptr == Params);
Params = (uint8*)FMemory::Malloc(Function->GetStructureSize());
Function->InitializeStruct(Params);
FCEFJSStructDeserializerBackend Backend = FCEFJSStructDeserializerBackend(SharedThis(this), NamedArgs);
FStructDeserializer::Deserialize(Params, *Function, Backend);
}
if (PromiseParam)
{
FWebJSResponse* PromisePtr = PromiseParam->ContainerPtrToValuePtr<FWebJSResponse>(Params);
if (PromisePtr)
{
*PromisePtr = FWebJSResponse(SharedThis(this), ResultCallbackId);
}
}
Object->ProcessEvent(Function, Params);
CefRefPtr<CefListValue> Results = CefListValue::Create();
if ( ! PromiseParam ) // If PromiseParam is set, we assume that the UFunction will ensure it is called with the result
{
if ( ReturnParam )
{
FStructSerializerPolicies ReturnPolicies;
ReturnPolicies.PropertyFilter = [&](const FProperty* CandidateProperty, const FProperty* ParentProperty)
{
return ParentProperty != nullptr || CandidateProperty == ReturnParam;
};
FCEFJSStructSerializerBackend ReturnBackend(SharedThis(this));
FStructSerializer::Serialize(Params, *Function, ReturnBackend, ReturnPolicies);
CefRefPtr<CefDictionaryValue> ResultDict = ReturnBackend.GetResult();
// Extract the single return value from the serialized dictionary to an array
CopyContainerValue(Results, ResultDict, 0, TCHAR_TO_WCHAR(*GetBindingName(ReturnParam)));
}
InvokeJSFunction(ResultCallbackId, Results, false);
}
if (Params)
{
Function->DestroyStruct(Params);
FMemory::Free(Params);
Params = nullptr;
}
return true;
}
void FCEFJSScripting::UnbindCefBrowser()
{
InternalCefBrowser = nullptr;
}
void FCEFJSScripting::InvokeJSErrorResult(FGuid FunctionId, const FString& Error)
{
CefRefPtr<CefListValue> FunctionArguments = CefListValue::Create();
FunctionArguments->SetString(0, TCHAR_TO_WCHAR(*Error));
InvokeJSFunction(FunctionId, FunctionArguments, true);
}
void FCEFJSScripting::InvokeJSFunction(FGuid FunctionId, int32 ArgCount, FWebJSParam Arguments[], bool bIsError)
{
CefRefPtr<CefListValue> FunctionArguments = CefListValue::Create();
for ( int32 i=0; i<ArgCount; i++)
{
SetConverted(FunctionArguments, i, Arguments[i]);
}
InvokeJSFunction(FunctionId, FunctionArguments, bIsError);
}
void FCEFJSScripting::InvokeJSFunction(FGuid FunctionId, const CefRefPtr<CefListValue>& FunctionArguments, bool bIsError)
{
CefRefPtr<CefProcessMessage> Message = CefProcessMessage::Create(TCHAR_TO_WCHAR(TEXT("UE::ExecuteJSFunction")));
CefRefPtr<CefListValue> MessageArguments = Message->GetArgumentList();
MessageArguments->SetString(0, TCHAR_TO_WCHAR(*FunctionId.ToString(EGuidFormats::Digits)));
MessageArguments->SetList(1, FunctionArguments);
MessageArguments->SetBool(2, bIsError);
SendProcessMessage(Message);
}
#endif