CEFInterfaceBrowserHandler.cpp 32 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918
  1. // Engine/Source/Runtime/WebBrowser/Private/CEF/CEFBrowserHandler.cpp
  2. #include "CEF/CEFInterfaceBrowserHandler.h"
  3. #include "HAL/PlatformApplicationMisc.h"
  4. #if WITH_CEF3
  5. //#define DEBUG_ONBEFORELOAD // Debug print beforebrowse steps
  6. #include "WebInterfaceBrowserModule.h"
  7. #include "CEFInterfaceBrowserClosureTask.h"
  8. #include "IWebInterfaceBrowserSingleton.h"
  9. #include "WebInterfaceBrowserSingleton.h"
  10. #include "CEFInterfaceBrowserPopupFeatures.h"
  11. #include "CEFWebInterfaceBrowserWindow.h"
  12. #include "CEFInterfaceBrowserByteResource.h"
  13. #include "Framework/Application/SlateApplication.h"
  14. #include "HAL/ThreadingBase.h"
  15. #include "PlatformHttp.h"
  16. #include "Misc/CommandLine.h"
  17. #define LOCTEXT_NAMESPACE "WebInterfaceBrowserHandler"
  18. #ifdef DEBUG_ONBEFORELOAD
  19. // Debug helper function to track URL loads
  20. void LogCEFLoad(const FString &Msg, CefRefPtr<CefRequest> Request)
  21. {
  22. auto url = Request->GetURL();
  23. auto type = Request->GetResourceType();
  24. if (type == CefRequest::ResourceType::RT_MAIN_FRAME || type == CefRequest::ResourceType::RT_XHR || type == CefRequest::ResourceType::RT_SUB_RESOURCE|| type == CefRequest::ResourceType::RT_SUB_FRAME)
  25. {
  26. GLog->Logf(ELogVerbosity::Display, TEXT("%s :%s type:%s"), *Msg, url.c_str(), *ResourceTypeToString(type));
  27. }
  28. }
  29. #define LOG_CEF_LOAD(MSG) LogCEFLoad(#MSG, Request)
  30. #else
  31. #define LOG_CEF_LOAD(MSG)
  32. #endif
  33. // Used to force returning custom content instead of performing a request.
  34. const FString CustomContentMethod(TEXT("X-GET-CUSTOM-CONTENT"));
  35. FCEFInterfaceBrowserHandler::FCEFInterfaceBrowserHandler(bool InUseTransparency, bool InInterceptLoadRequests, const TArray<FString>& InAltRetryDomains, const TArray<FString>& InAuthorizationHeaderAllowListURLS)
  36. : bUseTransparency(InUseTransparency),
  37. bAllowAllCookies(false),
  38. bInterceptLoadRequests(InInterceptLoadRequests),
  39. AltRetryDomains(InAltRetryDomains),
  40. AuthorizationHeaderAllowListURLS(InAuthorizationHeaderAllowListURLS)
  41. {
  42. // should we forcefully allow all cookies to be set rather than filtering a couple store side ones
  43. bAllowAllCookies = FParse::Param(FCommandLine::Get(), TEXT("CefAllowAllCookies"));
  44. }
  45. void FCEFInterfaceBrowserHandler::OnTitleChange(CefRefPtr<CefBrowser> Browser, const CefString& Title)
  46. {
  47. TSharedPtr<FCEFWebInterfaceBrowserWindow> BrowserWindow = BrowserWindowPtr.Pin();
  48. if (BrowserWindow.IsValid())
  49. {
  50. BrowserWindow->SetTitle(Title);
  51. }
  52. }
  53. void FCEFInterfaceBrowserHandler::OnAddressChange(CefRefPtr<CefBrowser> Browser, CefRefPtr<CefFrame> Frame, const CefString& Url)
  54. {
  55. if (Frame->IsMain())
  56. {
  57. TSharedPtr<FCEFWebInterfaceBrowserWindow> BrowserWindow = BrowserWindowPtr.Pin();
  58. if (BrowserWindow.IsValid())
  59. {
  60. BrowserWindow->SetUrl(Url);
  61. }
  62. }
  63. }
  64. bool FCEFInterfaceBrowserHandler::OnTooltip(CefRefPtr<CefBrowser> Browser, CefString& Text)
  65. {
  66. TSharedPtr<FCEFWebInterfaceBrowserWindow> BrowserWindow = BrowserWindowPtr.Pin();
  67. if (BrowserWindow.IsValid())
  68. {
  69. BrowserWindow->SetToolTip(Text);
  70. }
  71. return false;
  72. }
  73. bool FCEFInterfaceBrowserHandler::OnConsoleMessage(CefRefPtr<CefBrowser> Browser, cef_log_severity_t level, const CefString& Message, const CefString& Source, int Line)
  74. {
  75. ConsoleMessageDelegate.ExecuteIfBound(Browser, level, Message, Source, Line);
  76. // Return false to let it output to console.
  77. return false;
  78. }
  79. void FCEFInterfaceBrowserHandler::OnAfterCreated(CefRefPtr<CefBrowser> Browser)
  80. {
  81. if(Browser->IsPopup())
  82. {
  83. TSharedPtr<FCEFWebInterfaceBrowserWindow> BrowserWindowParent = ParentHandler.get() ? ParentHandler->BrowserWindowPtr.Pin() : nullptr;
  84. if(BrowserWindowParent.IsValid() && ParentHandler->OnCreateWindow().IsBound())
  85. {
  86. TSharedPtr<FWebInterfaceBrowserWindowInfo> NewBrowserWindowInfo = MakeShareable(new FWebInterfaceBrowserWindowInfo(Browser, this));
  87. TSharedPtr<IWebInterfaceBrowserWindow> NewBrowserWindow = IWebInterfaceBrowserModule::Get().GetSingleton()->CreateBrowserWindow(
  88. BrowserWindowParent,
  89. NewBrowserWindowInfo
  90. );
  91. {
  92. // @todo: At the moment we need to downcast since the handler does not support using the interface.
  93. TSharedPtr<FCEFWebInterfaceBrowserWindow> HandlerSpecificBrowserWindow = StaticCastSharedPtr<FCEFWebInterfaceBrowserWindow>(NewBrowserWindow);
  94. BrowserWindowPtr = HandlerSpecificBrowserWindow;
  95. }
  96. // Request a UI window for the browser. If it is not created we do some cleanup.
  97. bool bUIWindowCreated = ParentHandler->OnCreateWindow().Execute(TWeakPtr<IWebInterfaceBrowserWindow>(NewBrowserWindow), TWeakPtr<IWebInterfaceBrowserPopupFeatures>(BrowserPopupFeatures));
  98. if(!bUIWindowCreated)
  99. {
  100. NewBrowserWindow->CloseBrowser(true);
  101. }
  102. else
  103. {
  104. checkf(!NewBrowserWindow.IsUnique(), TEXT("Handler indicated that new window UI was created, but failed to save the new WebBrowserWindow instance."));
  105. }
  106. }
  107. else
  108. {
  109. Browser->GetHost()->CloseBrowser(true);
  110. }
  111. }
  112. }
  113. bool FCEFInterfaceBrowserHandler::DoClose(CefRefPtr<CefBrowser> Browser)
  114. {
  115. TSharedPtr<FCEFWebInterfaceBrowserWindow> BrowserWindow = BrowserWindowPtr.Pin();
  116. if(BrowserWindow.IsValid())
  117. {
  118. BrowserWindow->OnBrowserClosing();
  119. }
  120. #if PLATFORM_WINDOWS
  121. // If we have a window handle, we're rendering directly to the screen and not off-screen
  122. HWND NativeWindowHandle = Browser->GetHost()->GetWindowHandle();
  123. if (NativeWindowHandle != nullptr)
  124. {
  125. HWND ParentWindow = ::GetParent(NativeWindowHandle);
  126. if (ParentWindow)
  127. {
  128. HWND FocusHandle = ::GetFocus();
  129. if (FocusHandle && (FocusHandle == NativeWindowHandle || ::IsChild(NativeWindowHandle, FocusHandle)))
  130. {
  131. // Set focus to the parent window, otherwise keyboard and mouse wheel input will become wonky
  132. ::SetFocus(ParentWindow);
  133. }
  134. // CEF will send a WM_CLOSE to the parent window and potentially exit the application if we don't do this
  135. ::SetParent(NativeWindowHandle, nullptr);
  136. }
  137. }
  138. #endif
  139. return false;
  140. }
  141. void FCEFInterfaceBrowserHandler::OnBeforeClose(CefRefPtr<CefBrowser> Browser)
  142. {
  143. TSharedPtr<FCEFWebInterfaceBrowserWindow> BrowserWindow = BrowserWindowPtr.Pin();
  144. if (BrowserWindow.IsValid())
  145. {
  146. BrowserWindow->OnBrowserClosed();
  147. }
  148. }
  149. bool FCEFInterfaceBrowserHandler::OnBeforePopup( CefRefPtr<CefBrowser> Browser,
  150. CefRefPtr<CefFrame> Frame,
  151. const CefString& TargetUrl,
  152. const CefString& TargetFrameName,
  153. const CefPopupFeatures& PopupFeatures,
  154. CefWindowInfo& OutWindowInfo,
  155. CefRefPtr<CefClient>& OutClient,
  156. CefBrowserSettings& OutSettings,
  157. bool* OutNoJavascriptAccess )
  158. {
  159. FString URL = WCHAR_TO_TCHAR(TargetUrl.ToWString().c_str());
  160. FString FrameName = WCHAR_TO_TCHAR(TargetFrameName.ToWString().c_str());
  161. /* If OnBeforePopup() is not bound, we allow creating new windows as long as OnCreateWindow() is bound.
  162. The BeforePopup delegate is always executed even if OnCreateWindow is not bound to anything .
  163. */
  164. if((OnBeforePopup().IsBound() && OnBeforePopup().Execute(URL, FrameName)) || !OnCreateWindow().IsBound())
  165. {
  166. return true;
  167. }
  168. else
  169. {
  170. TSharedPtr<FCEFInterfaceBrowserPopupFeatures> NewBrowserPopupFeatures = MakeShareable(new FCEFInterfaceBrowserPopupFeatures(PopupFeatures));
  171. bool bIsDevtools = URL.Contains(TEXT("chrome-devtools"));
  172. bool shouldUseTransparency = bIsDevtools ? false : bUseTransparency;
  173. NewBrowserPopupFeatures->SetResizable(bIsDevtools); // only have the window for DevTools have resize options
  174. cef_color_t Alpha = shouldUseTransparency ? 0 : CefColorGetA(OutSettings.background_color);
  175. cef_color_t R = CefColorGetR(OutSettings.background_color);
  176. cef_color_t G = CefColorGetG(OutSettings.background_color);
  177. cef_color_t B = CefColorGetB(OutSettings.background_color);
  178. OutSettings.background_color = CefColorSetARGB(Alpha, R, G, B);
  179. CefRefPtr<FCEFInterfaceBrowserHandler> NewHandler(new FCEFInterfaceBrowserHandler(shouldUseTransparency, true /*InterceptLoadRequests*/));
  180. NewHandler->ParentHandler = this;
  181. NewHandler->SetPopupFeatures(NewBrowserPopupFeatures);
  182. OutClient = NewHandler;
  183. // Always use off screen rendering so we can integrate with our windows
  184. #if PLATFORM_LINUX
  185. OutWindowInfo.SetAsWindowless(kNullWindowHandle);
  186. #elif PLATFORM_WINDOWS
  187. OutWindowInfo.SetAsWindowless(kNullWindowHandle);
  188. OutWindowInfo.shared_texture_enabled = 0; // always render popups with the simple OSR renderer
  189. #elif PLATFORM_MAC
  190. OutWindowInfo.SetAsWindowless(kNullWindowHandle);
  191. TSharedPtr<FCEFWebInterfaceBrowserWindow> BrowserWindow = BrowserWindowPtr.Pin();
  192. if (BrowserWindow.IsValid())
  193. {
  194. OutWindowInfo.shared_texture_enabled = BrowserWindow->UsingAcceleratedPaint() ? 1 : 0; // match what other windows do
  195. }
  196. else
  197. {
  198. OutWindowInfo.shared_texture_enabled = 0;
  199. }
  200. #else
  201. OutWindowInfo.SetAsWindowless(kNullWindowHandle);
  202. #endif
  203. // We need to rely on CEF to create our window so we set the WindowInfo, BrowserSettings, Client, and then return false
  204. return false;
  205. }
  206. }
  207. bool FCEFInterfaceBrowserHandler::OnCertificateError(CefRefPtr<CefBrowser> Browser,
  208. cef_errorcode_t CertError,
  209. const CefString &RequestUrl,
  210. CefRefPtr<CefSSLInfo> SslInfo,
  211. CefRefPtr<CefRequestCallback> Callback)
  212. {
  213. // Forward the cert error to the normal load error handler
  214. CefString ErrorText = "Certificate error";
  215. OnLoadError(Browser, Browser->GetMainFrame(), CertError, ErrorText, RequestUrl);
  216. return false;
  217. }
  218. void FCEFInterfaceBrowserHandler::OnLoadError(CefRefPtr<CefBrowser> Browser,
  219. CefRefPtr<CefFrame> Frame,
  220. CefLoadHandler::ErrorCode InErrorCode,
  221. const CefString& ErrorText,
  222. const CefString& FailedUrl)
  223. {
  224. // notify browser window
  225. if (Frame->IsMain())
  226. {
  227. TSharedPtr<FCEFWebInterfaceBrowserWindow> BrowserWindow = BrowserWindowPtr.Pin();
  228. if (BrowserWindow.IsValid())
  229. {
  230. if (AltRetryDomains.Num() > 0 && AltRetryDomainIdx < (uint32)AltRetryDomains.Num())
  231. {
  232. FString Url = WCHAR_TO_TCHAR(FailedUrl.ToWString().c_str());
  233. FString OriginalUrlDomain = FPlatformHttp::GetUrlDomain(Url);
  234. if (!OriginalUrlDomain.IsEmpty())
  235. {
  236. const FString NewUrl(Url.Replace(*OriginalUrlDomain, *AltRetryDomains[AltRetryDomainIdx++]));
  237. BrowserWindow->LoadURL(NewUrl);
  238. return;
  239. }
  240. }
  241. BrowserWindow->NotifyDocumentError(InErrorCode, ErrorText, FailedUrl);
  242. }
  243. }
  244. }
  245. void FCEFInterfaceBrowserHandler::OnLoadStart(CefRefPtr<CefBrowser> Browser, CefRefPtr<CefFrame> Frame, TransitionType CefTransitionType)
  246. {
  247. }
  248. void FCEFInterfaceBrowserHandler::OnLoadingStateChange(CefRefPtr<CefBrowser> Browser, bool bIsLoading, bool bCanGoBack, bool bCanGoForward)
  249. {
  250. TSharedPtr<FCEFWebInterfaceBrowserWindow> BrowserWindow = BrowserWindowPtr.Pin();
  251. if (BrowserWindow.IsValid())
  252. {
  253. BrowserWindow->NotifyDocumentLoadingStateChange(bIsLoading);
  254. }
  255. }
  256. bool FCEFInterfaceBrowserHandler::GetRootScreenRect(CefRefPtr<CefBrowser> Browser, CefRect& Rect)
  257. {
  258. if (CefCurrentlyOn(TID_UI))
  259. {
  260. // CEF may call this off the main gamethread which slate requires, so double check here
  261. FDisplayMetrics DisplayMetrics;
  262. FSlateApplication::Get().GetDisplayMetrics(DisplayMetrics);
  263. Rect.width = DisplayMetrics.PrimaryDisplayWidth;
  264. Rect.height = DisplayMetrics.PrimaryDisplayHeight;
  265. return true;
  266. }
  267. return false;
  268. }
  269. void FCEFInterfaceBrowserHandler::GetViewRect(CefRefPtr<CefBrowser> Browser, CefRect& Rect)
  270. {
  271. TSharedPtr<FCEFWebInterfaceBrowserWindow> BrowserWindow = BrowserWindowPtr.Pin();
  272. if (BrowserWindow.IsValid())
  273. {
  274. BrowserWindow->GetViewRect(Rect);
  275. }
  276. else
  277. {
  278. // CEF requires at least a 1x1 area for painting
  279. Rect.x = Rect.y = 0;
  280. Rect.width = Rect.height = 1;
  281. }
  282. }
  283. void FCEFInterfaceBrowserHandler::OnPaint(CefRefPtr<CefBrowser> Browser,
  284. PaintElementType Type,
  285. const RectList& DirtyRects,
  286. const void* Buffer,
  287. int Width, int Height)
  288. {
  289. TSharedPtr<FCEFWebInterfaceBrowserWindow> BrowserWindow = BrowserWindowPtr.Pin();
  290. if (BrowserWindow.IsValid())
  291. {
  292. BrowserWindow->OnPaint(Type, DirtyRects, Buffer, Width, Height);
  293. }
  294. }
  295. void FCEFInterfaceBrowserHandler::OnAcceleratedPaint(CefRefPtr<CefBrowser> Browser,
  296. PaintElementType Type,
  297. const RectList& DirtyRects,
  298. void* SharedHandle)
  299. {
  300. TSharedPtr<FCEFWebInterfaceBrowserWindow> BrowserWindow = BrowserWindowPtr.Pin();
  301. if (BrowserWindow.IsValid())
  302. {
  303. BrowserWindow->OnAcceleratedPaint(Type, DirtyRects, SharedHandle);
  304. }
  305. }
  306. bool FCEFInterfaceBrowserHandler::OnCursorChange(CefRefPtr<CefBrowser> Browser, CefCursorHandle Cursor, cef_cursor_type_t Type, const CefCursorInfo& CustomCursorInfo)
  307. {
  308. TSharedPtr<FCEFWebInterfaceBrowserWindow> BrowserWindow = BrowserWindowPtr.Pin();
  309. if (BrowserWindow.IsValid())
  310. {
  311. return BrowserWindow->OnCursorChange(Cursor, Type, CustomCursorInfo);
  312. }
  313. return false;
  314. }
  315. void FCEFInterfaceBrowserHandler::OnPopupShow(CefRefPtr<CefBrowser> Browser, bool bShow)
  316. {
  317. TSharedPtr<FCEFWebInterfaceBrowserWindow> BrowserWindow = BrowserWindowPtr.Pin();
  318. if (BrowserWindow.IsValid())
  319. {
  320. BrowserWindow->ShowPopupMenu(bShow);
  321. }
  322. }
  323. void FCEFInterfaceBrowserHandler::OnPopupSize(CefRefPtr<CefBrowser> Browser, const CefRect& Rect)
  324. {
  325. TSharedPtr<FCEFWebInterfaceBrowserWindow> BrowserWindow = BrowserWindowPtr.Pin();
  326. if (BrowserWindow.IsValid())
  327. {
  328. BrowserWindow->SetPopupMenuPosition(Rect);
  329. }
  330. }
  331. bool FCEFInterfaceBrowserHandler::GetScreenInfo(CefRefPtr<CefBrowser> Browser, CefScreenInfo& ScreenInfo)
  332. {
  333. TSharedPtr<FWebInterfaceBrowserWindow> BrowserWindow = BrowserWindowPtr.Pin();
  334. ScreenInfo.depth = 24;
  335. if (BrowserWindow.IsValid() && BrowserWindow->GetParentWindow().IsValid())
  336. {
  337. ScreenInfo.device_scale_factor = BrowserWindow->GetParentWindow()->GetNativeWindow()->GetDPIScaleFactor();
  338. }
  339. else
  340. {
  341. FDisplayMetrics DisplayMetrics;
  342. FDisplayMetrics::RebuildDisplayMetrics(DisplayMetrics);
  343. ScreenInfo.device_scale_factor = FPlatformApplicationMisc::GetDPIScaleFactorAtPoint(DisplayMetrics.PrimaryDisplayWorkAreaRect.Left, DisplayMetrics.PrimaryDisplayWorkAreaRect.Top);
  344. }
  345. return true;
  346. }
  347. #if !PLATFORM_LINUX
  348. void FCEFInterfaceBrowserHandler::OnImeCompositionRangeChanged(
  349. CefRefPtr<CefBrowser> Browser,
  350. const CefRange& SelectionRange,
  351. const CefRenderHandler::RectList& CharacterBounds)
  352. {
  353. TSharedPtr<FCEFWebInterfaceBrowserWindow> BrowserWindow = BrowserWindowPtr.Pin();
  354. if (BrowserWindow.IsValid())
  355. {
  356. BrowserWindow->OnImeCompositionRangeChanged(Browser, SelectionRange, CharacterBounds);
  357. }
  358. }
  359. #endif
  360. CefResourceRequestHandler::ReturnValue FCEFInterfaceBrowserHandler::OnBeforeResourceLoad(CefRefPtr<CefBrowser> Browser, CefRefPtr<CefFrame> Frame, CefRefPtr<CefRequest> Request, CefRefPtr<CefRequestCallback> Callback)
  361. {
  362. if (Request->IsReadOnly())
  363. {
  364. LOG_CEF_LOAD("FCEFInterfaceBrowserHandler::OnBeforeResourceLoad - readonly");
  365. // we can't alter this request so just allow it through
  366. return RV_CONTINUE;
  367. }
  368. // Current thread is IO thread. We need to invoke BrowserWindow->GetResourceContent on the UI (aka Game) thread:
  369. CefPostTask(TID_UI, new FCEFInterfaceBrowserClosureTask(this, [=]()
  370. {
  371. const FString LanguageHeaderText(TEXT("Accept-Language"));
  372. const FString LocaleCode = FWebInterfaceBrowserSingleton::GetCurrentLocaleCode();
  373. CefRequest::HeaderMap HeaderMap;
  374. Request->GetHeaderMap(HeaderMap);
  375. auto LanguageHeader = HeaderMap.find(TCHAR_TO_WCHAR(*LanguageHeaderText));
  376. if (LanguageHeader != HeaderMap.end())
  377. {
  378. (*LanguageHeader).second = TCHAR_TO_WCHAR(*LocaleCode);
  379. }
  380. else
  381. {
  382. HeaderMap.insert(std::pair<CefString, CefString>(TCHAR_TO_WCHAR(*LanguageHeaderText), TCHAR_TO_WCHAR(*LocaleCode)));
  383. }
  384. LOG_CEF_LOAD( "FCEFInterfaceBrowserHandler::OnBeforeResourceLoad" );
  385. if (BeforeResourceLoadDelegate.IsBound())
  386. {
  387. // Allow appending the Authorization header if this was NOT a RT_XHR type of page load
  388. bool bAllowCredentials = URLRequestAllowsCredentials(WCHAR_TO_TCHAR(Request->GetURL().ToWString().c_str()));
  389. FRequestHeaders AdditionalHeaders;
  390. BeforeResourceLoadDelegate.Execute(Request->GetURL(), Request->GetResourceType(), AdditionalHeaders, bAllowCredentials);
  391. for (auto Iter = AdditionalHeaders.CreateConstIterator(); Iter; ++Iter)
  392. {
  393. const FString& Header = Iter.Key();
  394. const FString& Value = Iter.Value();
  395. HeaderMap.insert(std::pair<CefString, CefString>(TCHAR_TO_WCHAR(*Header), TCHAR_TO_WCHAR(*Value)));
  396. }
  397. }
  398. TSharedPtr<FCEFWebInterfaceBrowserWindow> BrowserWindow = BrowserWindowPtr.Pin();
  399. if (BrowserWindow.IsValid())
  400. {
  401. TOptional<FString> Contents = BrowserWindow->GetResourceContent(Frame, Request);
  402. if(Contents.IsSet())
  403. {
  404. Contents.GetValue().ReplaceInline(TEXT("\n"), TEXT(""), ESearchCase::CaseSensitive);
  405. Contents.GetValue().ReplaceInline(TEXT("\r"), TEXT(""), ESearchCase::CaseSensitive);
  406. // pass the text we'd like to come back as a response to the request post data
  407. CefRefPtr<CefPostData> PostData = CefPostData::Create();
  408. CefRefPtr<CefPostDataElement> Element = CefPostDataElement::Create();
  409. FTCHARToUTF8 UTF8String(*Contents.GetValue());
  410. Element->SetToBytes(UTF8String.Length(), UTF8String.Get());
  411. PostData->AddElement(Element);
  412. Request->SetPostData(PostData);
  413. // Set a custom request header, so we know the mime type if it was specified as a hash on the dummy URL
  414. std::string Url = Request->GetURL().ToString();
  415. std::string::size_type HashPos = Url.find_last_of('#');
  416. if (HashPos != std::string::npos)
  417. {
  418. std::string MimeType = Url.substr(HashPos + 1);
  419. HeaderMap.insert(std::pair<CefString, CefString>(TCHAR_TO_WCHAR(TEXT("Content-Type")), MimeType));
  420. }
  421. // Change http method to tell GetResourceHandler to return the content
  422. Request->SetMethod(TCHAR_TO_WCHAR(*CustomContentMethod));
  423. }
  424. }
  425. if (Request->IsReadOnly())
  426. {
  427. LOG_CEF_LOAD("FCEFInterfaceBrowserHandler::OnBeforeResourceLoad - readonly");
  428. }
  429. else
  430. {
  431. Request->SetHeaderMap(HeaderMap);
  432. }
  433. Callback->Continue(true);
  434. }));
  435. // Tell CEF that we're handling this asynchronously.
  436. return RV_CONTINUE_ASYNC;
  437. }
  438. void FCEFInterfaceBrowserHandler::OnResourceLoadComplete(
  439. CefRefPtr<CefBrowser> Browser,
  440. CefRefPtr<CefFrame> Frame,
  441. CefRefPtr<CefRequest> Request,
  442. CefRefPtr<CefResponse> Response,
  443. URLRequestStatus Status,
  444. int64 Received_content_length)
  445. {
  446. LOG_CEF_LOAD("FCEFInterfaceBrowserHandler::OnResourceLoadComplete");
  447. // Current thread is IO thread. We need to invoke our delegates on the UI (aka Game) thread:
  448. CefPostTask(TID_UI, new FCEFInterfaceBrowserClosureTask(this, [=]()
  449. {
  450. auto resType = Request->GetResourceType();
  451. const FString URL = WCHAR_TO_TCHAR(Request->GetURL().ToWString().c_str());
  452. if (MainFrameLoadTypes.Contains(URL))
  453. {
  454. // CEF has a bug where it confuses a MAIN_FRAME load for a XHR one, so fix it up here if we detect it.
  455. resType = CefRequest::ResourceType::RT_MAIN_FRAME;
  456. }
  457. ResourceLoadCompleteDelegate.ExecuteIfBound(Request->GetURL(), resType, Status, Received_content_length);
  458. // this load is done, clear the request from our map
  459. MainFrameLoadTypes.Remove(URL);
  460. }));
  461. }
  462. void FCEFInterfaceBrowserHandler::OnResourceRedirect(CefRefPtr<CefBrowser> browser,
  463. CefRefPtr<CefFrame> Frame,
  464. CefRefPtr<CefRequest> Request,
  465. CefRefPtr<CefResponse> Response,
  466. CefString& new_url)
  467. {
  468. LOG_CEF_LOAD("FCEFInterfaceBrowserHandler::OnResourceRedirect");
  469. // Current thread is IO thread. We need to invoke our delegates on the UI (aka Game) thread:
  470. CefPostTask(TID_UI, new FCEFInterfaceBrowserClosureTask(this, [=]()
  471. {
  472. // this load is effectively done, clear the request from our map
  473. MainFrameLoadTypes.Remove(WCHAR_TO_TCHAR(Request->GetURL().ToWString().c_str()));
  474. }));
  475. }
  476. void FCEFInterfaceBrowserHandler::OnRenderProcessTerminated(CefRefPtr<CefBrowser> Browser, TerminationStatus Status)
  477. {
  478. TSharedPtr<FCEFWebInterfaceBrowserWindow> BrowserWindow = BrowserWindowPtr.Pin();
  479. if (BrowserWindow.IsValid())
  480. {
  481. BrowserWindow->OnRenderProcessTerminated(Status);
  482. }
  483. }
  484. bool FCEFInterfaceBrowserHandler::OnBeforeBrowse(CefRefPtr<CefBrowser> Browser,
  485. CefRefPtr<CefFrame> Frame,
  486. CefRefPtr<CefRequest> Request,
  487. bool user_gesture,
  488. bool IsRedirect)
  489. {
  490. CefRequest::ResourceType RequestType = Request->GetResourceType();
  491. // We only want to append Authorization headers to main frame and similar requests
  492. // BUGBUG - in theory we want to support XHR requests that have the access-control-allow-credentials header but CEF doesn't give us preflight details here
  493. if (RequestType == CefRequest::ResourceType::RT_MAIN_FRAME || RequestType == CefRequest::ResourceType::RT_SUB_FRAME || RequestType == CefRequest::ResourceType::RT_SUB_RESOURCE)
  494. {
  495. // record that we saw this URL request as a main frame load
  496. MainFrameLoadTypes.Add(WCHAR_TO_TCHAR(Request->GetURL().ToWString().c_str()), RequestType);
  497. }
  498. // Current thread: UI thread
  499. TSharedPtr<FCEFWebInterfaceBrowserWindow> BrowserWindow = BrowserWindowPtr.Pin();
  500. if (BrowserWindow.IsValid())
  501. {
  502. LOG_CEF_LOAD("FCEFInterfaceBrowserHandler::OnBeforeBrowse");
  503. if(BrowserWindow->OnBeforeBrowse(Browser, Frame, Request, user_gesture, IsRedirect))
  504. {
  505. return true;
  506. }
  507. }
  508. return false;
  509. }
  510. CefRefPtr<CefResourceHandler> FCEFInterfaceBrowserHandler::GetResourceHandler( CefRefPtr<CefBrowser> Browser, CefRefPtr< CefFrame > Frame, CefRefPtr< CefRequest > Request )
  511. {
  512. if (Request->GetMethod() == TCHAR_TO_WCHAR(*CustomContentMethod))
  513. {
  514. // Content override header will be set by OnBeforeResourceLoad before passing the request on to this.
  515. if (Request->GetPostData() && Request->GetPostData()->GetElementCount() > 0)
  516. {
  517. // get the mime type from Content-Type header (default to text/html to support old behavior)
  518. FString MimeType = TEXT("text/html"); // default if not specified
  519. CefRequest::HeaderMap HeaderMap;
  520. Request->GetHeaderMap(HeaderMap);
  521. auto ContentOverride = HeaderMap.find(TCHAR_TO_WCHAR(TEXT("Content-Type")));
  522. if (ContentOverride != HeaderMap.end())
  523. {
  524. MimeType = WCHAR_TO_TCHAR(ContentOverride->second.ToWString().c_str());
  525. }
  526. // reply with the post data
  527. CefPostData::ElementVector Elements;
  528. Request->GetPostData()->GetElements(Elements);
  529. return new FCEFInterfaceBrowserByteResource(Elements[0], MimeType);
  530. }
  531. }
  532. return nullptr;
  533. }
  534. CefRefPtr<CefResourceRequestHandler> FCEFInterfaceBrowserHandler::GetResourceRequestHandler( CefRefPtr<CefBrowser> Browser, CefRefPtr<CefFrame> Frame,
  535. CefRefPtr<CefRequest> Request, bool is_navigation, bool is_download, const CefString& request_initiator, bool& disable_default_handling)
  536. {
  537. LOG_CEF_LOAD("FCEFInterfaceBrowserHandler::GetResourceRequestHandler");
  538. if (bInterceptLoadRequests)
  539. return this;
  540. return nullptr;
  541. }
  542. void FCEFInterfaceBrowserHandler::SetBrowserWindow(TSharedPtr<FCEFWebInterfaceBrowserWindow> InBrowserWindow)
  543. {
  544. BrowserWindowPtr = InBrowserWindow;
  545. if (InBrowserWindow.IsValid())
  546. {
  547. // Register any JS bindings that are setup in the new browser. In theory there should be 0 here as we are still being created.
  548. CefRefPtr<CefProcessMessage> SetValueMessage = CefProcessMessage::Create(TCHAR_TO_WCHAR(TEXT("CEF::STARTUP")));
  549. CefRefPtr<CefListValue> MessageArguments = SetValueMessage->GetArgumentList();
  550. CefRefPtr<CefDictionaryValue> Bindings = InBrowserWindow->GetProcessInfo();
  551. if (Bindings.get())
  552. {
  553. MessageArguments->SetDictionary(0, Bindings);
  554. }
  555. InBrowserWindow->GetCefBrowser()->GetMainFrame()->SendProcessMessage(PID_RENDERER, SetValueMessage);
  556. }
  557. }
  558. bool FCEFInterfaceBrowserHandler::OnProcessMessageReceived(CefRefPtr<CefBrowser> Browser,
  559. CefRefPtr<CefFrame> Frame,
  560. CefProcessId SourceProcess,
  561. CefRefPtr<CefProcessMessage> Message)
  562. {
  563. bool Retval = false;
  564. FString MessageName = WCHAR_TO_TCHAR(Message->GetName().ToWString().c_str());
  565. TSharedPtr<FCEFWebInterfaceBrowserWindow> BrowserWindow = BrowserWindowPtr.Pin();
  566. if (BrowserWindow.IsValid())
  567. {
  568. if (MessageName.StartsWith(TEXT("CEF::BROWSERCREATED")))
  569. {
  570. // Register any JS bindings that are setup in the new browser. In theory there should be 0 here as we are still being created.
  571. CefRefPtr<CefProcessMessage> SetValueMessage = CefProcessMessage::Create(TCHAR_TO_WCHAR(TEXT("CEF::STARTUP")));
  572. CefRefPtr<CefListValue> MessageArguments = SetValueMessage->GetArgumentList();
  573. CefRefPtr<CefDictionaryValue> Bindings = BrowserWindow->GetProcessInfo();
  574. if (Bindings.get())
  575. {
  576. MessageArguments->SetDictionary(0, Bindings);
  577. }
  578. // CEF has a race condition for newly constructed browser objects, we may route this to the wrong renderer if we send right away
  579. // so just PostTake to send this message next frame
  580. CefPostTask(TID_UI, new FCEFInterfaceBrowserClosureTask(this, [=]()
  581. {
  582. Frame->SendProcessMessage(PID_RENDERER, SetValueMessage);
  583. }));
  584. }
  585. else
  586. {
  587. Retval = BrowserWindow->OnProcessMessageReceived(Browser, Frame, SourceProcess, Message);
  588. }
  589. }
  590. return Retval;
  591. }
  592. bool FCEFInterfaceBrowserHandler::ShowDevTools(const CefRefPtr<CefBrowser>& Browser)
  593. {
  594. CefPoint Point;
  595. CefString TargetUrl = "chrome-devtools://devtools/devtools.html";
  596. CefString TargetFrameName = "devtools";
  597. CefPopupFeatures PopupFeatures;
  598. CefWindowInfo WindowInfo;
  599. CefRefPtr<CefClient> NewClient;
  600. CefBrowserSettings BrowserSettings;
  601. bool NoJavascriptAccess = false;
  602. PopupFeatures.xSet = false;
  603. PopupFeatures.ySet = false;
  604. PopupFeatures.heightSet = false;
  605. PopupFeatures.widthSet = false;
  606. PopupFeatures.menuBarVisible = false;
  607. PopupFeatures.toolBarVisible = false;
  608. PopupFeatures.statusBarVisible = false;
  609. // Set max framerate to maximum supported.
  610. BrowserSettings.windowless_frame_rate = 60;
  611. // Disable plugins
  612. BrowserSettings.plugins = STATE_DISABLED;
  613. // Dev Tools look best with a white background color
  614. BrowserSettings.background_color = CefColorSetARGB(255, 255, 255, 255);
  615. // OnBeforePopup already takes care of all the details required to ask the host application to create a new browser window.
  616. bool bSuppressWindowCreation = OnBeforePopup(Browser, Browser->GetFocusedFrame(), TargetUrl, TargetFrameName, PopupFeatures, WindowInfo, NewClient, BrowserSettings, &NoJavascriptAccess);
  617. if(! bSuppressWindowCreation)
  618. {
  619. Browser->GetHost()->ShowDevTools(WindowInfo, NewClient, BrowserSettings, Point);
  620. }
  621. return !bSuppressWindowCreation;
  622. }
  623. bool FCEFInterfaceBrowserHandler::OnKeyEvent(CefRefPtr<CefBrowser> Browser,
  624. const CefKeyEvent& Event,
  625. CefEventHandle OsEvent)
  626. {
  627. // Show dev tools on CMD/CTRL+SHIFT+I
  628. if( (Event.type == KEYEVENT_RAWKEYDOWN || Event.type == KEYEVENT_KEYDOWN || Event.type == KEYEVENT_CHAR) &&
  629. #if PLATFORM_MAC
  630. (Event.modifiers == (EVENTFLAG_COMMAND_DOWN | EVENTFLAG_SHIFT_DOWN)) &&
  631. #else
  632. (Event.modifiers == (EVENTFLAG_CONTROL_DOWN | EVENTFLAG_SHIFT_DOWN)) &&
  633. #endif
  634. (Event.windows_key_code == 'I' ||
  635. Event.unmodified_character == 'i' || Event.unmodified_character == 'I') &&
  636. IWebInterfaceBrowserModule::Get().GetSingleton()->IsDevToolsShortcutEnabled()
  637. )
  638. {
  639. return ShowDevTools(Browser);
  640. }
  641. #if PLATFORM_MAC
  642. // We need to handle standard Copy/Paste/etc... shortcuts on OS X
  643. if( (Event.type == KEYEVENT_RAWKEYDOWN || Event.type == KEYEVENT_KEYDOWN) &&
  644. (Event.modifiers & EVENTFLAG_COMMAND_DOWN) != 0 &&
  645. (Event.modifiers & EVENTFLAG_CONTROL_DOWN) == 0 &&
  646. (Event.modifiers & EVENTFLAG_ALT_DOWN) == 0 &&
  647. ( (Event.modifiers & EVENTFLAG_SHIFT_DOWN) == 0 || Event.unmodified_character == 'z' )
  648. )
  649. {
  650. CefRefPtr<CefFrame> Frame = Browser->GetFocusedFrame();
  651. if (Frame)
  652. {
  653. switch (Event.unmodified_character)
  654. {
  655. case 'a':
  656. Frame->SelectAll();
  657. return true;
  658. case 'c':
  659. Frame->Copy();
  660. return true;
  661. case 'v':
  662. Frame->Paste();
  663. return true;
  664. case 'x':
  665. Frame->Cut();
  666. return true;
  667. case 'z':
  668. if( (Event.modifiers & EVENTFLAG_SHIFT_DOWN) == 0 )
  669. {
  670. Frame->Undo();
  671. }
  672. else
  673. {
  674. Frame->Redo();
  675. }
  676. return true;
  677. }
  678. }
  679. }
  680. #endif
  681. TSharedPtr<FCEFWebInterfaceBrowserWindow> BrowserWindow = BrowserWindowPtr.Pin();
  682. if (BrowserWindow.IsValid())
  683. {
  684. return BrowserWindow->OnUnhandledKeyEvent(Event);
  685. }
  686. return false;
  687. }
  688. bool FCEFInterfaceBrowserHandler::OnJSDialog(CefRefPtr<CefBrowser> Browser, const CefString& OriginUrl, JSDialogType DialogType, const CefString& MessageText, const CefString& DefaultPromptText, CefRefPtr<CefJSDialogCallback> Callback, bool& OutSuppressMessage)
  689. {
  690. bool Retval = false;
  691. TSharedPtr<FCEFWebInterfaceBrowserWindow> BrowserWindow = BrowserWindowPtr.Pin();
  692. if (BrowserWindow.IsValid())
  693. {
  694. Retval = BrowserWindow->OnJSDialog(DialogType, MessageText, DefaultPromptText, Callback, OutSuppressMessage);
  695. }
  696. return Retval;
  697. }
  698. bool FCEFInterfaceBrowserHandler::OnBeforeUnloadDialog(CefRefPtr<CefBrowser> Browser, const CefString& MessageText, bool IsReload, CefRefPtr<CefJSDialogCallback> Callback)
  699. {
  700. bool Retval = false;
  701. TSharedPtr<FCEFWebInterfaceBrowserWindow> BrowserWindow = BrowserWindowPtr.Pin();
  702. if (BrowserWindow.IsValid())
  703. {
  704. Retval = BrowserWindow->OnBeforeUnloadDialog(MessageText, IsReload, Callback);
  705. }
  706. return Retval;
  707. }
  708. void FCEFInterfaceBrowserHandler::OnResetDialogState(CefRefPtr<CefBrowser> Browser)
  709. {
  710. TSharedPtr<FCEFWebInterfaceBrowserWindow> BrowserWindow = BrowserWindowPtr.Pin();
  711. if (BrowserWindow.IsValid())
  712. {
  713. BrowserWindow->OnResetDialogState();
  714. }
  715. }
  716. void FCEFInterfaceBrowserHandler::OnBeforeContextMenu(CefRefPtr<CefBrowser> Browser, CefRefPtr<CefFrame> Frame, CefRefPtr<CefContextMenuParams> Params, CefRefPtr<CefMenuModel> Model)
  717. {
  718. Model->Clear();
  719. }
  720. void FCEFInterfaceBrowserHandler::OnDraggableRegionsChanged(CefRefPtr<CefBrowser> Browser, CefRefPtr<CefFrame> frame, const std::vector<CefDraggableRegion>& Regions)
  721. {
  722. TSharedPtr<FCEFWebInterfaceBrowserWindow> BrowserWindow = BrowserWindowPtr.Pin();
  723. if (BrowserWindow.IsValid())
  724. {
  725. TArray<FWebInterfaceBrowserDragRegion> DragRegions;
  726. for (uint32 Idx = 0; Idx < Regions.size(); Idx++)
  727. {
  728. DragRegions.Add(FWebInterfaceBrowserDragRegion(
  729. FIntRect(Regions[Idx].bounds.x, Regions[Idx].bounds.y, Regions[Idx].bounds.x + Regions[Idx].bounds.width, Regions[Idx].bounds.y + Regions[Idx].bounds.height),
  730. Regions[Idx].draggable ? true : false));
  731. }
  732. BrowserWindow->UpdateDragRegions(DragRegions);
  733. }
  734. }
  735. CefRefPtr<CefCookieAccessFilter> FCEFInterfaceBrowserHandler::GetCookieAccessFilter(
  736. CefRefPtr<CefBrowser> Browser,
  737. CefRefPtr<CefFrame> Frame,
  738. CefRefPtr<CefRequest> Request)
  739. {
  740. FString Url = WCHAR_TO_TCHAR(Request->GetURL().ToWString().c_str());
  741. TArray<FString> UrlParts;
  742. if (Url.ParseIntoArray(UrlParts, TEXT("/"), true) >= 2)
  743. {
  744. if (UrlParts[1].Contains(TEXT(".epicgames.com")) || UrlParts[1].Contains(TEXT(".epicgames.net")))
  745. {
  746. // We only support custom cookie alteration for the epicgames domains right now.
  747. // There are limitations/bugs in CEF when the cookie filtering it on making it fail to pass cookies for some requests, so
  748. // we want to limit the scope of the filtering. See https://jira.it.epicgames.com/browse/DISTRO-1847 as an example of a bug
  749. // caused by filtering
  750. return this;
  751. }
  752. }
  753. return nullptr;
  754. }
  755. bool FCEFInterfaceBrowserHandler::CanSaveCookie(CefRefPtr<CefBrowser> browser,
  756. CefRefPtr<CefFrame> frame,
  757. CefRefPtr<CefRequest> request,
  758. CefRefPtr<CefResponse> response,
  759. const CefCookie& cookie)
  760. {
  761. if (bAllowAllCookies)
  762. {
  763. return true;
  764. }
  765. // these two cookies shouldn't be saved by the client. While we are debugging why the backend is causing them to be set filter them out
  766. if (CefString(&cookie.name).ToString() == "store-token" || CefString(&cookie.name) == "EPIC_SESSION_DIESEL")
  767. return false;
  768. return true;
  769. }
  770. bool FCEFInterfaceBrowserHandler::CanSendCookie(CefRefPtr<CefBrowser> Browser,
  771. CefRefPtr<CefFrame> Frame,
  772. CefRefPtr<CefRequest> Request,
  773. const CefCookie& Cookie)
  774. {
  775. if (bAllowAllCookies)
  776. {
  777. return true;
  778. }
  779. FString RequestURL(WCHAR_TO_TCHAR(Request->GetURL().ToWString().c_str()));
  780. FString ReffererURL(WCHAR_TO_TCHAR(Request->GetReferrerURL().ToWString().c_str()));
  781. if (ReffererURL.Contains("marketplace-website-node-launcher-") && RequestURL.Contains("graphql.epicgames.com"))
  782. {
  783. // requests from the marketplace UE4 page to graphql can exceed the header size limits so manually prune this large cookie here
  784. if (CefString(&Cookie.name).ToString() == "ecma")
  785. return false;
  786. }
  787. return true;
  788. }
  789. bool FCEFInterfaceBrowserHandler::URLRequestAllowsCredentials(const FString& URL) const
  790. {
  791. // if we inserted this URL into our map then we want to allow credentials for it
  792. if (MainFrameLoadTypes.Find(URL) != nullptr)
  793. return true;
  794. // check the explicit allowlist also
  795. for (const FString& AuthorizationHeaderAllowListURL : AuthorizationHeaderAllowListURLS)
  796. {
  797. if (URL.Contains(AuthorizationHeaderAllowListURL))
  798. {
  799. return true;
  800. }
  801. }
  802. return false;
  803. }
  804. #undef LOCTEXT_NAMESPACE
  805. #endif // WITH_CEF