diff --git a/CefSharp.Avalonia.Example/App.axaml b/CefSharp.Avalonia.Example/App.axaml
new file mode 100644
index 0000000..68b6696
--- /dev/null
+++ b/CefSharp.Avalonia.Example/App.axaml
@@ -0,0 +1,7 @@
+
+
+
+
+
diff --git a/CefSharp.Avalonia.Example/App.axaml.cs b/CefSharp.Avalonia.Example/App.axaml.cs
new file mode 100644
index 0000000..3452b65
--- /dev/null
+++ b/CefSharp.Avalonia.Example/App.axaml.cs
@@ -0,0 +1,35 @@
+using Avalonia;
+using Avalonia.Controls.ApplicationLifetimes;
+using Avalonia.Markup.Xaml;
+using CefSharp.Avalonia.Example.ViewModels;
+using CefSharp.Avalonia.Example.Views;
+using ReactiveUI;
+using Splat;
+
+namespace CefSharp.Avalonia.Example;
+
+public partial class App : Application
+{
+ public override void Initialize()
+ {
+ AvaloniaXamlLoader.Load(this);
+
+ Locator.CurrentMutable.Register(() => new BrowserView(), typeof(IViewFor));
+ }
+
+ public override void OnFrameworkInitializationCompleted()
+ {
+ if (ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop)
+ {
+ var mainWindow = new MainWindow();
+
+ desktop.MainWindow = mainWindow;
+ desktop.ShutdownRequested += (s, e) =>
+ {
+ mainWindow.Dispose();
+ };
+ }
+
+ base.OnFrameworkInitializationCompleted();
+ }
+}
diff --git a/CefSharp.Avalonia.Example/CefSharp.Avalonia.Example.csproj b/CefSharp.Avalonia.Example/CefSharp.Avalonia.Example.csproj
new file mode 100644
index 0000000..eaa4165
--- /dev/null
+++ b/CefSharp.Avalonia.Example/CefSharp.Avalonia.Example.csproj
@@ -0,0 +1,29 @@
+
+
+
+ WinExe
+ net6.0
+ CefSharp.Avalonia.Example
+ CefSharp.Avalonia.Example
+ app.manifest
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/CefSharp.Avalonia.Example/MainWindow.axaml b/CefSharp.Avalonia.Example/MainWindow.axaml
new file mode 100644
index 0000000..dd1bc13
--- /dev/null
+++ b/CefSharp.Avalonia.Example/MainWindow.axaml
@@ -0,0 +1,56 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/CefSharp.Avalonia.Example/MainWindow.axaml.cs b/CefSharp.Avalonia.Example/MainWindow.axaml.cs
new file mode 100644
index 0000000..3d3e00b
--- /dev/null
+++ b/CefSharp.Avalonia.Example/MainWindow.axaml.cs
@@ -0,0 +1,55 @@
+using Avalonia.Collections;
+using Avalonia.Controls;
+using Avalonia.Interactivity;
+using CefSharp.Avalonia.Example.ViewModels;
+using CefSharp.Avalonia.Example.Views;
+using CefSharp.OutOfProcess;
+using System;
+using System.IO;
+using System.Threading.Tasks;
+
+namespace CefSharp.Avalonia.Example;
+
+public partial class MainWindow : Window, IDisposable
+{
+#if DEBUG
+ private string _buildType = "Debug";
+#else
+ private string _buildType = "Release";
+#endif
+
+ private OutOfProcessHost _outOfProcessHost;
+
+ public MainWindow()
+ {
+ InitializeComponent();
+
+ _ = InitializeComponentAsync();
+ }
+
+ private async Task InitializeComponentAsync()
+ {
+ var outOfProcessHostPath = Path.GetFullPath($"..\\..\\..\\..\\CefSharp.OutOfProcess.BrowserProcess\\bin\\{_buildType}");
+ outOfProcessHostPath = Path.Combine(outOfProcessHostPath, OutOfProcessHost.HostExeName);
+ var cachePath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), "CefSharp\\OutOfProcessCache");
+
+ var settings = Settings.WithCachePath(cachePath);
+ _outOfProcessHost = await OutOfProcessHost.CreateAsync(outOfProcessHostPath, settings);
+
+ ChromiumWebBrowser.SetDefaultOutOfProcessHost(_outOfProcessHost);
+
+ DataContext = new MainWindowViewModel();
+ }
+
+ private BrowserView ActiveBrowserView => (BrowserView) this.FindControl("tabControl").SelectedContent;
+
+ private void OnFileExitMenuItemClick(object sender, RoutedEventArgs e)
+ {
+ Close();
+ }
+
+ public void Dispose()
+ {
+ _outOfProcessHost?.Dispose();
+ }
+}
diff --git a/CefSharp.Avalonia.Example/Program.cs b/CefSharp.Avalonia.Example/Program.cs
new file mode 100644
index 0000000..d8625a3
--- /dev/null
+++ b/CefSharp.Avalonia.Example/Program.cs
@@ -0,0 +1,22 @@
+using Avalonia;
+using Avalonia.ReactiveUI;
+using System;
+
+namespace CefSharp.Avalonia.Example;
+
+public static class Program
+{
+ // Initialization code. Don't use any Avalonia, third-party APIs or any
+ // SynchronizationContext-reliant code before AppMain is called: things aren't initialized
+ // yet and stuff might break.
+ [STAThread]
+ public static void Main(string[] args) => BuildAvaloniaApp()
+ .StartWithClassicDesktopLifetime(args);
+
+ // Avalonia configuration, don't remove; also used by visual designer.
+ public static AppBuilder BuildAvaloniaApp()
+ => AppBuilder.Configure()
+ .UseReactiveUI()
+ .UsePlatformDetect()
+ .LogToTrace();
+}
diff --git a/CefSharp.Avalonia.Example/Roots.xml b/CefSharp.Avalonia.Example/Roots.xml
new file mode 100644
index 0000000..d305257
--- /dev/null
+++ b/CefSharp.Avalonia.Example/Roots.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
diff --git a/CefSharp.Avalonia.Example/ViewModels/BrowserViewModel.cs b/CefSharp.Avalonia.Example/ViewModels/BrowserViewModel.cs
new file mode 100644
index 0000000..3882fdf
--- /dev/null
+++ b/CefSharp.Avalonia.Example/ViewModels/BrowserViewModel.cs
@@ -0,0 +1,17 @@
+using ReactiveUI;
+using System.Runtime.Serialization;
+
+namespace CefSharp.Avalonia.Example.ViewModels
+{
+ public class BrowserViewModel : ViewModelBase
+ {
+ private string _header;
+
+ [DataMember]
+ public string Header
+ {
+ get => _header;
+ set => this.RaiseAndSetIfChanged(ref _header, value);
+ }
+ }
+}
diff --git a/CefSharp.Avalonia.Example/ViewModels/MainWindowViewModel.cs b/CefSharp.Avalonia.Example/ViewModels/MainWindowViewModel.cs
new file mode 100644
index 0000000..c97a70a
--- /dev/null
+++ b/CefSharp.Avalonia.Example/ViewModels/MainWindowViewModel.cs
@@ -0,0 +1,19 @@
+using System.Collections.ObjectModel;
+
+namespace CefSharp.Avalonia.Example.ViewModels
+{
+ internal class MainWindowViewModel : ViewModelBase
+ {
+ public ObservableCollection Tabs { get; } = new();
+
+ public MainWindowViewModel()
+ {
+ AddTab();
+ }
+
+ public void AddTab()
+ {
+ Tabs.Add(new BrowserViewModel { Header = "New Tab" });
+ }
+ }
+}
diff --git a/CefSharp.Avalonia.Example/ViewModels/ViewModelBase.cs b/CefSharp.Avalonia.Example/ViewModels/ViewModelBase.cs
new file mode 100644
index 0000000..f5b728e
--- /dev/null
+++ b/CefSharp.Avalonia.Example/ViewModels/ViewModelBase.cs
@@ -0,0 +1,22 @@
+using ReactiveUI;
+using System.Reactive.Disposables;
+
+namespace CefSharp.Avalonia.Example.ViewModels
+{
+ public class ViewModelBase : ReactiveObject, IActivatableViewModel
+ {
+ public ViewModelActivator Activator { get; }
+
+ public ViewModelBase()
+ {
+ Activator = new ViewModelActivator();
+ this.WhenActivated((CompositeDisposable disposables) =>
+ {
+ /* handle activation */
+ Disposable
+ .Create(() => { /* handle deactivation */ })
+ .DisposeWith(disposables);
+ });
+ }
+ }
+}
diff --git a/CefSharp.Avalonia.Example/Views/BrowserView.axaml b/CefSharp.Avalonia.Example/Views/BrowserView.axaml
new file mode 100644
index 0000000..ee09521
--- /dev/null
+++ b/CefSharp.Avalonia.Example/Views/BrowserView.axaml
@@ -0,0 +1,15 @@
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/CefSharp.Avalonia.Example/Views/BrowserView.axaml.cs b/CefSharp.Avalonia.Example/Views/BrowserView.axaml.cs
new file mode 100644
index 0000000..e0a9cf0
--- /dev/null
+++ b/CefSharp.Avalonia.Example/Views/BrowserView.axaml.cs
@@ -0,0 +1,60 @@
+using Avalonia.Controls;
+using Avalonia.Input;
+using Avalonia.Threading;
+using CefSharp.Avalonia.Example.ViewModels;
+using CefSharp.OutOfProcess;
+using ReactiveUI;
+using Tmds.DBus;
+
+namespace CefSharp.Avalonia.Example.Views;
+
+public partial class BrowserView : UserControl, IViewFor
+{
+ public BrowserView()
+ {
+ InitializeComponent();
+
+ Browser.Address = "https://www.google.com";
+ Browser.AddressChanged += OnAddressChanged;
+ Browser.TitleChanged += OnTitleChanged;
+ }
+
+ public BrowserViewModel? ViewModel { get; set; }
+
+ object? IViewFor.ViewModel
+ {
+ get => ViewModel;
+ set => ViewModel = (BrowserViewModel?)value;
+ }
+
+ private void OnTitleChanged(object sender, TitleChangedEventArgs e)
+ {
+ Dispatcher.UIThread.Post(() =>
+ {
+ ViewModel.Header = e.Title;
+ });
+ }
+
+ private void OnAddressChanged(object sender, AddressChangedEventArgs e)
+ {
+ Dispatcher.UIThread.Post(() =>
+ {
+ var addressTextBox = this.FindControl("addressTextBox");
+
+ addressTextBox.Text = e.Address;
+ });
+ }
+
+ private void OnAddressTextBoxKeyDown(object sender, global::Avalonia.Input.KeyEventArgs e)
+ {
+ if (e.Key == Key.Enter)
+ {
+ Browser.Address = ((TextBox)sender).Text;
+ }
+ }
+
+ public void Dispose()
+ {
+ //browser.Dispose();
+ }
+}
\ No newline at end of file
diff --git a/CefSharp.Avalonia.Example/app.manifest b/CefSharp.Avalonia.Example/app.manifest
new file mode 100644
index 0000000..f2c18ac
--- /dev/null
+++ b/CefSharp.Avalonia.Example/app.manifest
@@ -0,0 +1,62 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ true
+ true
+
+
+
+
+
+
+
diff --git a/CefSharp.Avalonia/CefSharp.Avalonia.csproj b/CefSharp.Avalonia/CefSharp.Avalonia.csproj
new file mode 100644
index 0000000..d48ce5f
--- /dev/null
+++ b/CefSharp.Avalonia/CefSharp.Avalonia.csproj
@@ -0,0 +1,30 @@
+
+
+
+ netstandard2.0
+ CefSharp.Avalonia
+ CefSharp.Avalonia
+ true
+ CefSharp.Avalonia
+ true
+ $(AllowedOutputExtensionsInPackageBuildOutputFolder);.pdb
+ Latest
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/CefSharp.Avalonia/ChromiumWebBrowser.cs b/CefSharp.Avalonia/ChromiumWebBrowser.cs
new file mode 100644
index 0000000..330fe8c
--- /dev/null
+++ b/CefSharp.Avalonia/ChromiumWebBrowser.cs
@@ -0,0 +1,768 @@
+using Avalonia;
+using Avalonia.Controls;
+using Avalonia.Input;
+using Avalonia.Interactivity;
+using Avalonia.Platform;
+using Avalonia.Threading;
+using Avalonia.VisualTree;
+using CefSharp.Avalonia.Internals;
+using CefSharp.Dom;
+using CefSharp.OutOfProcess;
+using CefSharp.OutOfProcess.Internal;
+using CefSharp.OutOfProcess.Model;
+using PInvoke;
+using System;
+using System.Threading;
+using System.Threading.Tasks;
+using System.Windows.Input;
+
+namespace CefSharp.Avalonia
+{
+ ///
+ /// The Avalonia CEF browser.
+ ///
+ public class ChromiumWebBrowser : NativeControlHost, IChromiumWebBrowserInternal
+ {
+ private static OutOfProcessHost _defaultOutOfProcessHost = null;
+
+ private OutOfProcessHost _host;
+ private IntPtr _browserHwnd = IntPtr.Zero;
+ private OutOfProcessConnectionTransport _devToolsContextConnectionTransport;
+ private IDevToolsContext _devToolsContext;
+ private int _id;
+ private bool _devToolsReady;
+
+ ///
+ /// Handle we'll use to host the browser
+ ///
+ private IntPtr _hwndHost;
+ ///
+ /// The ignore URI change
+ ///
+ private bool _ignoreUriChange;
+ ///
+ /// Initial address
+ ///
+ private string _initialAddress;
+ ///
+ /// Has the underlying Cef Browser been created (slightly different to initliazed in that
+ /// the browser is initialized in an async fashion)
+ ///
+ private bool _browserCreated;
+ ///
+ /// The browser initialized - boolean represented as 0 (false) and 1(true) as we use Interlocker to increment/reset
+ ///
+ private int _browserInitialized;
+ ///
+ /// A flag that indicates whether or not the designer is active
+ /// NOTE: Needs to be static for OnApplicationExit
+ ///
+ private static bool DesignMode;
+
+ ///
+ /// The value for disposal, if it's 1 (one) then this instance is either disposed
+ /// or in the process of getting disposed
+ ///
+ private int _disposeSignaled;
+
+ ///
+ /// Current DPI Scale
+ ///
+ private double _dpiScale;
+
+ ///
+ /// This flag is set when the browser gets focus before the underlying CEF browser
+ /// has been initialized.
+ ///
+ private bool _initialFocus;
+
+ ///
+ /// Can the browser navigate back.
+ ///
+ private bool _canGoBack;
+
+ ///
+ /// Can the browser navigate forward.
+ ///
+ private bool _canGoForward;
+
+ ///
+ /// Is the browser currently loading a web page.
+ ///
+ private bool _isLoading;
+
+ ///
+ /// Browser Title.
+ ///
+ private string _title;
+
+ ///
+ /// Address
+ ///
+ private string _address;
+
+ ///
+ /// Activates browser upon creation, the default value is false. Prior to version 73
+ /// the default behaviour was to activate browser on creation (Equivilent of setting this property to true).
+ /// To restore this behaviour set this value to true immediately after you create the instance.
+ /// https://bitbucket.org/chromiumembedded/cef/issues/1856/branch-2526-cef-activates-browser-window
+ ///
+ public bool ActivateBrowserOnCreation { get; set; }
+
+ ///
+ /// Gets a value indicating whether this instance is disposed.
+ ///
+ /// if this instance is disposed; otherwise, .
+ public bool IsDisposed
+ {
+ get
+ {
+ return Interlocked.CompareExchange(ref _disposeSignaled, 1, 1) == 1;
+ }
+ }
+
+ ///
+ public event EventHandler DOMContentLoaded;
+ ///
+ public event EventHandler BrowserProcessCrashed;
+ ///
+ public event EventHandler FrameAttached;
+ ///
+ public event EventHandler FrameDetached;
+ ///
+ public event EventHandler FrameNavigated;
+ ///
+ public event EventHandler JavaScriptLoad;
+ ///
+ public event EventHandler RuntimeExceptionThrown;
+ ///
+ public event EventHandler Popup;
+ ///
+ public event EventHandler NetworkRequest;
+ ///
+ public event EventHandler NetworkRequestFailed;
+ ///
+ public event EventHandler NetworkRequestFinished;
+ ///
+ public event EventHandler NetworkRequestServedFromCache;
+ ///
+ public event EventHandler NetworkResponse;
+ ///
+ public event EventHandler AddressChanged;
+ ///
+ public event EventHandler LoadingStateChanged;
+ ///
+ public event EventHandler StatusMessage;
+ ///
+ public event EventHandler ConsoleMessage;
+ ///
+ public event EventHandler LifecycleEvent;
+ ///
+ public event EventHandler DevToolsContextAvailable;
+
+ ///
+ /// Event handler that will get called when the browser title changes
+ ///
+ public event EventHandler TitleChanged;
+
+ ///
+ /// Event called after the underlying CEF browser instance has been created
+ ///
+ public event EventHandler BrowserCreated;
+
+ ///
+ /// Navigates to the previous page in the browser history. Will automatically be enabled/disabled depending on the
+ /// browser state.
+ ///
+ /// The back command.
+ public ICommand BackCommand { get; private set; }
+ ///
+ /// Navigates to the next page in the browser history. Will automatically be enabled/disabled depending on the
+ /// browser state.
+ ///
+ /// The forward command.
+ public ICommand ForwardCommand { get; private set; }
+ ///
+ /// Reloads the content of the current page. Will automatically be enabled/disabled depending on the browser state.
+ ///
+ /// The reload command.
+ public ICommand ReloadCommand { get; private set; }
+
+ public ICommand StopCommand { get; private set; }
+
+ ///
+ /// CanGoBack Property
+ ///
+ public static readonly DirectProperty CanGoBackProperty =
+ AvaloniaProperty.RegisterDirect(nameof(CanGoBack), o => o.CanGoBack);
+
+ ///
+ /// A flag that indicates whether the state of the control current supports the GoBack action (true) or not (false).
+ ///
+ /// true if this instance can go back; otherwise, false.
+ public bool CanGoBack
+ {
+ get { return _canGoBack; }
+ private set { SetAndRaise(CanGoBackProperty, ref _canGoBack, value); }
+ }
+
+ ///
+ /// CanGoBack Property
+ ///
+ public static readonly DirectProperty CanGoForwardProperty =
+ AvaloniaProperty.RegisterDirect(nameof(CanGoForward), o => o.CanGoForward);
+
+ ///
+ /// A flag that indicates whether the state of the control current supports the GoForward action (true) or not (false).
+ ///
+ /// true if this instance can go forward; otherwise, false.
+ public bool CanGoForward
+ {
+ get { return _canGoForward; }
+ private set { SetAndRaise(CanGoForwardProperty, ref _canGoForward, value); }
+ }
+
+ ///
+ /// The title of the web page being currently displayed.
+ ///
+ /// The title.
+ public string Title
+ {
+ get { return _title; }
+ set { SetAndRaise(TitleProperty, ref _title, value); }
+ }
+
+ ///
+ /// The title property
+ ///
+ public static readonly DirectProperty TitleProperty =
+ AvaloniaProperty.RegisterDirect(nameof(Title), o => o.Title);
+
+ ///
+ /// Handles the event.
+ ///
+ /// The d.
+ /// The instance containing the event data.
+ private static void OnTitleChanged(ChromiumWebBrowser owner, AvaloniaPropertyChangedEventArgs e)
+ {
+ var args = new TitleChangedEventArgs(e.GetNewValue());
+ owner.TitleChanged?.Invoke(owner, args);
+ }
+
+ static ChromiumWebBrowser()
+ {
+ AddressProperty.Changed.AddClassHandler(OnAddressChanged);
+ TitleProperty.Changed.AddClassHandler(OnTitleChanged);
+ IsVisibleProperty.Changed.AddClassHandler(OnVisibleChanged);
+ }
+
+ ///
+ /// Initializes a new instance of the instance.
+ ///
+ public ChromiumWebBrowser() : this(null)
+ {
+ }
+
+ ///
+ /// Initializes a new instance of the instance.
+ ///
+ /// Out of process host
+ /// address to load initially
+ public ChromiumWebBrowser(OutOfProcessHost host, string initialAddress = null)
+ {
+ _host = host;
+ _initialAddress = initialAddress;
+
+ _host ??= _defaultOutOfProcessHost;
+
+ if (_host == null)
+ {
+ throw new ArgumentNullException(nameof(host));
+ }
+
+ Focusable = true;
+
+ BackCommand = new DelegateCommand(() => _devToolsContext.GoBackAsync(), () => CanGoBack);
+ ForwardCommand = new DelegateCommand(() => _devToolsContext.GoForwardAsync(), () => CanGoForward);
+ ReloadCommand = new DelegateCommand(() => _devToolsContext.ReloadAsync(), () => !IsLoading);
+ //StopCommand = new DelegateCommand(this.Stop);
+
+ UseLayoutRounding = true;
+ LayoutUpdated += OnLayoutUpdated;
+ }
+
+ private void OnLayoutUpdated(object sender, EventArgs e)
+ {
+ var bounds = Bounds;
+
+ ResizeBrowser((int)bounds.Width, (int)bounds.Height);
+ }
+
+ ///
+ int IChromiumWebBrowserInternal.Id
+ {
+ get { return _id; }
+ }
+
+ ///
+ /// DevToolsContext - provides communication with the underlying browser
+ ///
+ public IDevToolsContext DevToolsContext
+ {
+ get
+ {
+ if (_devToolsReady)
+ {
+ return _devToolsContext;
+ }
+
+ return default;
+ }
+ }
+
+ ///
+ public bool IsBrowserInitialized => _browserHwnd != IntPtr.Zero;
+
+
+ ///
+ public Frame[] Frames => _devToolsContext?.Frames;
+
+ ///
+ public Frame MainFrame => _devToolsContext?.MainFrame;
+
+ ///
+ void IChromiumWebBrowserInternal.OnDevToolsMessage(string jsonMsg)
+ {
+ _devToolsContextConnectionTransport?.InvokeMessageReceived(jsonMsg);
+ }
+
+ ///
+ void IChromiumWebBrowserInternal.OnDevToolsReady()
+ {
+ var ctx = (DevToolsContext)_devToolsContext;
+
+ ctx.DOMContentLoaded += DOMContentLoaded;
+ ctx.Error += BrowserProcessCrashed;
+ ctx.FrameAttached += FrameAttached;
+ ctx.FrameDetached += FrameDetached;
+ ctx.FrameNavigated += FrameNavigated;
+ ctx.Load += JavaScriptLoad;
+ ctx.PageError += RuntimeExceptionThrown;
+ ctx.Popup += Popup;
+ ctx.Request += NetworkRequest;
+ ctx.RequestFailed += NetworkRequestFailed;
+ ctx.RequestFinished += NetworkRequestFinished;
+ ctx.RequestServedFromCache += NetworkRequestServedFromCache;
+ ctx.Response += NetworkResponse;
+ ctx.Console += ConsoleMessage;
+ ctx.LifecycleEvent += LifecycleEvent;
+
+ _ = ctx.InvokeGetFrameTreeAsync().ContinueWith(t =>
+ {
+ _devToolsReady = true;
+
+ DevToolsContextAvailable?.Invoke(this, EventArgs.Empty);
+
+ //NOW the user can start using the devtools context
+ }, TaskScheduler.Current);
+
+ // Only call Load if initialAddress is null and Address is not empty
+ if (string.IsNullOrEmpty(_initialAddress) && !string.IsNullOrEmpty(Address))
+ {
+ LoadUrl(Address);
+ }
+ }
+
+ ///
+ public void LoadUrl(string url)
+ {
+ _ = _devToolsContext.GoToAsync(url);
+ }
+
+ ///
+ public Task LoadUrlAsync(string url, int? timeout = null, WaitUntilNavigation[] waitUntil = null)
+ {
+ return _devToolsContext.GoToAsync(url, timeout, waitUntil);
+ }
+
+ ///
+ public Task GoBackAsync(NavigationOptions options = null)
+ {
+ return _devToolsContext.GoBackAsync(options);
+ }
+
+ ///
+ public Task GoForwardAsync(NavigationOptions options = null)
+ {
+ return _devToolsContext.GoForwardAsync(options);
+ }
+
+ ///
+ public Task SetRequestContextPreferenceAsync(string name, object value)
+ {
+ if (_host == null)
+ {
+ throw new ObjectDisposedException(nameof(ChromiumWebBrowser));
+ }
+
+ return _host.SetRequestContextPreferenceAsync(_id, name, value);
+ }
+
+ ///
+ protected override IPlatformHandle CreateNativeControlCore(IPlatformHandle parent)
+ {
+ var handle = base.CreateNativeControlCore(parent);
+
+ _dpiScale = this.GetVisualRoot()?.RenderScaling ?? 1.0;
+
+ _hwndHost = handle.Handle;
+
+ _host.CreateBrowser(this, _hwndHost, url: _initialAddress, out _id);
+
+ _devToolsContextConnectionTransport = new OutOfProcessConnectionTransport(_id, _host);
+
+ var connection = DevToolsConnection.Attach(_devToolsContextConnectionTransport);
+ _devToolsContext = Dom.DevToolsContext.CreateForOutOfProcess(connection);
+
+ return handle;
+ }
+
+ ///
+ protected override void DestroyNativeControlCore(IPlatformHandle control)
+ {
+ _host.CloseBrowser(_id);
+
+ base.DestroyNativeControlCore(control);
+ }
+
+ protected override void OnGotFocus(GotFocusEventArgs e)
+ {
+ base.OnGotFocus(e);
+
+ if (InternalIsBrowserInitialized())
+ {
+ _host.SetFocus(_id, true);
+ }
+ else
+ {
+ _initialFocus = true;
+ }
+ }
+
+ protected override void OnLostFocus(RoutedEventArgs e)
+ {
+ base.OnLostFocus(e);
+
+ if (InternalIsBrowserInitialized())
+ {
+ _host.SetFocus(_id, false);
+ }
+ }
+
+ ///
+ void IChromiumWebBrowserInternal.SetAddress(string address)
+ {
+ UiThreadRun(() =>
+ {
+ _ignoreUriChange = true;
+ Address = address;
+ _ignoreUriChange = false;
+ });
+ }
+
+ ///
+ void IChromiumWebBrowserInternal.SetLoadingStateChange(bool canGoBack, bool canGoForward, bool isLoading)
+ {
+ UiThreadRun(() =>
+ {
+ CanGoBack = canGoBack;
+ CanGoForward = CanGoForward;
+ IsLoading = isLoading;
+
+ ((DelegateCommand)BackCommand).RaiseCanExecuteChanged();
+ ((DelegateCommand)ForwardCommand).RaiseCanExecuteChanged();
+ ((DelegateCommand)ReloadCommand).RaiseCanExecuteChanged();
+ });
+
+ LoadingStateChanged?.Invoke(this, new LoadingStateChangedEventArgs(canGoBack, canGoForward, isLoading));
+ }
+
+ ///
+ void IChromiumWebBrowserInternal.SetTitle(string title)
+ {
+ UiThreadRun(() => Title = title);
+ }
+
+ ///
+ void IChromiumWebBrowserInternal.SetStatusMessage(string msg)
+ {
+ StatusMessage?.Invoke(this, new StatusMessageEventArgs(msg));
+ }
+
+ ///
+ void IChromiumWebBrowserInternal.OnAfterBrowserCreated(IntPtr hwnd)
+ {
+ if (IsDisposed)
+ {
+ return;
+ }
+
+ _browserHwnd = hwnd;
+
+ Interlocked.Exchange(ref _browserInitialized, 1);
+
+ UiThreadRun(() =>
+ {
+ if (!IsDisposed)
+ {
+ BrowserCreated?.Invoke(this, EventArgs.Empty);
+
+ var bounds = Bounds;
+
+ ResizeBrowser((int)bounds.Width, (int)bounds.Height);
+ }
+ });
+
+ if (_initialFocus)
+ {
+ _host.SetFocus(_id, true);
+ }
+ }
+
+ ///
+ /// Resizes the browser to the specified and .
+ /// If and are both 0 then the browser
+ /// will be hidden and resource usage will be minimised.
+ ///
+ /// width
+ /// height
+ protected virtual void ResizeBrowser(int width, int height)
+ {
+ if (_browserHwnd != IntPtr.Zero)
+ {
+ if (_dpiScale > 1)
+ {
+ width = (int)(width * _dpiScale);
+ height = (int)(height * _dpiScale);
+ }
+
+ if (width == 0 && height == 0)
+ {
+ // For windowed browsers when the frame window is minimized set the
+ // browser window size to 0x0 to reduce resource usage.
+ HideInternal();
+ }
+ else
+ {
+ ShowInternal(width, height);
+ }
+ }
+ }
+
+ private void ShowInternal(int width, int height)
+ {
+ if (_browserHwnd != IntPtr.Zero)
+ {
+ User32.SetWindowPos(_browserHwnd, IntPtr.Zero, 0, 0, width, height, User32.SetWindowPosFlags.SWP_NOZORDER);
+ }
+ }
+
+ private void HideInternal()
+ {
+ if (_browserHwnd != IntPtr.Zero)
+ {
+ User32.SetWindowPos(_browserHwnd, IntPtr.Zero, 0, 0, 0, 0, User32.SetWindowPosFlags.SWP_NOZORDER | User32.SetWindowPosFlags.SWP_NOMOVE | User32.SetWindowPosFlags.SWP_NOACTIVATE);
+ }
+ }
+
+ ///
+ /// The address (URL) which the browser control is currently displaying.
+ /// Will automatically be updated as the user navigates to another page (e.g. by clicking on a link).
+ ///
+ /// The address.
+ public string Address
+ {
+ get { return _address; }
+ set { SetAndRaise(AddressProperty, ref _address, value); }
+ }
+
+ ///
+ /// The address property
+ ///
+ public static readonly DirectProperty AddressProperty = AvaloniaProperty.RegisterDirect(nameof(Address), o => o.Address);
+
+ private static void OnAddressChanged(ChromiumWebBrowser browser, AvaloniaPropertyChangedEventArgs e)
+ {
+ browser.OnAddressChanged(e.GetOldValue(), e.GetNewValue());
+
+ browser.AddressChanged?.Invoke(browser, new AddressChangedEventArgs(e.GetNewValue()));
+ }
+
+ ///
+ /// Called when [address changed].
+ ///
+ /// The old value.
+ /// The new value.
+ protected virtual void OnAddressChanged(string oldValue, string newValue)
+ {
+ if(!InternalIsBrowserInitialized())
+ {
+ _initialAddress = newValue;
+ return;
+ }
+
+ if (_ignoreUriChange || newValue == null)
+ {
+ return;
+ }
+
+ LoadUrl(newValue);
+ }
+
+ ///
+ /// A flag that indicates whether the control is currently loading one or more web pages (true) or not (false).
+ ///
+ /// true if this instance is loading; otherwise, false.
+ public bool IsLoading
+ {
+ get { return _isLoading ; }
+ private set { SetAndRaise(IsLoadingProperty, ref _isLoading, value); }
+ }
+
+ ///
+ /// The is loading property
+ ///
+ public static readonly DirectProperty IsLoadingProperty = AvaloniaProperty.RegisterDirect(nameof(IsLoading), o=> o.IsLoading);
+
+ private static void OnVisibleChanged(ChromiumWebBrowser owner, AvaloniaPropertyChangedEventArgs args)
+ {
+ if (owner.InternalIsBrowserInitialized())
+ {
+ var isVisible = args.GetNewValue();
+ if (isVisible)
+ {
+ var bounds = owner.Bounds;
+ owner.ResizeBrowser((int)bounds.Width, (int)bounds.Height);
+ }
+ else
+ {
+ //Hide browser
+ owner.ResizeBrowser(0, 0);
+ }
+ }
+ }
+
+ ///
+ /// Runs the specific Action on the Dispatcher in an async fashion
+ ///
+ /// The action.
+ /// The priority.
+ private void UiThreadRun(Action action)
+ {
+ if (Dispatcher.UIThread.CheckAccess())
+ {
+ action();
+ }
+
+ _ = Dispatcher.UIThread.InvokeAsync(action);
+ }
+
+ ///
+ /// Check is browserisinitialized
+ ///
+ /// true if browser is initialized
+ private bool InternalIsBrowserInitialized()
+ {
+ // Use CompareExchange to read the current value - if disposeCount is 1, we set it to 1, effectively a no-op
+ // Volatile.Read would likely use a memory barrier which I believe is unnecessary in this scenario
+ return Interlocked.CompareExchange(ref _browserInitialized, 0, 0) == 1;
+ }
+
+ ///
+ /// Sets a global (static) instance of that
+ /// will be used when no explicit implementation is provided.
+ ///
+ /// host
+ /// TODO: This needs improving
+ public static void SetDefaultOutOfProcessHost(OutOfProcessHost host)
+ {
+ _defaultOutOfProcessHost = host;
+ }
+
+ protected virtual void Dispose(bool disposing)
+ {
+ // Attempt to move the disposeSignaled state from 0 to 1.
+ // If successful, we can safely dispose of the object.
+ if (Interlocked.CompareExchange(ref _disposeSignaled, 1, 0) != 0)
+ {
+ return;
+ }
+
+ if (DesignMode)
+ {
+ return;
+ }
+
+ if (disposing)
+ {
+ Interlocked.Exchange(ref _browserInitialized, 0);
+
+ // Don't maintain a reference to event listeners anylonger:
+ BrowserCreated = null;
+ AddressChanged = null;
+ LoadingStateChanged = null;
+ StatusMessage = null;
+ TitleChanged = null;
+
+ var ctx = (DevToolsContext)_devToolsContext;
+
+ ctx.DOMContentLoaded -= DOMContentLoaded;
+ ctx.Error -= BrowserProcessCrashed;
+ ctx.FrameAttached -= FrameAttached;
+ ctx.FrameDetached -= FrameDetached;
+ ctx.FrameNavigated -= FrameNavigated;
+ ctx.Load -= JavaScriptLoad;
+ ctx.PageError -= RuntimeExceptionThrown;
+ ctx.Popup -= Popup;
+ ctx.Request -= NetworkRequest;
+ ctx.RequestFailed -= NetworkRequestFailed;
+ ctx.RequestFinished -= NetworkRequestFinished;
+ ctx.RequestServedFromCache -= NetworkRequestServedFromCache;
+ ctx.Response -= NetworkResponse;
+ ctx.Console -= ConsoleMessage;
+ ctx.LifecycleEvent -= LifecycleEvent;
+
+ DOMContentLoaded = null;
+ BrowserProcessCrashed = null;
+ FrameAttached = null;
+ FrameNavigated = null;
+ JavaScriptLoad = null;
+ RuntimeExceptionThrown = null;
+ Popup = null;
+ NetworkRequest = null;
+ NetworkRequestFailed = null;
+ NetworkRequestFinished = null;
+ NetworkRequestServedFromCache = null;
+ NetworkResponse = null;
+ ConsoleMessage = null;
+ LifecycleEvent = null;
+ }
+
+ _host?.CloseBrowser(_id);
+ _host = null;
+ }
+
+ ~ChromiumWebBrowser()
+ {
+ // Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method
+ Dispose(disposing: false);
+ }
+
+ public void Dispose()
+ {
+ // Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method
+ Dispose(disposing: true);
+ GC.SuppressFinalize(this);
+ }
+ }
+}
diff --git a/CefSharp.Avalonia/Internals/DelegateCommand.cs b/CefSharp.Avalonia/Internals/DelegateCommand.cs
new file mode 100644
index 0000000..95676ca
--- /dev/null
+++ b/CefSharp.Avalonia/Internals/DelegateCommand.cs
@@ -0,0 +1,67 @@
+// Copyright © 2019 The CefSharp Authors. 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.Windows.Input;
+
+namespace CefSharp.Avalonia.Internals;
+
+///
+/// DelegateCommand
+///
+///
+internal class DelegateCommand : ICommand
+{
+ ///
+ /// The command handler
+ ///
+ private readonly Action _commandHandler;
+ ///
+ /// The can execute handler
+ ///
+ private readonly Func _canExecuteHandler;
+
+ ///
+ /// Occurs when changes occur that affect whether or not the command should execute.
+ ///
+ public event EventHandler CanExecuteChanged;
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The command handler.
+ /// The can execute handler.
+ public DelegateCommand(Action commandHandler, Func canExecuteHandler = null)
+ {
+ _commandHandler = commandHandler;
+ _canExecuteHandler = canExecuteHandler;
+ }
+
+ ///
+ /// Defines the method to be called when the command is invoked.
+ ///
+ /// Data used by the command. If the command does not require data to be passed, this object can be set to null.
+ public void Execute(object parameter)
+ {
+ _commandHandler();
+ }
+
+ ///
+ /// Defines the method that determines whether the command can execute in its current state.
+ ///
+ /// Data used by the command. If the command does not require data to be passed, this object can be set to null.
+ /// true if this command can be executed; otherwise, false.
+ public bool CanExecute(object parameter)
+ {
+ return _canExecuteHandler == null || _canExecuteHandler();
+ }
+
+ ///
+ /// Raises the can execute changed.
+ ///
+ public void RaiseCanExecuteChanged()
+ {
+ CanExecuteChanged?.Invoke(this, EventArgs.Empty);
+ }
+}
diff --git a/CefSharp.OutOfProcess.sln b/CefSharp.OutOfProcess.sln
index 54e71e7..332f319 100644
--- a/CefSharp.OutOfProcess.sln
+++ b/CefSharp.OutOfProcess.sln
@@ -27,6 +27,10 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution
Directory.Build.props = Directory.Build.props
EndProjectSection
EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CefSharp.Avalonia", "CefSharp.Avalonia\CefSharp.Avalonia.csproj", "{5591951C-DAC1-4014-BD17-B692B91A6CC5}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CefSharp.Avalonia.Example", "CefSharp.Avalonia.Example\CefSharp.Avalonia.Example.csproj", "{3F8073FB-B167-4C2B-A1D8-5040BD1F6E84}"
+EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@@ -65,6 +69,14 @@ Global
{7D39CC02-FB02-4F03-B329-E5DB567EC354}.Debug|Any CPU.Build.0 = Debug|Any CPU
{7D39CC02-FB02-4F03-B329-E5DB567EC354}.Release|Any CPU.ActiveCfg = Release|Any CPU
{7D39CC02-FB02-4F03-B329-E5DB567EC354}.Release|Any CPU.Build.0 = Release|Any CPU
+ {5591951C-DAC1-4014-BD17-B692B91A6CC5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {5591951C-DAC1-4014-BD17-B692B91A6CC5}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {5591951C-DAC1-4014-BD17-B692B91A6CC5}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {5591951C-DAC1-4014-BD17-B692B91A6CC5}.Release|Any CPU.Build.0 = Release|Any CPU
+ {3F8073FB-B167-4C2B-A1D8-5040BD1F6E84}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {3F8073FB-B167-4C2B-A1D8-5040BD1F6E84}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {3F8073FB-B167-4C2B-A1D8-5040BD1F6E84}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {3F8073FB-B167-4C2B-A1D8-5040BD1F6E84}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE