public/controls/New-UiWebView.ps1
|
function New-UiWebView { <# .SYNOPSIS Creates an embedded WebView2 browser control. .DESCRIPTION Embeds a Chromium-based browser using Microsoft Edge WebView2. Essential for modern OAuth/SAML authentication flows, displaying HTML reports, or embedding vendor web dashboards within a PsUi window. Requires the WebView2 runtime to be installed on the system. If missing, displays an error message with installation instructions. The control automatically resizes to fit available space when the window is smaller than the requested height, preventing overflow issues. .PARAMETER Uri URL to load in the browser. Mutually exclusive with -Html. .PARAMETER Html Raw HTML content to render. Mutually exclusive with -Uri. .PARAMETER Variable Variable name to register the control for later access. .PARAMETER OnNavigated ScriptBlock to execute when navigation completes. Receives the URL as $args[0]. Useful for OAuth callback detection. .PARAMETER OnNavigating ScriptBlock to execute before navigation starts. Receives the URL as $args[0]. Return $false to cancel navigation. .PARAMETER EnableScripts Enable JavaScript execution. Disabled by default for security. .PARAMETER EnableDevTools Allow F12 developer tools. Disabled by default. .PARAMETER EnableDownloads Allow file downloads. Disabled by default. .PARAMETER Height Fixed height in pixels. If not specified, uses default or fills available space. .PARAMETER MinHeight Minimum height in pixels. Default is 200. .PARAMETER WPFProperties Hashtable of additional WPF properties to set on the control wrapper. These affect layout (Margin, Opacity, etc.) not the browser engine itself. .EXAMPLE New-UiWebView -Uri "https://example.com" -Variable "browser" # Simple URL loading .EXAMPLE New-UiWebView -Html "<h1>Report</h1><p>Generated at $(Get-Date)</p>" # Render HTML content .EXAMPLE New-UiWebView -Uri $authUrl -OnNavigated { param($url) if ($url -match 'callback.*code=([^&]+)') { $script:authCode = $matches[1] Close-UiParentWindow } } # OAuth flow with callback capture #> [CmdletBinding(DefaultParameterSetName = 'Uri')] param( [Parameter(ParameterSetName = 'Uri', Position = 0)] [string]$Uri, [Parameter(ParameterSetName = 'Html', Mandatory)] [string]$Html, [string]$Variable, [scriptblock]$OnNavigated, [scriptblock]$OnNavigating, [switch]$EnableScripts, [switch]$EnableDevTools, [switch]$EnableDownloads, [int]$Height, [int]$MinHeight = 200, [hashtable]$WPFProperties ) $session = Get-UiSession # Check runtime availability if (![PsUi.WebViewHelper]::IsRuntimeAvailable) { $errorMsg = [PsUi.WebViewHelper]::GetMissingRuntimeMessage() Write-Warning $errorMsg # Build a themed placeholder for the missing runtime $colors = Get-ThemeColors $placeholder = [System.Windows.Controls.Border]@{ Background = $colors.ControlBg BorderBrush = $colors.Error BorderThickness = [System.Windows.Thickness]::new(2) MinHeight = $MinHeight Padding = [System.Windows.Thickness]::new(16) } $errorPanel = [System.Windows.Controls.StackPanel]@{ VerticalAlignment = 'Center' } $iconText = [System.Windows.Controls.TextBlock]@{ Text = [char]0xE783 FontFamily = [System.Windows.Media.FontFamily]::new('Segoe MDL2 Assets') FontSize = 32 Foreground = $colors.Error HorizontalAlignment = 'Center' Margin = [System.Windows.Thickness]::new(0, 0, 0, 8) } $msgText = [System.Windows.Controls.TextBlock]@{ Text = $errorMsg TextWrapping = 'Wrap' FontSize = 12 Foreground = $colors.ControlFg } [void]$errorPanel.Children.Add($iconText) [void]$errorPanel.Children.Add($msgText) $placeholder.Child = $errorPanel [void]$session.CurrentParent.Children.Add($placeholder) return $null } $webView = [PsUi.WebViewHelper]::Create() if ($null -eq $webView) { Write-Warning "Failed to create WebView2 control." return } # Apply sizing if ($Height -gt 0) { $webView.Height = $Height $webView.MaxHeight = $Height } else { $webView.Height = $MinHeight $webView.MaxHeight = $MinHeight } $webView.MinHeight = $MinHeight $webView.VerticalAlignment = 'Top' # Capture for closure [bool]$capturedEnableScripts = $EnableScripts.IsPresent [bool]$capturedEnableDevTools = $EnableDevTools.IsPresent [bool]$capturedEnableDownloads = $EnableDownloads.IsPresent [string]$capturedUri = $Uri [string]$capturedHtml = $Html $capturedOnNavigating = $OnNavigating $capturedOnNavigated = $OnNavigated # Defer settings and navigation until CoreWebView2 is ready $webView.add_CoreWebView2InitializationCompleted({ param($sender, $eventArgs) if (!$eventArgs.IsSuccess) { Write-Warning "WebView2 initialization failed: $($eventArgs.InitializationException.Message)" return } $wv = $sender [PsUi.WebViewHelper]::ApplySecuritySettings($wv, $capturedEnableScripts, $capturedEnableDevTools, $capturedEnableDownloads) if ($capturedOnNavigating) { $wv.CoreWebView2.add_NavigationStarting({ param($navSender, $navArgs) $navUrl = $navArgs.Uri $result = & $capturedOnNavigating $navUrl if ($result -eq $false) { $navArgs.Cancel = $true } }.GetNewClosure()) } if ($capturedOnNavigated) { $wv.CoreWebView2.add_NavigationCompleted({ param($navSender, $navArgs) $navUrl = $navSender.Source & $capturedOnNavigated $navUrl }.GetNewClosure()) } if ($capturedHtml) { [PsUi.WebViewHelper]::NavigateToHtml($wv, $capturedHtml) } elseif ($capturedUri) { $wv.Source = [uri]$capturedUri } }.GetNewClosure()) if ($WPFProperties) { Set-UiProperties -Control $webView -Properties $WPFProperties } # Enforce minimum window height to prevent WebView overflow (airspace workaround) $webView.add_Loaded({ param($sender, $eventArgs) $wv = $sender $window = [System.Windows.Window]::GetWindow($wv) if ($null -eq $window) { return } # Dispose WebView2 when window closes $window.add_Closed({ try { $wv.Dispose() } catch { Write-Debug "WebView2 dispose failed: $_" } }.GetNewClosure()) # Calculate required minimum height after layout settles $wv.Dispatcher.BeginInvoke([System.Windows.Threading.DispatcherPriority]::Loaded, [Action]{ try { $point = $wv.TransformToAncestor($window).Transform([System.Windows.Point]::new(0, 0)) $webViewTop = $point.Y $requiredMinHeight = $webViewTop + $wv.Height + 40 if ($window.MinHeight -lt $requiredMinHeight) { $window.MinHeight = $requiredMinHeight } } catch { Write-Debug "WebView2 MinHeight calculation failed: $_" } }.GetNewClosure()) }.GetNewClosure()) if ($Variable) { $session.AddControlSafe($Variable, $webView) } [void]$session.CurrentParent.Children.Add($webView) return $webView } |