diff --git a/Directory.Build.props b/Directory.Build.props
index cc3e70cd..2ed049fc 100644
--- a/Directory.Build.props
+++ b/Directory.Build.props
@@ -4,13 +4,13 @@
net8.0
x64;ARM64
11.0.10
- 120.6099.207
+ 120.6099.210
2.0.0.0
2.0.0.0
- 3.120.8
+ 4.120.0
OutSystems
WebViewControl
Copyright © OutSystems 2023
diff --git a/Directory.Packages.props b/Directory.Packages.props
index 4c06c172..505c3456 100644
--- a/Directory.Packages.props
+++ b/Directory.Packages.props
@@ -1,39 +1,33 @@
-
- true
-
-
-
-
-
- $(PrivateAssets);compile
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+ $(PrivateAssets);compile
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/SampleWebView.Avalonia/MainWindow.xaml b/SampleWebView.Avalonia/MainWindow.xaml
index dc98c1c3..311d0179 100755
--- a/SampleWebView.Avalonia/MainWindow.xaml
+++ b/SampleWebView.Avalonia/MainWindow.xaml
@@ -7,35 +7,6 @@
ExtendClientAreaChromeHints="PreferSystemChrome"
TransparencyLevelHint="AcrylicBlur"
Background="Transparent">
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
@@ -47,14 +18,66 @@
+
+
+
+
+
+
+
+
+
-
-
-
-
-
-
-
+
+
+
+
+
+
+
diff --git a/SampleWebView.Avalonia/MainWindowV1.xaml b/SampleWebView.Avalonia/MainWindowV1.xaml
new file mode 100644
index 00000000..28c1671d
--- /dev/null
+++ b/SampleWebView.Avalonia/MainWindowV1.xaml
@@ -0,0 +1,122 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Text
+
+
+
+
+ Source
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/SampleWebView.Avalonia/MainWindowV1.xaml.cs b/SampleWebView.Avalonia/MainWindowV1.xaml.cs
new file mode 100644
index 00000000..b5d99596
--- /dev/null
+++ b/SampleWebView.Avalonia/MainWindowV1.xaml.cs
@@ -0,0 +1,16 @@
+using Avalonia.Controls;
+using Avalonia.Markup.Xaml;
+using WebViewControl;
+
+namespace SampleWebView.Avalonia {
+
+ internal partial class MainWindowV1 : Window {
+
+ public MainWindowV1() {
+ WebView.Settings.LogFile = "ceflog.txt";
+ AvaloniaXamlLoader.Load(this);
+
+ DataContext = new MainWindowV1ViewModel(this.FindControl("webview"));
+ }
+ }
+}
\ No newline at end of file
diff --git a/SampleWebView.Avalonia/MainWindowV1ViewModel.cs b/SampleWebView.Avalonia/MainWindowV1ViewModel.cs
new file mode 100644
index 00000000..3c5d70c4
--- /dev/null
+++ b/SampleWebView.Avalonia/MainWindowV1ViewModel.cs
@@ -0,0 +1,419 @@
+using System;
+using System.Collections.Generic;
+using System.ComponentModel;
+using System.Diagnostics;
+using System.IO;
+using System.Linq;
+using System.Reactive;
+using System.Threading.Tasks;
+using ReactiveUI;
+using WebViewControl;
+
+namespace SampleWebView.Avalonia;
+
+public class MainWindowV1ViewModel : ReactiveObject {
+ #region Fields
+
+ private readonly Dictionary downloads = new();
+ private readonly WebView webView;
+
+ private readonly ReactiveCommand navigateCommand;
+ private readonly ReactiveCommand showDevToolsCommand;
+ private readonly ReactiveCommand cutCommand;
+ private readonly ReactiveCommand copyCommand;
+ private readonly ReactiveCommand pasteCommand;
+ private readonly ReactiveCommand undoCommand;
+ private readonly ReactiveCommand redoCommand;
+ private readonly ReactiveCommand selectAllCommand;
+ private readonly ReactiveCommand deleteCommand;
+ private readonly ReactiveCommand backCommand;
+ private readonly ReactiveCommand forwardCommand;
+ private readonly ReactiveCommand getSourceCommand;
+ private readonly ReactiveCommand getTextCommand;
+
+ private string address;
+ private string currentAddress;
+
+ private bool isDownloading;
+ private bool isDownloadDeterminate;
+ private double downloadPercentage;
+ private string downloadMessage;
+ private string downloadProgress;
+
+ private string source;
+ private bool sourceAvailable;
+ private string text;
+ private bool textAvailable;
+
+ #endregion Fields
+
+ public MainWindowV1ViewModel(WebView webview) {
+ Address = CurrentAddress = "http://www.google.com/";
+ //Address = CurrentAddress = "http://www.testfile.org/";
+ webView = webview;
+
+ webview.AllowDeveloperTools = true;
+
+ webview.Navigated += OnNavigated;
+
+ webview.DownloadCancelled += OnDownloadCancelled;
+ webview.DownloadCompleted += OnDownloadCompleted;
+ webview.DownloadProgressChanged += OnDownloadProgressChanged;
+
+ navigateCommand = ReactiveCommand.Create(() => {
+ CurrentAddress = Address;
+ });
+
+ showDevToolsCommand = ReactiveCommand.Create(webview.ShowDeveloperTools);
+
+ cutCommand = ReactiveCommand.Create(() => {
+ webview.EditCommands.Cut();
+ });
+
+ copyCommand = ReactiveCommand.Create(() => {
+ webview.EditCommands.Copy();
+ });
+
+ pasteCommand = ReactiveCommand.Create(() => {
+ webview.EditCommands.Paste();
+ });
+
+ undoCommand = ReactiveCommand.Create(() => {
+ webview.EditCommands.Undo();
+ });
+
+ redoCommand = ReactiveCommand.Create(() => {
+ webview.EditCommands.Redo();
+ });
+
+ selectAllCommand = ReactiveCommand.Create(() => {
+ webview.EditCommands.SelectAll();
+ });
+
+ deleteCommand = ReactiveCommand.Create(() => {
+ webview.EditCommands.Delete();
+ });
+
+ backCommand = ReactiveCommand.Create(webview.GoBack);
+
+ forwardCommand = ReactiveCommand.Create(webview.GoForward);
+
+ getTextCommand = ReactiveCommand.Create(() => {
+ webview.GetText(OnTextAvailable);
+ });
+
+ getSourceCommand = ReactiveCommand.Create(() => {
+ webview.GetSource(OnSourceAvailable);
+ });
+
+ PropertyChanged += OnPropertyChanged;
+
+
+ }
+
+ public ReactiveCommand NavigateCommand => navigateCommand;
+
+ public ReactiveCommand ShowDevToolsCommand => showDevToolsCommand;
+
+ public ReactiveCommand CutCommand => cutCommand;
+
+ public ReactiveCommand CopyCommand => copyCommand;
+
+ public ReactiveCommand PasteCommand => pasteCommand;
+
+ public ReactiveCommand UndoCommand => undoCommand;
+
+ public ReactiveCommand RedoCommand => redoCommand;
+
+ public ReactiveCommand SelectAllCommand => selectAllCommand;
+
+ public ReactiveCommand DeleteCommand => deleteCommand;
+
+ public ReactiveCommand BackCommand => backCommand;
+
+ public ReactiveCommand ForwardCommand => forwardCommand;
+
+ public ReactiveCommand GetSourceCommand => getSourceCommand;
+
+ public ReactiveCommand GetTextCommand => getTextCommand;
+
+ public string Address {
+ get => address;
+ set => this.RaiseAndSetIfChanged(ref address, value);
+ }
+
+ public string CurrentAddress {
+ get => currentAddress;
+ set => this.RaiseAndSetIfChanged(ref currentAddress, value);
+ }
+
+ public bool IsDownloading {
+ get => isDownloading;
+ set => this.RaiseAndSetIfChanged(ref isDownloading, value);
+ }
+
+ public bool IsDownloadDeterminate {
+ get => isDownloadDeterminate;
+ set => this.RaiseAndSetIfChanged(ref isDownloadDeterminate, value);
+ }
+
+ public double DownloadPercentage {
+ get => downloadPercentage;
+ set => this.RaiseAndSetIfChanged(ref downloadPercentage, value);
+ }
+
+ public string DownloadMessage {
+ get => downloadMessage;
+ set => this.RaiseAndSetIfChanged(ref downloadMessage, value);
+ }
+
+ public string DownloadProgress {
+ get => downloadProgress;
+ set => this.RaiseAndSetIfChanged(ref downloadProgress, value);
+ }
+
+ public string Source {
+ get => source;
+ set => this.RaiseAndSetIfChanged(ref source, value);
+ }
+
+ public bool SourceAvailable {
+ get => sourceAvailable;
+ set => this.RaiseAndSetIfChanged(ref sourceAvailable, value);
+ }
+
+ public string Text {
+ get => text;
+ set => this.RaiseAndSetIfChanged(ref text, value);
+ }
+
+ public bool TextAvailable {
+ get => textAvailable;
+ set => this.RaiseAndSetIfChanged(ref textAvailable, value);
+ }
+
+ private void OnPropertyChanged(object sender, PropertyChangedEventArgs e) {
+ if (e.PropertyName == nameof(CurrentAddress)) {
+ Address = CurrentAddress;
+ }
+ }
+
+ private void OnNavigated(string url, string frameName) {
+ if (!string.IsNullOrWhiteSpace(frameName)) {
+ return;
+ }
+
+ Text = "";
+ TextAvailable = false;
+ webView.GetText(OnTextAvailable);
+
+ Source = "";
+ SourceAvailable = false;
+ }
+
+ private void OnSourceAvailable(string str) {
+ Source = str;
+ SourceAvailable = true;
+ }
+
+ private void OnTextAvailable(string str) {
+ Text = str;
+ TextAvailable = true;
+ }
+
+ #region Download V1 Event Implementation
+
+ private void OnDownloadProgressChanged(string resourcePath, long receivedBytes, long totalBytes) {
+ // Tracking multiple file downloads at once is potentially wonky due to not being given the downloadItem.Id
+
+ // Download Started
+ if (receivedBytes == 0) {
+ // Since the File Dialog is Modal we know that there can only ever be one downloadItem = "" at a time
+ downloads.Add("", new DownloadItemModel("", receivedBytes, totalBytes));
+ }
+
+ // Progress Changed
+ DownloadItemModel downloadItem;//= new DownloadItem(resourcePath, receivedBytes, totalBytes);
+ // The download proceeds in the background while waiting for the resourcePath from the user
+ if (!string.IsNullOrWhiteSpace(resourcePath)) {
+ // Try to get the downloadItem by resourcePath
+ if (!downloads.TryGetValue(resourcePath, out downloadItem)) {
+ // Not found, so get the downloadItem = ""
+ if (downloads.TryGetValue("", out downloadItem)) {
+ // Now we need to first remove it from the collection as "", then put it back in the collection as resourcePath
+ downloads.Remove("");
+ downloadItem.FullPath = resourcePath;
+ downloads.Add(resourcePath, downloadItem);
+ }
+ }
+ } else {
+ // Get the downloadItem = ""
+ downloads.TryGetValue("", out downloadItem);
+ }
+
+ // Now update the downloadItem...
+ downloadItem?.Update(resourcePath, receivedBytes, totalBytes);
+
+ UpdateDownloadPanel();
+
+ // Download Completed
+ if (receivedBytes == totalBytes && !string.IsNullOrWhiteSpace(resourcePath)) {
+ // We have to stop tracking here because the resourcePath could change between now and the DownloadCompleted firing
+ // the PropertyChanged Event will fire two more time after this???, so rather than remove it now we flag it for removal by the Completed Event
+ downloadItem?.SetCompleted();
+ }
+
+ //Debug.WriteLine($"{nameof(OnDownloadProgressChanged)}( count: {downloads.Count}, downloadItem: ( fullPath: {downloadItem.FullPath}, receivedBytes: {downloadItem.ReceivedBytes}, totalBytes {downloadItem.TotalBytes}, percentage {downloadItem.PercentComplete}, isCompleted {downloadItem.IsCompleted} ))");
+ }
+
+ private void OnDownloadCompleted(string resourcePath) {
+ // Here the resourcePath may be different from the resourcePath passed to PropertyChanged
+
+ // Remove the Completed DownloadItems
+ var completed = downloads.Values
+ .Where(x => x.IsCompleted)
+ .ToList();
+
+ if (completed.Count == 1) {
+ var downloadItem = completed[0];
+ downloadItem.Update(resourcePath);
+ downloads.Remove(downloadItem.FullPath);
+ completed.Add(downloadItem);
+ }
+ foreach (var item in completed) {
+ downloads.Remove(item.FullPath);
+ }
+
+ DownloadMessage = $"Download Completed: {Path.GetFileName(resourcePath)}";
+ DownloadPercentage = 100.0;
+ IsDownloadDeterminate = true;
+
+ CloseDownloadPanel();
+ }
+
+ private void OnDownloadCancelled(string resourcePath) {
+ // Here the resourcePath probably will be null
+ DownloadItemModel downloadItem;
+ if (string.IsNullOrWhiteSpace(resourcePath)) {
+ downloads.Remove("", out downloadItem);
+ } else {
+ downloads.Remove(resourcePath, out downloadItem);
+ }
+
+ downloadItem?.SetCancelled(resourcePath);
+
+ DownloadMessage = "Download Cancelled";
+ IsDownloadDeterminate = false;
+
+ CloseDownloadPanel();
+ }
+
+ private void CloseDownloadPanel() {
+ Debug.WriteLine($"{nameof(CloseDownloadPanel)}: ({downloads.Count})");
+ if (downloads.Count != 0) {
+ return;
+ }
+
+ Task.Delay(2000)
+ .ContinueWith(t => {
+ if (downloads.Count != 0) {
+ return;
+ }
+
+ IsDownloading = false;
+ });
+ }
+
+ private void UpdateDownloadPanel() {
+ var downloadItem = downloads.Values
+ .OrderByDescending(x => x.PercentComplete)
+ .First();
+
+ IsDownloading = true;
+ IsDownloadDeterminate = true;
+ DownloadPercentage = downloadItem.PercentComplete;
+ DownloadMessage = $"Downloading: {downloadItem.FullPath}";
+
+ var estimated = downloadItem.CurrentSpeed == 0 ?
+ "Unknown" : downloadItem.RemainingBytes == 0 ?
+ "None" :
+ $"{downloadItem.EstimatedTimeRemaining} sec.";
+
+ DownloadProgress = $"{downloadItem.ReceivedBytes}/{downloadItem.TotalBytes} bytes, Time Remaining: {estimated}";
+ }
+
+ private class DownloadItemModel(string fullPath, long receivedBytes, long totalBytes) {
+ private readonly Stopwatch elapsedTime = Stopwatch.StartNew();
+
+ public string FullPath { get; set; } = fullPath;
+
+ public string FileName {
+ get {
+ return Path.GetFileName(FullPath);
+ }
+ }
+
+ public long ReceivedBytes { get; set; } = receivedBytes;
+
+ public long TotalBytes { get; set; } = totalBytes;
+
+ public double PercentComplete {
+ get {
+ return (double)ReceivedBytes / TotalBytes * 100.0;
+ }
+ }
+
+ public long RemainingBytes {
+ get {
+ return TotalBytes - ReceivedBytes;
+ }
+ }
+
+ public long CurrentSpeed {
+ get {
+ return (ReceivedBytes / elapsedTime.Elapsed.Seconds);
+ }
+ } // BytesPerSecond
+
+ public long EstimatedTimeRemaining {
+ get;
+ set;
+ } // Seconds
+
+ public string StoppedReason { get; set; }
+
+ public bool IsCompleted { get; set; }
+
+ public void Update(string fullPath, long receivedBytes, long totalBytes) {
+ FullPath = fullPath;
+ ReceivedBytes = receivedBytes;
+ TotalBytes = totalBytes;
+
+ if (RemainingBytes == 0) {
+ elapsedTime.Stop();
+ }
+
+ if (CurrentSpeed > 0) {
+ EstimatedTimeRemaining = (long)((double)RemainingBytes / CurrentSpeed);
+ }
+ }
+
+ public void Update(string fullPath) {
+ FullPath = fullPath;
+ }
+
+ public void SetCompleted() {
+ elapsedTime.Stop();
+ IsCompleted = true;
+ }
+
+ public void SetCancelled(string fullPath) {
+ elapsedTime.Stop();
+ FullPath = fullPath;
+ StoppedReason = "UserCancelled";
+ ReceivedBytes = 0;
+ }
+ }
+
+ #endregion Download V1 Event Implementation
+}
\ No newline at end of file
diff --git a/SampleWebView.Avalonia/MainWindowViewModel.cs b/SampleWebView.Avalonia/MainWindowViewModel.cs
index 2d3ce863..d970449d 100755
--- a/SampleWebView.Avalonia/MainWindowViewModel.cs
+++ b/SampleWebView.Avalonia/MainWindowViewModel.cs
@@ -1,100 +1,356 @@
+using System;
+using System.Collections.Generic;
using System.ComponentModel;
+using System.Diagnostics;
+using System.IO;
+using System.Linq;
using System.Reactive;
+using System.Reflection;
+using System.Runtime.CompilerServices;
+using System.Threading.Tasks;
+using Avalonia;
+using Avalonia.Controls.ApplicationLifetimes;
using ReactiveUI;
using WebViewControl;
-namespace SampleWebView.Avalonia {
- class MainWindowViewModel : ReactiveObject {
+namespace SampleWebView.Avalonia;
- private string address;
- private string currentAddress;
+public class MainWindowViewModel : ReactiveObject {
+ #region Fields
- public MainWindowViewModel(WebView webview) {
- Address = CurrentAddress = "http://www.google.com/";
+ private readonly Dictionary downloads = new();
+ private readonly List completedDownloads = new();
+ private readonly WebView webView;
- NavigateCommand = ReactiveCommand.Create(() => {
- CurrentAddress = Address;
- });
+ private readonly ReactiveCommand navigateCommand;
+ private readonly ReactiveCommand showDevToolsCommand;
+ private readonly ReactiveCommand cutCommand;
+ private readonly ReactiveCommand copyCommand;
+ private readonly ReactiveCommand pasteCommand;
+ private readonly ReactiveCommand undoCommand;
+ private readonly ReactiveCommand redoCommand;
+ private readonly ReactiveCommand selectAllCommand;
+ private readonly ReactiveCommand deleteCommand;
+ private readonly ReactiveCommand backCommand;
+ private readonly ReactiveCommand forwardCommand;
+ private readonly ReactiveCommand getSourceCommand;
+ private readonly ReactiveCommand getTextCommand;
+ private readonly ReactiveCommand exitCommand;
- ShowDevToolsCommand = ReactiveCommand.Create(() => {
- webview.ShowDeveloperTools();
- });
+ private string address;
+ private string currentAddress;
+
+ private double downloadPercentage;
+ private bool isDownloading;
+ private bool isDownloadDeterminate;
+ private string downloadMessage;
+ private string downloadProgress;
- CutCommand = ReactiveCommand.Create(() => {
- webview.EditCommands.Cut();
- });
+ private string source;
+ private bool sourceAvailable;
+ private string text;
+ private bool textAvailable;
- CopyCommand = ReactiveCommand.Create(() => {
- webview.EditCommands.Copy();
- });
+ #endregion Fields
- PasteCommand = ReactiveCommand.Create(() => {
- webview.EditCommands.Paste();
- });
+ public MainWindowViewModel(WebView webview) {
+ Address = CurrentAddress = "http://www.google.com/";
+ //Address = CurrentAddress = "http://www.testfile.org/";
+ webView = webview;
- UndoCommand = ReactiveCommand.Create(() => {
- webview.EditCommands.Undo();
- });
+ webview.AllowDeveloperTools = true;
- RedoCommand = ReactiveCommand.Create(() => {
- webview.EditCommands.Redo();
- });
+ webview.Navigated += OnNavigated;
- SelectAllCommand = ReactiveCommand.Create(() => {
- webview.EditCommands.SelectAll();
- });
+ webView.PopupOpening += OnPopupOpening;
- DeleteCommand = ReactiveCommand.Create(() => {
- webview.EditCommands.Delete();
- });
-
- BackCommand = ReactiveCommand.Create(() => {
- webview.GoBack();
- });
-
- ForwardCommand = ReactiveCommand.Create(() => {
- webview.GoForward();
- });
+ webview.DownloadItemStarted += DownloadItemStarted;
+ webview.DownloadItemProgressChanged += DownloadItemProgressChanged;
+ webView.DownloadItemCompleted += DownloadItemCompleted;
+ webview.DownloadItemStopped += DownloadItemStopped;
- PropertyChanged += OnPropertyChanged;
- }
+ navigateCommand = ReactiveCommand.Create(() => {
+ CurrentAddress = Address;
+ });
+
+ showDevToolsCommand = ReactiveCommand.Create(webview.ShowDeveloperTools);
+
+ cutCommand = ReactiveCommand.Create(() => {
+ webview.EditCommands.Cut();
+ });
- private void OnPropertyChanged(object sender, PropertyChangedEventArgs e) {
- if (e.PropertyName == nameof(CurrentAddress)) {
- Address = CurrentAddress;
+ copyCommand = ReactiveCommand.Create(() => {
+ webview.EditCommands.Copy();
+ });
+
+ pasteCommand = ReactiveCommand.Create(() => {
+ webview.EditCommands.Paste();
+ });
+
+ undoCommand = ReactiveCommand.Create(() => {
+ webview.EditCommands.Undo();
+ });
+
+ redoCommand = ReactiveCommand.Create(() => {
+ webview.EditCommands.Redo();
+ });
+
+ selectAllCommand = ReactiveCommand.Create(() => {
+ webview.EditCommands.SelectAll();
+ });
+
+ deleteCommand = ReactiveCommand.Create(() => {
+ webview.EditCommands.Delete();
+ });
+
+ backCommand = ReactiveCommand.Create(webview.GoBack);
+
+ forwardCommand = ReactiveCommand.Create(webview.GoForward);
+
+ getTextCommand = ReactiveCommand.Create(() => {
+ webview.GetText(OnTextAvailable);
+ });
+
+ getSourceCommand = ReactiveCommand.Create(() => {
+ webview.GetSource(OnSourceAvailable);
+ });
+
+ exitCommand = ReactiveCommand.Create(() => {
+ if (Application.Current?.ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktopApp) {
+ desktopApp.Shutdown();
}
+ });
+
+ PropertyChanged += OnPropertyChanged;
+ }
+
+ public ReactiveCommand NavigateCommand => navigateCommand;
+
+ public ReactiveCommand ShowDevToolsCommand => showDevToolsCommand;
+
+ public ReactiveCommand CutCommand => cutCommand;
+
+ public ReactiveCommand CopyCommand => copyCommand;
+
+ public ReactiveCommand PasteCommand => pasteCommand;
+
+ public ReactiveCommand UndoCommand => undoCommand;
+
+ public ReactiveCommand RedoCommand => redoCommand;
+
+ public ReactiveCommand SelectAllCommand => selectAllCommand;
+
+ public ReactiveCommand DeleteCommand => deleteCommand;
+
+ public ReactiveCommand BackCommand => backCommand;
+
+ public ReactiveCommand ForwardCommand => forwardCommand;
+
+ public ReactiveCommand GetSourceCommand => getSourceCommand;
+
+ public ReactiveCommand GetTextCommand => getTextCommand;
+
+ public ReactiveCommand ExitCommand => exitCommand;
+
+ public string Address {
+ get => address;
+ set => this.RaiseAndSetIfChanged(ref address, value);
+ }
+
+ public string CurrentAddress {
+ get => currentAddress;
+ set => this.RaiseAndSetIfChanged(ref currentAddress, value);
+ }
+
+ public bool IsDownloading {
+ get => isDownloading;
+ set => this.RaiseAndSetIfChanged(ref isDownloading, value);
+ }
+
+ public bool IsDownloadDeterminate {
+ get => isDownloadDeterminate;
+ set => this.RaiseAndSetIfChanged(ref isDownloadDeterminate, value);
+ }
+
+ public double DownloadPercentage {
+ get => downloadPercentage;
+ set => this.RaiseAndSetIfChanged(ref downloadPercentage, value);
+ }
+
+ public string DownloadMessage {
+ get => downloadMessage;
+ set => this.RaiseAndSetIfChanged(ref downloadMessage, value);
+ }
+
+ public string DownloadProgress {
+ get => downloadProgress;
+ set => this.RaiseAndSetIfChanged(ref downloadProgress, value);
+ }
+
+ public string Source {
+ get => source;
+ set => this.RaiseAndSetIfChanged(ref source, value);
+ }
+
+ public bool SourceAvailable {
+ get => sourceAvailable;
+ set => this.RaiseAndSetIfChanged(ref sourceAvailable, value);
+ }
+
+ public string Text {
+ get => text;
+ set => this.RaiseAndSetIfChanged(ref text, value);
+ }
+
+ public bool TextAvailable {
+ get => textAvailable;
+ set => this.RaiseAndSetIfChanged(ref textAvailable, value);
+ }
+
+ private void OnPropertyChanged(object sender, PropertyChangedEventArgs e) {
+ if (e.PropertyName == nameof(CurrentAddress)) {
+ Address = CurrentAddress;
+ }
+ }
+
+ private void OnPopupOpening(string url) {
+ // Catch window.open(), since we are a SDI browser we will simply Navigate to the new url
+ CurrentAddress = url;
+ }
+
+ private void OnNavigated(string url, string frameName) {
+ if (!string.IsNullOrWhiteSpace(frameName)) {
+ return;
}
- public string Address {
- get => address;
- set => this.RaiseAndSetIfChanged(ref address, value);
+ Text = "";
+ TextAvailable = false;
+ //webView.GetText(OnTextAvailable);
+
+ Source = "";
+ SourceAvailable = false;
+ }
+
+ private void OnSourceAvailable(string str) {
+ Source = str;
+ SourceAvailable = true;
+
+ var sourceFilePath = SaveAs("Data\\sample.html", str);
+
+ Debug.WriteLine($"Page source saved successfully to {sourceFilePath}");
+ }
+
+ private void OnTextAvailable(string str) {
+ Text = str;
+ TextAvailable = true;
+
+ var textFilePath = SaveAs("Data\\sample.txt", str);
+
+ Debug.WriteLine($"Page text saved successfully to {textFilePath}");
+ }
+
+ private string SaveAs(string fileName, string fileContents) {
+ if (string.IsNullOrWhiteSpace(fileName) || fileName == "/" || fileName == "\\") {
+ return "";
}
- public string CurrentAddress {
- get => currentAddress;
- set => this.RaiseAndSetIfChanged(ref currentAddress, value);
+ try {
+ var fileInfo = new FileInfo(fileName);
+
+ if (fileInfo.Directory is null || fileInfo.Attributes == FileAttributes.Directory) {
+ return "";
+ }
+
+ if (!fileInfo.Directory.Exists) {
+ fileInfo.Directory.Create();
+ }
+
+ if (fileInfo.Exists && fileInfo.IsReadOnly) {
+ return "";
+ }
+
+ using var sourceStreamWriter = File.CreateText(fileName);
+ sourceStreamWriter.Write(fileContents);
+ sourceStreamWriter.Close();
+
+ return fileInfo.FullName;
+ } catch {
+ return "";
}
+ }
+
+ #region Download V2 Event Implementation
+
+ private void DownloadItemStarted(DownloadItem item) {
+ downloads.Add(item.Id, item);
+
+ IsDownloading = true;
+ }
- public ReactiveCommand NavigateCommand { get; }
+ private void DownloadItemProgressChanged(DownloadItem item) {
+ downloads[item.Id] = item;
- public ReactiveCommand ShowDevToolsCommand { get; }
+ UpdateDownloadPanel();
+ }
- public ReactiveCommand CutCommand { get; }
+ private void DownloadItemCompleted(DownloadItem item) {
+ downloads.Remove(item.Id);
+ completedDownloads.Add(item);
- public ReactiveCommand CopyCommand { get; }
+ // Manage UI
+ IsDownloadDeterminate = true;
+ DownloadPercentage = 100.0;
+ DownloadMessage = $"Download Completed: {item.FullPath}";
+ DownloadProgress = $"{item.ReceivedBytes}/{item.TotalBytes}, Time Remaining: None";
- public ReactiveCommand PasteCommand { get; }
+ CloseDownloadPanel();
+ }
- public ReactiveCommand UndoCommand { get; }
+ private void DownloadItemStopped(DownloadItem item) {
+ downloads.Remove(item.Id);
- public ReactiveCommand RedoCommand { get; }
+ // Manage UI
+ IsDownloadDeterminate = false;
+ DownloadProgress = "";
+ DownloadMessage = $"Download Stopped ({item.InterruptReason}): {item.FullPath}";
- public ReactiveCommand SelectAllCommand { get; }
+ CloseDownloadPanel();
+ }
- public ReactiveCommand DeleteCommand { get; }
+ private void CloseDownloadPanel() {
+ Debug.WriteLine($"{nameof(CloseDownloadPanel)}: ({downloads.Count})");
- public ReactiveCommand BackCommand { get; }
-
- public ReactiveCommand ForwardCommand { get; }
+ if (downloads.Count != 0) {
+ return;
+ }
+
+ Task.Delay(2000)
+ .ContinueWith(t => {
+ if (downloads.Count != 0) {
+ return;
+ }
+
+ IsDownloading = false;
+ });
}
-}
+
+ private void UpdateDownloadPanel() {
+ var downloadItem = downloads.Values
+ .OrderByDescending(x => x.PercentComplete)
+ .First();
+
+ IsDownloading = true;
+ IsDownloadDeterminate = true;
+ DownloadPercentage = downloadItem.PercentComplete;
+ DownloadMessage = $"Downloading: {downloadItem.FullPath}";
+
+ var estimated = downloadItem.CurrentSpeed == 0 ?
+ "Unknown" : downloadItem.RemainingBytes == 0 ?
+ "None" :
+ $"{downloadItem.EstimatedTimeRemaining} sec.";
+
+ DownloadProgress = $"{downloadItem.ReceivedBytes}/{downloadItem.TotalBytes} bytes, Time Remaining: {estimated}";
+ }
+
+ #endregion Download V2 Event Implementation
+}
\ No newline at end of file
diff --git a/SampleWebView.Avalonia/SampleWebView.Avalonia.csproj b/SampleWebView.Avalonia/SampleWebView.Avalonia.csproj
index 18bb2319..08d007fb 100755
--- a/SampleWebView.Avalonia/SampleWebView.Avalonia.csproj
+++ b/SampleWebView.Avalonia/SampleWebView.Avalonia.csproj
@@ -27,16 +27,23 @@
+
-
+
MSBuild:Compile
-
-
+
+
MSBuild:Compile
-
+
+
+
+
+
+ MSBuild:Compile
+
diff --git a/WebViewControl.Avalonia/WebView.Avalonia.cs b/WebViewControl.Avalonia/WebView.Avalonia.cs
index 79ef1179..387be777 100644
--- a/WebViewControl.Avalonia/WebView.Avalonia.cs
+++ b/WebViewControl.Avalonia/WebView.Avalonia.cs
@@ -1,4 +1,5 @@
using System;
+using System.Diagnostics;
using System.Runtime.ExceptionServices;
using Avalonia;
using Avalonia.Controls;
@@ -35,10 +36,11 @@ partial void ExtraInitialize() {
}
protected override void OnKeyDown(KeyEventArgs e) {
- if (AllowDeveloperTools && e.Key == Key.F12) {
- ToggleDeveloperTools();
- e.Handled = true;
- }
+ // See WebView.InternalKeyboardHandler
+ //if (AllowDeveloperTools && e.Key == Key.F12) {
+ // ToggleDeveloperTools();
+ // e.Handled = true;
+ //}
}
protected override void OnGotFocus(GotFocusEventArgs e) {
@@ -68,6 +70,24 @@ private T ExecuteInUI(Func action) {
return Dispatcher.UIThread.InvokeAsync(action).Result;
}
+ private void AsyncExecuteInUI(Action action, T value) {
+ if (isDisposing) {
+ return;
+ }
+ // use async call to avoid dead-locks, otherwise if the source action tries to to evaluate js it would block
+ Dispatcher.UIThread.InvokeAsync(
+ () => {
+ if (!isDisposing) {
+ try {
+ action?.Invoke(value);
+ } catch (Exception ex) {
+ ForwardUnhandledAsyncException(ex);
+ }
+ }
+ },
+ DispatcherPriority.Normal);
+ }
+
private void AsyncExecuteInUI(Action action) {
if (isDisposing) {
return;
diff --git a/WebViewControl.Avalonia/WebViewControl.Avalonia.csproj b/WebViewControl.Avalonia/WebViewControl.Avalonia.csproj
index 1fca803d..49d6bb52 100644
--- a/WebViewControl.Avalonia/WebViewControl.Avalonia.csproj
+++ b/WebViewControl.Avalonia/WebViewControl.Avalonia.csproj
@@ -59,6 +59,8 @@
+
+
diff --git a/WebViewControl/DelegateStringVisitor.cs b/WebViewControl/DelegateStringVisitor.cs
new file mode 100644
index 00000000..3063f1a9
--- /dev/null
+++ b/WebViewControl/DelegateStringVisitor.cs
@@ -0,0 +1,14 @@
+using System;
+using System.Diagnostics;
+using Xilium.CefGlue;
+
+namespace WebViewControl;
+
+public class DelegateStringVisitor(Action action, string frameName) : CefStringVisitor {
+ public string FrameName { get; } = frameName;
+
+ protected override void Visit(string value) {
+ // This is not guaranteed to be called from the UI thread...
+ action?.Invoke(value);
+ }
+}
\ No newline at end of file
diff --git a/WebViewControl/ThreadSafeDelegateStringVisitor.cs b/WebViewControl/ThreadSafeDelegateStringVisitor.cs
new file mode 100644
index 00000000..42fbd374
--- /dev/null
+++ b/WebViewControl/ThreadSafeDelegateStringVisitor.cs
@@ -0,0 +1,14 @@
+using System;
+
+namespace WebViewControl;
+
+public class ThreadSafeDelegateStringVisitor(
+ Action, string> executor,
+ Action action,
+ string frameName) : DelegateStringVisitor(action, frameName) {
+
+ protected override void Visit(string value) {
+ // Use WebView.AsyncExecuteInUI to Invoke the delegate on the UI thread!
+ executor.Invoke(action, value);
+ }
+}
\ No newline at end of file
diff --git a/WebViewControl/WebView.InternalDownloadHandler.cs b/WebViewControl/WebView.InternalDownloadHandler.cs
index f58d239c..a2ed0f04 100644
--- a/WebViewControl/WebView.InternalDownloadHandler.cs
+++ b/WebViewControl/WebView.InternalDownloadHandler.cs
@@ -1,4 +1,9 @@
-using Xilium.CefGlue;
+using System;
+using System.Diagnostics;
+using System.IO;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices.Marshalling;
+using Xilium.CefGlue;
using Xilium.CefGlue.Common.Handlers;
namespace WebViewControl {
@@ -18,20 +23,154 @@ protected override void OnBeforeDownload(CefBrowser browser, CefDownloadItem dow
}
protected override void OnDownloadUpdated(CefBrowser browser, CefDownloadItem downloadItem, CefDownloadItemCallback callback) {
+ var item = new DownloadItem(downloadItem);
+
if (downloadItem.IsComplete) {
- if (OwnerWebView.DownloadCompleted != null) {
- OwnerWebView.AsyncExecuteInUI(() => OwnerWebView.DownloadCompleted?.Invoke(downloadItem.FullPath));
- }
+ // DownloadCompleted
+ FireDownloadCompletedEvents(item);
} else if (downloadItem.IsCanceled) {
- if (OwnerWebView.DownloadCancelled != null) {
- OwnerWebView.AsyncExecuteInUI(() => OwnerWebView.DownloadCancelled?.Invoke(downloadItem.FullPath));
- }
+ // DownloadCancelled
+ FireDownloadCancelledEvents(item);
+ } else if (downloadItem.IsInterrupted) {
+ // Interrupted
+ FireDownloadStoppedEvent(item);
} else {
- if (OwnerWebView.DownloadProgressChanged != null) {
- OwnerWebView.AsyncExecuteInUI(() => OwnerWebView.DownloadProgressChanged?.Invoke(downloadItem.FullPath, downloadItem.ReceivedBytes, downloadItem.TotalBytes));
- }
+ // ProgressChanged
+ FireDownloadPropertyChangedEvents(item);
}
}
+
+ private void FireDownloadStartedEvents(DownloadItem downloadItem) {
+ if (OwnerWebView.DownloadItemStarted != null) {
+ OwnerWebView.AsyncExecuteInUI(() => OwnerWebView.DownloadItemStarted?.Invoke(downloadItem));
+ }
+ }
+
+ private void FireDownloadPropertyChangedEvents(DownloadItem downloadItem) {
+ if (OwnerWebView.DownloadProgressChanged != null) {
+ OwnerWebView.AsyncExecuteInUI(() => OwnerWebView.DownloadProgressChanged?.Invoke(downloadItem.FullPath, downloadItem.ReceivedBytes, downloadItem.TotalBytes));
+ }
+
+ if (downloadItem.ReceivedBytes == 0) {
+ // DownloadStarted
+ // We fire this here because CEF actually calls OnDownloadUpdated before OnBeforeDownload
+ FireDownloadStartedEvents(downloadItem);
+ }
+
+ if (OwnerWebView.DownloadItemProgressChanged != null) {
+ OwnerWebView.AsyncExecuteInUI(() =>
+ OwnerWebView.DownloadItemProgressChanged?.Invoke(downloadItem));
+ }
+ }
+
+ private void FireDownloadCancelledEvents(DownloadItem downloadItem) {
+ if (OwnerWebView.DownloadCancelled != null) {
+ OwnerWebView.AsyncExecuteInUI(() => OwnerWebView.DownloadCancelled?.Invoke(downloadItem.FullPath));
+ }
+ FireDownloadStoppedEvent(downloadItem);
+ }
+
+ private void FireDownloadStoppedEvent(DownloadItem downloadItem) {
+ if (OwnerWebView.DownloadItemStopped != null) {
+ OwnerWebView.AsyncExecuteInUI(() =>
+ OwnerWebView.DownloadItemStopped?.Invoke(downloadItem));
+ }
+ }
+
+ private void FireDownloadCompletedEvents(DownloadItem downloadItem) {
+ if (OwnerWebView.DownloadCompleted != null) {
+ OwnerWebView.AsyncExecuteInUI(() => OwnerWebView.DownloadCompleted?.Invoke(downloadItem.FullPath));
+ }
+
+ if (OwnerWebView.DownloadItemCompleted != null) {
+ OwnerWebView.AsyncExecuteInUI(() => OwnerWebView.DownloadItemCompleted?.Invoke(downloadItem));
+ }
+ }
+ }
+ }
+
+ public enum DownloadItemState {
+ InProgress = 0, Complete = 1, Cancelled = 2, Interrupted = 3
+ }
+
+ public enum DownloadItemInterruptReason {
+ None = 0,
+ FileFailed = 1,
+ FileAccessDenied = 2,
+ FileNoSpace = 3,
+ FileNameTooLong = 5,
+ FileTooLarge = 6,
+ FileVirusInfected = 7,
+ FileTransientError = 10, // 0x0000000A
+ FileBlocked = 11, // 0x0000000B
+ FileSecurityCheckFailed = 12, // 0x0000000C
+ FileTooShort = 13, // 0x0000000D
+ FileHashMismatch = 14, // 0x0000000E
+ FileSameAsSource = 15, // 0x0000000F
+ NetworkFailed = 20, // 0x00000014
+ NetworkTimeout = 21, // 0x00000015
+ NetworkDisconnected = 22, // 0x00000016
+ NetworkServerDown = 23, // 0x00000017
+ NetworkInvalidRequest = 24, // 0x00000018
+ ServerFailed = 30, // 0x0000001E
+ ServerNoRange = 31, // 0x0000001F
+ ServerBadContent = 33, // 0x00000021
+ ServerUnauthorized = 34, // 0x00000022
+ ServerCertProblem = 35, // 0x00000023
+ ServerForbidden = 36, // 0x00000024
+ ServerUnreachable = 37, // 0x00000025
+ ServerContentLengthMismatch = 38, // 0x00000026
+ ServerCrossOriginRedirect = 39, // 0x00000027
+ UserCanceled = 40, // 0x00000028
+ UserShutdown = 41, // 0x00000029
+ Crash = 50, // 0x00000032
+ }
+
+ public sealed record DownloadItem {
+ public DownloadItem(CefDownloadItem cefDownloadItem) {
+ Id = cefDownloadItem.Id;
+ TotalBytes = cefDownloadItem.TotalBytes;
+ ReceivedBytes = cefDownloadItem.ReceivedBytes;
+ CurrentSpeed = cefDownloadItem.CurrentSpeed;
+ PercentComplete = cefDownloadItem.PercentComplete;
+ Url = cefDownloadItem.Url;
+ OriginalUrl = cefDownloadItem.OriginalUrl;
+ FullPath = cefDownloadItem.FullPath ?? "";
+ MimeType = cefDownloadItem.MimeType;
+ InterruptReason = (DownloadItemInterruptReason)((int)cefDownloadItem.InterruptReason);
+
+ if (CurrentSpeed > 0) {
+ EstimatedTimeRemaining = RemainingBytes / CurrentSpeed;
+ }
+
+ if (cefDownloadItem.IsInProgress) {
+ State = DownloadItemState.InProgress;
+ } else if (cefDownloadItem.IsComplete) {
+ State = DownloadItemState.Complete;
+ } else if (cefDownloadItem.IsCanceled) {
+ State = DownloadItemState.Cancelled;
+ } else if (cefDownloadItem.IsInterrupted) {
+ State = DownloadItemState.Interrupted;
+ }
}
+
+ public uint Id { get; }
+ public long TotalBytes { get; }
+ public long ReceivedBytes { get; }
+ public long CurrentSpeed { get; }
+ public int PercentComplete { get; }
+
+ public string Url { get; } = "";
+ public string OriginalUrl { get; } = "";
+ public string FullPath { get; } = "";
+ public string MimeType { get; } = "";
+
+ public string FileName => Path.GetFileName(FullPath);
+ public string FileExtension => Path.GetExtension(FullPath);
+ public long RemainingBytes => TotalBytes - ReceivedBytes;
+ public long EstimatedTimeRemaining { get; }
+
+ public DownloadItemInterruptReason InterruptReason { get; }
+ public DownloadItemState State { get; }
}
}
diff --git a/WebViewControl/WebView.InternalJsDialogHandler.cs b/WebViewControl/WebView.InternalJsDialogHandler.cs
index 54cb9ccc..8a11749e 100644
--- a/WebViewControl/WebView.InternalJsDialogHandler.cs
+++ b/WebViewControl/WebView.InternalJsDialogHandler.cs
@@ -16,8 +16,8 @@ public InternalJsDialogHandler(WebView webView) {
protected override bool OnJSDialog(CefBrowser browser, string originUrl, CefJSDialogType dialogType, string message_text, string default_prompt_text, CefJSDialogCallback callback, out bool suppress_message) {
suppress_message = false;
- var javacriptDialogShown = OwnerWebView.JavacriptDialogShown;
- if (javacriptDialogShown == null) {
+ var javascriptDialogShown = OwnerWebView.JavascriptDialogShown;
+ if (javascriptDialogShown == null) {
return false;
}
@@ -26,7 +26,7 @@ void Close() {
callback.Dispose();
}
- javacriptDialogShown.Invoke(message_text, Close);
+ javascriptDialogShown.Invoke(message_text, Close);
return true;
}
diff --git a/WebViewControl/WebView.InternalKeyboardHandler.cs b/WebViewControl/WebView.InternalKeyboardHandler.cs
index fed7e771..b4e47fb0 100644
--- a/WebViewControl/WebView.InternalKeyboardHandler.cs
+++ b/WebViewControl/WebView.InternalKeyboardHandler.cs
@@ -2,27 +2,37 @@
using Xilium.CefGlue;
using Xilium.CefGlue.Common.Handlers;
-namespace WebViewControl {
+namespace WebViewControl;
- partial class WebView {
-
- private class InternalKeyboardHandler : KeyboardHandler {
+partial class WebView {
+ private class InternalKeyboardHandler : KeyboardHandler {
+ public InternalKeyboardHandler(WebView webView) {
+ OwnerWebView = webView;
+ }
- private WebView OwnerWebView { get; }
+ private WebView OwnerWebView { get; }
- public InternalKeyboardHandler(WebView webView) {
- OwnerWebView = webView;
+ protected override bool OnPreKeyEvent(CefBrowser browser, CefKeyEvent keyEvent, IntPtr os_event,
+ out bool isKeyboardShortcut) {
+ KeyPressedEventHandler handler = OwnerWebView.KeyPressed;
+ if (handler != null && !browser.IsPopup) {
+ handler(keyEvent, out bool handled);
+ isKeyboardShortcut = false;
+ return handled;
}
- protected override bool OnPreKeyEvent(CefBrowser browser, CefKeyEvent keyEvent, IntPtr os_event, out bool isKeyboardShortcut) {
- var handler = OwnerWebView.KeyPressed;
- if (handler != null && !browser.IsPopup) {
- handler(keyEvent, out var handled);
- isKeyboardShortcut = false;
- return handled;
- }
- return base.OnPreKeyEvent(browser, keyEvent, os_event, out isKeyboardShortcut);
+ if (OwnerWebView.AllowDeveloperTools && keyEvent.WindowsKeyCode == (int)KnownWindowsKeyCodes.F12) {
+ // F12 Pressed
+ OwnerWebView.ToggleDeveloperTools();
+ isKeyboardShortcut = true;
+ return true;
}
+
+ return base.OnPreKeyEvent(browser, keyEvent, os_event, out isKeyboardShortcut);
}
}
-}
+
+ private enum KnownWindowsKeyCodes {
+ F12 = 123
+ }
+}
\ No newline at end of file
diff --git a/WebViewControl/WebView.Wpf.cs b/WebViewControl/WebView.Wpf.cs
index b60811b3..54e4e6d7 100644
--- a/WebViewControl/WebView.Wpf.cs
+++ b/WebViewControl/WebView.Wpf.cs
@@ -1,5 +1,6 @@
using System;
using System.ComponentModel;
+using System.Diagnostics;
using System.Runtime.ExceptionServices;
using System.Windows;
using System.Windows.Controls;
@@ -52,10 +53,11 @@ partial void ExtraInitialize() {
}
protected override void OnPreviewKeyDown(KeyEventArgs e) {
- if (AllowDeveloperTools && e.Key == Key.F12) {
- ToggleDeveloperTools();
- e.Handled = true;
- }
+ // See WebView.InternalKeyboardHandler
+ //if (AllowDeveloperTools && e.Key == Key.F12) {
+ // ToggleDeveloperTools();
+ // e.Handled = true;
+ //}
}
private void OnHostWindowClosed(object sender, EventArgs e) {
@@ -76,6 +78,25 @@ private T ExecuteInUI(Func action) {
return Dispatcher.Invoke(action);
}
+ private void AsyncExecuteInUI(Action action, T value) {
+ if (isDisposing) {
+ return;
+ }
+ // use async call to avoid dead-locks, otherwise if the source action tries to to evaluate js it would block
+ Dispatcher.InvokeAsync(
+ () => {
+ if (!isDisposing) {
+ try {
+ action?.Invoke(value);
+ } catch (Exception ex) {
+ ForwardUnhandledAsyncException(ex);
+ }
+ }
+ },
+ DispatcherPriority.Normal,
+ AsyncCancellationTokenSource.Token);
+ }
+
private void AsyncExecuteInUI(Action action) {
if (isDisposing) {
return;
diff --git a/WebViewControl/WebView.cs b/WebViewControl/WebView.cs
index 25b0551f..b69a5e37 100644
--- a/WebViewControl/WebView.cs
+++ b/WebViewControl/WebView.cs
@@ -23,7 +23,9 @@ namespace WebViewControl {
public delegate void FilesDraggingEventHandler(string[] fileNames);
public delegate void TextDraggingEventHandler(string textContent);
- internal delegate void JavacriptDialogShowEventHandler(string text, Action closeDialog);
+ public delegate void DownloadItemEventHandler(DownloadItem downloadItem);
+
+ internal delegate void JavascriptDialogShownEventHandler(string text, Action closeDialog);
internal delegate void JavascriptContextReleasedEventHandler(string frameName);
internal delegate void KeyPressedEventHandler(CefKeyEvent keyEvent, out bool handled);
@@ -65,9 +67,18 @@ public partial class WebView : IDisposable {
public event NavigatedEventHandler Navigated;
public event LoadFailedEventHandler LoadFailed;
public event ResourceLoadFailedEventHandler ResourceLoadFailed;
+
+ // V1 Download Events
public event DownloadProgressChangedEventHandler DownloadProgressChanged;
public event DownloadStatusChangedEventHandler DownloadCompleted;
public event DownloadStatusChangedEventHandler DownloadCancelled;
+
+ // V2 Download Events
+ public event DownloadItemEventHandler DownloadItemStarted;
+ public event DownloadItemEventHandler DownloadItemProgressChanged;
+ public event DownloadItemEventHandler DownloadItemStopped;
+ public event DownloadItemEventHandler DownloadItemCompleted;
+
public event JavascriptContextCreatedEventHandler JavascriptContextCreated;
public event Action TitleChanged;
public event UnhandledAsyncExceptionEventHandler UnhandledAsyncException;
@@ -75,7 +86,7 @@ public partial class WebView : IDisposable {
internal event Action Disposed;
internal event JavascriptContextReleasedEventHandler JavascriptContextReleased;
- internal event JavacriptDialogShowEventHandler JavacriptDialogShown;
+ internal event JavascriptDialogShownEventHandler JavascriptDialogShown;
internal event FilesDraggingEventHandler FilesDragging;
internal event TextDraggingEventHandler TextDragging;
internal event KeyPressedEventHandler KeyPressed;
@@ -124,10 +135,11 @@ private void Initialize() {
chromium.BrowserInitialized += OnWebViewBrowserInitialized;
chromium.LoadEnd += OnWebViewLoadEnd;
chromium.LoadError += OnWebViewLoadError;
+
chromium.TitleChanged += delegate { TitleChanged?.Invoke(); };
chromium.JavascriptContextCreated += OnJavascriptContextCreated;
chromium.JavascriptContextReleased += OnJavascriptContextReleased;
- chromium.JavascriptUncaughException += OnJavascriptUncaughException;
+ chromium.JavascriptUncaughException += OnJavascriptUncaughtException;
chromium.UnhandledException += (o, e) => ForwardUnhandledAsyncException(e.Exception);
chromium.RequestHandler = new InternalRequestHandler(this);
@@ -229,6 +241,9 @@ void InternalDispose() {
internal bool IsDisposing => isDisposing;
+ ///
+ /// Allow F12 to ToggleDeveloperTools
+ ///
public bool AllowDeveloperTools { get; set; }
private string InternalAddress {
@@ -277,6 +292,9 @@ public double ZoomPercentage {
set { ExecuteWhenInitialized(() => chromium.ZoomLevel = Math.Log(value, PercentageToZoomFactor)); }
}
+ ///
+ /// Ignores AllowDeveloperTools
+ ///
public void ShowDeveloperTools() {
ExecuteWhenInitialized(() => {
chromium.ShowDeveloperTools();
@@ -284,6 +302,9 @@ public void ShowDeveloperTools() {
});
}
+ ///
+ /// Ignores AllowDeveloperTools
+ ///
public void CloseDeveloperTools() {
if (isDeveloperToolsOpened) {
chromium.CloseDeveloperTools();
@@ -291,6 +312,9 @@ public void CloseDeveloperTools() {
}
}
+ ///
+ /// Ignores AllowDeveloperTools
+ ///
private void ToggleDeveloperTools() {
if (isDeveloperToolsOpened) {
CloseDeveloperTools();
@@ -299,6 +323,24 @@ private void ToggleDeveloperTools() {
}
}
+ public void GetText(Action action, string frameName = MainFrameName) {
+ if (string.IsNullOrWhiteSpace(frameName)) {
+ frameName = "";
+ }
+
+ var visitor = new ThreadSafeDelegateStringVisitor(AsyncExecuteInUI, action, frameName);
+ this.GetFrame(frameName).GetText(visitor);
+ }
+
+ public void GetSource(Action action, string frameName = MainFrameName) {
+ if (string.IsNullOrWhiteSpace(frameName)) {
+ frameName = "";
+ }
+
+ var visitor = new ThreadSafeDelegateStringVisitor(AsyncExecuteInUI, action, frameName);
+ this.GetFrame(frameName).GetSource(visitor);
+ }
+
public void LoadUrl(string address, string frameName = MainFrameName) {
if (this.IsMainFrame(frameName) && address != DefaultLocalUrl) {
htmlToLoad = null;
@@ -491,7 +533,7 @@ private void OnJavascriptContextReleased(object sender, JavascriptContextLifetim
});
}
- private void OnJavascriptUncaughException(object sender, JavascriptUncaughtExceptionEventArgs e) {
+ private void OnJavascriptUncaughtException(object sender, JavascriptUncaughtExceptionEventArgs e) {
if (JavascriptExecutor.IsInternalException(e.Message)) {
// ignore internal exceptions, they will be handled by the EvaluateScript caller
return;