diff --git a/CefSharp.Test/Wpf.HwndHost/WpfBrowserTests.cs b/CefSharp.Test/Wpf.HwndHost/WpfBrowserTests.cs new file mode 100644 index 0000000000..c5d4561aff --- /dev/null +++ b/CefSharp.Test/Wpf.HwndHost/WpfBrowserTests.cs @@ -0,0 +1,113 @@ +// Copyright © 2017 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.Runtime.InteropServices; +using System.Threading.Tasks; +using System.Windows; +using System.Windows.Interop; +using CefSharp.Example; +using CefSharp.Wpf.HwndHost; +using Xunit; +using Xunit.Abstractions; + +namespace CefSharp.Test.Wpf.HwndHost +{ + //NOTE: All Test classes must be part of this collection as it manages the Cef Initialize/Shutdown lifecycle + [Collection(CefSharpFixtureCollection.Key)] + [BrowserRefCountDebugging(typeof(ChromiumWebBrowser))] + public class WpfBrowserTests + { + [DllImport("user32.dll")] + private static extern IntPtr SetParent(IntPtr hwnd, IntPtr hwndNewParent); + + private const int HWND_MESSAGE = -3; + + private readonly ITestOutputHelper output; + private readonly CefSharpFixture fixture; + + public WpfBrowserTests(ITestOutputHelper output, CefSharpFixture fixture) + { + this.fixture = fixture; + this.output = output; + } + + [WpfFact] + public async Task ShouldWorkWhenLoadingGoogle() + { + var window = CreateAndShowHiddenWindow(); + + using (var browser = new ChromiumWebBrowser("www.google.com")) + { + window.Content = browser; + + await browser.WaitForInitialLoadAsync(); + var mainFrame = browser.GetMainFrame(); + + Assert.True(mainFrame.IsValid); + Assert.Contains("www.google", mainFrame.Url); + + output.WriteLine("Url {0}", mainFrame.Url); + } + } + + [WpfFact] + public async Task ShouldWorkWhenLoadUrlAsyncImmediately() + { + var window = CreateAndShowHiddenWindow(); + + using (var browser = new ChromiumWebBrowser(string.Empty)) + { + window.Content = browser; + + var response = await browser.LoadUrlAsync("www.google.com"); + var mainFrame = browser.GetMainFrame(); + + Assert.True(response.Success); + Assert.True(mainFrame.IsValid); + Assert.Contains("www.google", mainFrame.Url); + + output.WriteLine("Url {0}", mainFrame.Url); + } + } + + [WpfFact] + public async Task ShouldRespectDisposed() + { + var window = CreateAndShowHiddenWindow(); + + ChromiumWebBrowser browser; + + using (browser = new ChromiumWebBrowser(CefExample.DefaultUrl)) + { + window.Content = browser; + + await browser.WaitForInitialLoadAsync(); + } + + Assert.True(browser.IsDisposed); + + var ex = Assert.Throws(() => + { + browser.Copy(); + }); + } + + private static Window CreateAndShowHiddenWindow() + { + var window = new Window(); + window.Width = 1024; + window.Height = 768; + + var helper = new WindowInteropHelper(window); + + helper.EnsureHandle(); + + SetParent(helper.Handle, (IntPtr)HWND_MESSAGE); + + window.Show(); + return window; + } + } +} diff --git a/CefSharp.Wpf.HwndHost.Example/App.config b/CefSharp.Wpf.HwndHost.Example/App.config new file mode 100644 index 0000000000..1e8988739d --- /dev/null +++ b/CefSharp.Wpf.HwndHost.Example/App.config @@ -0,0 +1,6 @@ + + + + + + diff --git a/CefSharp.Wpf.HwndHost.Example/App.xaml b/CefSharp.Wpf.HwndHost.Example/App.xaml new file mode 100644 index 0000000000..4f5c756edb --- /dev/null +++ b/CefSharp.Wpf.HwndHost.Example/App.xaml @@ -0,0 +1,11 @@ + + + + + + + diff --git a/CefSharp.Wpf.HwndHost.Example/App.xaml.cs b/CefSharp.Wpf.HwndHost.Example/App.xaml.cs new file mode 100644 index 0000000000..d2d4a5c6a8 --- /dev/null +++ b/CefSharp.Wpf.HwndHost.Example/App.xaml.cs @@ -0,0 +1,62 @@ +// 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.IO; +using System.Windows; +using CefSharp.Wpf.HwndHost.Handler; + +namespace CefSharp.Wpf.HwndHost.Example +{ + /// + /// Interaction logic for App.xaml + /// + public partial class App : Application + { + public App() + { +#if !NETCOREAPP3_1_OR_GREATER + CefRuntime.SubscribeAnyCpuAssemblyResolver(); +#endif + } + + protected override void OnStartup(StartupEventArgs e) + { + var settings = new CefSettings() + { + //By default CefSharp will use an in-memory cache, you need to specify a Cache Folder to persist data + CachePath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), "CefSharp\\Cache") + }; + + //Example of setting a command line argument + //Enables WebRTC + // - CEF Doesn't currently support permissions on a per browser basis see https://bitbucket.org/chromiumembedded/cef/issues/2582/allow-run-time-handling-of-media-access + // - CEF Doesn't currently support displaying a UI for media access permissions + // + //NOTE: WebRTC Device Id's aren't persisted as they are in Chrome see https://bitbucket.org/chromiumembedded/cef/issues/2064/persist-webrtc-deviceids-across-restart + settings.CefCommandLineArgs.Add("enable-media-stream"); + //https://peter.sh/experiments/chromium-command-line-switches/#use-fake-ui-for-media-stream + settings.CefCommandLineArgs.Add("use-fake-ui-for-media-stream"); + //For screen sharing add (see https://bitbucket.org/chromiumembedded/cef/issues/2582/allow-run-time-handling-of-media-access#comment-58677180) + settings.CefCommandLineArgs.Add("enable-usermedia-screen-capturing"); + + //See https://github.com/cefsharp/CefSharp/wiki/General-Usage#multithreadedmessageloop + //The default is true + const bool multiThreadedMessageLoop = true; + + IBrowserProcessHandler browserProcessHandler = null; + + if(!multiThreadedMessageLoop) + { + settings.MultiThreadedMessageLoop = false; + browserProcessHandler = new IntegratedMessageLoopBrowserProcessHandler(Dispatcher); + } + + // Make sure you set performDependencyCheck false + Cef.Initialize(settings, performDependencyCheck: false, browserProcessHandler: browserProcessHandler); + + base.OnStartup(e); + } + } +} diff --git a/CefSharp.Wpf.HwndHost.Example/AssemblyInfo.cs b/CefSharp.Wpf.HwndHost.Example/AssemblyInfo.cs new file mode 100644 index 0000000000..ed33db9bfa --- /dev/null +++ b/CefSharp.Wpf.HwndHost.Example/AssemblyInfo.cs @@ -0,0 +1,14 @@ +// 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.Windows; + +[assembly: ThemeInfo( + ResourceDictionaryLocation.None, //where theme specific resource dictionaries are located + //(used if a resource is not found in the page, + // or application resource dictionaries) + ResourceDictionaryLocation.SourceAssembly //where the generic resource dictionary is located + //(used if a resource is not found in the page, + // app, or any theme specific resource dictionaries) +)] diff --git a/CefSharp.Wpf.HwndHost.Example/Behaviours/HoverLinkBehaviour.cs b/CefSharp.Wpf.HwndHost.Example/Behaviours/HoverLinkBehaviour.cs new file mode 100644 index 0000000000..1fc77b3656 --- /dev/null +++ b/CefSharp.Wpf.HwndHost.Example/Behaviours/HoverLinkBehaviour.cs @@ -0,0 +1,34 @@ +using System.Windows; +using System; +using Microsoft.Xaml.Behaviors; + +namespace CefSharp.Wpf.HwndHost.Example.Behaviours +{ + public class HoverLinkBehaviour : Behavior + { + // Using a DependencyProperty as the backing store for HoverLink. This enables animation, styling, binding, etc... + public static readonly DependencyProperty HoverLinkProperty = DependencyProperty.Register("HoverLink", typeof(string), typeof(HoverLinkBehaviour), new PropertyMetadata(string.Empty)); + + public string HoverLink + { + get { return (string)GetValue(HoverLinkProperty); } + set { SetValue(HoverLinkProperty, value); } + } + + protected override void OnAttached() + { + AssociatedObject.StatusMessage += OnStatusMessageChanged; + } + + protected override void OnDetaching() + { + AssociatedObject.StatusMessage -= OnStatusMessageChanged; + } + + private void OnStatusMessageChanged(object sender, StatusMessageEventArgs e) + { + var chromiumWebBrowser = sender as ChromiumWebBrowser; + chromiumWebBrowser.Dispatcher.BeginInvoke((Action)(() => HoverLink = e.Value)); + } + } +} diff --git a/CefSharp.Wpf.HwndHost.Example/Behaviours/TextBoxBindingUpdateOnEnterBehaviour.cs b/CefSharp.Wpf.HwndHost.Example/Behaviours/TextBoxBindingUpdateOnEnterBehaviour.cs new file mode 100644 index 0000000000..24ad3b4e7a --- /dev/null +++ b/CefSharp.Wpf.HwndHost.Example/Behaviours/TextBoxBindingUpdateOnEnterBehaviour.cs @@ -0,0 +1,28 @@ +using System.Windows.Controls; +using System.Windows.Input; +using Microsoft.Xaml.Behaviors; + +namespace CefSharp.Wpf.HwndHost.Example.Behaviours +{ + public class TextBoxBindingUpdateOnEnterBehaviour : Behavior + { + protected override void OnAttached() + { + AssociatedObject.KeyDown += OnTextBoxKeyDown; + } + + protected override void OnDetaching() + { + AssociatedObject.KeyDown -= OnTextBoxKeyDown; + } + + private void OnTextBoxKeyDown(object sender, KeyEventArgs e) + { + if (e.Key == Key.Enter) + { + var txtBox = sender as TextBox; + txtBox.GetBindingExpression(TextBox.TextProperty).UpdateSource(); + } + } + } +} diff --git a/CefSharp.Wpf.HwndHost.Example/CefSharp.Wpf.HwndHost.Example.csproj b/CefSharp.Wpf.HwndHost.Example/CefSharp.Wpf.HwndHost.Example.csproj new file mode 100644 index 0000000000..e907a241ee --- /dev/null +++ b/CefSharp.Wpf.HwndHost.Example/CefSharp.Wpf.HwndHost.Example.csproj @@ -0,0 +1,50 @@ + + + net472 + WinExe + x86;x64 + false + true + AllRules.ruleset + app.manifest + CefSharp.Wpf.HwndHost.Example.App + false + win7-x86;win7-x64 + 9.0 + + + + + + + + PreserveNewest + + + + + + + + + + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + + + + + + + + + + \ No newline at end of file diff --git a/CefSharp.Wpf.HwndHost.Example/CefSharp.Wpf.HwndHost.Example.netcore.csproj b/CefSharp.Wpf.HwndHost.Example/CefSharp.Wpf.HwndHost.Example.netcore.csproj new file mode 100644 index 0000000000..5862b3ccb9 --- /dev/null +++ b/CefSharp.Wpf.HwndHost.Example/CefSharp.Wpf.HwndHost.Example.netcore.csproj @@ -0,0 +1,69 @@ + + + + + obj.netcore\ + bin.netcore\ + + + + + + WinExe + netcoreapp3.1;net5.0-windows + $(TargetFrameworks);net6.0-windows + CefSharp.Wpf.HwndHost.Example + CefSharp.Wpf.HwndHost.Example + true + false + app.manifest + MinimumRecommendedRules.ruleset + + + x86;x64 + + arm64 + CefSharp.Wpf.HwndHost.Example.App + + Major + win-x86;win-x64 + win-x86 + win-x64 + win-arm64 + false + 9.0 + + + + + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + + + + PreserveNewest + + + + + + + + + + + + + diff --git a/CefSharp.Wpf.HwndHost.Example/Converter/EnvironmentConverter.cs b/CefSharp.Wpf.HwndHost.Example/Converter/EnvironmentConverter.cs new file mode 100644 index 0000000000..a5564df204 --- /dev/null +++ b/CefSharp.Wpf.HwndHost.Example/Converter/EnvironmentConverter.cs @@ -0,0 +1,19 @@ +using System; +using System.Globalization; +using System.Windows.Data; + +namespace CefSharp.Wpf.HwndHost.Example.Converter +{ + public class EnvironmentConverter : IValueConverter + { + object IValueConverter.Convert(object value, Type targetType, object parameter, CultureInfo culture) + { + return Environment.Is64BitProcess ? "x64" : "x86"; + } + + object IValueConverter.ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) + { + return Binding.DoNothing; + } + } +} diff --git a/CefSharp.Wpf.HwndHost.Example/Converter/TitleConverter.cs b/CefSharp.Wpf.HwndHost.Example/Converter/TitleConverter.cs new file mode 100644 index 0000000000..60333176e4 --- /dev/null +++ b/CefSharp.Wpf.HwndHost.Example/Converter/TitleConverter.cs @@ -0,0 +1,19 @@ +using System; +using System.Globalization; +using System.Windows.Data; + +namespace CefSharp.Wpf.HwndHost.Example.Converter +{ + public class TitleConverter : IValueConverter + { + object IValueConverter.Convert(object value, Type targetType, object parameter, CultureInfo culture) + { + return "CefSharp.MinimalExample.Wpf.HwndHost - " + (value ?? "No Title Specified"); + } + + object IValueConverter.ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) + { + return Binding.DoNothing; + } + } +} diff --git a/CefSharp.Wpf.HwndHost.Example/Handlers/DisplayHandler.cs b/CefSharp.Wpf.HwndHost.Example/Handlers/DisplayHandler.cs new file mode 100644 index 0000000000..48b8f6c812 --- /dev/null +++ b/CefSharp.Wpf.HwndHost.Example/Handlers/DisplayHandler.cs @@ -0,0 +1,53 @@ +using System.Windows; +using System.Windows.Controls; +using System.Windows.Media; + +namespace CefSharp.Wpf.HwndHost.Example.Handlers +{ + public class DisplayHandler : CefSharp.Handler.DisplayHandler + { + private Border parent; + private Window fullScreenWindow; + + protected override void OnFullscreenModeChange(IWebBrowser chromiumWebBrowser, IBrowser browser, bool fullscreen) + { + var webBrowser = (ChromiumWebBrowser)chromiumWebBrowser; + + _ = webBrowser.Dispatcher.InvokeAsync(() => + { + if (fullscreen) + { + //In this example the parent is a Border, if your parent is a different type + //of control then update this code accordingly. + parent = (Border)VisualTreeHelper.GetParent(webBrowser); + + //NOTE: If the ChromiumWebBrowser instance doesn't have a direct reference to + //the DataContext in this case the BrowserTabViewModel then your bindings won't + //be updated/might cause issues like the browser reloads the Url when exiting + //fullscreen. + parent.Child = null; + + fullScreenWindow = new Window + { + WindowStyle = WindowStyle.None, + WindowState = WindowState.Maximized, + Content = webBrowser + }; + fullScreenWindow.Loaded += (_,_) => webBrowser.Focus(); + + fullScreenWindow.ShowDialog(); + } + else + { + fullScreenWindow.Content = null; + + parent.Child = webBrowser; + + fullScreenWindow.Close(); + fullScreenWindow = null; + parent = null; + } + }); + } + } +} diff --git a/CefSharp.Wpf.HwndHost.Example/MainWindow.xaml b/CefSharp.Wpf.HwndHost.Example/MainWindow.xaml new file mode 100644 index 0000000000..d775e488aa --- /dev/null +++ b/CefSharp.Wpf.HwndHost.Example/MainWindow.xaml @@ -0,0 +1,85 @@ + + + + + + + + + + + + + + + + + + + + +