1061 lines
38 KiB
C++
1061 lines
38 KiB
C++
// Copyright Epic Games, Inc. All Rights Reserved.
|
|
|
|
#include "WebBrowserSingleton.h"
|
|
#include "Misc/Paths.h"
|
|
#include "GenericPlatform/GenericPlatformFile.h"
|
|
#include "Misc/CommandLine.h"
|
|
#include "Misc/ConfigCacheIni.h"
|
|
#include "Internationalization/Culture.h"
|
|
#include "Misc/App.h"
|
|
#include "WebBrowserModule.h"
|
|
#include "Misc/EngineVersion.h"
|
|
#include "Framework/Application/SlateApplication.h"
|
|
#include "IWebBrowserCookieManager.h"
|
|
#include "WebBrowserLog.h"
|
|
|
|
#if WITH_CEF3
|
|
#include "Misc/ScopeLock.h"
|
|
#include "Async/Async.h"
|
|
#include "HAL/PlatformApplicationMisc.h"
|
|
#include "CEF/CEFBrowserApp.h"
|
|
#include "CEF/CEFBrowserHandler.h"
|
|
#include "CEF/CEFWebBrowserWindow.h"
|
|
#include "CEF/CEFSchemeHandler.h"
|
|
#include "CEF/CEFResourceContextHandler.h"
|
|
#include "CEF/CEFBrowserClosureTask.h"
|
|
# if PLATFORM_WINDOWS
|
|
# include "Windows/AllowWindowsPlatformTypes.h"
|
|
# endif
|
|
# pragma push_macro("OVERRIDE")
|
|
# undef OVERRIDE // cef headers provide their own OVERRIDE macro
|
|
THIRD_PARTY_INCLUDES_START
|
|
#if PLATFORM_APPLE
|
|
PRAGMA_DISABLE_DEPRECATION_WARNINGS
|
|
#endif
|
|
# include "include/cef_app.h"
|
|
# include "include/cef_version.h"
|
|
#if PLATFORM_APPLE
|
|
PRAGMA_ENABLE_DEPRECATION_WARNINGS
|
|
#endif
|
|
THIRD_PARTY_INCLUDES_END
|
|
# pragma pop_macro("OVERRIDE")
|
|
# if PLATFORM_WINDOWS
|
|
# include "Windows/HideWindowsPlatformTypes.h"
|
|
# endif
|
|
#endif
|
|
|
|
#if BUILD_EMBEDDED_APP
|
|
# include "Native/NativeWebBrowserProxy.h"
|
|
#endif
|
|
|
|
#if PLATFORM_ANDROID && USE_ANDROID_JNI
|
|
# include "Android/AndroidWebBrowserWindow.h"
|
|
# include <Android/AndroidCookieManager.h>
|
|
#elif PLATFORM_IOS
|
|
# include <IOS/IOSPlatformWebBrowser.h>
|
|
# include <IOS/IOSCookieManager.h>
|
|
#elif PLATFORM_SPECIFIC_WEB_BROWSER
|
|
# include COMPILED_PLATFORM_HEADER(PlatformWebBrowser.h)
|
|
#endif
|
|
|
|
// Define some platform-dependent file locations
|
|
#if WITH_CEF3
|
|
# if CEF3_USE_EXPERIMENTAL_VERSION
|
|
# define CEF_VERSION_DIR TEXT("/") TEXT(CEF_VERSION)
|
|
# else
|
|
# define CEF_VERSION_DIR TEXT("")
|
|
# endif
|
|
# define CEF3_BIN_DIR TEXT("Binaries/ThirdParty/CEF3")
|
|
# if PLATFORM_WINDOWS && PLATFORM_64BITS && PLATFORM_CPU_ARM_FAMILY
|
|
# if defined(_M_ARM64EC)
|
|
# define CEF3_RESOURCES_DIR CEF3_BIN_DIR TEXT("/Win64") CEF_VERSION_DIR TEXT("/Resources")
|
|
# else
|
|
# define CEF3_RESOURCES_DIR CEF3_BIN_DIR TEXT("/WinArm64") CEF_VERSION_DIR TEXT("/Resources")
|
|
# endif
|
|
# define CEF3_SUBPROCES_EXE TEXT("Binaries/Win64/EpicWebHelperarm64.exe")
|
|
# elif PLATFORM_WINDOWS && PLATFORM_64BITS && !PLATFORM_CPU_ARM_FAMILY
|
|
# define CEF3_RESOURCES_DIR CEF3_BIN_DIR TEXT("/Win64") CEF_VERSION_DIR TEXT("/Resources")
|
|
# define CEF3_SUBPROCES_EXE TEXT("Binaries/Win64/EpicWebHelper.exe")
|
|
# elif PLATFORM_WINDOWS && PLATFORM_32BITS
|
|
# define CEF3_RESOURCES_DIR CEF3_BIN_DIR TEXT("/Win32") CEF_VERSION_DIR TEXT("/Resources")
|
|
# define CEF3_SUBPROCES_EXE TEXT("Binaries/Win32/EpicWebHelper.exe")
|
|
# elif PLATFORM_MAC
|
|
# define CEF3_FRAMEWORK_DIR CEF3_BIN_DIR TEXT("/Mac") CEF_VERSION_DIR TEXT("/Chromium Embedded Framework.framework")
|
|
# define CEF3_RESOURCES_DIR CEF3_FRAMEWORK_DIR TEXT("/Resources")
|
|
# define CEF3_SUBPROCES_EXE TEXT("Binaries/Mac/EpicWebHelper")
|
|
# elif PLATFORM_LINUX // @todo Linux
|
|
# define CEF3_RESOURCES_DIR CEF3_BIN_DIR TEXT("/Linux") CEF_VERSION_DIR TEXT("/Resources")
|
|
# define CEF3_SUBPROCES_EXE TEXT("Binaries/Linux/EpicWebHelper")
|
|
# endif
|
|
// Caching is enabled by default.
|
|
# ifndef CEF3_DEFAULT_CACHE
|
|
# define CEF3_DEFAULT_CACHE 1
|
|
# endif
|
|
#endif
|
|
|
|
FString FWebBrowserSingleton::ApplicationCacheDir() const
|
|
{
|
|
#if PLATFORM_MAC
|
|
// OSX wants caches in a separate location from other app data
|
|
static TCHAR Result[MAC_MAX_PATH] = TEXT("");
|
|
if (!Result[0])
|
|
{
|
|
SCOPED_AUTORELEASE_POOL;
|
|
NSString *CacheBaseDir = [NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES) objectAtIndex: 0];
|
|
NSString* BundleID = [[NSBundle mainBundle] bundleIdentifier];
|
|
if(!BundleID)
|
|
{
|
|
BundleID = [[NSProcessInfo processInfo] processName];
|
|
}
|
|
check(BundleID);
|
|
|
|
NSString* AppCacheDir = [CacheBaseDir stringByAppendingPathComponent: BundleID];
|
|
FPlatformString::CFStringToTCHAR((CFStringRef)AppCacheDir, Result);
|
|
}
|
|
return FString(Result);
|
|
#else
|
|
// Other platforms use the application data directory
|
|
return FPaths::ProjectSavedDir();
|
|
#endif
|
|
}
|
|
|
|
|
|
class FWebBrowserWindowFactory
|
|
: public IWebBrowserWindowFactory
|
|
{
|
|
public:
|
|
|
|
virtual ~FWebBrowserWindowFactory()
|
|
{ }
|
|
|
|
virtual TSharedPtr<IWebBrowserWindow> Create(
|
|
TSharedPtr<FCEFWebBrowserWindow>& BrowserWindowParent,
|
|
TSharedPtr<FWebBrowserWindowInfo>& BrowserWindowInfo) override
|
|
{
|
|
return IWebBrowserModule::Get().GetSingleton()->CreateBrowserWindow(
|
|
BrowserWindowParent,
|
|
BrowserWindowInfo);
|
|
}
|
|
|
|
virtual TSharedPtr<IWebBrowserWindow> Create(
|
|
void* OSWindowHandle,
|
|
FString InitialURL,
|
|
bool bUseTransparency,
|
|
bool bThumbMouseButtonNavigation,
|
|
bool bInterceptLoadRequests = true,
|
|
TOptional<FString> ContentsToLoad = TOptional<FString>(),
|
|
bool ShowErrorMessage = true,
|
|
FColor BackgroundColor = FColor(255, 255, 255, 255)) override
|
|
{
|
|
FCreateBrowserWindowSettings Settings;
|
|
Settings.OSWindowHandle = OSWindowHandle;
|
|
Settings.InitialURL = MoveTemp(InitialURL);
|
|
Settings.bUseTransparency = bUseTransparency;
|
|
Settings.bThumbMouseButtonNavigation = bThumbMouseButtonNavigation;
|
|
Settings.ContentsToLoad = MoveTemp(ContentsToLoad);
|
|
Settings.bShowErrorMessage = ShowErrorMessage;
|
|
Settings.BackgroundColor = BackgroundColor;
|
|
Settings.bInterceptLoadRequests = bInterceptLoadRequests;
|
|
|
|
return IWebBrowserModule::Get().GetSingleton()->CreateBrowserWindow(Settings);
|
|
}
|
|
};
|
|
|
|
class FNoWebBrowserWindowFactory
|
|
: public IWebBrowserWindowFactory
|
|
{
|
|
public:
|
|
|
|
virtual ~FNoWebBrowserWindowFactory()
|
|
{ }
|
|
|
|
virtual TSharedPtr<IWebBrowserWindow> Create(
|
|
TSharedPtr<FCEFWebBrowserWindow>& BrowserWindowParent,
|
|
TSharedPtr<FWebBrowserWindowInfo>& BrowserWindowInfo) override
|
|
{
|
|
return nullptr;
|
|
}
|
|
|
|
virtual TSharedPtr<IWebBrowserWindow> Create(
|
|
void* OSWindowHandle,
|
|
FString InitialURL,
|
|
bool bUseTransparency,
|
|
bool bThumbMouseButtonNavigation,
|
|
bool bInterceptLoadRequests = true,
|
|
TOptional<FString> ContentsToLoad = TOptional<FString>(),
|
|
bool ShowErrorMessage = true,
|
|
FColor BackgroundColor = FColor(255, 255, 255, 255)) override
|
|
{
|
|
return nullptr;
|
|
}
|
|
};
|
|
|
|
#if WITH_CEF3
|
|
#if PLATFORM_MAC || PLATFORM_LINUX
|
|
class FPosixSignalPreserver
|
|
{
|
|
public:
|
|
FPosixSignalPreserver()
|
|
{
|
|
struct sigaction Sigact;
|
|
for (uint32 i = 0; i < UE_ARRAY_COUNT(PreserveSignals); ++i)
|
|
{
|
|
FMemory::Memset(&Sigact, 0, sizeof(Sigact));
|
|
if (sigaction(PreserveSignals[i], nullptr, &Sigact) != 0)
|
|
{
|
|
UE_LOG(LogWebBrowser, Warning, TEXT("Failed to backup signal handler for %i."), PreserveSignals[i]);
|
|
}
|
|
OriginalSignalHandlers[i] = Sigact;
|
|
}
|
|
}
|
|
|
|
~FPosixSignalPreserver()
|
|
{
|
|
for (uint32 i = 0; i < UE_ARRAY_COUNT(PreserveSignals); ++i)
|
|
{
|
|
if(sigaction(PreserveSignals[i], &OriginalSignalHandlers[i], nullptr) != 0)
|
|
{
|
|
UE_LOG(LogWebBrowser, Warning, TEXT("Failed to restore signal handler for %i."), PreserveSignals[i]);
|
|
}
|
|
}
|
|
}
|
|
|
|
private:
|
|
// Backup the list of signals that CEF/Chromium overrides, derived from SetupSignalHandlers() in
|
|
// https://chromium.googlesource.com/chromium/src.git/+/2fc330d0b93d4bfd7bd04b9fdd3102e529901f91/services/service_manager/embedder/main.cc
|
|
const int PreserveSignals[13] = {SIGHUP, SIGINT, SIGQUIT, SIGILL, SIGABRT,
|
|
SIGFPE, SIGSEGV, SIGALRM, SIGTERM, SIGCHLD, SIGBUS, SIGTRAP, SIGPIPE};
|
|
|
|
struct sigaction OriginalSignalHandlers[UE_ARRAY_COUNT(PreserveSignals)];
|
|
};
|
|
|
|
#endif // PLATFORM_MAC || PLATFORM_LINUX
|
|
#endif // WITH_CEF3
|
|
|
|
FWebBrowserSingleton::FWebBrowserSingleton(const FWebBrowserInitSettings& WebBrowserInitSettings)
|
|
#if WITH_CEF3
|
|
: WebBrowserWindowFactory(MakeShareable(new FWebBrowserWindowFactory()))
|
|
#else
|
|
: WebBrowserWindowFactory(MakeShareable(new FNoWebBrowserWindowFactory()))
|
|
#if PLATFORM_IOS || (PLATFORM_ANDROID && USE_ANDROID_JNI)
|
|
, UserAgentApplication(WebBrowserInitSettings.ProductVersion)
|
|
#endif
|
|
#endif
|
|
, bDevToolsShortcutEnabled(UE_BUILD_DEBUG)
|
|
, bJSBindingsToLoweringEnabled(true)
|
|
, bAppIsFocused(false)
|
|
#if WITH_CEF3
|
|
, bCEFInitialized(false)
|
|
#endif
|
|
, DefaultMaterial(nullptr)
|
|
, DefaultTranslucentMaterial(nullptr)
|
|
{
|
|
#if WITH_CEF3
|
|
|
|
// 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
|
|
// Disallow CEF if we never plan on rendering, ie, with CanEverRender. This includes servers
|
|
bAllowCEF = (!IsRunningCommandlet() || (IsAllowCommandletRendering() && FParse::Param(FCommandLine::Get(), TEXT("AllowCommandletCEF")))) &&
|
|
FApp::CanEverRender() && !FParse::Param(FCommandLine::Get(), TEXT("nocef"));
|
|
if (bAllowCEF)
|
|
{
|
|
// The FWebBrowserSingleton must be initialized on the game thread
|
|
check(IsInGameThread());
|
|
|
|
// Provide CEF with command-line arguments.
|
|
#if PLATFORM_WINDOWS
|
|
CefMainArgs MainArgs(hInstance);
|
|
#else
|
|
CefMainArgs MainArgs;
|
|
#endif
|
|
|
|
#if CEF_VERSION_MAJOR < 128
|
|
// Enable high-DPI support early in CEF startup. For this to work it also depends
|
|
// on FPlatformApplicationMisc::SetHighDPIMode() being called already which should happen by default
|
|
CefEnableHighDPISupport();
|
|
#endif
|
|
|
|
bool bVerboseLogging = FParse::Param(FCommandLine::Get(), TEXT("cefverbose")) || FParse::Param(FCommandLine::Get(), TEXT("debuglog"));
|
|
// CEFBrowserApp implements application-level callbacks.
|
|
CEFBrowserApp = new FCEFBrowserApp;
|
|
|
|
// Specify CEF global settings here.
|
|
CefSettings Settings;
|
|
Settings.no_sandbox = true;
|
|
Settings.command_line_args_disabled = true;
|
|
Settings.external_message_pump = true;
|
|
//@todo change to threaded version instead of using external_message_pump & OnScheduleMessagePumpWork
|
|
Settings.multi_threaded_message_loop = false;
|
|
//Set the default background for browsers to be opaque black, this is used for windowed (not OSR) browsers
|
|
// setting it black here prevents the white flash on load
|
|
Settings.background_color = CefColorSetARGB(255, 0, 0, 0);
|
|
|
|
#if PLATFORM_LINUX
|
|
Settings.windowless_rendering_enabled = true;
|
|
#endif
|
|
|
|
FString CefLogFile(FPaths::Combine(*FPaths::ProjectLogDir(), TEXT("cef3.log")));
|
|
CefLogFile = FPaths::ConvertRelativePathToFull(CefLogFile);
|
|
CefString(&Settings.log_file) = TCHAR_TO_WCHAR(*CefLogFile);
|
|
Settings.log_severity = bVerboseLogging ? LOGSEVERITY_VERBOSE : LOGSEVERITY_WARNING;
|
|
|
|
uint16 DebugPort;
|
|
if(FParse::Value(FCommandLine::Get(), TEXT("cefdebug="), DebugPort))
|
|
{
|
|
Settings.remote_debugging_port = DebugPort;
|
|
}
|
|
|
|
// Specify locale from our settings
|
|
FString LocaleCode = GetCurrentLocaleCode();
|
|
CefString(&Settings.locale) = TCHAR_TO_WCHAR(*LocaleCode);
|
|
|
|
// Append engine version to the user agent string.
|
|
FString UserAgentApplication = FString::Printf(TEXT("%s Chrome/%d.%d.%d.%d"), *WebBrowserInitSettings.ProductVersion, CHROME_VERSION_MAJOR, CHROME_VERSION_MINOR, CHROME_VERSION_BUILD, CHROME_VERSION_PATCH);
|
|
CefString(&Settings.user_agent_product) = TCHAR_TO_WCHAR(*UserAgentApplication);
|
|
|
|
#if CEF3_DEFAULT_CACHE
|
|
// Enable on disk cache
|
|
FString CachePath(FPaths::Combine(ApplicationCacheDir(), TEXT("webcache")));
|
|
CachePath = FPaths::ConvertRelativePathToFull(GenerateWebCacheFolderName(CachePath));
|
|
CefString(&Settings.cache_path) = TCHAR_TO_WCHAR(*CachePath);
|
|
#endif
|
|
|
|
// Specify path to resources
|
|
FString ResourcesPath(FPaths::Combine(*FPaths::EngineDir(), CEF3_RESOURCES_DIR));
|
|
ResourcesPath = FPaths::ConvertRelativePathToFull(ResourcesPath);
|
|
if (!FPaths::DirectoryExists(ResourcesPath))
|
|
{
|
|
UE_LOG(LogWebBrowser, Error, TEXT("Chromium Resources information not found at: %s."), *ResourcesPath);
|
|
}
|
|
CefString(&Settings.resources_dir_path) = TCHAR_TO_WCHAR(*ResourcesPath);
|
|
|
|
#if !PLATFORM_MAC
|
|
// On Mac Chromium ignores custom locales dir. Files need to be stored in Resources folder in the app bundle
|
|
FString LocalesPath(FPaths::Combine(*ResourcesPath, TEXT("locales")));
|
|
LocalesPath = FPaths::ConvertRelativePathToFull(LocalesPath);
|
|
if (!FPaths::DirectoryExists(LocalesPath))
|
|
{
|
|
UE_LOG(LogWebBrowser, Error, TEXT("Chromium Locales information not found at: %s."), *LocalesPath);
|
|
}
|
|
CefString(&Settings.locales_dir_path) = TCHAR_TO_WCHAR(*LocalesPath);
|
|
#else
|
|
// LocaleCode may contain region, which for some languages may make CEF unable to find the locale pak files
|
|
// In that case use the language name for CEF locale
|
|
FString LocalePakPath = ResourcesPath + TEXT("/") + LocaleCode.Replace(TEXT("-"), TEXT("_")) + TEXT(".lproj/locale.pak");
|
|
if (!FPaths::FileExists(LocalePakPath))
|
|
{
|
|
FCultureRef Culture = FInternationalization::Get().GetCurrentCulture();
|
|
LocaleCode = Culture->GetTwoLetterISOLanguageName();
|
|
LocalePakPath = ResourcesPath + TEXT("/") + LocaleCode + TEXT(".lproj/locale.pak");
|
|
if (FPaths::FileExists(LocalePakPath))
|
|
{
|
|
CefString(&Settings.locale) = TCHAR_TO_WCHAR(*LocaleCode);
|
|
}
|
|
}
|
|
|
|
// Let CEF know where we have put the framework bundle as it is non-default
|
|
FString CefFrameworkPath(FPaths::Combine(*FPaths::EngineDir(), CEF3_FRAMEWORK_DIR));
|
|
CefFrameworkPath = FPaths::ConvertRelativePathToFull(CefFrameworkPath);
|
|
CefString(&Settings.framework_dir_path) = TCHAR_TO_WCHAR(*CefFrameworkPath);
|
|
CefString(&Settings.main_bundle_path) = TCHAR_TO_WCHAR(*CefFrameworkPath);
|
|
#endif
|
|
|
|
// Specify path to sub process exe
|
|
FString SubProcessPath(FPaths::Combine(*FPaths::EngineDir(), CEF3_SUBPROCES_EXE));
|
|
SubProcessPath = FPaths::ConvertRelativePathToFull(SubProcessPath);
|
|
|
|
if (!IPlatformFile::GetPlatformPhysical().FileExists(*SubProcessPath))
|
|
{
|
|
UE_LOG(LogWebBrowser, Error, TEXT("EpicWebHelper.exe not found, check that this program has been built and is placed in: %s."), *SubProcessPath);
|
|
}
|
|
CefString(&Settings.browser_subprocess_path) = TCHAR_TO_WCHAR(*SubProcessPath);
|
|
|
|
#if PLATFORM_MAC || PLATFORM_LINUX
|
|
// this class automatically preserves the sigaction handlers we have set
|
|
FPosixSignalPreserver PosixSignalPreserver;
|
|
#endif
|
|
|
|
// Initialize CEF.
|
|
bCEFInitialized = CefInitialize(MainArgs, Settings, CEFBrowserApp.get(), nullptr);
|
|
check(bCEFInitialized);
|
|
|
|
// Set the thread name back to GameThread.
|
|
FPlatformProcess::SetThreadName(*FName(NAME_GameThread).GetPlainNameString());
|
|
|
|
DefaultCookieManager = FCefWebBrowserCookieManagerFactory::Create(CefCookieManager::GetGlobalManager(nullptr));
|
|
}
|
|
#elif PLATFORM_IOS && !BUILD_EMBEDDED_APP
|
|
DefaultCookieManager = MakeShareable(new FIOSCookieManager());
|
|
#elif PLATFORM_ANDROID
|
|
DefaultCookieManager = MakeShareable(new FAndroidCookieManager());
|
|
#endif
|
|
|
|
}
|
|
|
|
|
|
#if WITH_CEF3
|
|
void FWebBrowserSingleton::WaitForTaskQueueFlush()
|
|
{
|
|
// Keep pumping messages until we see the one below clear the queue
|
|
bTaskFinished = false;
|
|
CefPostTask(TID_UI, new FCEFBrowserClosureTask(nullptr, [=, this]()
|
|
{
|
|
bTaskFinished = true;
|
|
}));
|
|
|
|
const double StartWaitAppTime = FPlatformTime::Seconds();
|
|
while (!bTaskFinished)
|
|
{
|
|
FPlatformProcess::Sleep(0.01);
|
|
// CEF needs the windows message pump run to be able to finish closing a browser, so run it manually here
|
|
if (FSlateApplication::IsInitialized())
|
|
{
|
|
FSlateApplication::Get().PumpMessages();
|
|
}
|
|
CefDoMessageLoopWork();
|
|
// Wait at most 1 second for tasks to clear, in case CEF crashes/hangs during process lifetime
|
|
if (FPlatformTime::Seconds() - StartWaitAppTime > 1.0f)
|
|
{
|
|
break; // don't spin forever
|
|
}
|
|
}
|
|
}
|
|
#endif
|
|
|
|
|
|
FWebBrowserSingleton::~FWebBrowserSingleton()
|
|
{
|
|
#if WITH_CEF3
|
|
if (!bCEFInitialized)
|
|
return; // CEF failed to init so don't crash trying to shut it down
|
|
|
|
if (bAllowCEF)
|
|
{
|
|
{
|
|
FScopeLock Lock(&WindowInterfacesCS);
|
|
// Force all existing browsers to close in case any haven't been deleted
|
|
for (int32 Index = 0; Index < WindowInterfaces.Num(); ++Index)
|
|
{
|
|
auto BrowserWindow = WindowInterfaces[Index].Pin();
|
|
if (BrowserWindow.IsValid() && BrowserWindow->IsValid())
|
|
{
|
|
// Call CloseBrowser directly on the Host object as FWebBrowserWindow::CloseBrowser is delayed
|
|
BrowserWindow->InternalCefBrowser->GetHost()->CloseBrowser(true);
|
|
}
|
|
}
|
|
// Clear this before CefShutdown() below
|
|
WindowInterfaces.Reset();
|
|
}
|
|
|
|
// Remove references to the scheme handler factories
|
|
CefClearSchemeHandlerFactories();
|
|
for (const TPair<FString, CefRefPtr<CefRequestContext>>& RequestContextPair : RequestContexts)
|
|
{
|
|
RequestContextPair.Value->ClearSchemeHandlerFactories();
|
|
}
|
|
// Clear this before CefShutdown() below
|
|
RequestContexts.Reset();
|
|
|
|
// make sure any handler before load delegates are unbound
|
|
for (const TPair <FString,CefRefPtr<FCEFResourceContextHandler>>& HandlerPair : RequestResourceHandlers)
|
|
{
|
|
HandlerPair.Value->OnBeforeLoad().Unbind();
|
|
}
|
|
// Clear this before CefShutdown() below
|
|
RequestResourceHandlers.Reset();
|
|
// CefRefPtr takes care of delete
|
|
CEFBrowserApp = nullptr;
|
|
|
|
WaitForTaskQueueFlush();
|
|
|
|
// Shut down CEF.
|
|
CefShutdown();
|
|
}
|
|
bCEFInitialized = false;
|
|
#elif PLATFORM_IOS || PLATFORM_SPECIFIC_WEB_BROWSER || (PLATFORM_ANDROID && USE_ANDROID_JNI)
|
|
{
|
|
FScopeLock Lock(&WindowInterfacesCS);
|
|
// Clear this before CefShutdown() below
|
|
WindowInterfaces.Reset();
|
|
}
|
|
#endif
|
|
}
|
|
|
|
bool FWebBrowserSingleton::IsShuttingDown() const
|
|
{
|
|
#if WITH_CEF3
|
|
return bAllowCEF && !CEFBrowserApp;
|
|
#else
|
|
return false;
|
|
#endif
|
|
}
|
|
|
|
TSharedRef<IWebBrowserWindowFactory> FWebBrowserSingleton::GetWebBrowserWindowFactory() const
|
|
{
|
|
return WebBrowserWindowFactory;
|
|
}
|
|
|
|
TSharedPtr<IWebBrowserWindow> FWebBrowserSingleton::CreateBrowserWindow(
|
|
TSharedPtr<FCEFWebBrowserWindow>& BrowserWindowParent,
|
|
TSharedPtr<FWebBrowserWindowInfo>& BrowserWindowInfo
|
|
)
|
|
{
|
|
#if WITH_CEF3
|
|
if (bAllowCEF)
|
|
{
|
|
TOptional<FString> ContentsToLoad;
|
|
|
|
bool bShowErrorMessage = BrowserWindowParent->IsShowingErrorMessages();
|
|
bool bThumbMouseButtonNavigation = BrowserWindowParent->IsThumbMouseButtonNavigationEnabled();
|
|
bool bUseTransparency = BrowserWindowParent->UseTransparency();
|
|
bool bUsingAcceleratedPaint = BrowserWindowParent->UsingAcceleratedPaint();
|
|
FString InitialURL = WCHAR_TO_TCHAR(BrowserWindowInfo->Browser->GetMainFrame()->GetURL().ToWString().c_str());
|
|
TSharedPtr<FCEFWebBrowserWindow> NewBrowserWindow(new FCEFWebBrowserWindow(BrowserWindowInfo->Browser, BrowserWindowInfo->Handler, InitialURL, ContentsToLoad, bShowErrorMessage, bThumbMouseButtonNavigation, bUseTransparency, bJSBindingsToLoweringEnabled, bUsingAcceleratedPaint));
|
|
BrowserWindowInfo->Handler->SetBrowserWindow(NewBrowserWindow);
|
|
{
|
|
FScopeLock Lock(&WindowInterfacesCS);
|
|
WindowInterfaces.Add(NewBrowserWindow);
|
|
}
|
|
NewBrowserWindow->GetCefBrowser()->GetHost()->SetWindowlessFrameRate(BrowserWindowParent->GetCefBrowser()->GetHost()->GetWindowlessFrameRate());
|
|
return NewBrowserWindow;
|
|
}
|
|
#endif
|
|
return nullptr;
|
|
}
|
|
|
|
TSharedPtr<IWebBrowserWindow> FWebBrowserSingleton::CreateBrowserWindow(const FCreateBrowserWindowSettings& WindowSettings)
|
|
{
|
|
bool bBrowserEnabled = true;
|
|
GConfig->GetBool(TEXT("Browser"), TEXT("bEnabled"), bBrowserEnabled, GEngineIni);
|
|
if (!bBrowserEnabled || !FApp::CanEverRender())
|
|
{
|
|
return nullptr;
|
|
}
|
|
|
|
#if WITH_CEF3
|
|
if (bAllowCEF)
|
|
{
|
|
// Information used when creating the native window.
|
|
CefWindowInfo WindowInfo;
|
|
|
|
// Specify CEF browser settings here.
|
|
CefBrowserSettings BrowserSettings;
|
|
|
|
// The color to paint before a document is loaded
|
|
// if using a windowed(native) browser window AND bUseTransparency is true then the background actually uses Settings.background_color from above
|
|
// if using a OSR window and bUseTransparency is true then you get a transparency channel in your BGRA OnPaint
|
|
// if bUseTransparency is false then you get the background color defined by your RGB setting here
|
|
BrowserSettings.background_color = CefColorSetARGB(WindowSettings.bUseTransparency ? 0 : WindowSettings.BackgroundColor.A, WindowSettings.BackgroundColor.R, WindowSettings.BackgroundColor.G, WindowSettings.BackgroundColor.B);
|
|
|
|
#if CEF_VERSION_MAJOR < 128
|
|
// Disable plugins
|
|
BrowserSettings.plugins = STATE_DISABLED;
|
|
#endif
|
|
|
|
|
|
#if PLATFORM_WINDOWS
|
|
// Create the widget as a child window on windows when passing in a parent window
|
|
if (WindowSettings.OSWindowHandle != nullptr)
|
|
{
|
|
RECT ClientRect = { 0, 0, 0, 0 };
|
|
if (!GetClientRect((HWND)WindowSettings.OSWindowHandle, &ClientRect))
|
|
{
|
|
UE_LOG(LogWebBrowser, Error, TEXT("Failed to get client rect"));
|
|
}
|
|
#if CEF_VERSION_MAJOR < 128
|
|
WindowInfo.SetAsChild((CefWindowHandle)WindowSettings.OSWindowHandle, ClientRect);
|
|
#else
|
|
WindowInfo.SetAsChild((CefWindowHandle)WindowSettings.OSWindowHandle, { ClientRect.left, ClientRect.top, ClientRect.right - ClientRect.left, ClientRect.bottom - ClientRect.top });
|
|
#endif
|
|
}
|
|
else
|
|
#endif
|
|
{
|
|
// Use off screen rendering so we can integrate with our windows
|
|
WindowInfo.SetAsWindowless(kNullWindowHandle);
|
|
WindowInfo.shared_texture_enabled = FCEFWebBrowserWindow::CanSupportAcceleratedPaint() ? 1 : 0;
|
|
int BrowserFrameRate = WindowSettings.BrowserFrameRate;
|
|
if (FCEFWebBrowserWindow::CanSupportAcceleratedPaint() && BrowserFrameRate == 24)
|
|
{
|
|
// Use 60 fps if the accelerated renderer is enabled and the default framerate was otherwise selected
|
|
BrowserFrameRate = 60;
|
|
}
|
|
BrowserSettings.windowless_frame_rate = BrowserFrameRate;
|
|
}
|
|
|
|
TArray<FString> AuthorizationHeaderAllowListURLS;
|
|
GConfig->GetArray(TEXT("Browser"), TEXT("AuthorizationHeaderAllowListURLS"), AuthorizationHeaderAllowListURLS, GEngineIni);
|
|
|
|
// WebBrowserHandler implements browser-level callbacks.
|
|
CefRefPtr<FCEFBrowserHandler> NewHandler(new FCEFBrowserHandler(WindowSettings.bUseTransparency, WindowSettings.bInterceptLoadRequests ,WindowSettings.AltRetryDomains, AuthorizationHeaderAllowListURLS));
|
|
|
|
CefRefPtr<CefRequestContext> RequestContext = nullptr;
|
|
if (WindowSettings.Context.IsSet())
|
|
{
|
|
const FBrowserContextSettings Context = WindowSettings.Context.GetValue();
|
|
const CefRefPtr<CefRequestContext>* ExistingRequestContext = RequestContexts.Find(Context.Id);
|
|
|
|
if (ExistingRequestContext == nullptr)
|
|
{
|
|
CefRequestContextSettings RequestContextSettings;
|
|
CefString(&RequestContextSettings.accept_language_list) = Context.AcceptLanguageList.IsEmpty() ? TCHAR_TO_WCHAR(*GetCurrentLocaleCode()) : TCHAR_TO_WCHAR(*Context.AcceptLanguageList);
|
|
CefString(&RequestContextSettings.cache_path) = TCHAR_TO_WCHAR(*GenerateWebCacheFolderName(Context.CookieStorageLocation));
|
|
RequestContextSettings.persist_session_cookies = Context.bPersistSessionCookies;
|
|
#if CEF_VERSION_MAJOR < 128
|
|
RequestContextSettings.ignore_certificate_errors = Context.bIgnoreCertificateErrors;
|
|
#endif
|
|
|
|
CefRefPtr<FCEFResourceContextHandler> ResourceContextHandler = new FCEFResourceContextHandler(this);
|
|
ResourceContextHandler->OnBeforeLoad() = Context.OnBeforeContextResourceLoad;
|
|
RequestResourceHandlers.Add(Context.Id, ResourceContextHandler);
|
|
|
|
//Create a new one
|
|
RequestContext = CefRequestContext::CreateContext(RequestContextSettings, ResourceContextHandler);
|
|
RequestContexts.Add(Context.Id, RequestContext);
|
|
}
|
|
else
|
|
{
|
|
RequestContext = *ExistingRequestContext;
|
|
}
|
|
SchemeHandlerFactories.RegisterFactoriesWith(RequestContext);
|
|
UE_LOG(LogWebBrowser, Log, TEXT("Creating browser for ContextId=%s."), *WindowSettings.Context.GetValue().Id);
|
|
}
|
|
if (RequestContext == nullptr)
|
|
{
|
|
// As of CEF drop 4430 the CreateBrowserSync call requires a non-null request context, so fall back to the default one if needed
|
|
RequestContext = CefRequestContext::GetGlobalContext();
|
|
}
|
|
|
|
// Create the CEF browser window.
|
|
CefRefPtr<CefBrowser> Browser = CefBrowserHost::CreateBrowserSync(WindowInfo, NewHandler.get(), TCHAR_TO_WCHAR(*WindowSettings.InitialURL), BrowserSettings, nullptr, RequestContext);
|
|
if (Browser.get())
|
|
{
|
|
// Create new window
|
|
TSharedPtr<FCEFWebBrowserWindow> NewBrowserWindow = MakeShareable(new FCEFWebBrowserWindow(
|
|
Browser,
|
|
NewHandler,
|
|
WindowSettings.InitialURL,
|
|
WindowSettings.ContentsToLoad,
|
|
WindowSettings.bShowErrorMessage,
|
|
WindowSettings.bThumbMouseButtonNavigation,
|
|
WindowSettings.bUseTransparency,
|
|
bJSBindingsToLoweringEnabled,
|
|
WindowInfo.shared_texture_enabled == 1 ? true : false));
|
|
NewHandler->SetBrowserWindow(NewBrowserWindow);
|
|
{
|
|
FScopeLock Lock(&WindowInterfacesCS);
|
|
WindowInterfaces.Add(NewBrowserWindow);
|
|
}
|
|
|
|
return NewBrowserWindow;
|
|
}
|
|
}
|
|
#elif PLATFORM_ANDROID && USE_ANDROID_JNI
|
|
// Create new window
|
|
TSharedPtr<FAndroidWebBrowserWindow> NewBrowserWindow = MakeShareable(new FAndroidWebBrowserWindow(
|
|
WindowSettings.InitialURL,
|
|
WindowSettings.ContentsToLoad,
|
|
WindowSettings.bShowErrorMessage,
|
|
WindowSettings.bThumbMouseButtonNavigation,
|
|
WindowSettings.bUseTransparency,
|
|
bJSBindingsToLoweringEnabled,
|
|
UserAgentApplication));
|
|
|
|
{
|
|
FScopeLock Lock(&WindowInterfacesCS);
|
|
WindowInterfaces.Add(NewBrowserWindow);
|
|
}
|
|
return NewBrowserWindow;
|
|
#elif PLATFORM_IOS
|
|
// Create new window
|
|
TSharedPtr<FWebBrowserWindow> NewBrowserWindow = MakeShareable(new FWebBrowserWindow(
|
|
WindowSettings.InitialURL,
|
|
WindowSettings.ContentsToLoad,
|
|
WindowSettings.bShowErrorMessage,
|
|
WindowSettings.bThumbMouseButtonNavigation,
|
|
WindowSettings.bUseTransparency,
|
|
bJSBindingsToLoweringEnabled,
|
|
UserAgentApplication));
|
|
|
|
{
|
|
FScopeLock Lock(&WindowInterfacesCS);
|
|
WindowInterfaces.Add(NewBrowserWindow);
|
|
}
|
|
return NewBrowserWindow;
|
|
#elif PLATFORM_SPECIFIC_WEB_BROWSER
|
|
// Create new window
|
|
TSharedPtr<FWebBrowserWindow> NewBrowserWindow = MakeShareable(new FWebBrowserWindow(
|
|
WindowSettings.InitialURL,
|
|
WindowSettings.ContentsToLoad,
|
|
WindowSettings.bShowErrorMessage,
|
|
WindowSettings.bThumbMouseButtonNavigation,
|
|
WindowSettings.bUseTransparency));
|
|
|
|
{
|
|
FScopeLock Lock(&WindowInterfacesCS);
|
|
WindowInterfaces.Add(NewBrowserWindow);
|
|
}
|
|
return NewBrowserWindow;
|
|
#endif
|
|
return nullptr;
|
|
}
|
|
|
|
#if BUILD_EMBEDDED_APP
|
|
TSharedPtr<IWebBrowserWindow> FWebBrowserSingleton::CreateNativeBrowserProxy()
|
|
{
|
|
TSharedPtr<FNativeWebBrowserProxy> NewBrowserWindow = MakeShareable(new FNativeWebBrowserProxy(
|
|
bJSBindingsToLoweringEnabled
|
|
));
|
|
NewBrowserWindow->Initialize();
|
|
return NewBrowserWindow;
|
|
}
|
|
#endif //BUILD_EMBEDDED_APP
|
|
|
|
bool FWebBrowserSingleton::Tick(float DeltaTime)
|
|
{
|
|
QUICK_SCOPE_CYCLE_COUNTER(STAT_FWebBrowserSingleton_Tick);
|
|
|
|
#if WITH_CEF3
|
|
if (bAllowCEF)
|
|
{
|
|
{
|
|
FScopeLock Lock(&WindowInterfacesCS);
|
|
bool bIsSlateAwake = FSlateApplication::IsInitialized() && !FSlateApplication::Get().IsSlateAsleep();
|
|
// Remove any windows that have been deleted and check whether it's currently visible
|
|
for (int32 Index = WindowInterfaces.Num() - 1; Index >= 0; --Index)
|
|
{
|
|
if (!WindowInterfaces[Index].IsValid())
|
|
{
|
|
WindowInterfaces.RemoveAt(Index);
|
|
}
|
|
else if (bIsSlateAwake) // only check for Tick activity if Slate is currently ticking
|
|
{
|
|
TSharedPtr<FCEFWebBrowserWindow> BrowserWindow = WindowInterfaces[Index].Pin();
|
|
if(BrowserWindow.IsValid())
|
|
{
|
|
// Test if we've ticked recently. If not assume the browser window has become hidden.
|
|
BrowserWindow->CheckTickActivity();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (CEFBrowserApp != nullptr)
|
|
{
|
|
bool bForceMessageLoop = false;
|
|
GConfig->GetBool(TEXT("Browser"), TEXT("bForceMessageLoop"), bForceMessageLoop, GEngineIni);
|
|
|
|
// Get the configured minimum hertz and make sure the value is within a reasonable range
|
|
static const int MaxFrameRateClamp = 60;
|
|
int32 MinMessageLoopHz = 1;
|
|
GConfig->GetInt(TEXT("Browser"), TEXT("MinMessageLoopHertz"), MinMessageLoopHz, GEngineIni);
|
|
MinMessageLoopHz = FMath::Clamp(MinMessageLoopHz, 1, 60);
|
|
|
|
// Get the configured forced maximum hertz and make sure the value is within a reasonable range
|
|
int32 MaxForcedMessageLoopHz = 15;
|
|
GConfig->GetInt(TEXT("Browser"), TEXT("MaxForcedMessageLoopHertz"), MaxForcedMessageLoopHz, GEngineIni);
|
|
MaxForcedMessageLoopHz = FMath::Clamp(MaxForcedMessageLoopHz, MinMessageLoopHz, 60);
|
|
|
|
// @todo: Hack: We rely on OnScheduleMessagePumpWork() which tells us to drive the CEF message pump,
|
|
// there appear to be some edge cases where we might not be getting a signal from it so for the time being
|
|
// we force a minimum rates here and let it run at a configurable maximum rate when we have any WindowInterfaces.
|
|
|
|
// Convert to seconds which we'll use to compare against the time we accumulated since last pump / left till next pump
|
|
float MinMessageLoopSeconds = 1.0f / MinMessageLoopHz;
|
|
float MaxForcedMessageLoopSeconds = 1.0f / MaxForcedMessageLoopHz;
|
|
|
|
static float SecondsSinceLastPump = 0;
|
|
static float SecondsSinceLastAppFocusCheck = MaxForcedMessageLoopSeconds;
|
|
static float SecondsToNextForcedPump = MaxForcedMessageLoopSeconds;
|
|
|
|
// Accumulate time since last pump by adding DeltaTime which gives us the amount of time that has passed since last tick in seconds
|
|
SecondsSinceLastPump += DeltaTime;
|
|
SecondsSinceLastAppFocusCheck += DeltaTime;
|
|
// Time left till next pump
|
|
SecondsToNextForcedPump -= DeltaTime;
|
|
|
|
bool bWantForce = bForceMessageLoop; // True if we wish to force message pump
|
|
bool bCanForce = SecondsToNextForcedPump <= 0; // But can we?
|
|
bool bMustForce = SecondsSinceLastPump >= MinMessageLoopSeconds; // Absolutely must force (Min frequency rate hit)
|
|
if (SecondsSinceLastAppFocusCheck > MinMessageLoopSeconds && WindowInterfaces.Num() > 0)
|
|
{
|
|
SecondsSinceLastAppFocusCheck = 0;
|
|
// only check app being foreground at the min message loop rate (1hz) and if we have a browser window to save CPU
|
|
bAppIsFocused = FPlatformApplicationMisc::IsThisApplicationForeground();
|
|
}
|
|
// NOTE - bAppIsFocused could be stale if WindowInterfaces.Num() == 0
|
|
bool bAppIsFocusedAndWebWindows = WindowInterfaces.Num() > 0 && bAppIsFocused;
|
|
|
|
// 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
|
|
if (bWantForce == false && bMustForce == false && bAppIsFocusedAndWebWindows == true )
|
|
{
|
|
for (int32 Index = 0; Index < WindowInterfaces.Num(); Index++)
|
|
{
|
|
if (WindowInterfaces[Index].IsValid())
|
|
{
|
|
TSharedPtr<FCEFWebBrowserWindow> BrowserWindow = WindowInterfaces[Index].Pin();
|
|
if (BrowserWindow->GetParentWindow().IsValid())
|
|
{
|
|
TSharedPtr<SWindow> BrowserParentWindow = BrowserWindow->GetParentWindow();
|
|
if (!BrowserParentWindow->IsWindowMinimized())
|
|
{
|
|
bWantForce = true;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// tick the CEF app to determine when to run CefDoMessageLoopWork
|
|
if (CEFBrowserApp->TickMessagePump(DeltaTime, (bWantForce && bCanForce) || bMustForce))
|
|
{
|
|
SecondsSinceLastPump = 0;
|
|
SecondsToNextForcedPump = MaxForcedMessageLoopSeconds;
|
|
}
|
|
}
|
|
|
|
// Update video buffering for any windows that need it
|
|
for (int32 Index = 0; Index < WindowInterfaces.Num(); Index++)
|
|
{
|
|
if (WindowInterfaces[Index].IsValid())
|
|
{
|
|
TSharedPtr<FCEFWebBrowserWindow> BrowserWindow = WindowInterfaces[Index].Pin();
|
|
if (BrowserWindow.IsValid())
|
|
{
|
|
BrowserWindow->UpdateVideoBuffering();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
#elif PLATFORM_IOS || PLATFORM_SPECIFIC_WEB_BROWSER || (PLATFORM_ANDROID && USE_ANDROID_JNI)
|
|
FScopeLock Lock(&WindowInterfacesCS);
|
|
bool bIsSlateAwake = FSlateApplication::IsInitialized() && !FSlateApplication::Get().IsSlateAsleep();
|
|
// Remove any windows that have been deleted and check whether it's currently visible
|
|
for (int32 Index = WindowInterfaces.Num() - 1; Index >= 0; --Index)
|
|
{
|
|
if (!WindowInterfaces[Index].IsValid())
|
|
{
|
|
WindowInterfaces.RemoveAt(Index);
|
|
}
|
|
else if (bIsSlateAwake) // only check for Tick activity if Slate is currently ticking
|
|
{
|
|
TSharedPtr<IWebBrowserWindow> BrowserWindow = WindowInterfaces[Index].Pin();
|
|
if (BrowserWindow.IsValid())
|
|
{
|
|
// Test if we've ticked recently. If not assume the browser window has become hidden.
|
|
BrowserWindow->CheckTickActivity();
|
|
}
|
|
}
|
|
}
|
|
|
|
#endif
|
|
return true;
|
|
}
|
|
|
|
FString FWebBrowserSingleton::GetCurrentLocaleCode()
|
|
{
|
|
FCultureRef Culture = FInternationalization::Get().GetCurrentCulture();
|
|
FString LocaleCode = Culture->GetTwoLetterISOLanguageName();
|
|
FString Country = Culture->GetRegion();
|
|
if (!Country.IsEmpty())
|
|
{
|
|
LocaleCode = LocaleCode + TEXT("-") + Country;
|
|
}
|
|
return LocaleCode;
|
|
}
|
|
|
|
TSharedPtr<IWebBrowserCookieManager> FWebBrowserSingleton::GetCookieManager(TOptional<FString> ContextId) const
|
|
{
|
|
if (ContextId.IsSet())
|
|
{
|
|
#if WITH_CEF3
|
|
if (bAllowCEF)
|
|
{
|
|
const CefRefPtr<CefRequestContext>* ExistingContext = RequestContexts.Find(ContextId.GetValue());
|
|
|
|
if (ExistingContext && ExistingContext->get())
|
|
{
|
|
// Cache these cookie managers?
|
|
return FCefWebBrowserCookieManagerFactory::Create((*ExistingContext)->GetCookieManager(nullptr));
|
|
}
|
|
else
|
|
{
|
|
UE_LOG(LogWebBrowser, Log, TEXT("No cookie manager for ContextId=%s. Using default cookie manager"), *ContextId.GetValue());
|
|
}
|
|
}
|
|
#endif
|
|
}
|
|
// No ContextId or cookie manager instance associated with it. Use default
|
|
return DefaultCookieManager;
|
|
}
|
|
|
|
#if WITH_CEF3
|
|
bool FWebBrowserSingleton::URLRequestAllowsCredentials(const FString& URL)
|
|
{
|
|
FScopeLock Lock(&WindowInterfacesCS);
|
|
// The FCEFResourceContextHandler::OnBeforeResourceLoad call doesn't get the browser/frame associated with the load
|
|
// (because bugs) so just look at each browser and see if it thinks it knows about this URL
|
|
for (int32 Index = WindowInterfaces.Num() - 1; Index >= 0; --Index)
|
|
{
|
|
TSharedPtr<FCEFWebBrowserWindow> BrowserWindow = WindowInterfaces[Index].Pin();
|
|
if (BrowserWindow.IsValid() && BrowserWindow->URLRequestAllowsCredentials(URL))
|
|
{
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
FString FWebBrowserSingleton::GenerateWebCacheFolderName(const FString& InputPath)
|
|
{
|
|
if (InputPath.IsEmpty())
|
|
return InputPath;
|
|
|
|
// append the version of this CEF build to our requested cache folder path
|
|
// this means each new CEF build gets its own cache folder, making downgrading safe
|
|
return InputPath + "_" + MAKE_STRING(CHROME_VERSION_BUILD);
|
|
}
|
|
#endif
|
|
|
|
void FWebBrowserSingleton::ClearOldCacheFolders(const FString &CachePathRoot, const FString &CachePrefix)
|
|
{
|
|
#if WITH_CEF3
|
|
// only CEF3 currently has version dependant cache folders that may need cleanup
|
|
struct FDirectoryVisitor : public IPlatformFile::FDirectoryVisitor
|
|
{
|
|
const FString CachePrefix;
|
|
const FString CurrentCachePath;
|
|
|
|
FDirectoryVisitor(const FString &InCachePrefix, const FString &InCurrentCachePath)
|
|
: CachePrefix(InCachePrefix),
|
|
CurrentCachePath(InCurrentCachePath)
|
|
{
|
|
}
|
|
|
|
virtual bool Visit(const TCHAR* FilenameOrDirectory, bool bIsDirectory) override
|
|
{
|
|
static const FString CachePrefixSearch = "/" + CachePrefix;
|
|
if (bIsDirectory)
|
|
{
|
|
FString DirName(FilenameOrDirectory);
|
|
if (DirName.Contains(CachePrefixSearch) && DirName.Equals(CurrentCachePath)==false)
|
|
{
|
|
UE_LOG(LogWebBrowser, Log, TEXT("Old Cache folder found=%s, deleting"), *DirName);
|
|
// BUGBUG - enable this deletion once we are happy with the new CEF version rollout
|
|
// Also consider adding code to preserve the previous versions folder for a while?
|
|
/*Async<void>(EAsyncExecution::ThreadPool, [DirName]()
|
|
{
|
|
IPlatformFile::GetPlatformPhysical().DeleteDirectoryRecursively(*DirName);
|
|
});*/
|
|
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
};
|
|
|
|
// Enumerate the contents of the current directory
|
|
FDirectoryVisitor Visitor(CachePrefix, GenerateWebCacheFolderName(FPaths::Combine(CachePathRoot, CachePrefix)));
|
|
IPlatformFile::GetPlatformPhysical().IterateDirectory(*CachePathRoot, Visitor);
|
|
|
|
|
|
#endif
|
|
}
|
|
|
|
bool FWebBrowserSingleton::RegisterContext(const FBrowserContextSettings& Settings)
|
|
{
|
|
#if WITH_CEF3
|
|
if (bAllowCEF)
|
|
{
|
|
const CefRefPtr<CefRequestContext>* ExistingContext = RequestContexts.Find(Settings.Id);
|
|
|
|
if (ExistingContext != nullptr)
|
|
{
|
|
// You can't register the same context twice and
|
|
// you can't update the settings for a context that already exists
|
|
return false;
|
|
}
|
|
|
|
CefRequestContextSettings RequestContextSettings;
|
|
CefString(&RequestContextSettings.accept_language_list) = Settings.AcceptLanguageList.IsEmpty() ? TCHAR_TO_WCHAR(*GetCurrentLocaleCode()) : TCHAR_TO_WCHAR(*Settings.AcceptLanguageList);
|
|
CefString(&RequestContextSettings.cache_path) = TCHAR_TO_WCHAR(*GenerateWebCacheFolderName(Settings.CookieStorageLocation));
|
|
RequestContextSettings.persist_session_cookies = Settings.bPersistSessionCookies;
|
|
#if CEF_VERSION_MAJOR < 128
|
|
RequestContextSettings.ignore_certificate_errors = Settings.bIgnoreCertificateErrors;
|
|
#endif
|
|
|
|
//Create a new one
|
|
CefRefPtr<FCEFResourceContextHandler> ResourceContextHandler = new FCEFResourceContextHandler(this);
|
|
ResourceContextHandler->OnBeforeLoad() = Settings.OnBeforeContextResourceLoad;
|
|
RequestResourceHandlers.Add(Settings.Id, ResourceContextHandler);
|
|
CefRefPtr<CefRequestContext> RequestContext = CefRequestContext::CreateContext(RequestContextSettings, ResourceContextHandler);
|
|
RequestContexts.Add(Settings.Id, RequestContext);
|
|
SchemeHandlerFactories.RegisterFactoriesWith(RequestContext);
|
|
UE_LOG(LogWebBrowser, Log, TEXT("Registering ContextId=%s."), *Settings.Id);
|
|
return true;
|
|
}
|
|
#endif
|
|
return false;
|
|
}
|
|
|
|
bool FWebBrowserSingleton::UnregisterContext(const FString& ContextId)
|
|
{
|
|
#if WITH_CEF3
|
|
bool bFoundContext = false;
|
|
if (bAllowCEF)
|
|
{
|
|
UE_LOG(LogWebBrowser, Log, TEXT("Unregistering ContextId=%s."), *ContextId);
|
|
|
|
WaitForTaskQueueFlush();
|
|
|
|
CefRefPtr<CefRequestContext> Context;
|
|
if (RequestContexts.RemoveAndCopyValue(ContextId, Context))
|
|
{
|
|
bFoundContext = true;
|
|
Context->ClearSchemeHandlerFactories();
|
|
}
|
|
|
|
CefRefPtr<FCEFResourceContextHandler> ResourceHandler;
|
|
if (RequestResourceHandlers.RemoveAndCopyValue(ContextId, ResourceHandler))
|
|
{
|
|
ResourceHandler->OnBeforeLoad().Unbind();
|
|
}
|
|
}
|
|
return bFoundContext;
|
|
#else
|
|
return false;
|
|
#endif
|
|
}
|
|
|
|
bool FWebBrowserSingleton::RegisterSchemeHandlerFactory(FString Scheme, FString Domain, IWebBrowserSchemeHandlerFactory* WebBrowserSchemeHandlerFactory)
|
|
{
|
|
#if WITH_CEF3
|
|
if (bAllowCEF)
|
|
{
|
|
SchemeHandlerFactories.AddSchemeHandlerFactory(MoveTemp(Scheme), MoveTemp(Domain), WebBrowserSchemeHandlerFactory);
|
|
return true;
|
|
}
|
|
#endif
|
|
return false;
|
|
}
|
|
|
|
bool FWebBrowserSingleton::UnregisterSchemeHandlerFactory(IWebBrowserSchemeHandlerFactory* WebBrowserSchemeHandlerFactory)
|
|
{
|
|
#if WITH_CEF3
|
|
if (bAllowCEF)
|
|
{
|
|
SchemeHandlerFactories.RemoveSchemeHandlerFactory(WebBrowserSchemeHandlerFactory);
|
|
return true;
|
|
}
|
|
#endif
|
|
return false;
|
|
}
|
|
|
|
// Cleanup macros to avoid having them leak outside this source file
|
|
#undef CEF3_BIN_DIR
|
|
#undef CEF3_FRAMEWORK_DIR
|
|
#undef CEF3_RESOURCES_DIR
|
|
#undef CEF3_SUBPROCES_EXE
|