// Copyright Epic Games, Inc. All Rights Reserved. #include "ApplePlatformWebBrowser.h" #if !WITH_CEF3 && (PLATFORM_IOS || PLATFORM_MAC) #if PLATFORM_IOS #include "IOS/IOSView.h" #include "IOS/IOSAppDelegate.h" #else #include "Mac/CocoaWindow.h" #include "Mac/CocoaThread.h" #include "Mac/MacWindow.h" #include "Mac/MacApplication.h" #endif #include "Async/Async.h" #include "Widgets/SLeafWidget.h" #include "PlatformHttp.h" #include "HAL/PlatformProcess.h" #include "Framework/Application/SlateApplication.h" #include "Framework/Docking/TabManager.h" #include "Rendering/SlateRenderer.h" #include "Textures/SlateUpdatableTexture.h" #include "Widgets/Docking/SDockTab.h" #import #include "ExternalTexture.h" #include "WebBrowserModule.h" #include "WebViewCloseButton.h" #include "IWebBrowserSingleton.h" #include "Apple/AppleAsyncTask.h" #include "AppleCookieManager.h" #include "WebView3DInputHandler.h" DEFINE_LOG_CATEGORY(LogAppleWebBrowser); #if !UE_BUILD_SHIPPING TAutoConsoleVariable CVarWebViewEnableInspectable( TEXT("webview.EnableInspectable"), false, TEXT("Allows webview to be debuggable through Safari Web Inspector."), ECVF_Default ); #endif class SAppleWebBrowserWidget : public SViewport { SLATE_BEGIN_ARGS(SAppleWebBrowserWidget) : _InitialURL("about:blank") , _UseTransparency(false) { } SLATE_ARGUMENT(FString, InitialURL); SLATE_ARGUMENT(bool, UseTransparency); SLATE_ARGUMENT(TSharedPtr, WebBrowserWindow); SLATE_ARGUMENT(FString, UserAgentApplication); SLATE_END_ARGS() SAppleWebBrowserWidget() : WebViewWrapper(nil) {} void Construct(const FArguments& Args) { WebBrowserWindowPtr = Args._WebBrowserWindow; Is3DBrowser = false; bAcquiredInitialFocus = false; bFocused = false; bool bEnableFloatingCloseButton = false; GConfig->GetBool(TEXT("Browser"), TEXT("bEnableFloatingCloseButton"), bEnableFloatingCloseButton, GEngineIni); WebViewWrapper = [AppleWebViewWrapper alloc]; [WebViewWrapper create : this userAgentApplication : Args._UserAgentApplication.GetNSString() useTransparency : Args._UseTransparency enableFloatingCloseButton : bEnableFloatingCloseButton]; #if !PLATFORM_TVOS OnSafeFrameChangedEventHandle = FCoreDelegates::OnSafeFrameChangedEvent.AddLambda( [this]() { if (WebViewWrapper != nil) { [WebViewWrapper didRotate]; } }); TextureSamplePool = new FWebBrowserTextureSamplePool(); WebBrowserTextureSamplesQueue = MakeShared(); WebBrowserTexture = nullptr; WebBrowserMaterial = nullptr; WebBrowserBrush = nullptr; // create external texture WebBrowserTexture = NewObject((UObject*)GetTransientPackage(), NAME_None, RF_Transient | RF_Public); if (WebBrowserTexture.IsValid()) { WebBrowserTexture->UpdateResource(); WebBrowserTexture->AddToRoot(); } // create wrapper material IWebBrowserSingleton* WebBrowserSingleton = IWebBrowserModule::Get().GetSingleton(); UMaterialInterface* DefaultWBMaterial = Args._UseTransparency ? WebBrowserSingleton->GetDefaultTranslucentMaterial() : WebBrowserSingleton->GetDefaultMaterial(); if (WebBrowserSingleton && DefaultWBMaterial) { // create wrapper material WebBrowserMaterial = UMaterialInstanceDynamic::Create(DefaultWBMaterial, nullptr); if (WebBrowserMaterial.IsValid()) { WebBrowserMaterial->SetTextureParameterValue("SlateUI", WebBrowserTexture.Get()); WebBrowserMaterial->AddToRoot(); // create Slate brush WebBrowserBrush = MakeShareable(new FSlateBrush()); { WebBrowserBrush->SetResourceObject(WebBrowserMaterial.Get()); } } } #endif LoadURL(Args._InitialURL); } void UpdateFrameWithViewport(FIntPoint WindowSize, FIntPoint WindowPos) { CGRect NewFrame; #if PLATFORM_MAC TSharedPtr EnclosedWindow = WebBrowserWindowPtr.Pin()->GetParentWindow()->GetNativeWindow(); if(!Is3DBrowser) { NSWindow* ParentWindow = (NSWindow*)EnclosedWindow->GetOSWindowHandle(); float ScaleFactor = [ParentWindow backingScaleFactor]; const FVector2D CocoaPosition = FMacApplication::ConvertSlatePositionToCocoa(WindowPos.X, WindowPos.Y); WindowSize.X /= ScaleFactor; WindowSize.Y /= ScaleFactor; NSRect ParentFrame = [ParentWindow frame]; NSRect Rect = NSMakeRect(CocoaPosition.X - ParentFrame.origin.x, (CocoaPosition.Y - ParentFrame.origin.y) - WindowSize.Y, FMath::Max(WindowSize.X, 1), FMath::Max(WindowSize.Y, 1)); Rect = [ParentWindow frameRectForContentRect : Rect]; // CGRect and NSRect have the same memory layout NewFrame = NSRectToCGRect(Rect); if(PastEnclosedWindow != EnclosedWindow) { WindowFrame = NewFrame; PastEnclosedWindow = EnclosedWindow; LeaveDeadWindow(); } } #else { UIView* View = [IOSAppDelegate GetDelegate].IOSView; CGFloat ContentScaleFactor = View.contentScaleFactor; FVector2D PosVector2D = WindowPos * ContentScaleFactor; WindowPos /= ContentScaleFactor; WindowSize /= ContentScaleFactor; NewFrame.size.width = WindowSize.X; NewFrame.size.height = WindowSize.Y; NewFrame.origin.x = WindowPos.X; NewFrame.origin.y = WindowPos.Y; } #endif if(!Is3DBrowser && (bForceRecomputeWindowOnPaint || NewFrame.size.width != WindowFrame.size.width || NewFrame.size.height != WindowFrame.size.height)) { // WindowFrame is only used when we are a 2D web browser WindowFrame = NewFrame; bForceRecomputeWindowOnPaint = false; } #if PLATFORM_IOS [WebViewWrapper updateframe : WindowFrame window : nil]; #else [WebViewWrapper updateframe : WindowFrame window : EnclosedWindow.Get()]; #endif } void WriteWebBrowserTexture(AppleWebViewWrapper* NativeWebBrowserPtr, FGuid PlayerGuid, FIntPoint Size) { ENQUEUE_RENDER_COMMAND(WriteWebBrowser)( [NativeWebBrowserPtr, PlayerGuid, Size](FRHICommandListImmediate& RHICmdList) { AppleWebViewWrapper* NativeWebBrowser = NativeWebBrowserPtr; if (NativeWebBrowser == nil) { return; } FTextureRHIRef VideoTexture = [NativeWebBrowser GetVideoTexture]; bool bStaleVideoTexture = VideoTexture != nullptr && VideoTexture->GetSizeXY() != Size; if (VideoTexture == nullptr || bStaleVideoTexture) { // We need this texture to be accessible and writable from the CPU because we need to call // replaceRegion:mipmapLevel:withBytes:bytesPerRow: to copy over a screenshot captured in WKWebView // using takeSnapshotWithConfiguration:completionHandler: which gives us an UI/NSImage. const FRHITextureCreateDesc Desc = FRHITextureCreateDesc::Create2D(TEXT("SAppleWebBrowserWidget_VideoTexture"), Size, PF_R8G8B8A8) .SetFlags(ETextureCreateFlags::CPUWritable | ETextureCreateFlags::CPUReadback); VideoTexture = RHICmdList.CreateTexture(Desc); [NativeWebBrowser SetVideoTexture : VideoTexture]; if (VideoTexture == nullptr) { UE_LOG(LogAppleWebBrowser, Warning, TEXT("CreateTexture failed!")); return; } [NativeWebBrowser SetVideoTextureValid : false]; } if ([NativeWebBrowser UpdateVideoFrame : VideoTexture->GetNativeResource()]) { // if region changed, need to reregister UV scale/offset //UE_LOG(LogAppleWebBrowser, Log, TEXT("UpdateVideoFrame RT: %s"), *Params.PlayerGuid.ToString()); } if (![NativeWebBrowser IsVideoTextureValid]) { FSamplerStateInitializerRHI SamplerStateInitializer(SF_Bilinear, AM_Clamp, AM_Clamp, AM_Clamp); FSamplerStateRHIRef SamplerStateRHI = RHICreateSamplerState(SamplerStateInitializer); FExternalTextureRegistry::Get().RegisterExternalTexture(PlayerGuid, VideoTexture, SamplerStateRHI); [NativeWebBrowser SetVideoTextureValid : true]; } }); } void Tick(const FGeometry& AllottedGeometry, const double InCurrentTime, const float InDeltaTime) { if (WebViewWrapper != nil) { if (WebBrowserWindowPtr.IsValid()) { TSharedPtr WebBrowserWindow = WebBrowserWindowPtr.Pin(); WebBrowserWindow->SetTickLastFrame(); if (TSharedPtr ParentWindow = WebBrowserWindow->GetParentWindow()) { bool ShouldSet3DBrowser = ParentWindow->IsVirtualWindow(); if (Is3DBrowser != ShouldSet3DBrowser) { Is3DBrowser = ShouldSet3DBrowser; [WebViewWrapper set3D : Is3DBrowser]; } } } // If we are a 3D browser, we should let the Widget Interaction component fully control focus // Otherwise, to keep parity with CEF, we should initially gain focus on the WebBrowser widget. if(!bAcquiredInitialFocus && !Is3DBrowser && InKeyWindow()) { FSlateApplication::Get().SetKeyboardFocus(SharedThis(this), EFocusCause::SetDirectly); FSlateApplication::Get().ReleaseAllPointerCapture(); bAcquiredInitialFocus = true; } FVector2D Position = AllottedGeometry.GetAccumulatedRenderTransform().GetTranslation(); FVector2D Size = TransformVector(AllottedGeometry.GetAccumulatedRenderTransform(), AllottedGeometry.GetLocalSize()); FSlateRect SlateRect = AllottedGeometry.GetRenderBoundingRect(); UpdateFrameWithViewport(FIntPoint(static_cast(Size.X), static_cast(Size.Y)), FIntPoint(static_cast(Position.X), static_cast(Position.Y))); #if !PLATFORM_TVOS if (Is3DBrowser) { if (WebBrowserTexture.IsValid()) { TSharedPtr WebBrowserTextureSample; WebBrowserTextureSamplesQueue->Peek(WebBrowserTextureSample); WebBrowserTexture->TickResource(WebBrowserTextureSample); } if (WebBrowserTexture.IsValid()) { TSharedPtr WebBrowserWindow = WebBrowserWindowPtr.Pin(); bool TextureWritten = false; // GetViewportSize not 100% accurate in 3D mode, we use it as a // reasonable default if we need to only FIntPoint ViewportSize = WebBrowserWindow->GetViewportSize(); if(TSharedPtr ParentWindow = WebBrowserWindow->GetParentWindow()) { FVector2f WindowSize = ParentWindow->GetSizeInScreen(); ViewportSize.X = static_cast(WindowSize.X); ViewportSize.Y = static_cast(WindowSize.Y); #if !PLATFORM_MAC // needs to be on main thread for getScreenScale dispatch_async(dispatch_get_main_queue(), ^ { FIntPoint ScaledViewportSize = ViewportSize; CGFloat ScreenScale = [WebViewWrapper getScreenScale]; ScaledViewportSize.X *= ScreenScale; ScaledViewportSize.Y *= ScreenScale; WriteWebBrowserTexture(WebViewWrapper, WebBrowserTexture->GetExternalTextureGuid(), ScaledViewportSize); }); TextureWritten = true; #endif } if(!TextureWritten) { WriteWebBrowserTexture(WebViewWrapper, WebBrowserTexture->GetExternalTextureGuid(), ViewportSize); } } } #endif } } int32 OnPaint(const FPaintArgs& Args, const FGeometry& AllottedGeometry, const FSlateRect& MyCullingRect, FSlateWindowElementList& OutDrawElements, int32 LayerId, const FWidgetStyle& InWidgetStyle, bool bParentEnabled) const { #if !PLATFORM_TVOS bool bIsVisible = !WebBrowserWindowPtr.IsValid() || WebBrowserWindowPtr.Pin()->IsVisible(); if (bIsVisible && Is3DBrowser && WebBrowserBrush.IsValid()) { FSlateDrawElement::MakeBox(OutDrawElements, LayerId, AllottedGeometry.ToPaintGeometry(), WebBrowserBrush.Get(), ESlateDrawEffect::None); } #endif return LayerId; } virtual FVector2D ComputeDesiredSize(float) const override { return FVector2D(640, 480); } void LoadURL(const FString& InNewURL) { if (WebViewWrapper != nil) { [WebViewWrapper loadurl : [NSURL URLWithString : [NSString stringWithUTF8String : TCHAR_TO_UTF8(*InNewURL)]]]; } } void LoadString(const FString& InContents, const FString& InDummyURL) { if (WebViewWrapper != nil) { [WebViewWrapper loadstring : [NSString stringWithUTF8String : TCHAR_TO_UTF8(*InContents)] dummyurl : [NSURL URLWithString : [NSString stringWithUTF8String : TCHAR_TO_UTF8(*InDummyURL)]]]; } } void StopLoad() { if (WebViewWrapper != nil) { [WebViewWrapper stopLoading]; } } void Reload() { if (WebViewWrapper != nil) { [WebViewWrapper reload]; } } void Close() { #if !PLATFORM_TVOS FCoreDelegates::OnSafeFrameChangedEvent.Remove(OnSafeFrameChangedEventHandle); #endif if (WebViewWrapper != nil) { [WebViewWrapper close]; [WebViewWrapper release]; WebViewWrapper = nil; } WebBrowserWindowPtr.Reset(); #if !PLATFORM_TVOS delete TextureSamplePool; TextureSamplePool = nullptr; WebBrowserTextureSamplesQueue->RequestFlush(); if (WebBrowserBrush.IsValid()) { WebBrowserBrush->SetResourceObject(nullptr); } if (WebBrowserMaterial.IsValid()) { WebBrowserMaterial->RemoveFromRoot(); WebBrowserMaterial = nullptr; } if (WebBrowserTexture.IsValid()) { WebBrowserTexture->RemoveFromRoot(); WebBrowserTexture = nullptr; } #endif } void ShowFloatingCloseButton(bool bShow, bool bDraggable) { if (WebViewWrapper != nil) { [WebViewWrapper showFloatingCloseButton: bShow setDraggable: bDraggable]; } } void GoBack() { if (WebViewWrapper != nil) { [WebViewWrapper goBack]; } } void GoForward() { if (WebViewWrapper != nil) { [WebViewWrapper goForward]; } } bool CanGoBack() { if (WebViewWrapper != nil) { return [WebViewWrapper canGoBack]; } return false; } bool CanGoForward() { if (WebViewWrapper != nil) { return [WebViewWrapper canGoForward]; } return false; } void SetWebBrowserVisibility(bool InIsVisible) { if (WebViewWrapper != nil) { UE_LOG(LogAppleWebBrowser, Warning, TEXT("SetWebBrowserVisibility %d!"), InIsVisible); [WebViewWrapper setVisibility : InIsVisible]; } } bool HandleOnBeforePopup(const FString& UrlStr, const FString& FrameName) { TSharedPtr BrowserWindow = WebBrowserWindowPtr.Pin(); if (BrowserWindow.IsValid() && BrowserWindow->OnBeforePopup().IsBound()) { return BrowserWindow->OnBeforePopup().Execute(UrlStr, FrameName); } return false; } bool HandleShouldOverrideUrlLoading(const FString& Url) { // UE-347936 Abnormal requests in EDA webpage, ignore these if (WebBrowserWindowPtr.IsValid() && !Url.Equals(TEXT("about:blank")) && !Url.Equals(TEXT("about:srcdoc"))) { // Capture vars needed for AsyncTask NSString* UrlString = [NSString stringWithUTF8String : TCHAR_TO_UTF8(*Url)]; TWeakPtr AsyncWebBrowserWindowPtr = WebBrowserWindowPtr; // Notify on the game thread [FAppleAsyncTask CreateTaskWithBlock : ^ bool(void) { TSharedPtr BrowserWindow = AsyncWebBrowserWindowPtr.Pin(); if (BrowserWindow.IsValid()) { if (BrowserWindow->OnBeforeBrowse().IsBound()) { FWebNavigationRequest RequestDetails; RequestDetails.bIsRedirect = false; RequestDetails.bIsMainFrame = true; // shouldOverrideUrlLoading is only called on the main frame BrowserWindow->OnBeforeBrowse().Execute(UrlString, RequestDetails); BrowserWindow->SetTitle(""); } } return true; }]; } return true; } void HandleReceivedTitle(const FString& Title) { if (WebBrowserWindowPtr.IsValid()) { TSharedPtr BrowserWindow = WebBrowserWindowPtr.Pin(); if (BrowserWindow.IsValid() && !BrowserWindow->GetTitle().Equals(Title)) { BrowserWindow->SetTitle(Title); } } } void ProcessScriptMessage(const FString& InMessage) { if (WebBrowserWindowPtr.IsValid()) { FString Message = InMessage; TWeakPtr AsyncWebBrowserWindowPtr = WebBrowserWindowPtr; [FAppleAsyncTask CreateTaskWithBlock : ^ bool(void) { TSharedPtr BrowserWindow = AsyncWebBrowserWindowPtr.Pin(); if (BrowserWindow.IsValid()) { TArray Params; Message.ParseIntoArray(Params, TEXT("/"), false); if (Params.Num() > 0) { for (int I = 0; I < Params.Num(); I++) { Params[I] = FPlatformHttp::UrlDecode(Params[I]); } FString Command = Params[0]; Params.RemoveAt(0, 1); BrowserWindow->OnJsMessageReceived(Command, Params, ""); } else { GLog->Logf(ELogVerbosity::Error, TEXT("Invalid message from browser view: %s"), *Message); } } return true; }]; } } void HandlePageLoad(const FString& InCurrentUrl, bool bIsLoading) { if (WebBrowserWindowPtr.IsValid()) { TSharedPtr BrowserWindow = WebBrowserWindowPtr.Pin(); if (BrowserWindow.IsValid()) { BrowserWindow->NotifyDocumentLoadingStateChange(InCurrentUrl, bIsLoading); } } } void HandleReceivedError(int ErrorCode, const FString& InCurrentUrl) { if (WebBrowserWindowPtr.IsValid()) { TSharedPtr BrowserWindow = WebBrowserWindowPtr.Pin(); if (BrowserWindow.IsValid()) { BrowserWindow->NotifyDocumentError(InCurrentUrl, ErrorCode); } } } void ExecuteJavascript(const FString& Script) { if (WebViewWrapper != nil) { [WebViewWrapper executejavascript : [NSString stringWithUTF8String : TCHAR_TO_UTF8(*Script)]]; } } void FloatingCloseButtonPressed() { if (WebBrowserWindowPtr.IsValid()) { TWeakPtr AsyncWebBrowserWindowPtr = WebBrowserWindowPtr; #if !PLATFORM_MAC [FAppleAsyncTask CreateTaskWithBlock : ^ bool(void) { if (TSharedPtr BrowserWindow = AsyncWebBrowserWindowPtr.Pin()) { BrowserWindow->FloatingCloseButtonPressed(); } return true; }]; #endif } } void LeaveDeadWindow() { if(WebViewWrapper != nil) { [WebViewWrapper leaveDeadWindow]; } } void OnFocus(bool SetFocus) { if(SetFocus && !Is3D()) { FSlateApplication::Get().ReleaseAllPointerCapture(); } else if(!FSlateApplication::Get().IsActive() && bFocused) { // we changed windows but we still want to retain focus and prevent // mouse capture since we were the last widget focused. bAcquiredInitialFocus = false; } bFocused = SetFocus; } bool Is3D() const { return Is3DBrowser; } bool InKeyWindow() { return WebViewWrapper != nil && [WebViewWrapper inKeyWindow]; } void OnActiveTabChanged(TSharedPtr ActiveTab, TSharedPtr PreviouslyActiveTab) { } ~SAppleWebBrowserWidget() { Close(); } protected: mutable __strong AppleWebViewWrapper* WebViewWrapper; private: TWeakPtr PastEnclosedWindow; TWeakPtr WebBrowserWindowPtr; /** Enable 3D appearance */ bool Is3DBrowser; bool bAcquiredInitialFocus; bool bFocused; bool bForceRecomputeWindowOnPaint = false; #if !PLATFORM_TVOS /** The external texture to render the webbrowser output. */ TWeakObjectPtr WebBrowserTexture; /** The material for the external texture. */ TWeakObjectPtr WebBrowserMaterial; /** The Slate brush that renders the material. */ TSharedPtr WebBrowserBrush; /** The sample queue. */ TSharedPtr WebBrowserTextureSamplesQueue; /** Texture sample object pool. */ FWebBrowserTextureSamplePool* TextureSamplePool; // Handle to detect changes in valid area to move the floating close button */ FDelegateHandle OnSafeFrameChangedEventHandle; #endif CGRect WindowFrame; }; @implementation AppleWebViewWrapper #if !PLATFORM_TVOS @synthesize WebView; #if PLATFORM_IOS @synthesize CloseButton; #endif @synthesize WebViewContainer; @synthesize UserContentController; #endif @synthesize NextURL; @synthesize NextContent; #if PLATFORM_MAC @synthesize GameWindowView; #endif -(void)create:(SAppleWebBrowserWidget*)InWebBrowserWidget userAgentApplication: (NSString*)UserAgentApplication useTransparency : (bool)InUseTransparency enableFloatingCloseButton : (bool)bEnableFloatingCloseButton; { WebBrowserWidget = InWebBrowserWidget; NextURL = nil; NextContent = nil; VideoTexture = nil; bNeedsAddToView = true; Is3DBrowser = false; bVideoTextureValid = false; #if !UE_BUILD_SHIPPING BOOL Inspectable = CVarWebViewEnableInspectable.GetValueOnAnyThread()? YES : NO; #endif #if PLATFORM_MAC GameWindowView = nil; #endif #if !PLATFORM_TVOS dispatch_async(dispatch_get_main_queue(), ^ { #if PLATFORM_IOS self.WebViewContainer = [[UIView alloc]initWithFrame:CGRectMake(1, 1, 100, 100)]; [self.WebViewContainer setOpaque : NO]; [self.WebViewContainer setBackgroundColor : [UIColor clearColor]]; #else self.WebViewContainer = [[NSView alloc]initWithFrame:CGRectMake(1, 1, 100, 100)]; #endif WKWebViewConfiguration* theConfiguration = [[WKWebViewConfiguration alloc] init]; self.UserContentController = [[WKUserContentController alloc] init]; theConfiguration.websiteDataStore = [WKWebsiteDataStore dataStoreForIdentifier: FAppleCookieManager::CookieManagerIdentifier]; [theConfiguration.preferences setValue:@YES forKey:@"allowFileAccessFromFileURLs"]; [theConfiguration.preferences setValue:@YES forKey:@"developerExtrasEnabled"]; NSString* MessageHandlerName = [NSString stringWithFString : FMobileJSScripting::JSMessageHandler]; [self.UserContentController addScriptMessageHandler:self name: MessageHandlerName]; theConfiguration.applicationNameForUserAgent = UserAgentApplication; theConfiguration.userContentController = self.UserContentController; self.WebView = [[WKWebView alloc]initWithFrame:CGRectMake(1, 1, 100, 100)configuration : theConfiguration]; #if !UE_BUILD_SHIPPING self.WebView.inspectable = Inspectable; #endif [self.WebViewContainer addSubview : WebView]; self.WebView.navigationDelegate = self; self.WebView.UIDelegate = self; #if PLATFORM_IOS self.WebView.scrollView.bounces = NO; if (InUseTransparency) { [self.WebView setOpaque : NO]; [self.WebView setBackgroundColor : [UIColor clearColor]]; } else { [self.WebView setOpaque : YES]; } if (bEnableFloatingCloseButton) { CloseButton = MakeCloseButton(); [self.WebView addSubview : CloseButton]; CloseButton.TapHandler = [^ { // WebBrowserWidget should have the same lifecycle as this // CloseButton (see [AppleWebViewWrapper cleanup], // which is only called by SAppleWebBrowserWidget::Close()), // so using this raw ptr should be safe. Previously, a // shared ptr was used, but the shared ptr was created in the // SAppleWebBrowserWidget's Construct function, // which caused strange behavior with the TSharedRef SNew creates, // potentially leading to over-releasing. if(WebBrowserWidget != nil) { WebBrowserWidget->FloatingCloseButtonPressed(); } } copy]; } #endif [theConfiguration release]; [self setDefaultVisibility]; }); #endif } -(void)didRotate; { #if !PLATFORM_TVOS && !PLATFORM_MAC dispatch_async(dispatch_get_main_queue(), ^ { if (CloseButton) { [CloseButton setupLayout]; } }); #endif } -(void)cleanup; { #if !PLATFORM_TVOS if(self.UserContentController != nil) { [self.UserContentController removeAllScriptMessageHandlers]; [self.UserContentController removeAllUserScripts]; [self.UserContentController release]; self.UserContentController = nil; } if (self.WebView != nil) { #if PLATFORM_MAC if(self.WebView.window.firstResponder == self.WebView) { [self.WebView.window makeFirstResponder: nil]; } #endif self.WebView.navigationDelegate = nil; self.WebView.UIDelegate = nil; [self.WebView removeFromSuperview]; [self.WebViewContainer removeFromSuperview]; [self.WebView release]; [self.WebViewContainer release]; self.WebView = nil; self.WebViewContainer = nil; WebBrowserWidget = nil; } #if !PLATFORM_MAC if (CloseButton != nil) { [CloseButton release]; CloseButton = nil; } #endif #endif } -(void)close; { dispatch_async(dispatch_get_main_queue(), ^ { [self cleanup]; }); } -(void)showFloatingCloseButton:(BOOL)bShow setDraggable:(BOOL)bDraggable; { #if !PLATFORM_TVOS && !PLATFORM_MAC dispatch_async(dispatch_get_main_queue(), ^ { if (CloseButton) { [CloseButton showButton:bShow setDraggable: bDraggable]; } else { UE_LOG(LogAppleWebBrowser, Warning, TEXT("[PlatformWebBrowser]: Close button not enabled in config. Add config for Engine:[Browser]bEnableFloatingCloseButton=true")); } }); #endif } -(void)dealloc; { [self cleanup]; [NextContent release]; NextContent = nil; [NextURL release]; NextURL = nil; [super dealloc]; } -(void)leaveDeadWindow; { if(!bNeedsAddToView && !Is3DBrowser) { bNeedsAddToView = true; } } -(void)updateframe:(CGRect)InFrame window:(FGenericWindow*)EnclosedWindow; { self.DesiredFrame = InFrame; #if !UE_BUILD_SHIPPING BOOL Inspectable = CVarWebViewEnableInspectable.GetValueOnAnyThread()? YES : NO; #endif #if !PLATFORM_TVOS dispatch_async(dispatch_get_main_queue(), ^ { if (self.WebView != nil) { #if !UE_BUILD_SHIPPING self.WebView.inspectable = Inspectable; #endif #if PLATFORM_MAC if(Is3DBrowser) { if(bNeedsAddToView || !Current3DWindow.IsValid()) { // we handle display of 3D WebViews but choose to still add it to the view hierarchy // as a completely transparent view for better integration // (allows audio playback, some tick-like JS events dispatched for us, probably some other stuff) self.WebViewContainer.alphaValue = 0.0; bNeedsAddToView = false; AsyncTask(ENamedThreads::Type::GameThread, [self]() { if (const TSharedPtr ActiveWindow = FSlateApplication::Get().GetActiveTopLevelWindow()) { FMacWindow* MacGameWindow = static_cast(ActiveWindow->GetNativeWindow().Get()); if(MacGameWindow != nullptr) { FCocoaWindow* GameCocoaWindow = MacGameWindow->GetWindowHandle(); Current3DWindow = ActiveWindow; self.GameWindowView = GameCocoaWindow.openGLView; } } else { bNeedsAddToView = true; } }); } else if(GameWindowView != nil) { [self.GameWindowView addSubview : WebViewContainer]; self.GameWindowView = nil; } } else { self.WebViewContainer.frame = self.DesiredFrame; self.WebView.frame = WebViewContainer.bounds; } #else if(!Is3DBrowser) { self.WebViewContainer.frame = self.DesiredFrame; self.WebView.frame = WebViewContainer.bounds; } #endif if (bNeedsAddToView) { bNeedsAddToView = false; #if PLATFORM_IOS [self.WebViewContainer setOpaque : NO]; [self.WebViewContainer setBackgroundColor : [UIColor clearColor]]; [[IOSAppDelegate GetDelegate].IOSView addSubview : WebViewContainer]; if (CloseButton != nil) { [CloseButton setupLayout]; } #else FCocoaWindow *CocoaWindow = ((FMacWindow*)EnclosedWindow)->GetWindowHandle(); NSView *CocoaWindowView = CocoaWindow.openGLView; [CocoaWindowView addSubview : self.WebViewContainer]; #endif } else { if (NextContent != nil) { // Load web content from string [self.WebView loadHTMLString : NextContent baseURL : NextURL]; NextContent = nil; NextURL = nil; } else if (NextURL != nil) { // Load web content from URL NSURLRequest *nsrequest = [NSURLRequest requestWithURL : NextURL]; [self.WebView loadRequest : nsrequest]; NextURL = nil; } } } }); #endif } -(NSString *)UrlDecode:(NSString *)stringToDecode { NSString *result = [stringToDecode stringByReplacingOccurrencesOfString : @"+" withString:@" "]; result = [result stringByRemovingPercentEncoding]; return result; } #if !PLATFORM_TVOS -(void)userContentController:(WKUserContentController *)userContentController didReceiveScriptMessage : (WKScriptMessage *)message { if ([message.body isKindOfClass : [NSString class]]) { NSString *Message = message.body; if (WebBrowserWidget != nil && Message != nil) { //NSLog(@"Received message %@", Message); WebBrowserWidget->ProcessScriptMessage(Message); } } } #endif -(void)executejavascript:(NSString*)InJavaScript { #if !PLATFORM_TVOS dispatch_async(dispatch_get_main_queue(), ^ { const char* InJavaScriptCString = [InJavaScript UTF8String]; FString InJavaScriptFString = FString(InJavaScriptCString); //NSLog(@"executejavascript %@", InJavaScript); [self.WebView evaluateJavaScript : InJavaScript completionHandler : ^(id Object, NSError* Error) { if(Error != nil) { const char *domainString = [Error.domain UTF8String]; /*UE_LOG(LogAppleWebBrowser, Error, TEXT("executeJavaScript(\"%s\") errored with NSError %d in domain %hs!"), *InJavaScriptFString, Error.code, domainString);*/ } }]; }); #endif } -(void)loadurl:(NSURL*)InURL; { dispatch_async(dispatch_get_main_queue(), ^ { self.NextURL = InURL; }); } -(void)loadstring:(NSString*)InString dummyurl : (NSURL*)InURL; { dispatch_async(dispatch_get_main_queue(), ^ { self.NextContent = InString; self.NextURL = InURL; }); } -(void)set3D:(bool)InIs3DBrowser; { dispatch_async(dispatch_get_main_queue(), ^ { if (Is3DBrowser != InIs3DBrowser) { //default is 2D Is3DBrowser = InIs3DBrowser; [self setDefaultVisibility]; } }); } -(void)setDefaultVisibility; { #if !PLATFORM_TVOS dispatch_async(dispatch_get_main_queue(), ^ { if (Is3DBrowser) { [self.WebViewContainer setHidden : YES]; } else { [self.WebViewContainer setHidden : NO]; } }); #endif } -(void)setVisibility:(bool)InIsVisible; { #if !PLATFORM_TVOS dispatch_async(dispatch_get_main_queue(), ^ { if (InIsVisible) { [self setDefaultVisibility]; } else { [self.WebViewContainer setHidden : YES]; } }); #endif } -(void)stopLoading; { #if !PLATFORM_TVOS dispatch_async(dispatch_get_main_queue(), ^ { [self.WebView stopLoading]; }); #endif } -(void)reload; { #if !PLATFORM_TVOS dispatch_async(dispatch_get_main_queue(), ^ { [self.WebView reload]; }); #endif } -(void)goBack; { #if !PLATFORM_TVOS dispatch_async(dispatch_get_main_queue(), ^ { [self.WebView goBack]; }); #endif } -(void)goForward; { #if !PLATFORM_TVOS dispatch_async(dispatch_get_main_queue(), ^ { [self.WebView goForward]; }); #endif } -(bool)canGoBack; { #if PLATFORM_TVOS return false; #else return [self.WebView canGoBack]; #endif } -(bool)canGoForward; { #if PLATFORM_TVOS return false; #else return [self.WebView canGoForward]; #endif } -(FTextureRHIRef)GetVideoTexture; { return VideoTexture; } -(void)SetVideoTexture:(FTextureRHIRef)Texture; { VideoTexture = Texture; } -(void)SetVideoTextureValid:(bool)Condition; { bVideoTextureValid = Condition; } -(bool)IsVideoTextureValid; { return bVideoTextureValid; } -(bool)UpdateVideoFrame:(void*)ptr; { #if !PLATFORM_TVOS @synchronized(self) // Briefly block render thread { id ptrToMetalTexture = (id)ptr; NSUInteger width = [ptrToMetalTexture width]; NSUInteger height = [ptrToMetalTexture height]; [self updateWebViewMetalTexture : ptrToMetalTexture]; } #endif return true; } #if !PLATFORM_MAC -(CGFloat)getScreenScale { #if !PLATFORM_VISIONOS for(UIScene* scene in UIApplication.sharedApplication.connectedScenes) { if([scene isMemberOfClass:[UIWindowScene class]]) { for(UIWindow* window in ((UIWindowScene*)scene).windows) { if(window.isKeyWindow) { return ((UIWindowScene*)scene).screen.scale; } } } } #endif return 1; } #endif -(void)updateWebViewMetalTexture:(id)texture { #if !PLATFORM_TVOS dispatch_async(dispatch_get_main_queue(), ^ { @autoreleasepool { WKSnapshotConfiguration *SnapshotConfig = [[WKSnapshotConfiguration alloc] init]; NSUInteger TextureWidth = [texture width]; NSUInteger TextureHeight = [texture height]; #if PLATFORM_MAC NSUInteger Width = TextureWidth; NSUInteger Height = TextureHeight; WebView.frame = CGRectMake(0, 0, Width, Height); #else float ScreenScale = [self getScreenScale]; NSUInteger Width = TextureWidth / ScreenScale; NSUInteger Height = TextureHeight / ScreenScale; UIView* View = [IOSAppDelegate GetDelegate].IOSView; CGFloat ContentScaleFactor = View.contentScaleFactor; WebView.frame = CGRectMake(0, 0, ContentScaleFactor * Width, ContentScaleFactor * Height); #endif SnapshotConfig.rect = CGRectMake(0, 0, WebView.bounds.size.width, WebView.bounds.size.height); [WebView takeSnapshotWithConfiguration : SnapshotConfig completionHandler : #if PLATFORM_MAC ^ void(NSImage *Image, NSError *Error) #else ^ void(UIImage *Image, NSError *Error) #endif { if(Error != nil) { UE_LOG(LogAppleWebBrowser, Error, TEXT("takeSnapshotWithConfiguration errored with NSError %d!"), Error.code); [SnapshotConfig release]; return; } CGColorSpaceRef ColorSpace = CGColorSpaceCreateDeviceRGB(); CGContextRef WebViewContext = CGBitmapContextCreate(NULL, TextureWidth, TextureHeight, 8, 4 * TextureWidth, ColorSpace, (CGBitmapInfo)kCGImageAlphaPremultipliedLast); #if PLATFORM_MAC NSGraphicsContext *NewGfxContext = [NSGraphicsContext graphicsContextWithCGContext : WebViewContext flipped : NO]; NSGraphicsContext *OldGfxContext = NSGraphicsContext.currentContext; NSGraphicsContext.currentContext = NewGfxContext; [Image drawInRect: WebView.frame]; NSGraphicsContext.currentContext = OldGfxContext; #else CGContextDrawImage(WebViewContext, CGRectMake(0, 0, TextureWidth, TextureHeight), Image.CGImage); #endif [texture replaceRegion : MTLRegionMake2D(0, 0, TextureWidth, TextureHeight) mipmapLevel : 0 withBytes : CGBitmapContextGetData(WebViewContext) bytesPerRow : 4 * TextureWidth]; Image = nil; CGColorSpaceRelease(ColorSpace); CGContextRelease(WebViewContext); [SnapshotConfig release]; }]; } }); #endif } -(bool)inKeyWindow; { #if PLATFORM_TVOS return YES; #else if(self.WebViewContainer != nil && [self.WebViewContainer window] != nil) { return [[self.WebViewContainer window] isKeyWindow]; } else { return NO; } #endif } #if !PLATFORM_TVOS - (nullable WKWebView *)webView:(WKWebView *)InWebView createWebViewWithConfiguration:(WKWebViewConfiguration *)InConfiguration forNavigationAction:(WKNavigationAction *)InNavigationAction windowFeatures:(WKWindowFeatures *)InWindowFeatures { NSURLRequest *request = InNavigationAction.request; FString UrlStr([[request URL] absoluteString]); if (InNavigationAction.targetFrame == nil && !UrlStr.IsEmpty() && FPlatformProcess::CanLaunchURL(*UrlStr)) { if (WebBrowserWidget->HandleOnBeforePopup(UrlStr, TEXT("_blank"))) { // Launched the URL in external browser, don't create a new webview return nil; } } return nil; } - (void)webView:(WKWebView*)InWebView decidePolicyForNavigationAction : (WKNavigationAction*)InNavigationAction decisionHandler : (void(^)(WKNavigationActionPolicy))InDecisionHandler { NSURLRequest *request = InNavigationAction.request; FString UrlStr([[request URL] absoluteString]); if (InNavigationAction.targetFrame == nil && !UrlStr.IsEmpty() && FPlatformProcess::CanLaunchURL(*UrlStr)) { if (WebBrowserWidget->HandleOnBeforePopup(UrlStr, TEXT("_blank"))) { // Launched the URL in external browser, don't open the link here too InDecisionHandler(WKNavigationActionPolicyCancel); return; } } WebBrowserWidget->HandleShouldOverrideUrlLoading(UrlStr); InDecisionHandler(WKNavigationActionPolicyAllow); } -(void)webView:(WKWebView *)InWebView didCommitNavigation : (WKNavigation *)InNavigation { NSString* CurrentUrl = [self.WebView URL].absoluteString; NSString* Title = [self.WebView title]; // NSLog(@"didCommitNavigation: %@", CurrentUrl); WebBrowserWidget->HandleReceivedTitle(Title); WebBrowserWidget->HandlePageLoad(CurrentUrl, true); } -(void)webView:(WKWebView *)InWebView didFinishNavigation : (WKNavigation *)InNavigation { NSString* CurrentUrl = [self.WebView URL].absoluteString; NSString* Title = [self.WebView title]; // NSLog(@"didFinishNavigation: %@", CurrentUrl); WebBrowserWidget->HandleReceivedTitle(Title); WebBrowserWidget->HandlePageLoad(CurrentUrl, false); } -(void)webView:(WKWebView *)InWebView didFailNavigation : (WKNavigation *)InNavigation withError : (NSError*)InError { if (InError.domain == NSURLErrorDomain && InError.code == NSURLErrorCancelled) { //ignore this one, interrupted load return; } NSString* CurrentUrl = [InError.userInfo objectForKey : @"NSErrorFailingURLStringKey"]; // NSLog(@"didFailNavigation: %@, error %@", CurrentUrl, InError); WebBrowserWidget->HandleReceivedError(InError.code, CurrentUrl); } -(void)webView:(WKWebView *)InWebView didFailProvisionalNavigation : (WKNavigation *)InNavigation withError : (NSError*)InError { NSString* CurrentUrl = [InError.userInfo objectForKey : @"NSErrorFailingURLStringKey"]; // NSLog(@"didFailProvisionalNavigation: %@, error %@", CurrentUrl, InError); WebBrowserWidget->HandleReceivedError(InError.code, CurrentUrl); } #endif @end namespace { static const FString JSGetSourceCommand = TEXT("GetSource"); static const FString JSMessageGetSourceScript = TEXT(" window.webkit.messageHandlers.") + FMobileJSScripting::JSMessageHandler + TEXT(".postMessage('")+ JSGetSourceCommand + TEXT("/' + encodeURIComponent(document.documentElement.innerHTML));"); } FWebBrowserWindow::FWebBrowserWindow(FString InUrl, TOptional InContentsToLoad, bool InShowErrorMessage, bool InThumbMouseButtonNavigation, bool InUseTransparency, bool bInJSBindingToLoweringEnabled, FString InUserAgentApplication, bool bMobileJSReturnInDict) : CurrentUrl(MoveTemp(InUrl)) , UserAgentApplication(MoveTemp(InUserAgentApplication)) , ContentsToLoad(MoveTemp(InContentsToLoad)) , bUseTransparency(InUseTransparency) , DocumentState(EWebBrowserDocumentState::NoDocument) , ErrorCode(0) , WindowSize(FIntPoint(500, 500)) , bIsDisabled(false) , bIsVisible(true) , bTickedLastFrame(true) { bool bInjectJSOnPageStarted = false; GConfig->GetBool(TEXT("Browser"), TEXT("bInjectJSOnPageStarted"), bInjectJSOnPageStarted, GEngineIni); Scripting = MakeShared(bInJSBindingToLoweringEnabled, bInjectJSOnPageStarted, bMobileJSReturnInDict); WebView3DInput = MakeShared(); } FWebBrowserWindow::~FWebBrowserWindow() { CloseBrowser(true, false); } void FWebBrowserWindow::LoadURL(FString NewURL) { BrowserWidget->LoadURL(NewURL); } void FWebBrowserWindow::LoadString(FString Contents, FString DummyURL) { BrowserWidget->LoadString(Contents, DummyURL); } TOptional FWebBrowserWindow::GetWebBrowserRenderTransform() const { return FSlateRenderTransform(); } TSharedRef FWebBrowserWindow::CreateWidget() { TSharedRef BrowserWidgetRef = SNew(SAppleWebBrowserWidget) .UseTransparency(bUseTransparency) .InitialURL(CurrentUrl) .UserAgentApplication(UserAgentApplication) .WebBrowserWindow(SharedThis(this)) .RenderTransform(this, &FWebBrowserWindow::GetWebBrowserRenderTransform); BrowserWidget = BrowserWidgetRef; Scripting->SetWindow(SharedThis(this)); return BrowserWidgetRef; } void FWebBrowserWindow::OnActiveTabChanged(TSharedPtr ActiveTab, TSharedPtr PreviouslyActiveTab) { } void FWebBrowserWindow::SetViewportSize(FIntPoint ViewportSize, FIntPoint ViewportPos) { WindowSize = ViewportSize; if(BrowserWidget != nullptr) { BrowserWidget->UpdateFrameWithViewport(WindowSize, ViewportPos); } } FIntPoint FWebBrowserWindow::GetViewportSize() const { return WindowSize; } FSlateShaderResource* FWebBrowserWindow::GetTexture(bool bIsPopup /*= false*/) { return nullptr; } bool FWebBrowserWindow::IsValid() const { return true; } bool FWebBrowserWindow::IsInitialized() const { return true; } bool FWebBrowserWindow::IsClosing() const { return false; } bool FWebBrowserWindow::Is3D() { return BrowserWidget != nil ? BrowserWidget->Is3D() : false; } EWebBrowserDocumentState FWebBrowserWindow::GetDocumentLoadingState() const { return DocumentState; } FString FWebBrowserWindow::GetTitle() const { return Title; } FString FWebBrowserWindow::GetUrl() const { return CurrentUrl; } bool FWebBrowserWindow::OnKeyDown(const FKeyEvent& InKeyEvent) { if(Is3D()) { WebView3DInput->SendKeyEventToJS(*this, "keydown", InKeyEvent); } else { RedirectNSEvent(); } return true; } bool FWebBrowserWindow::OnKeyUp(const FKeyEvent& InKeyEvent) { if(Is3D()) { WebView3DInput->SendKeyEventToJS(*this, "keyup", InKeyEvent); } else { RedirectNSEvent(); } return true; } bool FWebBrowserWindow::OnKeyChar(const FCharacterEvent& InCharacterEvent) { if(Is3D()) { WebView3DInput->SendCharEventToJS(*this, InCharacterEvent); } return true; } void FWebBrowserWindow::RedirectNSEvent() { // by default, some events (mainly keystrokes) don't get redirected to NSApp when consumed in Slate #if PLATFORM_MAC if (const TSharedPtr Application = FSlateApplication::Get().GetPlatformApplication()) { if(BrowserWidget != nullptr && BrowserWidget->InKeyWindow()) { static_cast(Application.Get())->ResendLastEvent(); } } #endif } FReply FWebBrowserWindow::OnMouseButtonDown(const FGeometry& MyGeometry, const FPointerEvent& MouseEvent, bool bIsPopup) { if(Is3D()) { WebView3DInput->SendMouseEventToJS(*this, "down", MouseEvent); } return FReply::Handled().ReleaseMouseCapture().ReleaseMouseLock(); } FReply FWebBrowserWindow::OnMouseButtonUp(const FGeometry& MyGeometry, const FPointerEvent& MouseEvent, bool bIsPopup) { if(Is3D()) { WebView3DInput->SendMouseEventToJS(*this, "up", MouseEvent); WebView3DInput->SendMouseEventToJS(*this, "click", MouseEvent); } return FReply::Handled().ReleaseMouseCapture().ReleaseMouseLock(); } FReply FWebBrowserWindow::OnMouseButtonDoubleClick(const FGeometry& MyGeometry, const FPointerEvent& MouseEvent, bool bIsPopup) { if(Is3D()) { WebView3DInput->SendMouseEventToJS(*this, "dblclick", MouseEvent); } return FReply::Handled().ReleaseMouseCapture().ReleaseMouseLock(); } FReply FWebBrowserWindow::OnMouseMove(const FGeometry& MyGeometry, const FPointerEvent& MouseEvent, bool bIsPopup) { if(Is3D()) { WebView3DInput->SendMouseEventToJS(*this, "move", MouseEvent); } return FReply::Handled().ReleaseMouseCapture().ReleaseMouseLock(); } void FWebBrowserWindow::OnMouseLeave(const FPointerEvent& MouseEvent) { } void FWebBrowserWindow::SetSupportsMouseWheel(bool bValue) { } bool FWebBrowserWindow::GetSupportsMouseWheel() const { return false; } FReply FWebBrowserWindow::OnMouseWheel(const FGeometry& MyGeometry, const FPointerEvent& MouseEvent, bool bIsPopup) { if(Is3D()) { WebView3DInput->SendMouseEventToJS(*this, "wheel", MouseEvent); } return FReply::Handled(); } FReply FWebBrowserWindow::OnTouchGesture(const FGeometry& MyGeometry, const FPointerEvent& GestureEvent, bool bIsPopup) { return FReply::Unhandled(); } void FWebBrowserWindow::OnFocus(bool SetFocus, bool bIsPopup) { if(BrowserWidget != nullptr) { BrowserWidget->OnFocus(SetFocus); } } void FWebBrowserWindow::OnCaptureLost() { } bool FWebBrowserWindow::CanGoBack() const { return BrowserWidget->CanGoBack(); } void FWebBrowserWindow::GoBack() { BrowserWidget->GoBack(); } bool FWebBrowserWindow::CanGoForward() const { return BrowserWidget->CanGoForward(); } void FWebBrowserWindow::GoForward() { BrowserWidget->GoForward(); } bool FWebBrowserWindow::IsLoading() const { return DocumentState != EWebBrowserDocumentState::Loading; } void FWebBrowserWindow::Reload() { BrowserWidget->Reload(); } void FWebBrowserWindow::StopLoad() { BrowserWidget->StopLoad(); } void FWebBrowserWindow::GetSource(TFunction Callback) const { //@todo: decide what to do about multiple pending requests GetPageSourceCallback.Emplace(Callback); // Ugly hack: Work around the fact that ExecuteJavascript is non-const. const_cast(this)->ExecuteJavascript(JSMessageGetSourceScript); } int FWebBrowserWindow::GetLoadError() { return ErrorCode; } void FWebBrowserWindow::NotifyDocumentError(const FString& InCurrentUrl, int InErrorCode) { if (!CurrentUrl.Equals(InCurrentUrl, ESearchCase::CaseSensitive)) { CurrentUrl = InCurrentUrl; UrlChangedEvent.Broadcast(CurrentUrl); } ErrorCode = InErrorCode; DocumentState = EWebBrowserDocumentState::Error; DocumentStateChangedEvent.Broadcast(DocumentState); } void FWebBrowserWindow::NotifyDocumentLoadingStateChange(const FString& InCurrentUrl, bool IsLoading) { // Ignore a load completed notification if there was an error. // For load started, reset any errors from previous page load. bool bIsNotError = DocumentState != EWebBrowserDocumentState::Error; if (IsLoading || bIsNotError) { if (!CurrentUrl.Equals(InCurrentUrl, ESearchCase::CaseSensitive)) { CurrentUrl = InCurrentUrl; UrlChangedEvent.Broadcast(CurrentUrl); } if (bIsNotError && !InCurrentUrl.StartsWith("javascript:")) { if (IsLoading) { Scripting->PageStarted(SharedThis(this)); } else { Scripting->PageLoaded(SharedThis(this)); } } ErrorCode = 0; DocumentState = IsLoading ? EWebBrowserDocumentState::Loading : EWebBrowserDocumentState::Completed; DocumentStateChangedEvent.Broadcast(DocumentState); } } void FWebBrowserWindow::SetIsDisabled(bool bValue) { bIsDisabled = bValue; } TSharedPtr FWebBrowserWindow::GetParentWindow() const { if (ParentWindow.IsValid()) { return ParentWindow.Pin(); } return nullptr; } void FWebBrowserWindow::SetParentWindow(TSharedPtr Window) { ParentWindow = Window; } void FWebBrowserWindow::SetParentDockTab(TSharedPtr DockTab) { } TWeakPtr FWebBrowserWindow::GetParentDockTab() { return ParentDockTab; } void FWebBrowserWindow::ShowFloatingCloseButton(bool bShow, bool bDraggable) { if (BrowserWidget) { BrowserWidget->ShowFloatingCloseButton(bShow, bDraggable); } } void FWebBrowserWindow::ExecuteJavascript(const FString& Script) { BrowserWidget->ExecuteJavascript(Script); } void FWebBrowserWindow::CloseBrowser(bool bForce, bool bBlockTillClosed /* ignored */) { BrowserWidget->Close(); } bool FWebBrowserWindow::OnJsMessageReceived(const FString& Command, const TArray& Params, const FString& Origin) { if (Command.Equals(JSGetSourceCommand, ESearchCase::CaseSensitive) && GetPageSourceCallback.IsSet() && Params.Num() == 1) { GetPageSourceCallback.GetValue()(Params[0]); GetPageSourceCallback.Reset(); return true; } return Scripting->OnJsMessageReceived(Command, Params, Origin); } void FWebBrowserWindow::BindUObject(const FString& Name, UObject* Object, bool bIsPermanent /*= true*/) { Scripting->BindUObject(Name, Object, bIsPermanent); } void FWebBrowserWindow::UnbindUObject(const FString& Name, UObject* Object /*= nullptr*/, bool bIsPermanent /*= true*/) { Scripting->UnbindUObject(Name, Object, bIsPermanent); } void FWebBrowserWindow::CheckTickActivity() { if (bIsVisible != bTickedLastFrame) { bIsVisible = bTickedLastFrame; BrowserWidget->SetWebBrowserVisibility(bIsVisible); } bTickedLastFrame = false; } void FWebBrowserWindow::SetTickLastFrame() { bTickedLastFrame = !bIsDisabled; } bool FWebBrowserWindow::IsVisible() { return bIsVisible; } void FWebBrowserWindow::FloatingCloseButtonPressed() { if (OnFloatingCloseButtonPressed().IsBound()) { OnFloatingCloseButtonPressed().Execute(); } } #endif