Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Webview2 initialization #14671

Merged
merged 17 commits into from
Dec 6, 2023
Merged
Show file tree
Hide file tree
Changes from 10 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
using System.Windows.Controls;
using Dynamo.Logging;
using Dynamo.Utilities;
using DynamoUtilities;
using Microsoft.Web.WebView2.Core;
using Microsoft.Web.WebView2.Wpf;

Expand All @@ -21,7 +22,7 @@ public partial class DocumentationBrowserView : UserControl, IDisposable
private readonly DocumentationBrowserViewModel viewModel;
private const string VIRTUAL_FOLDER_MAPPING = "appassets";
static readonly string HTML_IMAGE_PATH_PREFIX = @"http://";
private bool hasBeenInitialized;
private AsyncMethodState initState = AsyncMethodState.NotStarted;
private ScriptingObject comScriptingObject;
private string fontStylePath = "Dynamo.Wpf.Views.GuidedTour.HtmlPages.Resources.ArtifaktElement-Regular.woff";

Expand Down Expand Up @@ -104,26 +105,22 @@ private void ShouldAllowNavigation(object sender, CoreWebView2NavigationStarting
/// <param name="link"></param>
public void NavigateToPage(Uri link)
{
InitializeAsync();
Dispatcher.Invoke(InitializeAsync);
pinzart90 marked this conversation as resolved.
Show resolved Hide resolved
pinzart90 marked this conversation as resolved.
Show resolved Hide resolved
}

protected virtual void Dispose(bool disposing)
{
// Cleanup
this.viewModel.LinkChanged -= NavigateToPage;
this.documentationBrowser.NavigationStarting -= ShouldAllowNavigation;
this.documentationBrowser.DpiChanged -= DocumentationBrowser_DpiChanged;

if (this.documentationBrowser.CoreWebView2 != null)
if (this.documentationBrowser != null)
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

code here was just moved under a null check

{
this.documentationBrowser.CoreWebView2.WebMessageReceived -= CoreWebView2OnWebMessageReceived;
}
this.documentationBrowser.NavigationStarting -= ShouldAllowNavigation;
this.documentationBrowser.DpiChanged -= DocumentationBrowser_DpiChanged;
if (this.documentationBrowser.CoreWebView2 != null)
{
this.documentationBrowser.CoreWebView2.WebMessageReceived -= CoreWebView2OnWebMessageReceived;
}

// Note to test writers
// Disposing the document browser will cause future tests
// that uses the Browser component to crash
if (!Models.DynamoModel.IsTestMode)
{
this.documentationBrowser.Dispose();
pinzart90 marked this conversation as resolved.
Show resolved Hide resolved
}
}
Expand Down Expand Up @@ -159,8 +156,9 @@ async void InitializeAsync()
}

// Only initialize once
if (!hasBeenInitialized)
if (initState == AsyncMethodState.NotStarted)
{
initState = AsyncMethodState.Started;
if (!string.IsNullOrEmpty(WebBrowserUserDataFolder))
{
//This indicates in which location will be created the WebView2 cache folder
Expand All @@ -169,9 +167,9 @@ async void InitializeAsync()
UserDataFolder = WebBrowserUserDataFolder
};
}

//Initialize the CoreWebView2 component otherwise we can't navigate to a web page
await documentationBrowser.EnsureCoreWebView2Async();


this.documentationBrowser.CoreWebView2.WebMessageReceived += CoreWebView2OnWebMessageReceived;
comScriptingObject = new ScriptingObject(this.viewModel);
Expand All @@ -181,7 +179,7 @@ async void InitializeAsync()
this.documentationBrowser.CoreWebView2.Settings.IsZoomControlEnabled = true;
this.documentationBrowser.CoreWebView2.Settings.AreDevToolsEnabled = true;

hasBeenInitialized = true;
initState = AsyncMethodState.Done;
}

if(Directory.Exists(VirtualFolderPath))
Expand All @@ -192,10 +190,7 @@ async void InitializeAsync()

htmlContent = ResourceUtilities.LoadResourceAndReplaceByKey(htmlContent, "#fontStyle", fontStylePath);

Dispatcher.BeginInvoke(new Action(() =>
{
this.documentationBrowser.NavigateToString(htmlContent);
}));
this.documentationBrowser.NavigateToString(htmlContent);
pinzart90 marked this conversation as resolved.
Show resolved Hide resolved
}

private void CoreWebView2OnWebMessageReceived(object sender, CoreWebView2WebMessageReceivedEventArgs e)
Expand All @@ -209,6 +204,10 @@ private void CoreWebView2OnWebMessageReceived(object sender, CoreWebView2WebMess
/// </summary>
public void Dispose()
{
if (initState == AsyncMethodState.Started)
{
Log("DocumentationBrowserView is being disposed but async initialization is still not done");
pinzart90 marked this conversation as resolved.
Show resolved Hide resolved
}
Dispose(true);
GC.SuppressFinalize(this);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -178,7 +178,7 @@ public override void Loaded(ViewLoadedParams viewLoadedParams)

public override void Shutdown()
{
Dispose();
// Do nothing for now
pinzart90 marked this conversation as resolved.
Show resolved Hide resolved
}

private void OnInsertFile(object sender, InsertDocumentationLinkEventArgs e)
Expand Down
4 changes: 3 additions & 1 deletion src/DynamoCoreWpf/Views/GuidedTour/PopupWindow.xaml.cs
Original file line number Diff line number Diff line change
Expand Up @@ -105,7 +105,9 @@ private void PopupWindow_Opened(object sender, EventArgs e)
{
if (hostControlInfo.HtmlPage != null && !string.IsNullOrEmpty(hostControlInfo.HtmlPage.FileName))
{
ContentRichTextBox.Visibility = Visibility.Hidden;
ContentRichTextBox.Visibility = Visibility.Hidden;

// Opened event ensures the webview2 will be visible when added to the popup layout tree.
InitWebView2Component();
}
}
Expand Down
2 changes: 2 additions & 0 deletions src/DynamoCoreWpf/Views/SplashScreen/SplashScreen.xaml.cs
Original file line number Diff line number Diff line change
Expand Up @@ -304,6 +304,8 @@ protected override async void OnContentRendered(EventArgs e)
{
UserDataFolder = webBrowserUserDataFolder.FullName
};

//ContentRendered ensures that the webview2 component is visible.
await webView.EnsureCoreWebView2Async();
// Context menu disabled
webView.CoreWebView2.Settings.AreDefaultContextMenusEnabled = false;
Expand Down
14 changes: 14 additions & 0 deletions src/DynamoUtilities/AsyncMethodState.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@

namespace DynamoUtilities
{
/// <summary>
/// Simple representation of the states of an async method
/// Used for identifying issues like Dispose called before async method is finished.
/// </summary>
internal enum AsyncMethodState
{
NotStarted = 0,// Async method not called yet
Started,// Async method called but not finished execution (usually set before any awaits)
Done// Async method has finished execution (all awaits have finished)
}
}
2 changes: 1 addition & 1 deletion src/DynamoUtilities/Properties/AssemblyInfo.cs
Original file line number Diff line number Diff line change
Expand Up @@ -25,4 +25,4 @@
[assembly: InternalsVisibleTo("DynamoCLI")]
[assembly: InternalsVisibleTo("NodeDocumentationMarkdownGenerator")]
[assembly: InternalsVisibleTo("DynamoUtilitiesTests")]

[assembly: InternalsVisibleTo("Notifications")]
42 changes: 18 additions & 24 deletions src/LibraryViewExtensionWebView2/LibraryViewController.cs
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,22 @@ internal LibraryViewController(Window dynamoView, ICommandExecutive commandExecu
{
WebBrowserUserDataFolder = webBrowserUserDataFolder.FullName;
}

/// Create and add the library view to the WPF visual tree.
/// Also load the library.html and js files.
LibraryViewModel model = new LibraryViewModel();
LibraryView view = new LibraryView(model);

//Adding the LibraryView to the sidebar ensures that the webview2 component is visible.
var sidebarGrid = dynamoWindow.FindName("sidebarGrid") as Grid;
sidebarGrid.Children.Add(view);

browser = view.mainGrid.Children.OfType<WebView2>().FirstOrDefault();
browser.Loaded += Browser_Loaded;
browser.SizeChanged += Browser_SizeChanged;

LibraryViewController.SetupSearchModelEventsObserver(browser, dynamoViewModel.Model.SearchModel,
this, this.customization);
pinzart90 marked this conversation as resolved.
Show resolved Hide resolved
}

private void DynamoViewModel_PreferencesWindowChanged(object sender, EventArgs e)
Expand Down Expand Up @@ -302,30 +318,6 @@ private string ReplaceUrlWithBase64Image(string html, string minifiedURL, bool m
Tuple.Create("/resources/search-icon-clear.svg",true)
};

/// <summary>
/// Creates and adds the library view to the WPF visual tree.
/// Also loads the library.html and js files.
/// </summary>
/// <returns>LibraryView control</returns>
internal void AddLibraryView()
{
LibraryViewModel model = new LibraryViewModel();
LibraryView view = new LibraryView(model);

var sidebarGrid = dynamoWindow.FindName("sidebarGrid") as Grid;
sidebarGrid.Children.Add(view);

browser = view.mainGrid.Children.OfType<WebView2>().FirstOrDefault();
browser.Loaded += Browser_Loaded;
browser.SizeChanged += Browser_SizeChanged;

this.browser = view.mainGrid.Children.OfType<WebView2>().FirstOrDefault();
InitializeAsync();

LibraryViewController.SetupSearchModelEventsObserver(browser, dynamoViewModel.Model.SearchModel,
this, this.customization);
}

async void InitializeAsync()
{
try
Expand Down Expand Up @@ -422,6 +414,8 @@ private void Browser_Loaded(object sender, RoutedEventArgs e)
{
string msg = "Browser Loaded";
LogToDynamoConsole(msg);

InitializeAsync();
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We initialize webview2 only after it is loaded as part of the visual tree (Browser_Loaded)

}

// This enum is for matching the modifier keys between C# and javaScript
Expand Down
3 changes: 1 addition & 2 deletions src/LibraryViewExtensionWebView2/ViewExtension.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
using System.Windows;
using System.Windows;
using Dynamo.Models;
using Dynamo.ViewModels;
using Dynamo.Wpf.Extensions;
Expand Down Expand Up @@ -35,7 +35,6 @@ public void Loaded(ViewLoadedParams viewLoadedParams)
{
viewParams = viewLoadedParams;
controller = new LibraryViewController(viewLoadedParams.DynamoWindow, viewLoadedParams.CommandExecutive, customization);
controller.AddLibraryView();
(viewLoadedParams.DynamoWindow.DataContext as DynamoViewModel).PropertyChanged += handleDynamoViewPropertyChanges;
}

Expand Down
88 changes: 55 additions & 33 deletions src/Notifications/NotificationCenterController.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
using Dynamo.Controls;
using Dynamo.Logging;
using Dynamo.Notifications.View;
using DynamoUtilities;
using Dynamo.ViewModels;
using Dynamo.Wpf.ViewModels.Core;
using Newtonsoft.Json;
Expand Down Expand Up @@ -48,7 +49,7 @@ public void UpdateNotificationWindowSize(int height)
}
}

public class NotificationCenterController
public class NotificationCenterController : IDisposable
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

proper disposable

{
private readonly NotificationUI notificationUIPopup;
private readonly DynamoView dynamoView;
Expand All @@ -64,6 +65,8 @@ public class NotificationCenterController
private static readonly string NotificationCenterButtonName = "notificationsButton";
internal DirectoryInfo webBrowserUserDataFolder;

private AsyncMethodState initState = AsyncMethodState.NotStarted;

private readonly DynamoLogger logger;
private string jsonStringFile;
private NotificationsModel notificationsModel;
Expand All @@ -82,7 +85,6 @@ internal NotificationCenterController(DynamoView view, DynamoLogger dynLogger)
dynamoView.SizeChanged += DynamoView_SizeChanged;
dynamoView.LocationChanged += DynamoView_LocationChanged;
notificationsButton.Click += NotificationsButton_Click;
dynamoView.Closing += View_Closing;

notificationUIPopup = new NotificationUI
{
Expand All @@ -96,40 +98,14 @@ internal NotificationCenterController(DynamoView view, DynamoLogger dynLogger)

// If user turns on the feature, they will need to restart Dynamo to see the count
// This ensures no network traffic when Notification center feature is turned off
if (dynamoViewModel.PreferenceSettings.EnableNotificationCenter && !dynamoViewModel.Model.NoNetworkMode )
{
InitializeBrowserAsync();
if (dynamoViewModel.PreferenceSettings.EnableNotificationCenter && !dynamoViewModel.Model.NoNetworkMode )
{
notificationUIPopup.webView.Loaded += InitializeBrowserAsync;
pinzart90 marked this conversation as resolved.
Show resolved Hide resolved
RequestNotifications();
}
}

private void View_Closing(object sender, System.ComponentModel.CancelEventArgs e)
{
dynamoView.Closing -= View_Closing;
SuspendCoreWebviewAsync();
}

async void SuspendCoreWebviewAsync()
{
if (notificationUIPopup == null) return;

notificationUIPopup.IsOpen = false;
notificationUIPopup.webView.Visibility = Visibility.Hidden;

if (notificationUIPopup.webView.CoreWebView2 != null)
{
notificationUIPopup.webView.CoreWebView2.Stop();
notificationUIPopup.webView.CoreWebView2InitializationCompleted -= WebView_CoreWebView2InitializationCompleted;
notificationUIPopup.webView.CoreWebView2.NewWindowRequested -= WebView_NewWindowRequested;

dynamoView.SizeChanged -= DynamoView_SizeChanged;
dynamoView.LocationChanged -= DynamoView_LocationChanged;
notificationsButton.Click -= NotificationsButton_Click;
notificationUIPopup.webView.NavigationCompleted -= WebView_NavigationCompleted;
}
}

private void InitializeBrowserAsync()
private async void InitializeBrowserAsync(object sender, RoutedEventArgs e)
{
if (webBrowserUserDataFolder != null)
{
Expand All @@ -140,7 +116,10 @@ private void InitializeBrowserAsync()
};
}
notificationUIPopup.webView.CoreWebView2InitializationCompleted += WebView_CoreWebView2InitializationCompleted;
notificationUIPopup.webView.EnsureCoreWebView2Async();

initState = AsyncMethodState.Started;
await notificationUIPopup.webView.EnsureCoreWebView2Async();
initState = AsyncMethodState.Done;
}

private void WebView_NavigationCompleted(object sender, Microsoft.Web.WebView2.Core.CoreWebView2NavigationCompletedEventArgs e)
Expand Down Expand Up @@ -315,5 +294,48 @@ public void RefreshNotifications(string url="") {
InvokeJS(@"window.RequestNotifications('" + DynamoUtilities.PathHelper.GetServiceBackendAddress(this, "notificationAddress") + "');");
}
}

protected virtual void Dispose(bool disposing)
{
if (dynamoView != null)
{
dynamoView.SizeChanged -= DynamoView_SizeChanged;
dynamoView.LocationChanged -= DynamoView_LocationChanged;
}

if (notificationUIPopup == null)
pinzart90 marked this conversation as resolved.
Show resolved Hide resolved
{
notificationUIPopup.IsOpen = false;
notificationsButton.Click -= NotificationsButton_Click;

if (notificationUIPopup.webView != null)
{
notificationUIPopup.webView.Visibility = Visibility.Hidden;
notificationUIPopup.webView.Loaded -= InitializeBrowserAsync;
notificationUIPopup.webView.NavigationCompleted -= WebView_NavigationCompleted;
notificationUIPopup.webView.CoreWebView2InitializationCompleted -= WebView_CoreWebView2InitializationCompleted;

if (notificationUIPopup.webView.CoreWebView2 != null)
{
notificationUIPopup.webView.CoreWebView2.Stop();
notificationUIPopup.webView.CoreWebView2.NewWindowRequested -= WebView_NewWindowRequested;
}
notificationUIPopup.webView.Dispose();
}
}
}

/// <summary>
/// Dispose function for DocumentationBrowser
/// </summary>
public void Dispose()
{
if (initState == AsyncMethodState.Started)
{
logger?.Log("NotificationCenterController is being disposed but async initialization is still not done");
}
Dispose(true);
GC.SuppressFinalize(this);
}
}
}
8 changes: 8 additions & 0 deletions src/Notifications/NotificationsViewExtension.cs
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,8 @@ public void Dispose()
{
BindingOperations.ClearAllBindings(notificationsMenuItem.CountLabel);
}
notificationCenterController?.Dispose();
notificationCenterController = null;
notificationsMenuItem = null;
disposed = true;
}
Expand Down Expand Up @@ -115,6 +117,12 @@ public void Loaded(ViewLoadedParams viewStartupParams)
private void LoadNotificationCenter()
{
var dynamoView = viewLoadedParams.DynamoWindow as DynamoView;

if (notificationCenterController != null)
{
notificationCenterController.Dispose();
notificationCenterController = null;
pinzart90 marked this conversation as resolved.
Show resolved Hide resolved
}
notificationCenterController = new NotificationCenterController(dynamoView, logger);
}

Expand Down
Loading