// Copyright 1998-2015 Epic Games, Inc. All Rights Reserved. #include "WebBrowserPrivatePCH.h" #include "WebBrowserHandler.h" #include "WebBrowserWindow.h" #include "WebBrowserSingleton.h" #include "WebBrowserPopupFeatures.h" #define LOCTEXT_NAMESPACE "WebBrowserHandler" #if WITH_CEF3 FWebBrowserHandler::FWebBrowserHandler() : ShowErrorMessage(true) { } void FWebBrowserHandler::OnTitleChange(CefRefPtr Browser, const CefString& Title) { TSharedPtr BrowserWindow = BrowserWindowPtr.Pin(); if (BrowserWindow.IsValid()) { BrowserWindow->SetTitle(Title); } } void FWebBrowserHandler::OnAddressChange(CefRefPtr Browser, CefRefPtr Frame, const CefString& Url) { if (Frame->IsMain()) { TSharedPtr BrowserWindow = BrowserWindowPtr.Pin(); if (BrowserWindow.IsValid()) { BrowserWindow->SetUrl(Url); } } } bool FWebBrowserHandler::OnTooltip(CefRefPtr Browser, CefString& Text) { TSharedPtr BrowserWindow = BrowserWindowPtr.Pin(); if (BrowserWindow.IsValid()) { BrowserWindow->SetToolTip(Text); } return false; } void FWebBrowserHandler::OnAfterCreated(CefRefPtr Browser) { if(Browser->IsPopup()) { TSharedPtr BrowserWindowParent = BrowserWindowParentPtr.Pin(); if(BrowserWindowParent.IsValid() && BrowserWindowParent->SupportsNewWindows()) { TSharedPtr NewBrowserWindowInfo = MakeShareable(new FWebBrowserWindowInfo(Browser, this)); TSharedPtr NewBrowserWindow = IWebBrowserModule::Get().GetSingleton()->CreateBrowserWindow( BrowserWindowParent, NewBrowserWindowInfo ); { // @todo: At the moment we need to downcast since the handler does not support using the interface. TSharedPtr HandlerSpecificBrowserWindow = StaticCastSharedPtr(NewBrowserWindow); BrowserWindowPtr = HandlerSpecificBrowserWindow; } // Request a UI window for the browser. If it is not created we do some cleanup. bool bUIWindowCreated = BrowserWindowParent->RequestCreateWindow(NewBrowserWindow.ToSharedRef(), BrowserPopupFeatures); if(!bUIWindowCreated) { NewBrowserWindow->CloseBrowser(true); } } else { Browser->GetHost()->CloseBrowser(true); } } } bool FWebBrowserHandler::DoClose(CefRefPtr Browser) { TSharedPtr BrowserWindow = BrowserWindowPtr.Pin(); if(BrowserWindow.IsValid()) { BrowserWindow->OnBrowserClosing(); } return false; } void FWebBrowserHandler::OnBeforeClose(CefRefPtr Browser) { TSharedPtr BrowserWindow = BrowserWindowPtr.Pin(); if (BrowserWindow.IsValid()) { BrowserWindow->OnBrowserClosed(); } } bool FWebBrowserHandler::OnBeforePopup( CefRefPtr Browser, CefRefPtr Frame, const CefString& TargetUrl, const CefString& TArgetFrameName, const CefPopupFeatures& PopupFeatures, CefWindowInfo& WindowInfo, CefRefPtr& Client, CefBrowserSettings& Settings, bool* NoJavascriptAccess ) { // By default, we ignore any popup requests unless they are handled by us in some way. bool bSupressCEFWindowCreation = true; TSharedPtr BrowserWindow = BrowserWindowPtr.Pin(); if (BrowserWindow.IsValid()) { bSupressCEFWindowCreation = BrowserWindow->OnCefBeforePopup(TargetUrl, TArgetFrameName); if(!bSupressCEFWindowCreation) { if(BrowserWindow->SupportsNewWindows()) { CefRefPtr NewHandler(new FWebBrowserHandler); NewHandler->SetBrowserWindowParent(BrowserWindow); NewHandler->SetPopupFeatures(MakeShareable(new FWebBrowserPopupFeatures(PopupFeatures))); Client = NewHandler; CefWindowHandle ParentWindowHandle = BrowserWindow->GetCefBrowser()->GetHost()->GetWindowHandle(); // Allow overriding transparency setting for child windows bool bUseTransparency = BrowserWindow->UseTransparency() ? NewHandler->BrowserPopupFeatures->GetAdditionalFeatures().Find(TEXT("Epic_NoTransparency")) == INDEX_NONE : NewHandler->BrowserPopupFeatures->GetAdditionalFeatures().Find(TEXT("Epic_UseTransparency")) != INDEX_NONE; // Always use off screen rendering so we can integrate with our windows WindowInfo.SetAsWindowless(ParentWindowHandle, bUseTransparency); // We need to rely on CEF to create our window so we set the WindowInfo, BrowserSettings, Client, and then return false bSupressCEFWindowCreation = false; } else { bSupressCEFWindowCreation = true; } } } return bSupressCEFWindowCreation; } void FWebBrowserHandler::OnLoadError(CefRefPtr Browser, CefRefPtr Frame, CefLoadHandler::ErrorCode InErrorCode, const CefString& ErrorText, const CefString& FailedUrl) { // Don't display an error for downloaded files. if (InErrorCode == ERR_ABORTED) { return; } // notify browser window if (Frame->IsMain()) { TSharedPtr BrowserWindow = BrowserWindowPtr.Pin(); if (BrowserWindow.IsValid()) { BrowserWindow->NotifyDocumentError(); } } // Display a load error message. if (ShowErrorMessage) { FFormatNamedArguments Args; { Args.Add(TEXT("FailedUrl"), FText::FromString(FailedUrl.ToWString().c_str())); Args.Add(TEXT("ErrorText"), FText::FromString(ErrorText.ToWString().c_str())); Args.Add(TEXT("ErrorCode"), FText::AsNumber(InErrorCode)); } FText ErrorMsg = FText::Format(LOCTEXT("WebBrowserLoadError", "Failed to load URL {FailedUrl} with error {ErrorText} ({ErrorCode})."), Args); FString ErrorHTML = TEXT("

") + ErrorMsg.ToString() + TEXT("

"); Frame->LoadString(*ErrorHTML, FailedUrl); } } void FWebBrowserHandler::OnLoadStart(CefRefPtr Browser, CefRefPtr Frame) { } void FWebBrowserHandler::OnLoadingStateChange(CefRefPtr Browser, bool bIsLoading, bool bCanGoBack, bool bCanGoForward) { TSharedPtr BrowserWindow = BrowserWindowPtr.Pin(); if (BrowserWindow.IsValid()) { BrowserWindow->NotifyDocumentLoadingStateChange(bIsLoading); } } bool FWebBrowserHandler::GetViewRect(CefRefPtr Browser, CefRect& Rect) { TSharedPtr BrowserWindow = BrowserWindowPtr.Pin(); if (BrowserWindow.IsValid()) { return BrowserWindow->GetViewRect(Rect); } else { // Seems that if we return false from here or without a size > 0, popup menus will not show up. Rect.width = 800; Rect.height = 600; return true; } } void FWebBrowserHandler::OnPaint(CefRefPtr Browser, PaintElementType Type, const RectList& DirtyRects, const void* Buffer, int Width, int Height) { TSharedPtr BrowserWindow = BrowserWindowPtr.Pin(); if (BrowserWindow.IsValid()) { BrowserWindow->OnPaint(Type, DirtyRects, Buffer, Width, Height); } } void FWebBrowserHandler::OnCursorChange(CefRefPtr Browser, CefCursorHandle Cursor, CefRenderHandler::CursorType Type, const CefCursorInfo& CustomCursorInfo) { TSharedPtr BrowserWindow = BrowserWindowPtr.Pin(); if (BrowserWindow.IsValid()) { BrowserWindow->OnCursorChange(Cursor, Type, CustomCursorInfo); } } void FWebBrowserHandler::OnPopupShow(CefRefPtr Browser, bool bShow) { TSharedPtr BrowserWindow = BrowserWindowPtr.Pin(); if (BrowserWindow.IsValid()) { BrowserWindow->ShowPopupMenu(bShow); } } void FWebBrowserHandler::OnPopupSize(CefRefPtr Browser, const CefRect& Rect) { TSharedPtr BrowserWindow = BrowserWindowPtr.Pin(); if (BrowserWindow.IsValid()) { BrowserWindow->SetPopupMenuPosition(Rect); } } bool FWebBrowserHandler::OnBeforeResourceLoad(CefRefPtr Browser, CefRefPtr Frame, CefRefPtr Request) { const FString LanguageHeaderText(TEXT("Accept-Language")); const FString LocaleCode = FWebBrowserSingleton::GetCurrentLocaleCode(); CefRequest::HeaderMap HeaderMap; Request->GetHeaderMap(HeaderMap); auto LanguageHeader = HeaderMap.find(*LanguageHeaderText); if (LanguageHeader != HeaderMap.end()) { (*LanguageHeader).second = *LocaleCode; } else { HeaderMap.insert(std::pair(*LanguageHeaderText, *LocaleCode)); } Request->SetHeaderMap(HeaderMap); return false; } void FWebBrowserHandler::OnRenderProcessTerminated(CefRefPtr Browser, TerminationStatus Status) { } bool FWebBrowserHandler::OnBeforeBrowse(CefRefPtr Browser, CefRefPtr Frame, CefRefPtr Request, bool IsRedirect) { TSharedPtr BrowserWindow = BrowserWindowPtr.Pin(); if (BrowserWindow.IsValid()) { if(BrowserWindow->OnBeforeBrowse(Browser, Frame, Request, IsRedirect)) { return true; } } return false; } CefRefPtr FWebBrowserHandler::GetResourceHandler( CefRefPtr Browser, CefRefPtr< CefFrame > Frame, CefRefPtr< CefRequest > Request ) { TSharedPtr BrowserWindow = BrowserWindowPtr.Pin(); if (BrowserWindow.IsValid()) { return BrowserWindow->GetResourceHandler(Frame, Request); } return NULL; } void FWebBrowserHandler::SetBrowserWindow(TSharedPtr InBrowserWindow) { BrowserWindowPtr = InBrowserWindow; } void FWebBrowserHandler::SetBrowserWindowParent(TSharedPtr InBrowserWindow) { BrowserWindowParentPtr = InBrowserWindow; } bool FWebBrowserHandler::OnProcessMessageReceived(CefRefPtr Browser, CefProcessId SourceProcess, CefRefPtr Message) { bool Retval = false; TSharedPtr BrowserWindow = BrowserWindowPtr.Pin(); if (BrowserWindow.IsValid()) { Retval = BrowserWindow->OnProcessMessageReceived(Browser, SourceProcess, Message); } return Retval; } bool FWebBrowserHandler::ShowDevTools(const CefRefPtr& Browser) { CefPoint Point; CefString TargetUrl = "chrome-devtools://devtools/devtools.html"; CefString TargetFrameName = "devtools"; CefPopupFeatures PopupFeatures; CefWindowInfo WindowInfo; CefRefPtr NewClient; CefBrowserSettings BrowserSettings; bool NoJavascriptAccess = false; PopupFeatures.xSet = false; PopupFeatures.ySet = false; PopupFeatures.heightSet = false; PopupFeatures.widthSet = false; PopupFeatures.locationBarVisible = false; PopupFeatures.menuBarVisible = false; PopupFeatures.toolBarVisible = false; PopupFeatures.statusBarVisible = false; PopupFeatures.resizable = true; PopupFeatures.additionalFeatures = cef_string_list_alloc(); // Override the parent window setting for transparency, as the Dev Tools require a solid background CefString OverrideTransparencyFeature = "Epic_NoTransparency"; cef_string_list_append(PopupFeatures.additionalFeatures, OverrideTransparencyFeature.GetStruct()); // Set max framerate to maximum supported. BrowserSettings.windowless_frame_rate = 60; // Disable plugins BrowserSettings.plugins = STATE_DISABLED; // Dev Tools look best with a white background color BrowserSettings.background_color = CefColorSetARGB(255, 255, 255, 255); // OnBeforePopup already takes care of all the details required to ask the host application to create a new browser window. bool bSuppressWindowCreation = OnBeforePopup(Browser, Browser->GetFocusedFrame(), TargetUrl, TargetFrameName, PopupFeatures, WindowInfo, NewClient, BrowserSettings, &NoJavascriptAccess); if(! bSuppressWindowCreation) { Browser->GetHost()->ShowDevTools(WindowInfo, NewClient, BrowserSettings, Point); } return !bSuppressWindowCreation; } bool FWebBrowserHandler::OnKeyEvent(CefRefPtr Browser, const CefKeyEvent& Event, CefEventHandle OsEvent) { #if UE_BUILD_DEBUG // Show dev tools on CMD/CTRL+ALT+I if( (Event.type == KEYEVENT_RAWKEYDOWN || Event.type == KEYEVENT_KEYDOWN) && #if PLATFORM_MAC (Event.modifiers == (EVENTFLAG_COMMAND_DOWN | EVENTFLAG_SHIFT_DOWN)) && #else (Event.modifiers == (EVENTFLAG_CONTROL_DOWN | EVENTFLAG_SHIFT_DOWN)) && #endif (Event.unmodified_character == 'i' || Event.unmodified_character == 'I') ) { return ShowDevTools(Browser); } #endif #if PLATFORM_MAC // We need to handle standard Copy/Paste/etc... shortcuts on OS X if( (Event.type == KEYEVENT_RAWKEYDOWN || Event.type == KEYEVENT_KEYDOWN) && (Event.modifiers & EVENTFLAG_COMMAND_DOWN) != 0 && (Event.modifiers & EVENTFLAG_CONTROL_DOWN) == 0 && (Event.modifiers & EVENTFLAG_ALT_DOWN) == 0 && ( (Event.modifiers & EVENTFLAG_SHIFT_DOWN) == 0 || Event.unmodified_character == 'z' ) ) { CefRefPtr Frame = Browser->GetFocusedFrame(); if (Frame) { switch (Event.unmodified_character) { case 'a': Frame->SelectAll(); return true; case 'c': Frame->Copy(); return true; case 'v': Frame->Paste(); return true; case 'x': Frame->Cut(); return true; case 'z': if( (Event.modifiers & EVENTFLAG_SHIFT_DOWN) == 0 ) { Frame->Undo(); } else { Frame->Redo(); } return true; } } } #endif TSharedPtr BrowserWindow = BrowserWindowPtr.Pin(); if (BrowserWindow.IsValid()) { return BrowserWindow->OnUnhandledKeyEvent(Event); } return false; } bool FWebBrowserHandler::OnJSDialog(CefRefPtr Browser, const CefString& OriginUrl, const CefString& AcceptLang, JSDialogType DialogType, const CefString& MessageText, const CefString& DefaultPromptText, CefRefPtr Callback, bool& OutSuppressMessage) { bool Retval = false; TSharedPtr BrowserWindow = BrowserWindowPtr.Pin(); if (BrowserWindow.IsValid()) { Retval = BrowserWindow->OnJSDialog(DialogType, MessageText, DefaultPromptText, Callback, OutSuppressMessage); } return Retval; } bool FWebBrowserHandler::OnBeforeUnloadDialog(CefRefPtr Browser, const CefString& MessageText, bool IsReload, CefRefPtr Callback) { bool Retval = false; TSharedPtr BrowserWindow = BrowserWindowPtr.Pin(); if (BrowserWindow.IsValid()) { Retval = BrowserWindow->OnBeforeUnloadDialog(MessageText, IsReload, Callback); } return Retval; } void FWebBrowserHandler::OnResetDialogState(CefRefPtr Browser) { TSharedPtr BrowserWindow = BrowserWindowPtr.Pin(); if (BrowserWindow.IsValid()) { BrowserWindow->OnResetDialogState(); } } #endif // WITH_CEF #undef LOCTEXT_NAMESPACE