NativeInterfaceJSScripting.cpp 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655
  1. // Engine/Source/Runtime/WebBrowser/Private/Native/NativeJSScripting.cpp
  2. #include "NativeInterfaceJSScripting.h"
  3. #include "NativeInterfaceJSStructSerializerBackend.h"
  4. #include "NativeInterfaceJSStructDeserializerBackend.h"
  5. #include "StructSerializer.h"
  6. #include "StructDeserializer.h"
  7. #include "UObject/UnrealType.h"
  8. #include "NativeWebInterfaceBrowserProxy.h"
  9. namespace NativeInterfaceFuncs
  10. {
  11. const FString ExecuteMethodCommand = TEXT("ExecuteUObjectMethod");
  12. typedef TSharedRef<TJsonWriter<>> FJsonWriterRef;
  13. template<typename ValueType> void WriteValue(FJsonWriterRef Writer, const FString& Key, const ValueType& Value)
  14. {
  15. Writer->WriteValue(Key, Value);
  16. }
  17. void WriteNull(FJsonWriterRef Writer, const FString& Key)
  18. {
  19. Writer->WriteNull(Key);
  20. }
  21. void WriteArrayStart(FJsonWriterRef Writer, const FString& Key)
  22. {
  23. Writer->WriteArrayStart(Key);
  24. }
  25. void WriteObjectStart(FJsonWriterRef Writer, const FString& Key)
  26. {
  27. Writer->WriteObjectStart(Key);
  28. }
  29. void WriteRaw(FJsonWriterRef Writer, const FString& Key, const FString& Value)
  30. {
  31. Writer->WriteRawJSONValue(Key, Value);
  32. }
  33. template<typename ValueType> void WriteValue(FJsonWriterRef Writer, const int, const ValueType& Value)
  34. {
  35. Writer->WriteValue(Value);
  36. }
  37. void WriteNull(FJsonWriterRef Writer, int)
  38. {
  39. Writer->WriteNull();
  40. }
  41. void WriteArrayStart(FJsonWriterRef Writer, int)
  42. {
  43. Writer->WriteArrayStart();
  44. }
  45. void WriteObjectStart(FJsonWriterRef Writer, int)
  46. {
  47. Writer->WriteObjectStart();
  48. }
  49. void WriteRaw(FJsonWriterRef Writer, int, const FString& Value)
  50. {
  51. Writer->WriteRawJSONValue(Value);
  52. }
  53. template<typename KeyType>
  54. bool WriteJsParam(FNativeInterfaceJSScriptingRef Scripting, FJsonWriterRef Writer, const KeyType& Key, FWebInterfaceJSParam& Param)
  55. {
  56. switch (Param.Tag)
  57. {
  58. case FWebInterfaceJSParam::PTYPE_NULL:
  59. WriteNull(Writer, Key);
  60. break;
  61. case FWebInterfaceJSParam::PTYPE_BOOL:
  62. WriteValue(Writer, Key, Param.BoolValue);
  63. break;
  64. case FWebInterfaceJSParam::PTYPE_DOUBLE:
  65. WriteValue(Writer, Key, Param.DoubleValue);
  66. break;
  67. case FWebInterfaceJSParam::PTYPE_INT:
  68. WriteValue(Writer, Key, Param.IntValue);
  69. break;
  70. case FWebInterfaceJSParam::PTYPE_STRING:
  71. WriteValue(Writer, Key, *Param.StringValue);
  72. break;
  73. case FWebInterfaceJSParam::PTYPE_OBJECT:
  74. {
  75. if (Param.ObjectValue == nullptr)
  76. {
  77. WriteNull(Writer, Key);
  78. }
  79. else
  80. {
  81. FString ConvertedObject = Scripting->ConvertObject(Param.ObjectValue);
  82. WriteRaw(Writer, Key, ConvertedObject);
  83. }
  84. break;
  85. }
  86. case FWebInterfaceJSParam::PTYPE_STRUCT:
  87. {
  88. FString ConvertedStruct = Scripting->ConvertStruct(Param.StructValue->GetTypeInfo(), Param.StructValue->GetData());
  89. WriteRaw(Writer, Key, ConvertedStruct);
  90. break;
  91. }
  92. case FWebInterfaceJSParam::PTYPE_ARRAY:
  93. {
  94. WriteArrayStart(Writer, Key);
  95. for(int i=0; i < Param.ArrayValue->Num(); ++i)
  96. {
  97. WriteJsParam(Scripting, Writer, i, (*Param.ArrayValue)[i]);
  98. }
  99. Writer->WriteArrayEnd();
  100. break;
  101. }
  102. case FWebInterfaceJSParam::PTYPE_MAP:
  103. {
  104. WriteObjectStart(Writer, Key);
  105. for(auto& Pair : *Param.MapValue)
  106. {
  107. WriteJsParam(Scripting, Writer, *Pair.Key, Pair.Value);
  108. }
  109. Writer->WriteObjectEnd();
  110. break;
  111. }
  112. default:
  113. return false;
  114. }
  115. return true;
  116. }
  117. }
  118. FString _GetObjectPostInitScript(const FString& Name, const FString& FullyQualifiedName)
  119. {
  120. return FString::Printf(TEXT("(function(){document.dispatchEvent(new CustomEvent('%s:ready', {details: %s}));})();"), *Name, *FullyQualifiedName);
  121. }
  122. void FNativeInterfaceJSScripting::BindUObject(const FString& Name, UObject* Object, bool bIsPermanent )
  123. {
  124. const FString ExposedName = GetBindingName(Name, Object);
  125. FString Converted = ConvertObject(Object);
  126. if (bIsPermanent)
  127. {
  128. // Existing permanent objects must be removed first and each object can only have one permanent binding
  129. if (PermanentUObjectsByName.Contains(ExposedName) || BoundObjects[Object].bIsPermanent)
  130. {
  131. return;
  132. }
  133. BoundObjects[Object]={true, -1};
  134. PermanentUObjectsByName.Add(ExposedName, Object);
  135. }
  136. if(!bLoaded)
  137. {
  138. PageLoaded();
  139. }
  140. else
  141. {
  142. const FString& EscapedName = ExposedName.ReplaceCharWithEscapedChar();
  143. FString SetValueScript = FString::Printf(TEXT("window.ue['%s'] = %s;"), *EscapedName, *Converted);
  144. SetValueScript.Append(_GetObjectPostInitScript(EscapedName, FString::Printf(TEXT("window.ue['%s']"), *EscapedName)));
  145. ExecuteJavascript(SetValueScript);
  146. }
  147. }
  148. void FNativeInterfaceJSScripting::ExecuteJavascript(const FString& Javascript)
  149. {
  150. TSharedPtr<FNativeWebInterfaceBrowserProxy> Window = WindowPtr.Pin();
  151. if (Window.IsValid())
  152. {
  153. Window->ExecuteJavascript(Javascript);
  154. }
  155. }
  156. void FNativeInterfaceJSScripting::UnbindUObject(const FString& Name, UObject* Object, bool bIsPermanent)
  157. {
  158. const FString ExposedName = GetBindingName(Name, Object);
  159. if (bIsPermanent)
  160. {
  161. // If overriding an existing permanent object, make it non-permanent
  162. if (PermanentUObjectsByName.Contains(ExposedName) && (Object == nullptr || PermanentUObjectsByName[ExposedName] == Object))
  163. {
  164. Object = PermanentUObjectsByName.FindAndRemoveChecked(ExposedName);
  165. BoundObjects.Remove(Object);
  166. return;
  167. }
  168. else
  169. {
  170. return;
  171. }
  172. }
  173. FString DeleteValueScript = FString::Printf(TEXT("delete window.ue['%s'];"), *ExposedName.ReplaceCharWithEscapedChar());
  174. ExecuteJavascript(DeleteValueScript);
  175. }
  176. int32 _ParseParams(const FString& ParamStr, TArray<FString>& OutArray)
  177. {
  178. OutArray.Reset();
  179. const TCHAR *Start = *ParamStr;
  180. if (Start && *Start != TEXT('\0'))
  181. {
  182. int32 DelimLimit = 4;
  183. while (const TCHAR *At = FCString::Strstr(Start, TEXT("/")))
  184. {
  185. OutArray.Emplace(At - Start, Start);
  186. Start = At + 1;
  187. if (--DelimLimit == 0)
  188. {
  189. break;
  190. }
  191. }
  192. if (*Start)
  193. {
  194. OutArray.Emplace(Start);
  195. }
  196. }
  197. return OutArray.Num();
  198. }
  199. bool FNativeInterfaceJSScripting::OnJsMessageReceived(const FString& Message)
  200. {
  201. check(IsInGameThread());
  202. bool Result = false;
  203. TArray<FString> Params;
  204. if (_ParseParams(Message, Params))
  205. {
  206. FString Command = Params[0];
  207. Params.RemoveAt(0, 1);
  208. if (Command == NativeInterfaceFuncs::ExecuteMethodCommand)
  209. {
  210. Result = HandleExecuteUObjectMethodMessage(Params);
  211. }
  212. }
  213. return Result;
  214. }
  215. FString FNativeInterfaceJSScripting::ConvertStruct(UStruct* TypeInfo, const void* StructPtr)
  216. {
  217. TArray<uint8> ReturnBuffer;
  218. FMemoryWriter Writer(ReturnBuffer);
  219. FNativeInterfaceJSStructSerializerBackend ReturnBackend = FNativeInterfaceJSStructSerializerBackend(SharedThis(this), Writer);
  220. FStructSerializer::Serialize(StructPtr, *TypeInfo, ReturnBackend);
  221. // Extract the result value from the serialized JSON object:
  222. ReturnBuffer.Add(0);
  223. ReturnBuffer.Add(0); // Add two as we're dealing with UTF-16, so 2 bytes
  224. return UTF16_TO_TCHAR((UTF16CHAR*)ReturnBuffer.GetData());
  225. }
  226. FString FNativeInterfaceJSScripting::ConvertObject(UObject* Object)
  227. {
  228. RetainBinding(Object);
  229. UClass* Class = Object->GetClass();
  230. bool first = true;
  231. FString Result = TEXT("(function(){ return Object.create({");
  232. for (TFieldIterator<UFunction> FunctionIt(Class, EFieldIteratorFlags::IncludeSuper); FunctionIt; ++FunctionIt)
  233. {
  234. UFunction* Function = *FunctionIt;
  235. if(!first)
  236. {
  237. Result.Append(TEXT(","));
  238. }
  239. else
  240. {
  241. first = false;
  242. }
  243. Result.Append(*GetBindingName(Function));
  244. Result.Append(TEXT(": function "));
  245. Result.Append(*GetBindingName(Function));
  246. Result.Append(TEXT(" ("));
  247. bool firstArg = true;
  248. for ( TFieldIterator<FProperty> It(Function); It; ++It )
  249. {
  250. FProperty* Param = *It;
  251. if (Param->PropertyFlags & CPF_Parm && ! (Param->PropertyFlags & CPF_ReturnParm) )
  252. {
  253. FStructProperty *StructProperty = CastField<FStructProperty>(Param);
  254. if (!StructProperty || !StructProperty->Struct->IsChildOf(FWebInterfaceJSResponse::StaticStruct()))
  255. {
  256. if(!firstArg)
  257. {
  258. Result.Append(TEXT(", "));
  259. }
  260. else
  261. {
  262. firstArg = false;
  263. }
  264. Result.Append(*GetBindingName(Param));
  265. }
  266. }
  267. }
  268. Result.Append(TEXT(")"));
  269. // We hijack the RPCResponseId and use it for our priority value. 0 means it has not been assigned and we default to 2. 1-5 is high-low priority which we map to the 0-4 range used by EmbeddedCommunication.
  270. int32 Priority = Function->RPCResponseId == 0 ? 2 : FMath::Clamp((int32)Function->RPCResponseId, 1, 5) - 1;
  271. Result.Append(TEXT(" {return window.ue.$.executeMethod('"));
  272. Result.Append(FString::FromInt(Priority));
  273. Result.Append(TEXT("',this.$id, arguments)}"));
  274. }
  275. Result.Append(TEXT("},{"));
  276. Result.Append(TEXT("$id: {writable: false, configurable:false, enumerable: false, value: '"));
  277. Result.Append(*PtrToGuid(Object).ToString(EGuidFormats::Digits));
  278. Result.Append(TEXT("'}})})()"));
  279. return Result;
  280. }
  281. void FNativeInterfaceJSScripting::InvokeJSFunction(FGuid FunctionId, int32 ArgCount, FWebInterfaceJSParam Arguments[], bool bIsError)
  282. {
  283. if (!IsValid())
  284. {
  285. return;
  286. }
  287. FString CallbackScript = FString::Printf(TEXT("window.ue.$.invokeCallback('%s', %s, "), *FunctionId.ToString(EGuidFormats::Digits), (bIsError) ? TEXT("true") : TEXT("false"));
  288. {
  289. TArray<uint8> Buffer;
  290. FMemoryWriter MemoryWriter(Buffer);
  291. NativeInterfaceFuncs::FJsonWriterRef JsonWriter = TJsonWriter<>::Create(&MemoryWriter);
  292. JsonWriter->WriteArrayStart();
  293. for (int i = 0; i < ArgCount; i++)
  294. {
  295. NativeInterfaceFuncs::WriteJsParam(SharedThis(this), JsonWriter, i, Arguments[i]);
  296. }
  297. JsonWriter->WriteArrayEnd();
  298. CallbackScript.Append((TCHAR*)Buffer.GetData(), Buffer.Num() / sizeof(TCHAR));
  299. }
  300. CallbackScript.Append(TEXT(")"));
  301. ExecuteJavascript(CallbackScript);
  302. }
  303. void FNativeInterfaceJSScripting::InvokeJSFunctionRaw(FGuid FunctionId, const FString& RawJSValue, bool bIsError)
  304. {
  305. if (!IsValid())
  306. {
  307. return;
  308. }
  309. FString CallbackScript = FString::Printf(TEXT("window.ue.$.invokeCallback('%s', %s, [%s])"),
  310. *FunctionId.ToString(EGuidFormats::Digits), (bIsError)?TEXT("true"):TEXT("false"), *RawJSValue);
  311. ExecuteJavascript(CallbackScript);
  312. }
  313. void FNativeInterfaceJSScripting::InvokeJSErrorResult(FGuid FunctionId, const FString& Error)
  314. {
  315. FWebInterfaceJSParam Args[1] = {FWebInterfaceJSParam(Error)};
  316. InvokeJSFunction(FunctionId, 1, Args, true);
  317. }
  318. bool FNativeInterfaceJSScripting::HandleExecuteUObjectMethodMessage(const TArray<FString>& MessageArgs)
  319. {
  320. if (MessageArgs.Num() != 4)
  321. {
  322. return false;
  323. }
  324. const FString& ObjectIdStr = MessageArgs[0];
  325. FGuid ObjectKey;
  326. UObject* Object = nullptr;
  327. if (FGuid::Parse(ObjectIdStr, ObjectKey))
  328. {
  329. Object = GuidToPtr(ObjectKey);
  330. }
  331. else if(PermanentUObjectsByName.Contains(ObjectIdStr))
  332. {
  333. Object = PermanentUObjectsByName[ObjectIdStr];
  334. }
  335. if(Object == nullptr)
  336. {
  337. // Unknown uobject id/name
  338. return false;
  339. }
  340. // Get the promise callback and use that to report any results from executing this function.
  341. FGuid ResultCallbackId;
  342. if (!FGuid::Parse(MessageArgs[1], ResultCallbackId))
  343. {
  344. // Invalid GUID
  345. return false;
  346. }
  347. FName MethodName = FName(*MessageArgs[2]);
  348. UFunction* Function = Object->FindFunction(MethodName);
  349. if (!Function)
  350. {
  351. InvokeJSErrorResult(ResultCallbackId, TEXT("Unknown UObject Function"));
  352. return true;
  353. }
  354. // Coerce arguments to function arguments.
  355. uint16 ParamsSize = Function->ParmsSize;
  356. TArray<uint8> Params;
  357. FProperty* ReturnParam = nullptr;
  358. FProperty* PromiseParam = nullptr;
  359. if (ParamsSize > 0)
  360. {
  361. // Find return parameter and a promise argument if present, as we need to handle them differently
  362. for ( TFieldIterator<FProperty> It(Function); It; ++It )
  363. {
  364. FProperty* Param = *It;
  365. if (Param->PropertyFlags & CPF_Parm)
  366. {
  367. if (Param->PropertyFlags & CPF_ReturnParm)
  368. {
  369. ReturnParam = Param;
  370. }
  371. else
  372. {
  373. FStructProperty *StructProperty = CastField<FStructProperty>(Param);
  374. if (StructProperty && StructProperty->Struct->IsChildOf(FWebInterfaceJSResponse::StaticStruct()))
  375. {
  376. PromiseParam = Param;
  377. }
  378. }
  379. if (ReturnParam && PromiseParam)
  380. {
  381. break;
  382. }
  383. }
  384. }
  385. // UFunction is a subclass of UStruct, so we can treat the arguments as a struct for deserialization
  386. Params.AddUninitialized(ParamsSize);
  387. Function->InitializeStruct(Params.GetData());
  388. // Note: This is a no-op on platforms that are using a 16-bit TCHAR
  389. FTCHARToUTF16 UTF16String(*MessageArgs[3], MessageArgs[3].Len());
  390. TArray<uint8> JsonData;
  391. JsonData.Append((uint8*)UTF16String.Get(), UTF16String.Length() * sizeof(UTF16CHAR));
  392. FMemoryReader Reader(JsonData);
  393. FNativeInterfaceJSStructDeserializerBackend Backend = FNativeInterfaceJSStructDeserializerBackend(SharedThis(this), Reader);
  394. FStructDeserializer::Deserialize(Params.GetData(), *Function, Backend);
  395. }
  396. if (PromiseParam)
  397. {
  398. FWebInterfaceJSResponse* PromisePtr = PromiseParam->ContainerPtrToValuePtr<FWebInterfaceJSResponse>(Params.GetData());
  399. if (PromisePtr)
  400. {
  401. *PromisePtr = FWebInterfaceJSResponse(SharedThis(this), ResultCallbackId);
  402. }
  403. }
  404. Object->ProcessEvent(Function, Params.GetData());
  405. if ( ! PromiseParam ) // If PromiseParam is set, we assume that the UFunction will ensure it is called with the result
  406. {
  407. if ( ReturnParam )
  408. {
  409. FStructSerializerPolicies ReturnPolicies;
  410. ReturnPolicies.PropertyFilter = [&ReturnParam](const FProperty* CandidateProperty, const FProperty* ParentProperty)
  411. {
  412. return ParentProperty != nullptr || CandidateProperty == ReturnParam;
  413. };
  414. TArray<uint8> ReturnBuffer;
  415. FMemoryWriter Writer(ReturnBuffer);
  416. FNativeInterfaceJSStructSerializerBackend ReturnBackend = FNativeInterfaceJSStructSerializerBackend(SharedThis(this), Writer);
  417. FStructSerializer::Serialize(Params.GetData(), *Function, ReturnBackend, ReturnPolicies);
  418. // Extract the result value from the serialized JSON object:
  419. ReturnBuffer.Add(0);
  420. ReturnBuffer.Add(0); // Add two as we're dealing with UTF-16, so 2 bytes
  421. const FString ResultJS = UTF16_TO_TCHAR((UTF16CHAR*)ReturnBuffer.GetData());
  422. InvokeJSFunctionRaw(ResultCallbackId, ResultJS, false);
  423. }
  424. else
  425. {
  426. InvokeJSFunction(ResultCallbackId, 0, nullptr, false);
  427. }
  428. }
  429. return true;
  430. }
  431. FString FNativeInterfaceJSScripting::GetInitializeScript()
  432. {
  433. const FString NativeScriptingInit =
  434. TEXT("(function() {")
  435. TEXT("var util = Object.create({")
  436. // Simple random-based (RFC-4122 version 4) UUID generator.
  437. // Version 4 UUIDs have the form xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx where x is any hexadecimal digit and y is one of 8, 9, a, or b
  438. // This function returns the UUID as a hex string without the dashes
  439. TEXT("uuid: function()")
  440. TEXT("{")
  441. TEXT(" var b = new Uint8Array(16); window.crypto.getRandomValues(b);")
  442. TEXT(" b[6] = b[6]&0xf|0x40; b[8]=b[8]&0x3f|0x80;") // Set the reserved bits to the correct values
  443. TEXT(" return Array.prototype.reduce.call(b, function(a,i){return a+((0x100|i).toString(16).substring(1))},'').toUpperCase();")
  444. TEXT("}, ")
  445. // save a callback function in the callback registry
  446. // returns the uuid of the callback for passing to the host application
  447. // ensures that each function object is only stored once.
  448. // (Closures executed multiple times are considered separate objects.)
  449. TEXT("registerCallback: function(callback)")
  450. TEXT("{")
  451. TEXT(" var key;")
  452. TEXT(" for(key in this.callbacks)")
  453. TEXT(" {")
  454. TEXT(" if (!this.callbacks[key].isOneShot && this.callbacks[key].accept === callback)")
  455. TEXT(" {")
  456. TEXT(" return key;")
  457. TEXT(" }")
  458. TEXT(" }")
  459. TEXT(" key = this.uuid();")
  460. TEXT(" this.callbacks[key] = {accept:callback, reject:callback, bIsOneShot:false};")
  461. TEXT(" return key;")
  462. TEXT("}, ")
  463. TEXT("registerPromise: function(accept, reject, name)")
  464. TEXT("{")
  465. TEXT(" var key = this.uuid();")
  466. TEXT(" this.callbacks[key] = {accept:accept, reject:reject, bIsOneShot:true, name:name};")
  467. TEXT(" return key;")
  468. TEXT("}, ")
  469. // strip ReturnValue object wrapper if present
  470. TEXT("returnValToObj: function(args)")
  471. TEXT("{")
  472. TEXT(" return Array.prototype.map.call(args, function(item){return item.ReturnValue || item});")
  473. TEXT("}, ")
  474. // invoke a callback method or promise by uuid
  475. TEXT("invokeCallback: function(key, bIsError, args)")
  476. TEXT("{")
  477. TEXT(" var callback = this.callbacks[key];")
  478. TEXT(" if (typeof callback === 'undefined')")
  479. TEXT(" {")
  480. TEXT(" console.error('Unknown callback id', key);")
  481. TEXT(" return;")
  482. TEXT(" }")
  483. TEXT(" if (callback.bIsOneShot)")
  484. TEXT(" {")
  485. TEXT(" callback.iwanttodeletethis=true;")
  486. TEXT(" delete this.callbacks[key];")
  487. TEXT(" }")
  488. TEXT(" callback[bIsError?'reject':'accept'].apply(window, this.returnValToObj(args));")
  489. TEXT("}, ")
  490. // convert an argument list to a dictionary of arguments.
  491. // The args argument must be an argument object as it uses the callee member to deduce the argument names
  492. TEXT("argsToDict: function(args)")
  493. TEXT("{")
  494. TEXT(" var res = {};")
  495. TEXT(" args.callee.toString().match(/\\((.+?)\\)/)[1].split(/\\s*,\\s*/).forEach(function(name, idx){res[name]=args[idx]});")
  496. TEXT(" return res;")
  497. TEXT("}, ")
  498. // encodes and sends a message to the host application
  499. TEXT("sendMessage: function()")
  500. TEXT("{")
  501. // @todo: Each kairos native browser will have a different way of passing a message out, here we use webkit postmessage but we'll need
  502. // to be aware of our target platform when generating this script and adjust accordingly
  503. TEXT(" var delimiter = '/';")
  504. #if PLATFORM_ANDROID
  505. TEXT(" if(window.JSBridge){")
  506. TEXT(" window.JSBridge.postMessage('', 'browserProxy', 'handlejs', Array.prototype.slice.call(arguments).join(delimiter));")
  507. TEXT(" }")
  508. #else
  509. TEXT(" if(window.webkit && window.webkit.messageHandlers && window.webkit.messageHandlers.browserProxy){")
  510. TEXT(" window.webkit.messageHandlers.browserProxy.postMessage(Array.prototype.slice.call(arguments).join(delimiter));")
  511. TEXT(" }")
  512. #endif
  513. TEXT("}, ")
  514. // custom replacer function passed into JSON.stringify to handle cases where there are function objects in the argument list
  515. // of the executeMethod call. In those cases we want to be able to pass them as callbacks.
  516. TEXT("customReplacer: function(key, value)")
  517. TEXT("{")
  518. TEXT(" if (typeof value === 'function')")
  519. TEXT(" {")
  520. TEXT(" return window.ue.$.registerCallback(value);")
  521. TEXT(" }")
  522. TEXT(" return value;")
  523. TEXT("},")
  524. // uses the above helper methods to execute a method on a uobject instance.
  525. // the method set as callee on args needs to be a named function, as the name of the method to invoke is taken from it
  526. TEXT("executeMethod: function(priority, id, args)")
  527. TEXT("{")
  528. TEXT(" var self = this;") // the closures need access to the outer this object
  529. // Create a promise object to return back to the caller and create a callback function to handle the response
  530. TEXT(" var promiseID;")
  531. TEXT(" var promise = new Promise(function (accept, reject) ")
  532. TEXT(" {")
  533. TEXT(" promiseID = self.registerPromise(accept, reject, args.callee.name)")
  534. TEXT(" });")
  535. // Actually invoke the method by sending a message to the host app
  536. TEXT(" this.sendMessage(priority, '") + NativeInterfaceFuncs::ExecuteMethodCommand + TEXT("', id, promiseID, args.callee.name, JSON.stringify(this.argsToDict(args), this.customReplacer));")
  537. // Return the promise object to the caller
  538. TEXT(" return promise;")
  539. TEXT("}")
  540. TEXT("},{callbacks: {value:{}}});")
  541. // Create the global window.ue variable
  542. TEXT("window.ue = Object.create({}, {'$': {writable: false, configurable:false, enumerable: false, value:util}});")
  543. TEXT("})();")
  544. ;
  545. return NativeScriptingInit;
  546. }
  547. void FNativeInterfaceJSScripting::PageLoaded()
  548. {
  549. // Expunge temporary objects.
  550. for (TMap<UObject*, ObjectBinding>::TIterator It(BoundObjects); It; ++It)
  551. {
  552. if (!It->Value.bIsPermanent)
  553. {
  554. It.RemoveCurrent();
  555. }
  556. }
  557. FString Script = GetInitializeScript();
  558. for(auto& Item : PermanentUObjectsByName)
  559. {
  560. Script.Append(*FString::Printf(TEXT("window.ue['%s'] = %s;"), *Item.Key.ReplaceCharWithEscapedChar(), *ConvertObject(Item.Value)));
  561. }
  562. // Append postinit for each object we added.
  563. for (auto& Item : PermanentUObjectsByName)
  564. {
  565. const FString& Name = Item.Key.ReplaceCharWithEscapedChar();
  566. Script.Append(_GetObjectPostInitScript(Name, FString::Printf(TEXT("window.ue['%s']"), *Name)));
  567. }
  568. // Append postinit for window.ue
  569. Script.Append(_GetObjectPostInitScript(TEXT("ue"), TEXT("window.ue")));
  570. bLoaded = true;
  571. ExecuteJavascript(Script);
  572. }
  573. FNativeInterfaceJSScripting::FNativeInterfaceJSScripting(bool bJSBindingToLoweringEnabled, TSharedRef<FNativeWebInterfaceBrowserProxy> Window)
  574. : FWebInterfaceJSScripting(bJSBindingToLoweringEnabled)
  575. , bLoaded(false)
  576. {
  577. WindowPtr = Window;
  578. }