// Copyright (C) Microsoft Corporation. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. using System; using System.Collections.Generic; using System.Diagnostics; using System.Drawing; using System.Threading; using System.Text; using System.IO; using System.Windows.Forms; using Microsoft.Web.WebView2.Core; using Microsoft.Web.WebView2.WinForms; using System.Linq; using System.ComponentModel; namespace WebView2WindowsFormsBrowser { public partial class BrowserForm : Form { private CoreWebView2CreationProperties _creationProperties = null; public CoreWebView2CreationProperties CreationProperties { get { if (_creationProperties == null) { _creationProperties = new Microsoft.Web.WebView2.WinForms.CoreWebView2CreationProperties(); } return _creationProperties; } set { _creationProperties = value; } } public BrowserForm() { InitializeComponent(); AttachControlEventHandlers(this.webView2Control); HandleResize(); } public BrowserForm(CoreWebView2CreationProperties creationProperties = null) { this.CreationProperties = creationProperties; InitializeComponent(); AttachControlEventHandlers(this.webView2Control); HandleResize(); } private void UpdateTitleWithEvent(string message) { string currentDocumentTitle = this.webView2Control?.CoreWebView2?.DocumentTitle ?? "Uninitialized"; this.Text = currentDocumentTitle + " (" + message + ")"; } CoreWebView2Environment _webViewEnvironment; CoreWebView2Environment WebViewEnvironment { get { if (_webViewEnvironment == null && webView2Control?.CoreWebView2 != null) { _webViewEnvironment = webView2Control.CoreWebView2.Environment; } return _webViewEnvironment; } } CoreWebView2Settings _webViewSettings; CoreWebView2Settings WebViewSettings { get { if (_webViewSettings == null && webView2Control?.CoreWebView2 != null) { _webViewSettings = webView2Control.CoreWebView2.Settings; } return _webViewSettings; } } string _lastInitializeScriptId; List _webViewFrames = new List(); void WebView_HandleIFrames(object sender, CoreWebView2FrameCreatedEventArgs args) { _webViewFrames.Add(args.Frame); args.Frame.Destroyed += WebViewFrames_DestoryedNestedIFrames; } void WebViewFrames_DestoryedNestedIFrames(object sender, object args) { try { var frameToRemove = _webViewFrames.SingleOrDefault(r => r.IsDestroyed() == 1); if (frameToRemove != null) _webViewFrames.Remove(frameToRemove); } catch (InvalidOperationException ex) { MessageBox.Show(ex.Message); } } string WebViewFrames_ToString() { string result = ""; for (var i = 0; i < _webViewFrames.Count; i++) { if (i > 0) result += "; "; result += i.ToString() + " " + (String.IsNullOrEmpty(_webViewFrames[i].Name) ? "" : _webViewFrames[i].Name); } return String.IsNullOrEmpty(result) ? "no iframes available." : result; } #region Event Handlers // Enable (or disable) buttons when webview2 is init (or disposed). Similar to the CanExecute feature of WPF. private void UpdateButtons(bool isEnabled) { this.btnEvents.Enabled = isEnabled; this.btnBack.Enabled = isEnabled && webView2Control != null && webView2Control.CanGoBack; this.btnForward.Enabled = isEnabled && webView2Control != null && webView2Control.CanGoForward; this.btnRefresh.Enabled = isEnabled; this.btnGo.Enabled = isEnabled; this.closeWebViewToolStripMenuItem.Enabled = isEnabled; this.allowExternalDropMenuItem.Enabled = isEnabled; this.xToolStripMenuItem.Enabled = isEnabled; this.xToolStripMenuItem1.Enabled = isEnabled; this.xToolStripMenuItem2.Enabled = isEnabled; this.xToolStripMenuItem3.Enabled = isEnabled; this.whiteBackgroundColorMenuItem.Enabled = isEnabled; this.redBackgroundColorMenuItem.Enabled = isEnabled; this.blueBackgroundColorMenuItem.Enabled = isEnabled; this.transparentBackgroundColorMenuItem.Enabled = isEnabled; } private void EnableButtons() { UpdateButtons(true); } private void DisableButtons(object sender, EventArgs e) { UpdateButtons(false); } private void WebView2Control_NavigationStarting(object sender, CoreWebView2NavigationStartingEventArgs e) { UpdateTitleWithEvent("NavigationStarting"); } private void WebView2Control_NavigationCompleted(object sender, CoreWebView2NavigationCompletedEventArgs e) { UpdateTitleWithEvent("NavigationCompleted"); } private void WebView2Control_SourceChanged(object sender, CoreWebView2SourceChangedEventArgs e) { txtUrl.Text = webView2Control.Source.AbsoluteUri; } private void WebView2Control_CoreWebView2InitializationCompleted(object sender, CoreWebView2InitializationCompletedEventArgs e) { if (!e.IsSuccess) { MessageBox.Show($"WebView2 creation failed with exception = {e.InitializationException}"); UpdateTitleWithEvent("CoreWebView2InitializationCompleted failed"); return; } // Setup host resource mapping for local files this.webView2Control.CoreWebView2.SetVirtualHostNameToFolderMapping("appassets.example", "assets", CoreWebView2HostResourceAccessKind.DenyCors); this.webView2Control.Source = new Uri(GetStartPageUri(this.webView2Control.CoreWebView2)); this.webView2Control.CoreWebView2.SourceChanged += CoreWebView2_SourceChanged; this.webView2Control.CoreWebView2.HistoryChanged += CoreWebView2_HistoryChanged; this.webView2Control.CoreWebView2.DocumentTitleChanged += CoreWebView2_DocumentTitleChanged; this.webView2Control.CoreWebView2.AddWebResourceRequestedFilter("*", CoreWebView2WebResourceContext.Image, CoreWebView2WebResourceRequestSourceKinds.Document); this.webView2Control.CoreWebView2.ProcessFailed += CoreWebView2_ProcessFailed; this.webView2Control.CoreWebView2.FrameCreated += WebView_HandleIFrames; UpdateTitleWithEvent("CoreWebView2InitializationCompleted succeeded"); EnableButtons(); } void AttachControlEventHandlers(Microsoft.Web.WebView2.WinForms.WebView2 control) { control.CoreWebView2InitializationCompleted += WebView2Control_CoreWebView2InitializationCompleted; control.NavigationStarting += WebView2Control_NavigationStarting; control.NavigationCompleted += WebView2Control_NavigationCompleted; control.SourceChanged += WebView2Control_SourceChanged; control.KeyDown += WebView2Control_KeyDown; control.KeyUp += WebView2Control_KeyUp; control.Disposed += DisableButtons; } private void WebView2Control_KeyUp(object sender, KeyEventArgs e) { UpdateTitleWithEvent($"KeyUp key={e.KeyCode}"); if (!this.acceleratorKeysEnabledToolStripMenuItem.Checked) e.Handled = true; } private void WebView2Control_KeyDown(object sender, KeyEventArgs e) { UpdateTitleWithEvent($"KeyDown key={e.KeyCode}"); if (!this.acceleratorKeysEnabledToolStripMenuItem.Checked) e.Handled = true; } private void CoreWebView2_HistoryChanged(object sender, object e) { // No explicit check for webView2Control initialization because the events can only start // firing after the CoreWebView2 and its events exist for us to subscribe. btnBack.Enabled = webView2Control.CoreWebView2.CanGoBack; btnForward.Enabled = webView2Control.CoreWebView2.CanGoForward; UpdateTitleWithEvent("HistoryChanged"); } private void CoreWebView2_SourceChanged(object sender, CoreWebView2SourceChangedEventArgs e) { this.txtUrl.Text = this.webView2Control.Source.AbsoluteUri; UpdateTitleWithEvent("SourceChanged"); } private void CoreWebView2_DocumentTitleChanged(object sender, object e) { this.Text = this.webView2Control.CoreWebView2.DocumentTitle; UpdateTitleWithEvent("DocumentTitleChanged"); } #endregion #region UI event handlers private void BtnRefresh_Click(object sender, EventArgs e) { webView2Control.Reload(); } private void BtnGo_Click(object sender, EventArgs e) { var rawUrl = txtUrl.Text; Uri uri = null; if (Uri.IsWellFormedUriString(rawUrl, UriKind.Absolute)) { uri = new Uri(rawUrl); } else if (!rawUrl.Contains(" ") && rawUrl.Contains(".")) { // An invalid URI contains a dot and no spaces, try tacking http:// on the front. uri = new Uri("http://" + rawUrl); } else { // Otherwise treat it as a web search. uri = new Uri("https://bing.com/search?q=" + String.Join("+", Uri.EscapeDataString(rawUrl).Split(new string[] { "%20" }, StringSplitOptions.RemoveEmptyEntries))); } webView2Control.Source = uri; if (ShouldBlockUri()) { webView2Control.CoreWebView2.NavigateToString("You've attempted to navigate to a domain in the blocked sites list. Press back to return to the previous page."); } } private void btnBack_Click(object sender, EventArgs e) { webView2Control.GoBack(); } private void btnEvents_Click(object sender, EventArgs e) { (new EventMonitor(this.webView2Control)).Show(this); } private void btnForward_Click(object sender, EventArgs e) { webView2Control.GoForward(); } private void Form_Resize(object sender, EventArgs e) { HandleResize(); } private void closeWebViewToolStripMenuItem_Click(object sender, EventArgs e) { this.Controls.Remove(webView2Control); webView2Control.Dispose(); } private void createWebViewToolStripMenuItem_Click(object sender, EventArgs e) { void EnsureProcessIsClose(uint pid) { try { var process = Process.GetProcessById((int)pid); process.Kill(); } catch (ArgumentException) { // Process already exited. } } if (this.webView2Control.CoreWebView2 != null) { var processId = this.webView2Control.CoreWebView2.BrowserProcessId; this.Controls.Remove(this.webView2Control); this.webView2Control.Dispose(); EnsureProcessIsClose(processId); } this.webView2Control = GetReplacementControl(false); // Set background transparent this.webView2Control.DefaultBackgroundColor = System.Drawing.Color.Transparent; this.Controls.Add(this.webView2Control); HandleResize(); } private void createNewWindowToolStripMenuItem_Click(object sender, EventArgs e) { new BrowserForm().Show(); } private void createNewWindowWithOptionsToolStripMenuItem_Click(object sender, EventArgs e) { var dialog = new NewWindowOptionsDialog(); if (dialog.ShowDialog() == DialogResult.OK) { new BrowserForm(dialog.CreationProperties).Show(); } } private void ThreadProc(CoreWebView2CreationProperties creationProperties) { try { var creationProps = new CoreWebView2CreationProperties(); // The CoreWebView2CreationProperties object cannot be assigned directly, because its member _task will also be assigned. creationProps.BrowserExecutableFolder = creationProperties.BrowserExecutableFolder; creationProps.UserDataFolder = creationProperties.UserDataFolder; creationProps.Language = creationProperties.Language; creationProps.AdditionalBrowserArguments = creationProperties.AdditionalBrowserArguments; creationProps.ProfileName = creationProperties.ProfileName; creationProps.IsInPrivateModeEnabled = creationProperties.IsInPrivateModeEnabled; var tempForm = new BrowserForm(creationProps); tempForm.Show(); // Run the message pump Application.Run(); } catch (Exception exception) { MessageBox.Show("Create New Thread Failed: " + exception.Message, "Create New Thread"); } } private void createNewThreadToolStripMenuItem_Click(object sender, EventArgs e) { Thread newFormThread = new Thread(() => { ThreadProc(webView2Control.CreationProperties); }); newFormThread.SetApartmentState(ApartmentState.STA); newFormThread.IsBackground = false; newFormThread.Start(); } private void xToolStripMenuItem05_Click(object sender, EventArgs e) { this.webView2Control.ZoomFactor = 0.5; } private void xToolStripMenuItem1_Click(object sender, EventArgs e) { this.webView2Control.ZoomFactor = 1.0; } private void xToolStripMenuItem2_Click(object sender, EventArgs e) { this.webView2Control.ZoomFactor = 2.0; } private void xToolStripMenuItem3_Click(object sender, EventArgs e) { MessageBox.Show($"Zoom factor: {this.webView2Control.ZoomFactor}", "WebView Zoom factor"); } private void backgroundColorMenuItem_Click(object sender, EventArgs e) { var menuItem = (ToolStripMenuItem)sender; Color backgroundColor = Color.FromName(menuItem.Text); this.webView2Control.DefaultBackgroundColor = backgroundColor; } private void taskManagerToolStripMenuItem_Click(object sender, EventArgs e) { try { this.webView2Control.CoreWebView2.OpenTaskManagerWindow(); } catch (Exception ex) { MessageBox.Show(this, ex.ToString(), "Open Task Manager Window failed"); } } private async void methodCDPToolStripMenuItem_Click(object sender, EventArgs e) { TextInputDialog dialog = new TextInputDialog( title: "Call CDP Method", description: "Enter the CDP method name to call, followed by a space,\r\n" + "followed by the parameters in JSON format.", defaultInput: "Runtime.evaluate {\"expression\":\"alert(\\\"test\\\")\"}" ); if (dialog.ShowDialog() == DialogResult.OK) { string[] words = dialog.inputBox().Trim().Split(' '); if (words.Length == 1 && words[0] == "") { MessageBox.Show(this, "Invalid argument:" + dialog.inputBox(), "CDP Method call failed"); return; } string methodName = words[0]; string methodParams = (words.Length == 2 ? words[1] : "{}"); try { string cdpResult = await this.webView2Control.CoreWebView2.CallDevToolsProtocolMethodAsync(methodName, methodParams); MessageBox.Show(this, cdpResult, "CDP method call successfully"); } catch (Exception ex) { MessageBox.Show(this, ex.ToString(), "CDP method call failed"); } } } private void allowExternalDropMenuItem_Click(object sender, EventArgs e) { this.webView2Control.AllowExternalDrop = this.allowExternalDropMenuItem.Checked; } private void setUsersAgentMenuItem_Click(object sender, EventArgs e) { var dialog = new TextInputDialog( title: "SetUserAgent", description: "Enter UserAgent"); if (dialog.ShowDialog() == DialogResult.OK) { // WebViewSettings.UserAgent = dialog.inputBox(); // } } private void getDocumentTitleMenuItem_Click(object sender, EventArgs e) { MessageBox.Show(webView2Control.CoreWebView2.DocumentTitle, "Document Title"); } private bool _isPrintToPdfInProgress = false; private async void portraitMenuItem_Click(object sender, EventArgs e) { if (_isPrintToPdfInProgress) { MessageBox.Show(this, "Print to PDF in progress", "Print To PDF"); return; } try { // SaveFileDialog saveFileDialog = new SaveFileDialog(); saveFileDialog.InitialDirectory = "C:\\"; saveFileDialog.Filter = "Pdf Files|*.pdf"; if (saveFileDialog.ShowDialog() == DialogResult.OK) { _isPrintToPdfInProgress = true; bool isSuccessful = await webView2Control.CoreWebView2.PrintToPdfAsync( saveFileDialog.FileName); _isPrintToPdfInProgress = false; string message = (isSuccessful) ? "Print to PDF succeeded" : "Print to PDF failed"; MessageBox.Show(this, message, "Print To PDF Completed"); } // } catch (NotImplementedException exception) { MessageBox.Show(this, "Print to PDF Failed: " + exception.Message, "Print to PDF"); } } private async void landscapeMenuItem_Click(object sender, EventArgs e) { { if (_isPrintToPdfInProgress) { MessageBox.Show(this, "Print to PDF in progress", "Print To PDF"); return; } try { // CoreWebView2PrintSettings printSettings = WebViewEnvironment.CreatePrintSettings(); printSettings.Orientation = CoreWebView2PrintOrientation.Landscape; SaveFileDialog saveFileDialog = new SaveFileDialog(); saveFileDialog.InitialDirectory = "C:\\"; saveFileDialog.Filter = "Pdf Files|*.pdf"; if (saveFileDialog.ShowDialog() == DialogResult.OK) { _isPrintToPdfInProgress = true; bool isSuccessful = await webView2Control.CoreWebView2.PrintToPdfAsync( saveFileDialog.FileName, printSettings); _isPrintToPdfInProgress = false; string message = (isSuccessful) ? "Print to PDF succeeded" : "Print to PDF failed"; MessageBox.Show(this, message, "Print To PDF Completed"); } // } catch (NotImplementedException exception) { MessageBox.Show(this, "Print to PDF Failed: " + exception.Message, "Print to PDF"); } } } private void exitMenuItem_Click(object sender, EventArgs e) { if (_isPrintToPdfInProgress) { var selection = MessageBox.Show( "Print to PDF in progress. Continue closing?", "Print to PDF", MessageBoxButtons.YesNo); if (selection == DialogResult.No) { return; } } this.Close(); } private void getUserDataFolderMenuItem_Click(object sender, EventArgs e) { try { MessageBox.Show(WebViewEnvironment.UserDataFolder, "User Data Folder"); } catch (Exception exception) { MessageBox.Show(this, "Get User Data Folder Failed: " + exception.Message, "User Data Folder"); } } private void toggleVisibilityMenuItem_Click(object sender, EventArgs e) { this.webView2Control.Visible = this.toggleVisibilityMenuItem.Checked; } private void toggleCustomServerCertificateSupportMenuItem_Click(object sender, EventArgs e) { ToggleCustomServerCertificateSupport(); } private void clearServerCertificateErrorActionsMenuItem_Click(object sender, EventArgs e) { ClearServerCertificateErrorActions(); } private void toggleDefaultScriptDialogsMenuItem_Click(object sender, EventArgs e) { WebViewSettings.AreDefaultScriptDialogsEnabled = !WebViewSettings.AreDefaultScriptDialogsEnabled; MessageBox.Show("Default script dialogs will be " + (WebViewSettings.AreDefaultScriptDialogsEnabled ? "enabled" : "disabled"), "after the next navigation."); } private void addRemoteObjectMenuItem_Click(object sender, EventArgs e) { try { this.webView2Control.CoreWebView2.AddHostObjectToScript("bridge", new BridgeAddRemoteObject()); } catch (NotSupportedException exception) { MessageBox.Show("CoreWebView2.AddRemoteObject failed: " + exception.Message); } this.webView2Control.CoreWebView2.FrameCreated += (s, args) => { if (args.Frame.Name.Equals("iframe_name")) { try { string[] origins = new string[] { "https://appassets.example" }; args.Frame.AddHostObjectToScript("bridge", new BridgeAddRemoteObject(), origins); } catch (NotSupportedException exception) { MessageBox.Show("Frame.AddHostObjectToScript failed: " + exception.Message); } } args.Frame.NameChanged += (nameChangedSender, nameChangedArgs) => { CoreWebView2Frame frame = (CoreWebView2Frame)nameChangedSender; MessageBox.Show("Frame.NameChanged: " + frame.Name); }; args.Frame.Destroyed += (frameDestroyedSender, frameDestroyedArgs) => { // Handle frame destroyed }; }; this.webView2Control.CoreWebView2.SetVirtualHostNameToFolderMapping( "appassets.example", "assets", CoreWebView2HostResourceAccessKind.DenyCors); this.webView2Control.Source = new Uri("https://appassets.example/hostObject.html"); } // private void domContentLoadedMenuItem_Click(object sender, EventArgs e) { this.webView2Control.CoreWebView2.DOMContentLoaded += WebView_DOMContentLoaded; this.webView2Control.CoreWebView2.FrameCreated += WebView_FrameCreatedDOMContentLoaded; this.webView2Control.NavigateToString(@"" + "

DOMContentLoaded sample page

" + "

The content to the iframe and below will be added after DOM content is loaded

" + "