WebInterfaceBrowserSingleton.cpp 34 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940
  1. // Engine/Source/Runtime/WebBrowser/Private/WebBrowserSingleton.cpp
  2. #include "WebInterfaceBrowserSingleton.h"
  3. #include "Misc/Paths.h"
  4. #include "GenericPlatform/GenericPlatformFile.h"
  5. #include "Misc/CommandLine.h"
  6. #include "Misc/ConfigCacheIni.h"
  7. #include "Internationalization/Culture.h"
  8. #include "Misc/App.h"
  9. #include "WebInterfaceBrowserModule.h"
  10. #include "Misc/EngineVersion.h"
  11. #include "Framework/Application/SlateApplication.h"
  12. #include "IWebInterfaceBrowserCookieManager.h"
  13. #include "WebInterfaceBrowserLog.h"
  14. #if PLATFORM_WINDOWS
  15. #include "Windows/WindowsHWrapper.h"
  16. #endif
  17. #if WITH_CEF3
  18. #include "Misc/ScopeLock.h"
  19. #include "Async/Async.h"
  20. #include "HAL/PlatformApplicationMisc.h"
  21. #include "CEF/CEFInterfaceBrowserApp.h"
  22. #include "CEF/CEFInterfaceBrowserHandler.h"
  23. #include "CEF/CEFWebInterfaceBrowserWindow.h"
  24. #include "CEF/CEFInterfaceSchemeHandler.h"
  25. #include "CEF/CEFInterfaceResourceContextHandler.h"
  26. #include "CEF/CEFInterfaceBrowserClosureTask.h"
  27. # if PLATFORM_WINDOWS
  28. # include "Windows/AllowWindowsPlatformTypes.h"
  29. # endif
  30. # pragma push_macro("OVERRIDE")
  31. # undef OVERRIDE // cef headers provide their own OVERRIDE macro
  32. THIRD_PARTY_INCLUDES_START
  33. #if PLATFORM_APPLE
  34. PRAGMA_DISABLE_DEPRECATION_WARNINGS
  35. #endif
  36. # include "include/cef_app.h"
  37. # include "include/cef_version.h"
  38. #if PLATFORM_APPLE
  39. PRAGMA_ENABLE_DEPRECATION_WARNINGS
  40. #endif
  41. THIRD_PARTY_INCLUDES_END
  42. # pragma pop_macro("OVERRIDE")
  43. # if PLATFORM_WINDOWS
  44. # include "Windows/HideWindowsPlatformTypes.h"
  45. # endif
  46. #endif
  47. #if BUILD_EMBEDDED_APP
  48. # include "Native/NativeWebInterfaceBrowserProxy.h"
  49. #endif
  50. // Define some platform-dependent file locations
  51. #if WITH_CEF3
  52. # define CEF3_BIN_DIR TEXT("Binaries/ThirdParty/CEF3")
  53. # if PLATFORM_WINDOWS && PLATFORM_64BITS
  54. # define CEF3_RESOURCES_DIR CEF3_BIN_DIR TEXT("/Win64/Resources")
  55. # define CEF3_SUBPROCES_EXE TEXT("Binaries/Win64/EpicWebHelper.exe")
  56. # elif PLATFORM_WINDOWS && PLATFORM_32BITS
  57. # define CEF3_RESOURCES_DIR CEF3_BIN_DIR TEXT("/Win32/Resources")
  58. # define CEF3_SUBPROCES_EXE TEXT("Binaries/Win32/EpicWebHelper.exe")
  59. # elif PLATFORM_MAC
  60. # if PLATFORM_MAC_ARM64
  61. # define CEF3_FRAMEWORK_DIR CEF3_BIN_DIR TEXT("/Mac/Chromium Embedded Framework arm64.framework")
  62. # else
  63. # define CEF3_FRAMEWORK_DIR CEF3_BIN_DIR TEXT("/Mac/Chromium Embedded Framework x86.framework")
  64. # endif
  65. # define CEF3_RESOURCES_DIR CEF3_FRAMEWORK_DIR TEXT("/Resources")
  66. # define CEF3_SUBPROCES_EXE TEXT("Binaries/Mac/EpicWebHelper.app/Contents/MacOS/EpicWebHelper")
  67. # elif PLATFORM_LINUX // @todo Linux
  68. # define CEF3_RESOURCES_DIR CEF3_BIN_DIR TEXT("/Linux/Resources")
  69. # define CEF3_SUBPROCES_EXE TEXT("Binaries/Linux/EpicWebHelper")
  70. # endif
  71. // Caching is enabled by default.
  72. # ifndef CEF3_DEFAULT_CACHE
  73. # define CEF3_DEFAULT_CACHE 1
  74. # endif
  75. #endif
  76. FString FWebInterfaceBrowserSingleton::ApplicationCacheDir() const
  77. {
  78. #if PLATFORM_MAC
  79. // OSX wants caches in a separate location from other app data
  80. static TCHAR Result[MAC_MAX_PATH] = TEXT("");
  81. if (!Result[0])
  82. {
  83. SCOPED_AUTORELEASE_POOL;
  84. NSString *CacheBaseDir = [NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES) objectAtIndex: 0];
  85. NSString* BundleID = [[NSBundle mainBundle] bundleIdentifier];
  86. if(!BundleID)
  87. {
  88. BundleID = [[NSProcessInfo processInfo] processName];
  89. }
  90. check(BundleID);
  91. NSString* AppCacheDir = [CacheBaseDir stringByAppendingPathComponent: BundleID];
  92. FPlatformString::CFStringToTCHAR((CFStringRef)AppCacheDir, Result);
  93. }
  94. return FString(Result);
  95. #else
  96. // Other platforms use the application data directory
  97. return FPaths::ProjectSavedDir();
  98. #endif
  99. }
  100. class FWebInterfaceBrowserWindowFactory
  101. : public IWebInterfaceBrowserWindowFactory
  102. {
  103. public:
  104. virtual ~FWebInterfaceBrowserWindowFactory()
  105. { }
  106. virtual TSharedPtr<IWebInterfaceBrowserWindow> Create(
  107. TSharedPtr<FCEFWebInterfaceBrowserWindow>& BrowserWindowParent,
  108. TSharedPtr<FWebInterfaceBrowserWindowInfo>& BrowserWindowInfo) override
  109. {
  110. return IWebInterfaceBrowserModule::Get().GetSingleton()->CreateBrowserWindow(
  111. BrowserWindowParent,
  112. BrowserWindowInfo);
  113. }
  114. virtual TSharedPtr<IWebInterfaceBrowserWindow> Create(
  115. void* OSWindowHandle,
  116. FString InitialURL,
  117. bool bUseTransparency,
  118. bool bThumbMouseButtonNavigation,
  119. bool bInterceptLoadRequests = false,
  120. TOptional<FString> ContentsToLoad = TOptional<FString>(),
  121. bool ShowErrorMessage = true,
  122. FColor BackgroundColor = FColor(255, 255, 255, 255)) override
  123. {
  124. FCreateInterfaceBrowserWindowSettings Settings;
  125. Settings.OSWindowHandle = OSWindowHandle;
  126. Settings.InitialURL = MoveTemp(InitialURL);
  127. Settings.bUseTransparency = bUseTransparency;
  128. Settings.bThumbMouseButtonNavigation = bThumbMouseButtonNavigation;
  129. Settings.ContentsToLoad = MoveTemp(ContentsToLoad);
  130. Settings.bShowErrorMessage = ShowErrorMessage;
  131. Settings.BackgroundColor = BackgroundColor;
  132. Settings.bInterceptLoadRequests = bInterceptLoadRequests;
  133. return IWebInterfaceBrowserModule::Get().GetSingleton()->CreateBrowserWindow(Settings);
  134. }
  135. };
  136. class FNoWebInterfaceBrowserWindowFactory
  137. : public IWebInterfaceBrowserWindowFactory
  138. {
  139. public:
  140. virtual ~FNoWebInterfaceBrowserWindowFactory()
  141. { }
  142. virtual TSharedPtr<IWebInterfaceBrowserWindow> Create(
  143. TSharedPtr<FCEFWebInterfaceBrowserWindow>& BrowserWindowParent,
  144. TSharedPtr<FWebInterfaceBrowserWindowInfo>& BrowserWindowInfo) override
  145. {
  146. return nullptr;
  147. }
  148. virtual TSharedPtr<IWebInterfaceBrowserWindow> Create(
  149. void* OSWindowHandle,
  150. FString InitialURL,
  151. bool bUseTransparency,
  152. bool bThumbMouseButtonNavigation,
  153. bool bInterceptLoadRequests = false,
  154. TOptional<FString> ContentsToLoad = TOptional<FString>(),
  155. bool ShowErrorMessage = true,
  156. FColor BackgroundColor = FColor(255, 255, 255, 255)) override
  157. {
  158. return nullptr;
  159. }
  160. };
  161. #if WITH_CEF3
  162. #if PLATFORM_MAC || PLATFORM_LINUX
  163. class FInterfacePosixSignalPreserver
  164. {
  165. public:
  166. FInterfacePosixSignalPreserver()
  167. {
  168. struct sigaction Sigact;
  169. for (uint32 i = 0; i < UE_ARRAY_COUNT(PreserveSignals); ++i)
  170. {
  171. FMemory::Memset(&Sigact, 0, sizeof(Sigact));
  172. if (sigaction(PreserveSignals[i], nullptr, &Sigact) != 0)
  173. {
  174. UE_LOG(LogWebInterfaceBrowser, Warning, TEXT("Failed to backup signal handler for %i."), PreserveSignals[i]);
  175. }
  176. OriginalSignalHandlers[i] = Sigact;
  177. }
  178. }
  179. ~FInterfacePosixSignalPreserver()
  180. {
  181. for (uint32 i = 0; i < UE_ARRAY_COUNT(PreserveSignals); ++i)
  182. {
  183. if(sigaction(PreserveSignals[i], &OriginalSignalHandlers[i], nullptr) != 0)
  184. {
  185. UE_LOG(LogWebInterfaceBrowser, Warning, TEXT("Failed to restore signal handler for %i."), PreserveSignals[i]);
  186. }
  187. }
  188. }
  189. private:
  190. // Backup the list of signals that CEF/Chromium overrides, derived from SetupSignalHandlers() in
  191. // https://chromium.googlesource.com/chromium/src.git/+/2fc330d0b93d4bfd7bd04b9fdd3102e529901f91/services/service_manager/embedder/main.cc
  192. const int PreserveSignals[13] = {SIGHUP, SIGINT, SIGQUIT, SIGILL, SIGABRT,
  193. SIGFPE, SIGSEGV, SIGALRM, SIGTERM, SIGCHLD, SIGBUS, SIGTRAP, SIGPIPE};
  194. struct sigaction OriginalSignalHandlers[UE_ARRAY_COUNT(PreserveSignals)];
  195. };
  196. #endif // PLATFORM_MAC || PLATFORM_LINUX
  197. #endif // WITH_CEF3
  198. FWebInterfaceBrowserSingleton::FWebInterfaceBrowserSingleton(const FWebInterfaceBrowserInitSettings& WebBrowserInitSettings)
  199. #if WITH_CEF3
  200. : WebBrowserWindowFactory(MakeShareable(new FWebInterfaceBrowserWindowFactory()))
  201. #else
  202. : WebBrowserWindowFactory(MakeShareable(new FNoWebInterfaceBrowserWindowFactory()))
  203. #endif
  204. , bDevToolsShortcutEnabled(UE_BUILD_DEVELOPMENT || UE_BUILD_DEBUG)
  205. , bJSBindingsToLoweringEnabled(true)
  206. , bAppIsFocused(false)
  207. #if WITH_CEF3
  208. , bCEFInitialized(false)
  209. #endif
  210. , DefaultMaterial(nullptr)
  211. , DefaultTranslucentMaterial(nullptr)
  212. {
  213. #if WITH_CEF3
  214. // Only enable CEF if we have CEF3, we are not running a commandlet without rendering (e.g. cooking assets) and it has not been explicitly disabled
  215. // Disallow CEF if we never plan on rendering, ie, with CanEverRender. This includes servers
  216. bAllowCEF = (!IsRunningCommandlet() || (IsAllowCommandletRendering() && FParse::Param(FCommandLine::Get(), TEXT("AllowCommandletCEF")))) &&
  217. FApp::CanEverRender() && !FParse::Param(FCommandLine::Get(), TEXT("nocef"));
  218. if (bAllowCEF)
  219. {
  220. // The FWebBrowserSingleton must be initialized on the game thread
  221. check(IsInGameThread());
  222. // Provide CEF with command-line arguments.
  223. #if PLATFORM_WINDOWS
  224. CefMainArgs MainArgs(hInstance);
  225. #else
  226. CefMainArgs MainArgs;
  227. #endif
  228. // Enable high-DPI support early in CEF startup. For this to work it also depends
  229. // on FPlatformApplicationMisc::SetHighDPIMode() being called already which should happen by default
  230. CefEnableHighDPISupport();
  231. bool bWebGL = FParse::Param(FCommandLine::Get(), TEXT("webgl"));
  232. bool bVerboseLogging = FParse::Param(FCommandLine::Get(), TEXT("cefverbose")) || FParse::Param(FCommandLine::Get(), TEXT("debuglog"));
  233. // CEFBrowserApp implements application-level callbacks.
  234. CEFBrowserApp = new FCEFInterfaceBrowserApp(bWebGL);
  235. // Specify CEF global settings here.
  236. CefSettings Settings;
  237. Settings.no_sandbox = true;
  238. Settings.command_line_args_disabled = true;
  239. Settings.external_message_pump = true;
  240. //@todo change to threaded version instead of using external_message_pump & OnScheduleMessagePumpWork
  241. Settings.multi_threaded_message_loop = false;
  242. //Set the default background for browsers to be opaque black, this is used for windowed (not OSR) browsers
  243. // setting it black here prevents the white flash on load
  244. Settings.background_color = CefColorSetARGB(255, 0, 0, 0);
  245. #if PLATFORM_LINUX
  246. Settings.windowless_rendering_enabled = true;
  247. #endif
  248. FString CefLogFile(FPaths::Combine(*FPaths::ProjectLogDir(), TEXT("cef3.log")));
  249. CefLogFile = FPaths::ConvertRelativePathToFull(CefLogFile);
  250. CefString(&Settings.log_file) = TCHAR_TO_WCHAR(*CefLogFile);
  251. Settings.log_severity = bVerboseLogging ? LOGSEVERITY_VERBOSE : LOGSEVERITY_WARNING;
  252. uint16 DebugPort;
  253. if(FParse::Value(FCommandLine::Get(), TEXT("cefdebug="), DebugPort))
  254. {
  255. Settings.remote_debugging_port = DebugPort;
  256. }
  257. // Specify locale from our settings
  258. FString LocaleCode = GetCurrentLocaleCode();
  259. CefString(&Settings.locale) = TCHAR_TO_WCHAR(*LocaleCode);
  260. // Append engine version to the user agent string.
  261. CefString(&Settings.user_agent_product) = TCHAR_TO_WCHAR(*WebBrowserInitSettings.ProductVersion);
  262. #if CEF3_DEFAULT_CACHE
  263. // Enable on disk cache
  264. FString CachePath(FPaths::Combine(ApplicationCacheDir(), TEXT("webcache")));
  265. CachePath = FPaths::ConvertRelativePathToFull(GenerateWebCacheFolderName(CachePath));
  266. CefString(&Settings.cache_path) = TCHAR_TO_WCHAR(*CachePath);
  267. #endif
  268. // Specify path to resources
  269. FString ResourcesPath(FPaths::Combine(*FPaths::EngineDir(), CEF3_RESOURCES_DIR));
  270. ResourcesPath = FPaths::ConvertRelativePathToFull(ResourcesPath);
  271. if (!FPaths::DirectoryExists(ResourcesPath))
  272. {
  273. UE_LOG(LogWebInterfaceBrowser, Error, TEXT("Chromium Resources information not found at: %s."), *ResourcesPath);
  274. }
  275. CefString(&Settings.resources_dir_path) = TCHAR_TO_WCHAR(*ResourcesPath);
  276. #if !PLATFORM_MAC
  277. // On Mac Chromium ignores custom locales dir. Files need to be stored in Resources folder in the app bundle
  278. FString LocalesPath(FPaths::Combine(*ResourcesPath, TEXT("locales")));
  279. LocalesPath = FPaths::ConvertRelativePathToFull(LocalesPath);
  280. if (!FPaths::DirectoryExists(LocalesPath))
  281. {
  282. UE_LOG(LogWebInterfaceBrowser, Error, TEXT("Chromium Locales information not found at: %s."), *LocalesPath);
  283. }
  284. CefString(&Settings.locales_dir_path) = TCHAR_TO_WCHAR(*LocalesPath);
  285. #else
  286. // LocaleCode may contain region, which for some languages may make CEF unable to find the locale pak files
  287. // In that case use the language name for CEF locale
  288. FString LocalePakPath = ResourcesPath + TEXT("/") + LocaleCode.Replace(TEXT("-"), TEXT("_")) + TEXT(".lproj/locale.pak");
  289. if (!FPaths::FileExists(LocalePakPath))
  290. {
  291. FCultureRef Culture = FInternationalization::Get().GetCurrentCulture();
  292. LocaleCode = Culture->GetTwoLetterISOLanguageName();
  293. LocalePakPath = ResourcesPath + TEXT("/") + LocaleCode + TEXT(".lproj/locale.pak");
  294. if (FPaths::FileExists(LocalePakPath))
  295. {
  296. CefString(&Settings.locale) = TCHAR_TO_WCHAR(*LocaleCode);
  297. }
  298. }
  299. // Let CEF know where we have put the framework bundle as it is non-default
  300. FString CefFrameworkPath(FPaths::Combine(*FPaths::EngineDir(), CEF3_FRAMEWORK_DIR));
  301. CefFrameworkPath = FPaths::ConvertRelativePathToFull(CefFrameworkPath);
  302. CefString(&Settings.framework_dir_path) = TCHAR_TO_WCHAR(*CefFrameworkPath);
  303. CefString(&Settings.main_bundle_path) = TCHAR_TO_WCHAR(*CefFrameworkPath);
  304. #endif
  305. // Specify path to sub process exe
  306. FString SubProcessPath(FPaths::Combine(*FPaths::EngineDir(), CEF3_SUBPROCES_EXE));
  307. SubProcessPath = FPaths::ConvertRelativePathToFull(SubProcessPath);
  308. if (!IPlatformFile::GetPlatformPhysical().FileExists(*SubProcessPath))
  309. {
  310. UE_LOG(LogWebInterfaceBrowser, Error, TEXT("EpicWebHelper.exe not found, check that this program has been built and is placed in: %s."), *SubProcessPath);
  311. }
  312. CefString(&Settings.browser_subprocess_path) = TCHAR_TO_WCHAR(*SubProcessPath);
  313. #if PLATFORM_MAC || PLATFORM_LINUX
  314. // this class automatically preserves the sigaction handlers we have set
  315. FInterfacePosixSignalPreserver PosixSignalPreserver;
  316. #endif
  317. // Initialize CEF.
  318. bCEFInitialized = CefInitialize(MainArgs, Settings, CEFBrowserApp.get(), nullptr);
  319. check(bCEFInitialized);
  320. // Set the thread name back to GameThread.
  321. FPlatformProcess::SetThreadName(*FName(NAME_GameThread).GetPlainNameString());
  322. DefaultCookieManager = FCefWebInterfaceCookieManagerFactory::Create(CefCookieManager::GetGlobalManager(nullptr));
  323. }
  324. #endif
  325. }
  326. #if WITH_CEF3
  327. void FWebInterfaceBrowserSingleton::WaitForTaskQueueFlush()
  328. {
  329. // Keep pumping messages until we see the one below clear the queue
  330. bTaskFinished = false;
  331. CefPostTask(TID_UI, new FCEFInterfaceBrowserClosureTask(nullptr, [=]()
  332. {
  333. bTaskFinished = true;
  334. }));
  335. const double StartWaitAppTime = FPlatformTime::Seconds();
  336. while (!bTaskFinished)
  337. {
  338. FPlatformProcess::Sleep(0.01);
  339. // CEF needs the windows message pump run to be able to finish closing a browser, so run it manually here
  340. if (FSlateApplication::IsInitialized())
  341. {
  342. FSlateApplication::Get().PumpMessages();
  343. }
  344. CefDoMessageLoopWork();
  345. // Wait at most 1 second for tasks to clear, in case CEF crashes/hangs during process lifetime
  346. if (FPlatformTime::Seconds() - StartWaitAppTime > 1.0f)
  347. {
  348. break; // don't spin forever
  349. }
  350. }
  351. }
  352. #endif
  353. FWebInterfaceBrowserSingleton::~FWebInterfaceBrowserSingleton()
  354. {
  355. #if WITH_CEF3
  356. if (!bCEFInitialized)
  357. return; // CEF failed to init so don't crash trying to shut it down
  358. if (bAllowCEF)
  359. {
  360. {
  361. FScopeLock Lock(&WindowInterfacesCS);
  362. // Force all existing browsers to close in case any haven't been deleted
  363. for (int32 Index = 0; Index < WindowInterfaces.Num(); ++Index)
  364. {
  365. auto BrowserWindow = WindowInterfaces[Index].Pin();
  366. if (BrowserWindow.IsValid() && BrowserWindow->IsValid())
  367. {
  368. // Call CloseBrowser directly on the Host object as FWebBrowserWindow::CloseBrowser is delayed
  369. BrowserWindow->InternalCefBrowser->GetHost()->CloseBrowser(true);
  370. }
  371. }
  372. // Clear this before CefShutdown() below
  373. WindowInterfaces.Reset();
  374. }
  375. // Remove references to the scheme handler factories
  376. CefClearSchemeHandlerFactories();
  377. for (const TPair<FString, CefRefPtr<CefRequestContext>>& RequestContextPair : RequestContexts)
  378. {
  379. RequestContextPair.Value->ClearSchemeHandlerFactories();
  380. }
  381. // Clear this before CefShutdown() below
  382. RequestContexts.Reset();
  383. // make sure any handler before load delegates are unbound
  384. for (const TPair <FString,CefRefPtr<FCEFInterfaceResourceContextHandler>>& HandlerPair : RequestResourceHandlers)
  385. {
  386. HandlerPair.Value->OnBeforeLoad().Unbind();
  387. }
  388. // Clear this before CefShutdown() below
  389. RequestResourceHandlers.Reset();
  390. // CefRefPtr takes care of delete
  391. CEFBrowserApp = nullptr;
  392. WaitForTaskQueueFlush();
  393. // Shut down CEF.
  394. CefShutdown();
  395. }
  396. bCEFInitialized = false;
  397. #endif
  398. }
  399. TSharedRef<IWebInterfaceBrowserWindowFactory> FWebInterfaceBrowserSingleton::GetWebBrowserWindowFactory() const
  400. {
  401. return WebBrowserWindowFactory;
  402. }
  403. TSharedPtr<IWebInterfaceBrowserWindow> FWebInterfaceBrowserSingleton::CreateBrowserWindow(
  404. TSharedPtr<FCEFWebInterfaceBrowserWindow>& BrowserWindowParent,
  405. TSharedPtr<FWebInterfaceBrowserWindowInfo>& BrowserWindowInfo
  406. )
  407. {
  408. #if WITH_CEF3
  409. if (bAllowCEF)
  410. {
  411. TOptional<FString> ContentsToLoad;
  412. bool bShowErrorMessage = BrowserWindowParent->IsShowingErrorMessages();
  413. bool bThumbMouseButtonNavigation = BrowserWindowParent->IsThumbMouseButtonNavigationEnabled();
  414. bool bUseTransparency = BrowserWindowParent->UseTransparency();
  415. bool bUsingAcceleratedPaint = BrowserWindowParent->UsingAcceleratedPaint();
  416. bool bUseNativeCursors = BrowserWindowParent->UseNativeCursors();
  417. FString InitialURL = WCHAR_TO_TCHAR(BrowserWindowInfo->Browser->GetMainFrame()->GetURL().ToWString().c_str());
  418. TSharedPtr<FCEFWebInterfaceBrowserWindow> NewBrowserWindow(new FCEFWebInterfaceBrowserWindow(BrowserWindowInfo->Browser, BrowserWindowInfo->Handler, InitialURL, ContentsToLoad, bShowErrorMessage, bThumbMouseButtonNavigation, bUseTransparency, bUseNativeCursors, bJSBindingsToLoweringEnabled, bUsingAcceleratedPaint));
  419. BrowserWindowInfo->Handler->SetBrowserWindow(NewBrowserWindow);
  420. {
  421. FScopeLock Lock(&WindowInterfacesCS);
  422. WindowInterfaces.Add(NewBrowserWindow);
  423. }
  424. NewBrowserWindow->GetCefBrowser()->GetHost()->SetWindowlessFrameRate(BrowserWindowParent->GetCefBrowser()->GetHost()->GetWindowlessFrameRate());
  425. return NewBrowserWindow;
  426. }
  427. #endif
  428. return nullptr;
  429. }
  430. TSharedPtr<IWebInterfaceBrowserWindow> FWebInterfaceBrowserSingleton::CreateBrowserWindow(const FCreateInterfaceBrowserWindowSettings& WindowSettings)
  431. {
  432. bool bBrowserEnabled = true;
  433. GConfig->GetBool(TEXT("Browser"), TEXT("bEnabled"), bBrowserEnabled, GEngineIni);
  434. if (!bBrowserEnabled || !FApp::CanEverRender())
  435. {
  436. return nullptr;
  437. }
  438. #if WITH_CEF3
  439. if (bAllowCEF)
  440. {
  441. // Information used when creating the native window.
  442. CefWindowInfo WindowInfo;
  443. // Specify CEF browser settings here.
  444. CefBrowserSettings BrowserSettings;
  445. // The color to paint before a document is loaded
  446. // if using a windowed(native) browser window AND bUseTransparency is true then the background actually uses Settings.background_color from above
  447. // if using a OSR window and bUseTransparency is true then you get a transparency channel in your BGRA OnPaint
  448. // if bUseTransparency is false then you get the background color defined by your RGB setting here
  449. BrowserSettings.background_color = CefColorSetARGB(WindowSettings.bUseTransparency ? 0 : WindowSettings.BackgroundColor.A, WindowSettings.BackgroundColor.R, WindowSettings.BackgroundColor.G, WindowSettings.BackgroundColor.B);
  450. // Disable plugins
  451. BrowserSettings.plugins = STATE_DISABLED;
  452. #if PLATFORM_WINDOWS
  453. // Create the widget as a child window on windows when passing in a parent window
  454. if (WindowSettings.OSWindowHandle != nullptr)
  455. {
  456. RECT ClientRect = { 0, 0, 0, 0 };
  457. if (!GetClientRect((HWND)WindowSettings.OSWindowHandle, &ClientRect))
  458. {
  459. UE_LOG(LogWebInterfaceBrowser, Error, TEXT("Failed to get client rect"));
  460. }
  461. WindowInfo.SetAsChild((CefWindowHandle)WindowSettings.OSWindowHandle, ClientRect);
  462. }
  463. else
  464. #endif
  465. {
  466. // Use off screen rendering so we can integrate with our windows
  467. WindowInfo.SetAsWindowless(kNullWindowHandle);
  468. WindowInfo.shared_texture_enabled = WindowSettings.bAcceleratedPaint && FCEFWebInterfaceBrowserWindow::CanSupportAcceleratedPaint() ? 1 : 0;
  469. BrowserSettings.windowless_frame_rate = WindowSettings.BrowserFrameRate;
  470. }
  471. TArray<FString> AuthorizationHeaderAllowListURLS;
  472. GConfig->GetArray(TEXT("Browser"), TEXT("AuthorizationHeaderAllowListURLS"), AuthorizationHeaderAllowListURLS, GEngineIni);
  473. // WebBrowserHandler implements browser-level callbacks.
  474. CefRefPtr<FCEFInterfaceBrowserHandler> NewHandler(new FCEFInterfaceBrowserHandler(WindowSettings.bUseTransparency, WindowSettings.bInterceptLoadRequests ,WindowSettings.AltRetryDomains, AuthorizationHeaderAllowListURLS));
  475. CefRefPtr<CefRequestContext> RequestContext = nullptr;
  476. if (WindowSettings.Context.IsSet())
  477. {
  478. const FInterfaceBrowserContextSettings Context = WindowSettings.Context.GetValue();
  479. const CefRefPtr<CefRequestContext>* ExistingRequestContext = RequestContexts.Find(Context.Id);
  480. if (ExistingRequestContext == nullptr)
  481. {
  482. CefRequestContextSettings RequestContextSettings;
  483. CefString(&RequestContextSettings.accept_language_list) = Context.AcceptLanguageList.IsEmpty() ? TCHAR_TO_WCHAR(*GetCurrentLocaleCode()) : TCHAR_TO_WCHAR(*Context.AcceptLanguageList);
  484. CefString(&RequestContextSettings.cache_path) = TCHAR_TO_WCHAR(*GenerateWebCacheFolderName(Context.CookieStorageLocation));
  485. RequestContextSettings.persist_session_cookies = Context.bPersistSessionCookies;
  486. RequestContextSettings.ignore_certificate_errors = Context.bIgnoreCertificateErrors;
  487. CefRefPtr<FCEFInterfaceResourceContextHandler> ResourceContextHandler = new FCEFInterfaceResourceContextHandler(this);
  488. ResourceContextHandler->OnBeforeLoad() = Context.OnBeforeContextResourceLoad;
  489. RequestResourceHandlers.Add(Context.Id, ResourceContextHandler);
  490. //Create a new one
  491. RequestContext = CefRequestContext::CreateContext(RequestContextSettings, ResourceContextHandler);
  492. RequestContexts.Add(Context.Id, RequestContext);
  493. }
  494. else
  495. {
  496. RequestContext = *ExistingRequestContext;
  497. }
  498. SchemeHandlerFactories.RegisterFactoriesWith(RequestContext);
  499. UE_LOG(LogWebInterfaceBrowser, Log, TEXT("Creating browser for ContextId=%s."), *WindowSettings.Context.GetValue().Id);
  500. }
  501. if (RequestContext == nullptr)
  502. {
  503. // As of CEF drop 4430 the CreateBrowserSync call requires a non-null request context, so fall back to the default one if needed
  504. RequestContext = CefRequestContext::GetGlobalContext();
  505. }
  506. // Create the CEF browser window.
  507. CefRefPtr<CefBrowser> Browser = CefBrowserHost::CreateBrowserSync(WindowInfo, NewHandler.get(), TCHAR_TO_WCHAR(*WindowSettings.InitialURL), BrowserSettings, nullptr, RequestContext);
  508. if (Browser.get())
  509. {
  510. // Create new window
  511. TSharedPtr<FCEFWebInterfaceBrowserWindow> NewBrowserWindow = MakeShareable(new FCEFWebInterfaceBrowserWindow(
  512. Browser,
  513. NewHandler,
  514. WindowSettings.InitialURL,
  515. WindowSettings.ContentsToLoad,
  516. WindowSettings.bShowErrorMessage,
  517. WindowSettings.bThumbMouseButtonNavigation,
  518. WindowSettings.bUseTransparency,
  519. WindowSettings.bUseNativeCursors,
  520. bJSBindingsToLoweringEnabled,
  521. WindowInfo.shared_texture_enabled == 1 ? true : false));
  522. NewHandler->SetBrowserWindow(NewBrowserWindow);
  523. {
  524. FScopeLock Lock(&WindowInterfacesCS);
  525. WindowInterfaces.Add(NewBrowserWindow);
  526. }
  527. return NewBrowserWindow;
  528. }
  529. }
  530. #endif
  531. return nullptr;
  532. }
  533. #if BUILD_EMBEDDED_APP
  534. TSharedPtr<IWebInterfaceBrowserWindow> FWebInterfaceBrowserSingleton::CreateNativeBrowserProxy()
  535. {
  536. TSharedPtr<FNativeWebInterfaceBrowserProxy> NewBrowserWindow = MakeShareable(new FNativeWebInterfaceBrowserProxy(
  537. bJSBindingsToLoweringEnabled
  538. ));
  539. NewBrowserWindow->Initialize();
  540. return NewBrowserWindow;
  541. }
  542. #endif //BUILD_EMBEDDED_APP
  543. bool FWebInterfaceBrowserSingleton::Tick(float DeltaTime)
  544. {
  545. QUICK_SCOPE_CYCLE_COUNTER(STAT_FWebInterfaceBrowserSingleton_Tick);
  546. #if WITH_CEF3
  547. if (bAllowCEF)
  548. {
  549. {
  550. FScopeLock Lock(&WindowInterfacesCS);
  551. bool bIsSlateAwake = FSlateApplication::IsInitialized() && !FSlateApplication::Get().IsSlateAsleep();
  552. // Remove any windows that have been deleted and check whether it's currently visible
  553. for (int32 Index = WindowInterfaces.Num() - 1; Index >= 0; --Index)
  554. {
  555. if (!WindowInterfaces[Index].IsValid())
  556. {
  557. WindowInterfaces.RemoveAt(Index);
  558. }
  559. else if (bIsSlateAwake) // only check for Tick activity if Slate is currently ticking
  560. {
  561. TSharedPtr<FCEFWebInterfaceBrowserWindow> BrowserWindow = WindowInterfaces[Index].Pin();
  562. if(BrowserWindow.IsValid())
  563. {
  564. // Test if we've ticked recently. If not assume the browser window has become hidden.
  565. BrowserWindow->CheckTickActivity();
  566. }
  567. }
  568. }
  569. }
  570. if (CEFBrowserApp != nullptr)
  571. {
  572. bool bForceMessageLoop = false;
  573. GConfig->GetBool(TEXT("Browser"), TEXT("bForceMessageLoop"), bForceMessageLoop, GEngineIni);
  574. // Get the configured minimum hertz and make sure the value is within a reasonable range
  575. static const int MaxFrameRateClamp = 60;
  576. int32 MinMessageLoopHz = 1;
  577. GConfig->GetInt(TEXT("Browser"), TEXT("MinMessageLoopHertz"), MinMessageLoopHz, GEngineIni);
  578. MinMessageLoopHz = FMath::Clamp(MinMessageLoopHz, 1, 60);
  579. // Get the configured forced maximum hertz and make sure the value is within a reasonable range
  580. int32 MaxForcedMessageLoopHz = 15;
  581. GConfig->GetInt(TEXT("Browser"), TEXT("MaxForcedMessageLoopHertz"), MaxForcedMessageLoopHz, GEngineIni);
  582. MaxForcedMessageLoopHz = FMath::Clamp(MaxForcedMessageLoopHz, MinMessageLoopHz, 60);
  583. // @todo: Hack: We rely on OnScheduleMessagePumpWork() which tells us to drive the CEF message pump,
  584. // there appear to be some edge cases where we might not be getting a signal from it so for the time being
  585. // we force a minimum rates here and let it run at a configurable maximum rate when we have any WindowInterfaces.
  586. // Convert to seconds which we'll use to compare against the time we accumulated since last pump / left till next pump
  587. float MinMessageLoopSeconds = 1.0f / MinMessageLoopHz;
  588. float MaxForcedMessageLoopSeconds = 1.0f / MaxForcedMessageLoopHz;
  589. static float SecondsSinceLastPump = 0;
  590. static float SecondsSinceLastAppFocusCheck = MaxForcedMessageLoopSeconds;
  591. static float SecondsToNextForcedPump = MaxForcedMessageLoopSeconds;
  592. // Accumulate time since last pump by adding DeltaTime which gives us the amount of time that has passed since last tick in seconds
  593. SecondsSinceLastPump += DeltaTime;
  594. SecondsSinceLastAppFocusCheck += DeltaTime;
  595. // Time left till next pump
  596. SecondsToNextForcedPump -= DeltaTime;
  597. bool bWantForce = bForceMessageLoop; // True if we wish to force message pump
  598. bool bCanForce = SecondsToNextForcedPump <= 0; // But can we?
  599. bool bMustForce = SecondsSinceLastPump >= MinMessageLoopSeconds; // Absolutely must force (Min frequency rate hit)
  600. if (SecondsSinceLastAppFocusCheck > MinMessageLoopSeconds && WindowInterfaces.Num() > 0)
  601. {
  602. SecondsSinceLastAppFocusCheck = 0;
  603. // only check app being foreground at the min message loop rate (1hz) and if we have a browser window to save CPU
  604. bAppIsFocused = FPlatformApplicationMisc::IsThisApplicationForeground();
  605. }
  606. // NOTE - bAppIsFocused could be stale if WindowInterfaces.Num() == 0
  607. bool bAppIsFocusedAndWebWindows = WindowInterfaces.Num() > 0 && bAppIsFocused;
  608. // if we won't force AND are the foreground OS app AND we have windows created see if any are visible (not minimized) right now
  609. if (bWantForce == false && bMustForce == false && bAppIsFocusedAndWebWindows == true )
  610. {
  611. for (int32 Index = 0; Index < WindowInterfaces.Num(); Index++)
  612. {
  613. if (WindowInterfaces[Index].IsValid())
  614. {
  615. TSharedPtr<FCEFWebInterfaceBrowserWindow> BrowserWindow = WindowInterfaces[Index].Pin();
  616. if (BrowserWindow->GetParentWindow().IsValid())
  617. {
  618. TSharedPtr<SWindow> BrowserParentWindow = BrowserWindow->GetParentWindow();
  619. if (!BrowserParentWindow->IsWindowMinimized())
  620. {
  621. bWantForce = true;
  622. }
  623. }
  624. }
  625. }
  626. }
  627. // tick the CEF app to determine when to run CefDoMessageLoopWork
  628. if (CEFBrowserApp->TickMessagePump(DeltaTime, (bWantForce && bCanForce) || bMustForce))
  629. {
  630. SecondsSinceLastPump = 0;
  631. SecondsToNextForcedPump = MaxForcedMessageLoopSeconds;
  632. }
  633. }
  634. // Update video buffering for any windows that need it
  635. for (int32 Index = 0; Index < WindowInterfaces.Num(); Index++)
  636. {
  637. if (WindowInterfaces[Index].IsValid())
  638. {
  639. TSharedPtr<FCEFWebInterfaceBrowserWindow> BrowserWindow = WindowInterfaces[Index].Pin();
  640. if (BrowserWindow.IsValid())
  641. {
  642. BrowserWindow->UpdateVideoBuffering();
  643. }
  644. }
  645. }
  646. }
  647. #endif
  648. return true;
  649. }
  650. FString FWebInterfaceBrowserSingleton::GetCurrentLocaleCode()
  651. {
  652. FCultureRef Culture = FInternationalization::Get().GetCurrentCulture();
  653. FString LocaleCode = Culture->GetTwoLetterISOLanguageName();
  654. FString Country = Culture->GetRegion();
  655. if (!Country.IsEmpty())
  656. {
  657. LocaleCode = LocaleCode + TEXT("-") + Country;
  658. }
  659. return LocaleCode;
  660. }
  661. TSharedPtr<IWebInterfaceBrowserCookieManager> FWebInterfaceBrowserSingleton::GetCookieManager(TOptional<FString> ContextId) const
  662. {
  663. if (ContextId.IsSet())
  664. {
  665. #if WITH_CEF3
  666. if (bAllowCEF)
  667. {
  668. const CefRefPtr<CefRequestContext>* ExistingContext = RequestContexts.Find(ContextId.GetValue());
  669. if (ExistingContext && ExistingContext->get())
  670. {
  671. // Cache these cookie managers?
  672. return FCefWebInterfaceCookieManagerFactory::Create((*ExistingContext)->GetCookieManager(nullptr));
  673. }
  674. else
  675. {
  676. UE_LOG(LogWebInterfaceBrowser, Log, TEXT("No cookie manager for ContextId=%s. Using default cookie manager"), *ContextId.GetValue());
  677. }
  678. }
  679. #endif
  680. }
  681. // No ContextId or cookie manager instance associated with it. Use default
  682. return DefaultCookieManager;
  683. }
  684. #if WITH_CEF3
  685. bool FWebInterfaceBrowserSingleton::URLRequestAllowsCredentials(const FString& URL)
  686. {
  687. FScopeLock Lock(&WindowInterfacesCS);
  688. // The FCEFResourceContextHandler::OnBeforeResourceLoad call doesn't get the browser/frame associated with the load
  689. // (because bugs) so just look at each browser and see if it thinks it knows about this URL
  690. for (int32 Index = WindowInterfaces.Num() - 1; Index >= 0; --Index)
  691. {
  692. TSharedPtr<FCEFWebInterfaceBrowserWindow> BrowserWindow = WindowInterfaces[Index].Pin();
  693. if (BrowserWindow.IsValid() && BrowserWindow->URLRequestAllowsCredentials(URL))
  694. {
  695. return true;
  696. }
  697. }
  698. return false;
  699. }
  700. FString FWebInterfaceBrowserSingleton::GenerateWebCacheFolderName(const FString& InputPath)
  701. {
  702. if (InputPath.IsEmpty())
  703. return InputPath;
  704. // append the version of this CEF build to our requested cache folder path
  705. // this means each new CEF build gets its own cache folder, making downgrading safe
  706. return InputPath + "_" + MAKE_STRING(CHROME_VERSION_BUILD);
  707. }
  708. #endif
  709. void FWebInterfaceBrowserSingleton::ClearOldCacheFolders(const FString &CachePathRoot, const FString &CachePrefix)
  710. {
  711. #if WITH_CEF3
  712. // only CEF3 currently has version dependant cache folders that may need cleanup
  713. struct FDirectoryVisitor : public IPlatformFile::FDirectoryVisitor
  714. {
  715. const FString CachePrefix;
  716. const FString CurrentCachePath;
  717. FDirectoryVisitor(const FString &InCachePrefix, const FString &InCurrentCachePath)
  718. : CachePrefix(InCachePrefix),
  719. CurrentCachePath(InCurrentCachePath)
  720. {
  721. }
  722. virtual bool Visit(const TCHAR* FilenameOrDirectory, bool bIsDirectory) override
  723. {
  724. static const FString CachePrefixSearch = "/" + CachePrefix;
  725. if (bIsDirectory)
  726. {
  727. FString DirName(FilenameOrDirectory);
  728. if (DirName.Contains(CachePrefixSearch) && DirName.Equals(CurrentCachePath)==false)
  729. {
  730. UE_LOG(LogWebInterfaceBrowser, Log, TEXT("Old Cache folder found=%s, deleting"), *DirName);
  731. // BUGBUG - enable this deletion once we are happy with the new CEF version rollout
  732. // Also consider adding code to preserve the previous versions folder for a while?
  733. /*Async<void>(EAsyncExecution::ThreadPool, [DirName]()
  734. {
  735. IPlatformFile::GetPlatformPhysical().DeleteDirectoryRecursively(*DirName);
  736. });*/
  737. }
  738. }
  739. return true;
  740. }
  741. };
  742. // Enumerate the contents of the current directory
  743. FDirectoryVisitor Visitor(CachePrefix, GenerateWebCacheFolderName(FPaths::Combine(CachePathRoot, CachePrefix)));
  744. IPlatformFile::GetPlatformPhysical().IterateDirectory(*CachePathRoot, Visitor);
  745. #endif
  746. }
  747. bool FWebInterfaceBrowserSingleton::RegisterContext(const FInterfaceBrowserContextSettings& Settings)
  748. {
  749. #if WITH_CEF3
  750. if (bAllowCEF)
  751. {
  752. const CefRefPtr<CefRequestContext>* ExistingContext = RequestContexts.Find(Settings.Id);
  753. if (ExistingContext != nullptr)
  754. {
  755. // You can't register the same context twice and
  756. // you can't update the settings for a context that already exists
  757. return false;
  758. }
  759. CefRequestContextSettings RequestContextSettings;
  760. CefString(&RequestContextSettings.accept_language_list) = Settings.AcceptLanguageList.IsEmpty() ? TCHAR_TO_WCHAR(*GetCurrentLocaleCode()) : TCHAR_TO_WCHAR(*Settings.AcceptLanguageList);
  761. CefString(&RequestContextSettings.cache_path) = TCHAR_TO_WCHAR(*GenerateWebCacheFolderName(Settings.CookieStorageLocation));
  762. RequestContextSettings.persist_session_cookies = Settings.bPersistSessionCookies;
  763. RequestContextSettings.ignore_certificate_errors = Settings.bIgnoreCertificateErrors;
  764. //Create a new one
  765. CefRefPtr<FCEFInterfaceResourceContextHandler> ResourceContextHandler = new FCEFInterfaceResourceContextHandler(this);
  766. ResourceContextHandler->OnBeforeLoad() = Settings.OnBeforeContextResourceLoad;
  767. RequestResourceHandlers.Add(Settings.Id, ResourceContextHandler);
  768. CefRefPtr<CefRequestContext> RequestContext = CefRequestContext::CreateContext(RequestContextSettings, ResourceContextHandler);
  769. RequestContexts.Add(Settings.Id, RequestContext);
  770. SchemeHandlerFactories.RegisterFactoriesWith(RequestContext);
  771. UE_LOG(LogWebInterfaceBrowser, Log, TEXT("Registering ContextId=%s."), *Settings.Id);
  772. return true;
  773. }
  774. #endif
  775. return false;
  776. }
  777. bool FWebInterfaceBrowserSingleton::UnregisterContext(const FString& ContextId)
  778. {
  779. #if WITH_CEF3
  780. bool bFoundContext = false;
  781. if (bAllowCEF)
  782. {
  783. UE_LOG(LogWebInterfaceBrowser, Log, TEXT("Unregistering ContextId=%s."), *ContextId);
  784. WaitForTaskQueueFlush();
  785. CefRefPtr<CefRequestContext> Context;
  786. if (RequestContexts.RemoveAndCopyValue(ContextId, Context))
  787. {
  788. bFoundContext = true;
  789. Context->ClearSchemeHandlerFactories();
  790. }
  791. CefRefPtr<FCEFInterfaceResourceContextHandler> ResourceHandler;
  792. if (RequestResourceHandlers.RemoveAndCopyValue(ContextId, ResourceHandler))
  793. {
  794. ResourceHandler->OnBeforeLoad().Unbind();
  795. }
  796. }
  797. return bFoundContext;
  798. #else
  799. return false;
  800. #endif
  801. }
  802. bool FWebInterfaceBrowserSingleton::RegisterSchemeHandlerFactory(FString Scheme, FString Domain, IWebInterfaceBrowserSchemeHandlerFactory* WebBrowserSchemeHandlerFactory)
  803. {
  804. #if WITH_CEF3
  805. if (bAllowCEF)
  806. {
  807. SchemeHandlerFactories.AddSchemeHandlerFactory(MoveTemp(Scheme), MoveTemp(Domain), WebBrowserSchemeHandlerFactory);
  808. return true;
  809. }
  810. #endif
  811. return false;
  812. }
  813. bool FWebInterfaceBrowserSingleton::UnregisterSchemeHandlerFactory(IWebInterfaceBrowserSchemeHandlerFactory* WebBrowserSchemeHandlerFactory)
  814. {
  815. #if WITH_CEF3
  816. if (bAllowCEF)
  817. {
  818. SchemeHandlerFactories.RemoveSchemeHandlerFactory(WebBrowserSchemeHandlerFactory);
  819. return true;
  820. }
  821. #endif
  822. return false;
  823. }
  824. // Cleanup macros to avoid having them leak outside this source file
  825. #undef CEF3_BIN_DIR
  826. #undef CEF3_FRAMEWORK_DIR
  827. #undef CEF3_RESOURCES_DIR
  828. #undef CEF3_SUBPROCES_EXE