// Copyright Epic Games, Inc. All Rights Reserved. #include "EpicWebHelperRemoteScripting.h" #include "EpicWebHelper.h" #include "EpicWebHelperRemoteMethodHandler.h" #if WITH_CEF3 CefRefPtr FEpicWebHelperRemoteScripting::CefToV8(CefRefPtr Dictionary) { // Custom types are encoded inside dictionary values with a $type and a $value property if ( Dictionary->GetType("$type") == VTYPE_STRING) { FString Type = WCHAR_TO_TCHAR(Dictionary->GetString("$type").ToWString().c_str()); if ( Type == "struct" && Dictionary->GetType("$value") == VTYPE_DICTIONARY) { return CefToPlainV8Object(Dictionary->GetDictionary("$value")); } if ( Type == "uobject" && Dictionary->GetType("$id") == VTYPE_STRING && Dictionary->GetType("$methods") == VTYPE_LIST) { FGuid Guid; if (FGuid::Parse(WCHAR_TO_TCHAR(Dictionary->GetString("$id").ToWString().c_str()), Guid)) { CefRefPtr Methods = Dictionary->GetList("$methods"); return CreateUObjectProxy(Guid, Methods); } } } return CefToPlainV8Object(Dictionary); } CefRefPtr FEpicWebHelperRemoteScripting::CreateUObjectProxy(FGuid ObjectId, CefRefPtr Methods) { CefRefPtr Context = CefV8Context::GetCurrentContext(); CefRefPtr Browser = Context->GetBrowser(); CefRefPtr Result = CefV8Value::CreateObject(nullptr, nullptr); CefRefPtr Remote = new FEpicWebHelperRemoteObject(this, Browser, Context, ObjectId); for (size_t I=0; I < Methods->GetSize(); ++I) { CefString MethodName = Methods->GetString(I); CefRefPtr FunctionProxy = CefV8Value::CreateFunction(MethodName, new FEpicWebHelperRemoteMethodHandler(Remote, MethodName)); Result->SetValue(MethodName, FunctionProxy, static_cast(V8_PROPERTY_ATTRIBUTE_DONTDELETE | V8_PROPERTY_ATTRIBUTE_READONLY )); } RemoteObjects.Add(ObjectId,Remote); Result->SetValue("$id", CefV8Value::CreateString(TCHAR_TO_WCHAR(*ObjectId.ToString(EGuidFormats::Digits))), static_cast(V8_PROPERTY_ATTRIBUTE_DONTDELETE | V8_PROPERTY_ATTRIBUTE_READONLY | V8_PROPERTY_ATTRIBUTE_DONTENUM)); return Result; } CefRefPtr FEpicWebHelperRemoteScripting::CefToPlainV8Object(CefRefPtr Dictionary) { CefRefPtr Result = CefV8Value::CreateObject(nullptr, nullptr); CefDictionaryValue::KeyList Keys; Dictionary->GetKeys(Keys); for (CefString Key : Keys) { Result->SetValue(Key, CefToV8(Dictionary, Key), V8_PROPERTY_ATTRIBUTE_NONE); } return Result; } CefRefPtr FEpicWebHelperRemoteScripting::CefToV8(CefRefPtr List) { CefRefPtr Result = CefV8Value::CreateArray(List->GetSize()); for (size_t i = 0; i < List->GetSize(); ++i) { Result->SetValue(i, CefToV8(List, i)); } return Result; } void FEpicWebHelperRemoteScripting::CefToV8Arglist(CefRefPtr List, CefV8ValueList& Values) { for (size_t i = 0; i < List->GetSize(); ++i) { Values.push_back(CefToV8(List, i)); } } CefRefPtr FEpicWebHelperRemoteScripting::V8ArrayToCef(const CefV8ValueList& Values) { CefRefPtr Result = CefListValue::Create(); for (size_t I = 0; I < Values.size(); ++I) { V8ToCef(Result, nullptr, I, Values[I]); } return Result; } CefRefPtr FEpicWebHelperRemoteScripting::V8ArrayToCef(CefRefPtr Array) { CefRefPtr Result = CefListValue::Create(); if (Array->IsArray()) { for (int I = 0; I < Array->GetArrayLength(); ++I) { V8ToCef(Result, Array, I, Array->GetValue(I)); } } return Result; } CefRefPtr FEpicWebHelperRemoteScripting::V8ObjectToCef(CefRefPtr Object) { CefRefPtr Result = CefDictionaryValue::Create(); if (Object->IsObject()) { std::vector Keys; Object->GetKeys(Keys); for (CefString Key : Keys) { V8ToCef(Result, Object, Key, Object->GetValue(Key)); } } return Result; } CefRefPtr FEpicWebHelperRemoteScripting::V8FunctionToCef(CefRefPtr Object, CefRefPtr Function) { CefRefPtr Result = CefDictionaryValue::Create(); FGuid Guid = CallbackRegistry.FindOrAdd(CefV8Context::GetCurrentContext(), Object, Function); Result->SetString("$type", "callback"); Result->SetString("$id", TCHAR_TO_WCHAR(*Guid.ToString(EGuidFormats::Digits))); Result->SetString("$name", Function->GetFunctionName()); return Result; } bool FEpicWebHelperRemoteScripting::HandleExecuteJSFunctionMessage(CefRefPtr MessageArguments) { FGuid Guid; // Message arguments are CallbackGuid, FunctionArguments, bIsError if (MessageArguments->GetSize() != 3 || MessageArguments->GetType(0) != VTYPE_STRING || MessageArguments->GetType(1) != VTYPE_LIST || MessageArguments->GetType(2) != VTYPE_BOOL) { // Wrong message argument types or count return false; } if (!FGuid::Parse(FString(WCHAR_TO_TCHAR(MessageArguments->GetString(0).ToWString().c_str())), Guid)) { // Invalid GUID return false; } if (!CallbackRegistry.Contains(Guid)) { // Unknown callback id return false; } auto Callback = CallbackRegistry[Guid]; { ScopedV8Context ContextScope(Callback.Context); bool bIsErrorCallback = MessageArguments->GetBool(2); CefRefPtr Function = bIsErrorCallback?Callback.OnError:Callback.Function; if (!Function.get()) { // Either invalid entry or no error handler return false; } CefV8ValueList FunctionArguments; CefToV8Arglist(MessageArguments->GetList(1), FunctionArguments); CefRefPtr Retval = Function->ExecuteFunction(Callback.Object, FunctionArguments); if (!Retval.get()) { // Function call resulted in an error return false; } // Remove callback if it's a one-shot callback and successful. if (Callback.bOneShot) { CallbackRegistry.Remove(Guid); } return true; } } bool FEpicWebHelperRemoteScripting::HandleSetValueMessage(CefRefPtr Browser, CefRefPtr MessageArguments) { CefRefPtr MainFrame = Browser->GetMainFrame(); CefRefPtr Context = MainFrame->GetV8Context(); ScopedV8Context ContextScope(Context); CefRefPtr RootObject = Context->GetGlobal()->GetValue("ue"); if (!RootObject.get()) { // The root object should always be created on context creation. return false; } for (size_t I = 0; I < MessageArguments->GetSize(); I++) { if (MessageArguments->GetType(I) != VTYPE_DICTIONARY) { return false; } CefRefPtr Argument = MessageArguments->GetDictionary(I); if (Argument->GetType("name") != VTYPE_STRING || Argument->GetType("value") != VTYPE_DICTIONARY || Argument->GetType("permanent") != VTYPE_BOOL) { // Wrong message argument types or count return false; } CefString Name = Argument->GetString("name"); CefRefPtr CefValue = Argument->GetDictionary("value"); bool bPermanent = Argument->GetBool("permanent"); if (bPermanent) { int32 BrowserID = Browser->GetIdentifier(); CefRefPtr Bindings; if (PermanentBindings.Contains(BrowserID)) { Bindings = PermanentBindings[BrowserID]; } else { Bindings = CefDictionaryValue::Create(); PermanentBindings.Add(BrowserID, Bindings); } Bindings->SetDictionary(Name, CefValue); } CefRefPtr Value = CefToV8(CefValue); RootObject->SetValue(Name, Value, V8_PROPERTY_ATTRIBUTE_NONE); } return true; } bool FEpicWebHelperRemoteScripting::HandleDeleteValueMessage(CefRefPtr Browser, CefRefPtr MessageArguments) { CefRefPtr MainFrame = Browser->GetMainFrame(); CefRefPtr Context = MainFrame->GetV8Context(); ScopedV8Context ContextScope(Context); CefRefPtr RootObject = Context->GetGlobal()->GetValue("ue"); if (!RootObject.get()) { // The root object should always be created on context creation. return false; } for (size_t I = 0; I < MessageArguments->GetSize(); I++) { if (MessageArguments->GetType(I) != VTYPE_DICTIONARY) { return false; } CefRefPtr Argument = MessageArguments->GetDictionary(I); if (Argument->GetType("name") != VTYPE_STRING || Argument->GetType("id") != VTYPE_STRING || Argument->GetType("permanent") != VTYPE_BOOL) { // Wrong message argument types or count return false; } CefString Name = Argument->GetString("name"); CefString Id = Argument->GetString("id"); bool bPermanent = Argument->GetBool("permanent"); FGuid Guid; if (!FGuid::Parse(WCHAR_TO_TCHAR(Id.ToWString().c_str()), Guid)) { return false; } if (bPermanent) { int32 BrowserID = Browser->GetIdentifier(); CefRefPtr Bindings; if (PermanentBindings.Contains(BrowserID)) { Bindings = PermanentBindings[BrowserID]; if (!Bindings->HasKey(Name)) { return false; } if (Guid.IsValid()) { CefRefPtr CefValue = Bindings->GetDictionary(Name); if (CefValue.get() && CefValue->GetString("$id") != Id) { return false; } } Bindings->Remove(Name); } } if (!RootObject->HasValue(Name)) { return false; } if (Guid.IsValid()) { CefRefPtr Value = RootObject->GetValue(Name); if (!Value->HasValue("$id") || !Value->GetValue("$id")->IsString() || Value->GetValue("$id")->GetStringValue() != Id) { return false; } } RootObject->DeleteValue(Name); } return true; } bool FEpicWebHelperRemoteScripting::OnProcessMessageReceived(CefRefPtr Browser, CefProcessId SourceProcess, CefRefPtr Message) { bool Result = false; FString MessageName = WCHAR_TO_TCHAR(Message->GetName().ToWString().c_str()); if (MessageName == TEXT("UE::ExecuteJSFunction")) { Result = HandleExecuteJSFunctionMessage(Message->GetArgumentList()); } else if (MessageName == TEXT("UE::SetValue")) { Result = HandleSetValueMessage(Browser, Message->GetArgumentList()); } else if (MessageName == TEXT("UE::DeleteValue")) { Result = HandleDeleteValueMessage(Browser, Message->GetArgumentList()); } return Result; } void FEpicWebHelperRemoteScripting::OnContextCreated(CefRefPtr Browser, CefRefPtr Frame, CefRefPtr Context) { ScopedV8Context ContextScope(Context); CefRefPtr Global = Context->GetGlobal(); if ( !Global->HasValue("ue") ) { Global->SetValue("ue", CefV8Value::CreateObject(nullptr, nullptr), V8_PROPERTY_ATTRIBUTE_DONTDELETE); } CefRefPtr RootObject = Context->GetGlobal()->GetValue("ue"); int32 BrowserID = Browser->GetIdentifier(); if (PermanentBindings.Contains(BrowserID)) { CefRefPtr Bindings = PermanentBindings[BrowserID]; CefDictionaryValue::KeyList Keys; Bindings->GetKeys(Keys); for (CefString Key : Keys) { CefRefPtr Value = CefToV8(Bindings->GetDictionary(Key)); RootObject->SetValue(Key, Value, V8_PROPERTY_ATTRIBUTE_NONE); } } } void FEpicWebHelperRemoteScripting::OnContextReleased(CefRefPtr Browser, CefRefPtr Frame, CefRefPtr Context) { for(const TPair> &RemoteObject : RemoteObjects) { if (RemoteObject.Value->Browser->IsSame(Browser) && RemoteObject.Value->CreationContext->IsSame(Context)) { RemoteObject.Value->ReleaseMethod(); RemoteObjects.Remove(RemoteObject.Key); break; } } // Invalidate JS functions that were created in the context being released. CallbackRegistry.RemoveByContext(Context); } void FEpicWebHelperRemoteScripting::InitPermanentBindings(int32 BrowserID, CefRefPtr Values) { // The CefDictionary in PermanentBindings needs to be writable, so we'll copy the Values argument before saving it. PermanentBindings.Add(BrowserID, Values->Copy(true)); } #endif