├── .github
└── CODEOWNERS
├── tests
├── Tests.WebView
│ ├── Resources
│ │ ├── EmbeddedHtml.html
│ │ ├── EmbeddedJavascriptFile.js
│ │ ├── ResourceJavascriptFile.js
│ │ └── dash-folder
│ │ │ └── EmbeddedJavascriptFile-With-Dashes.js
│ ├── Properties
│ │ └── AssemblyInfo.cs
│ ├── App.xaml
│ ├── App.xaml.cs
│ ├── TestObject.cs
│ ├── Assertions.cs
│ ├── LoadTests.cs
│ ├── IsolateCommonTests.cs
│ ├── SerializationTests.cs
│ ├── Tests.WebView.csproj
│ ├── CommonTests.cs
│ ├── WebViewTestBase.cs
│ ├── RequestInterception.cs
│ ├── TestBase.cs
│ ├── ResourcesLoading.cs
│ ├── JavascriptEvaluation.cs
│ └── IsolatedJavascriptEvaluation.cs
├── TestResourceAssembly.V1.0.0.0
│ ├── Resource.txt
│ └── TestResourceAssembly.csproj
└── TestResourceAssembly.V2.0.0.0
│ ├── Resource.txt
│ └── TestResourceAssembly.csproj
├── SampleWebView.Avalonia
├── screenshot.png
├── bundle-osx-x64.sh
├── bundle-osx-arm64.sh
├── app.manifest
├── Program.cs
├── MainWindow.xaml.cs
├── App.xaml
├── App.xaml.cs
├── SampleWebView.Avalonia.csproj
├── MainWindowViewModel.cs
└── MainWindow.xaml
├── WebViewControl
├── ProxyAuthentication.cs
├── app.config
├── ChromiumBrowser.cs
├── AssemblyLoader.NETFramework.cs
├── ChromiumBrowser.Wpf.cs
├── SchemeHandlerFactory.cs
├── ResourceType.cs
├── ResourcesManager.Wpf.cs
├── ResourceHandlerExtensions.cs
├── UnhandledAsyncExceptionEventArgs.cs
├── WebViewControl.nuspec
├── HttpResourceRequestHandler.cs
├── Properties
│ └── AssemblyInfo.cs
├── WebView.InternalContextMenuHandler.cs
├── RenderProcessTerminatedException.cs
├── WebView.InternalDialogHandler.cs
├── WebView.InternalFocusHandler.cs
├── EditCommands.cs
├── WebView.InternalKeyboardHandler.cs
├── UrlHelper.cs
├── WebViewControl.csproj
├── Request.cs
├── WebView.Extensions.cs
├── WebView.InternalDragHandler.cs
├── WebView.InternalJsDialogHandler.cs
├── WebView.InternalDownloadHandler.cs
├── WebView.JavascriptException.cs
├── WebView.InternalLifeSpanHandler.cs
├── HttpResourceHandler.cs
├── WebView.ResourceHandler.cs
├── JavascriptSerializationHelper.cs
├── WebView.InternalResourceRequestHandler.cs
├── AsyncResourceHandler.cs
├── WebViewLoader.cs
├── WebView.Wpf.cs
├── AssemblyCache.cs
├── GlobalSettings.cs
├── WebView.InternalRequestHandler.cs
├── ResourcesManager.cs
├── ResourceUrl.cs
├── WebView.JavascriptExecutionApi.cs
└── WebView.JavascriptExecutor.cs
├── WebViewControl.Avalonia
├── Properties
│ └── AssemblyInfo.cs
├── ResourcesManager.Avalonia.cs
├── AssemblyLoader.NETCore.cs
├── ChromiumBrowser.Avalonia.cs
├── WebViewControl.nuspec
├── BaseControl.cs
├── WebView.Avalonia.cs
└── WebViewControl.Avalonia.csproj
├── .gitignore
├── Nuget.config
├── Directory.Build.props
├── Directory.Packages.props
├── README.md
├── LICENSE
├── WebView.sln
└── .editorconfig
/.github/CODEOWNERS:
--------------------------------------------------------------------------------
1 | * @OutSystems/ide-team
--------------------------------------------------------------------------------
/tests/Tests.WebView/Resources/EmbeddedHtml.html:
--------------------------------------------------------------------------------
1 | test
--------------------------------------------------------------------------------
/tests/TestResourceAssembly.V1.0.0.0/Resource.txt:
--------------------------------------------------------------------------------
1 | Resource with V1.0.0.0 content
--------------------------------------------------------------------------------
/tests/TestResourceAssembly.V2.0.0.0/Resource.txt:
--------------------------------------------------------------------------------
1 | Resource with V2.0.0.0 content
--------------------------------------------------------------------------------
/tests/Tests.WebView/Resources/EmbeddedJavascriptFile.js:
--------------------------------------------------------------------------------
1 | embeddedFileLoaded = true;
--------------------------------------------------------------------------------
/tests/Tests.WebView/Resources/ResourceJavascriptFile.js:
--------------------------------------------------------------------------------
1 | resourceFileLoaded = true;
--------------------------------------------------------------------------------
/tests/Tests.WebView/Resources/dash-folder/EmbeddedJavascriptFile-With-Dashes.js:
--------------------------------------------------------------------------------
1 | embeddedFileLoaded = true;
--------------------------------------------------------------------------------
/SampleWebView.Avalonia/screenshot.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/OutSystems/WebView/HEAD/SampleWebView.Avalonia/screenshot.png
--------------------------------------------------------------------------------
/tests/Tests.WebView/Properties/AssemblyInfo.cs:
--------------------------------------------------------------------------------
1 | using NUnit.Framework;
2 |
3 | #if !DEBUG
4 | [assembly: Timeout(60000)]
5 | #endif
--------------------------------------------------------------------------------
/WebViewControl/ProxyAuthentication.cs:
--------------------------------------------------------------------------------
1 | namespace WebViewControl {
2 | public class ProxyAuthentication {
3 | public string UserName { get; set; }
4 | public string Password { get; set; }
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/WebViewControl/app.config:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
--------------------------------------------------------------------------------
/WebViewControl/ChromiumBrowser.cs:
--------------------------------------------------------------------------------
1 | using Xilium.CefGlue;
2 |
3 | namespace WebViewControl {
4 |
5 | internal partial class ChromiumBrowser {
6 |
7 | internal CefBrowser GetBrowser() => UnderlyingBrowser;
8 | }
9 | }
10 |
--------------------------------------------------------------------------------
/WebViewControl.Avalonia/Properties/AssemblyInfo.cs:
--------------------------------------------------------------------------------
1 | using System.Runtime.CompilerServices;
2 |
3 | [assembly: InternalsVisibleTo("Tests.ReactView")]
4 | [assembly: InternalsVisibleTo("Tests.WebView")]
5 | [assembly: InternalsVisibleTo("ReactViewControl.Avalonia")]
6 |
--------------------------------------------------------------------------------
/WebViewControl/AssemblyLoader.NETFramework.cs:
--------------------------------------------------------------------------------
1 | using System.Reflection;
2 |
3 | namespace WebViewControl {
4 |
5 | internal static class AssemblyLoader {
6 |
7 | internal static Assembly LoadAssembly(string path) => Assembly.LoadFile(path);
8 |
9 | }
10 | }
--------------------------------------------------------------------------------
/tests/Tests.WebView/App.xaml:
--------------------------------------------------------------------------------
1 |
4 |
5 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/WebViewControl/ChromiumBrowser.Wpf.cs:
--------------------------------------------------------------------------------
1 | using Xilium.CefGlue.WPF;
2 |
3 | namespace WebViewControl {
4 |
5 | partial class ChromiumBrowser : WpfCefBrowser {
6 |
7 | public new void CreateBrowser(int width, int height) {
8 | base.CreateBrowser(width, height);
9 | }
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/WebViewControl.Avalonia/ResourcesManager.Avalonia.cs:
--------------------------------------------------------------------------------
1 | using System.IO;
2 |
3 | namespace WebViewControl {
4 | partial class ResourcesManager {
5 |
6 | private static Stream GetApplicationResource(string assemblyName, string resourceName) {
7 | return null; // TODO
8 | }
9 | }
10 | }
11 |
--------------------------------------------------------------------------------
/tests/Tests.WebView/App.xaml.cs:
--------------------------------------------------------------------------------
1 | using Avalonia;
2 | using Avalonia.Markup.Xaml;
3 |
4 | namespace Tests {
5 |
6 | public class App : Application {
7 |
8 | public App() { }
9 |
10 | public override void Initialize() {
11 | AvaloniaXamlLoader.Load(this);
12 | }
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/tests/Tests.WebView/TestObject.cs:
--------------------------------------------------------------------------------
1 | namespace Tests.WebView {
2 |
3 | public enum Kind {
4 | A,
5 | B,
6 | C
7 | }
8 |
9 | public class TestObject {
10 | public string Name;
11 | public int Age;
12 | public TestObject Parent;
13 | public Kind Kind;
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/SampleWebView.Avalonia/bundle-osx-x64.sh:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 |
3 | dotnet msbuild -t:BundleApp -p:RuntimeIdentifier=osx-x64 -p:Platform=x64
4 |
5 | TARGETAPP=bin/x64/Debug/net8.0/osx-x64/publish/SampleWebView.app/Contents/MacOS
6 | chmod +x "$TARGETAPP/CefGlueBrowserProcess/Xilium.CefGlue.BrowserProcess"
7 | chmod +x "$TARGETAPP/SampleWebView.Avalonia"
8 |
--------------------------------------------------------------------------------
/WebViewControl.Avalonia/AssemblyLoader.NETCore.cs:
--------------------------------------------------------------------------------
1 | using System.Reflection;
2 | using System.Runtime.Loader;
3 |
4 | namespace WebViewControl {
5 |
6 | internal static class AssemblyLoader {
7 |
8 | internal static Assembly LoadAssembly(string path) => AssemblyLoadContext.Default.LoadFromAssemblyPath(path);
9 |
10 | }
11 | }
--------------------------------------------------------------------------------
/SampleWebView.Avalonia/bundle-osx-arm64.sh:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 |
3 | dotnet msbuild -t:BundleApp -p:RuntimeIdentifier=osx-arm64 -p:Platform=ARM64
4 |
5 | TARGETAPP=bin/ARM64/Debug/net8.0/osx-arm64/publish/SampleWebView.app/Contents/MacOS
6 | chmod +x "$TARGETAPP/CefGlueBrowserProcess/Xilium.CefGlue.BrowserProcess"
7 | chmod +x "$TARGETAPP/SampleWebView.Avalonia"
8 |
--------------------------------------------------------------------------------
/WebViewControl/SchemeHandlerFactory.cs:
--------------------------------------------------------------------------------
1 | using Xilium.CefGlue;
2 |
3 | namespace WebViewControl {
4 |
5 | internal class SchemeHandlerFactory : CefSchemeHandlerFactory {
6 |
7 | protected override CefResourceHandler Create(CefBrowser browser, CefFrame frame, string schemeName, CefRequest request) {
8 | return null;
9 | }
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/SampleWebView.Avalonia/app.manifest:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
--------------------------------------------------------------------------------
/WebViewControl.Avalonia/ChromiumBrowser.Avalonia.cs:
--------------------------------------------------------------------------------
1 | using Xilium.CefGlue.Avalonia;
2 |
3 | namespace WebViewControl {
4 |
5 | partial class ChromiumBrowser : AvaloniaCefBrowser {
6 |
7 | public new void CreateBrowser(int width, int height) {
8 | if (IsBrowserInitialized) {
9 | return;
10 | }
11 | base.CreateBrowser(width, height);
12 | }
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/WebViewControl/ResourceType.cs:
--------------------------------------------------------------------------------
1 | using Xilium.CefGlue;
2 |
3 | namespace WebViewControl {
4 |
5 | public enum ResourceType {
6 | Stylesheet = CefResourceType.Stylesheet,
7 | Script = CefResourceType.Script,
8 | Image = CefResourceType.Image,
9 | FontResource = CefResourceType.FontResource,
10 | Xhr = CefResourceType.Xhr,
11 | ServiceWorker = CefResourceType.ServiceWorker
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/SampleWebView.Avalonia/Program.cs:
--------------------------------------------------------------------------------
1 | using Avalonia;
2 | using Avalonia.ReactiveUI;
3 |
4 | namespace SampleWebView.Avalonia {
5 |
6 | class Program {
7 | static void Main(string[] args) {
8 | AppBuilder.Configure()
9 | .UsePlatformDetect()
10 | .UseReactiveUI()
11 | .StartWithClassicDesktopLifetime(args);
12 | }
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/WebViewControl/ResourcesManager.Wpf.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.IO;
3 | using System.Windows;
4 |
5 | namespace WebViewControl {
6 |
7 | partial class ResourcesManager {
8 |
9 | private static Stream GetApplicationResource(string assemblyName, string resourceName) {
10 | return Application.GetResourceStream(new Uri($"/{assemblyName};component/{resourceName}", UriKind.Relative))?.Stream;
11 | }
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/WebViewControl/ResourceHandlerExtensions.cs:
--------------------------------------------------------------------------------
1 | using System;
2 |
3 | namespace WebViewControl {
4 |
5 | internal static class ResourceHandlerExtensions {
6 |
7 | public static void LoadEmbeddedResource(this ResourceHandler resourceHandler, Uri url) {
8 | var stream = ResourcesManager.TryGetResource(url, true, out string extension);
9 |
10 | if (stream != null) {
11 | resourceHandler.RespondWith(stream, extension);
12 | }
13 | }
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/tests/TestResourceAssembly.V1.0.0.0/TestResourceAssembly.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 | $(TargetDotnetVersion)
4 | $(MSBuildProjectDirectory)\bin\
5 | TestResourceAssembly
6 | 1.0.0.0
7 |
8 |
9 |
10 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/tests/TestResourceAssembly.V2.0.0.0/TestResourceAssembly.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 | $(TargetDotnetVersion)
4 | $(MSBuildProjectDirectory)\bin\
5 | TestResourceAssembly
6 | 2.0.0.0
7 |
8 |
9 |
10 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | packages/
2 | .csproj.user
3 | *.user
4 | bin
5 | obj
6 | *.suo
7 | *.opendb
8 | *.db
9 | *.nupkg
10 | Release/
11 | Debug/
12 | .vs/config/applicationhost.config
13 | .vs/
14 | *.js
15 | *.tgz
16 | *.css
17 | *.scss.d.ts
18 | manifest.json
19 | WebViewControl/cake_tools
20 | WebViewControl.Avalonia/cake_tools
21 | launchSettings.json
22 | *.cache
23 | .npm-install-stamp
24 | **/TestResults.xml
25 |
26 | .idea
27 | WebViewControl/FodyWeavers.xsd
28 | WebViewControl.Avalonia/FodyWeavers.xsd
29 | **/*.DS_Store
30 |
--------------------------------------------------------------------------------
/WebViewControl/UnhandledAsyncExceptionEventArgs.cs:
--------------------------------------------------------------------------------
1 | using System;
2 |
3 | namespace WebViewControl {
4 |
5 | public class UnhandledAsyncExceptionEventArgs {
6 |
7 | public UnhandledAsyncExceptionEventArgs(Exception e, string frameName) {
8 | Exception = e;
9 | FrameName = frameName;
10 | }
11 |
12 | public Exception Exception { get; private set; }
13 |
14 | public string FrameName { get; private set; }
15 |
16 | public bool Handled { get; set; }
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/WebViewControl/WebViewControl.nuspec:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | $id$
5 | $version$
6 | $title$
7 | $authors$
8 | OutSystems
9 | false
10 | $description$
11 | $copyright$
12 | $packageProjectUrl$
13 |
14 |
--------------------------------------------------------------------------------
/SampleWebView.Avalonia/MainWindow.xaml.cs:
--------------------------------------------------------------------------------
1 | using System.Drawing;
2 | using Avalonia.Controls;
3 | using Avalonia.Markup.Xaml;
4 | using WebViewControl;
5 |
6 | namespace SampleWebView.Avalonia {
7 |
8 | internal class MainWindow : Window {
9 |
10 | public MainWindow() {
11 | WebView.Settings.LogFile = "ceflog.txt";
12 | WebView.Settings.BackgroundColor = Color.Bisque;
13 | AvaloniaXamlLoader.Load(this);
14 |
15 | DataContext = new MainWindowViewModel(this.FindControl("webview"));
16 | }
17 | }
18 | }
--------------------------------------------------------------------------------
/WebViewControl.Avalonia/WebViewControl.nuspec:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | $id$
5 | $version$
6 | $title$
7 | $authors$
8 | OutSystems
9 | false
10 | $description$
11 | $copyright$
12 | $packageProjectUrl$
13 |
14 |
--------------------------------------------------------------------------------
/WebViewControl/HttpResourceRequestHandler.cs:
--------------------------------------------------------------------------------
1 | using Xilium.CefGlue;
2 |
3 | namespace WebViewControl {
4 |
5 | internal class HttpResourceRequestHandler : CefResourceRequestHandler {
6 |
7 | protected override CefCookieAccessFilter GetCookieAccessFilter(CefBrowser browser, CefFrame frame, CefRequest request) {
8 | return null;
9 | }
10 |
11 | protected override CefResourceHandler GetResourceHandler(CefBrowser browser, CefFrame frame, CefRequest request) {
12 | return new HttpResourceHandler();
13 | }
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/WebViewControl.Avalonia/BaseControl.cs:
--------------------------------------------------------------------------------
1 | using Avalonia.Controls;
2 | using Avalonia.LogicalTree;
3 |
4 | namespace WebViewControl {
5 | public abstract class BaseControl : Control {
6 | protected abstract void InternalDispose();
7 |
8 | protected override void OnDetachedFromLogicalTree(LogicalTreeAttachmentEventArgs e) {
9 | base.OnDetachedFromLogicalTree(e);
10 |
11 | if (e.Root is Window w && w.PlatformImpl is null) {
12 | // Window was closed.
13 | InternalDispose();
14 | }
15 | }
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/SampleWebView.Avalonia/App.xaml:
--------------------------------------------------------------------------------
1 |
4 |
5 | 0
6 | 0
7 | 15,10,15,8
8 |
9 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/SampleWebView.Avalonia/App.xaml.cs:
--------------------------------------------------------------------------------
1 | using Avalonia;
2 | using Avalonia.Controls.ApplicationLifetimes;
3 | using Avalonia.Markup.Xaml;
4 |
5 | namespace SampleWebView.Avalonia {
6 | public class App : Application {
7 | public override void Initialize() {
8 | AvaloniaXamlLoader.Load(this);
9 | }
10 |
11 | public override void OnFrameworkInitializationCompleted() {
12 | if (ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop) {
13 | desktop.MainWindow = new MainWindow();
14 | }
15 | base.OnFrameworkInitializationCompleted();
16 | }
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/tests/Tests.WebView/Assertions.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Threading.Tasks;
3 | using NUnit.Framework;
4 |
5 | namespace Tests.WebView {
6 |
7 | public static class Assertions {
8 |
9 | public static async Task AssertThrows(AsyncTestDelegate action) where T : Exception {
10 | try {
11 | await action();
12 | Assert.Fail("Should have thrown exception");
13 | } catch (Exception exception) {
14 | // ThrowsAsync is not working well
15 | Assert.IsInstanceOf(exception);
16 | return (T)exception;
17 | }
18 | return null;
19 | }
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/WebViewControl/Properties/AssemblyInfo.cs:
--------------------------------------------------------------------------------
1 | using System.Runtime.CompilerServices;
2 | using System.Runtime.InteropServices;
3 |
4 | // Setting ComVisible to false makes the types in this assembly not visible
5 | // to COM components. If you need to access a type in this assembly from
6 | // COM, set the ComVisible attribute to true on that type.
7 | [assembly: ComVisible(false)]
8 |
9 | // The following GUID is for the ID of the typelib if this project is exposed to COM
10 | [assembly: Guid("a1c2a0c7-df81-4a8f-aeb5-b5375d5d1b47")]
11 |
12 | [assembly: InternalsVisibleTo("Tests.ReactView")]
13 | [assembly: InternalsVisibleTo("Tests.WebView")]
14 | [assembly: InternalsVisibleTo("ReactViewControl")]
15 |
--------------------------------------------------------------------------------
/WebViewControl/WebView.InternalContextMenuHandler.cs:
--------------------------------------------------------------------------------
1 | using Xilium.CefGlue;
2 | using Xilium.CefGlue.Common.Handlers;
3 |
4 | namespace WebViewControl {
5 |
6 | partial class WebView {
7 |
8 | private class InternalContextMenuHandler : ContextMenuHandler {
9 |
10 | private WebView OwnerWebView { get; }
11 |
12 | public InternalContextMenuHandler(WebView webView) {
13 | OwnerWebView = webView;
14 | }
15 |
16 | protected override void OnBeforeContextMenu(CefBrowser browser, CefFrame frame, CefContextMenuParams state, CefMenuModel model) {
17 | if (OwnerWebView.DisableBuiltinContextMenus) {
18 | model.Clear();
19 | }
20 | }
21 | }
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/WebViewControl/RenderProcessTerminatedException.cs:
--------------------------------------------------------------------------------
1 | using System;
2 |
3 | namespace WebViewControl {
4 |
5 | public class RenderProcessCrashedException : Exception {
6 |
7 | internal RenderProcessCrashedException(string message) : base(message) {
8 | }
9 | }
10 |
11 | public class RenderProcessKilledException : Exception {
12 |
13 | public bool IsWebViewDisposing { get; }
14 |
15 | internal RenderProcessKilledException(string message, bool webViewDisposing = false) : base(message) {
16 | IsWebViewDisposing = webViewDisposing;
17 | }
18 | }
19 |
20 | public class RenderProcessOutOfMemoryException : Exception {
21 |
22 | internal RenderProcessOutOfMemoryException(string message) : base(message) {
23 | }
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/WebViewControl/WebView.InternalDialogHandler.cs:
--------------------------------------------------------------------------------
1 | using Xilium.CefGlue;
2 | using Xilium.CefGlue.Common.Handlers;
3 |
4 | namespace WebViewControl {
5 |
6 | partial class WebView {
7 | private class InternalDialogHandler : DialogHandler {
8 |
9 | private WebView OwnerWebView { get; }
10 |
11 | public InternalDialogHandler(WebView webView) {
12 | OwnerWebView = webView;
13 | }
14 |
15 | protected override bool OnFileDialog(CefBrowser browser, CefFileDialogMode mode, string title, string defaultFilePath, string[] acceptFilters, CefFileDialogCallback callback) {
16 | if (OwnerWebView.DisableFileDialogs) {
17 | return true;
18 | }
19 | return false;
20 | }
21 | }
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/Nuget.config:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
--------------------------------------------------------------------------------
/WebViewControl/WebView.InternalFocusHandler.cs:
--------------------------------------------------------------------------------
1 | using Xilium.CefGlue;
2 | using Xilium.CefGlue.Common.Handlers;
3 |
4 | namespace WebViewControl {
5 |
6 | partial class WebView {
7 |
8 | private class InternalFocusHandler : FocusHandler {
9 |
10 | private WebView OwnerWebView { get; }
11 |
12 | public InternalFocusHandler(WebView webView) {
13 | OwnerWebView = webView;
14 | }
15 |
16 | protected override void OnGotFocus(CefBrowser browser) {
17 | OwnerWebView.OnGotFocus();
18 | }
19 |
20 | protected override bool OnSetFocus(CefBrowser browser, CefFocusSource source) {
21 | return OwnerWebView.OnSetFocus(source == CefFocusSource.System);
22 | }
23 |
24 | protected override void OnTakeFocus(CefBrowser browser, bool next) {
25 | OwnerWebView.OnLostFocus();
26 | }
27 | }
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/WebViewControl/EditCommands.cs:
--------------------------------------------------------------------------------
1 | using Xilium.CefGlue;
2 |
3 | namespace WebViewControl {
4 |
5 | public class EditCommands {
6 |
7 | private ChromiumBrowser ChromiumBrowser { get; }
8 |
9 | internal EditCommands(ChromiumBrowser chromiumBrowser) {
10 | ChromiumBrowser = chromiumBrowser;
11 | }
12 |
13 | private CefFrame GetFocusedFrame() => ChromiumBrowser.GetBrowser()?.GetFocusedFrame() ?? ChromiumBrowser.GetBrowser()?.GetMainFrame();
14 |
15 | public void Cut() => GetFocusedFrame()?.Cut();
16 |
17 | public void Copy() => GetFocusedFrame()?.Copy();
18 |
19 | public void Paste() => GetFocusedFrame()?.Paste();
20 |
21 | public void SelectAll() => GetFocusedFrame()?.SelectAll();
22 |
23 | public void Undo() => GetFocusedFrame()?.Undo();
24 |
25 | public void Redo() => GetFocusedFrame()?.Redo();
26 |
27 | public void Delete() => GetFocusedFrame()?.Delete();
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/WebViewControl/WebView.InternalKeyboardHandler.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using Xilium.CefGlue;
3 | using Xilium.CefGlue.Common.Handlers;
4 |
5 | namespace WebViewControl {
6 |
7 | partial class WebView {
8 |
9 | private class InternalKeyboardHandler : KeyboardHandler {
10 |
11 | private WebView OwnerWebView { get; }
12 |
13 | public InternalKeyboardHandler(WebView webView) {
14 | OwnerWebView = webView;
15 | }
16 |
17 | protected override bool OnPreKeyEvent(CefBrowser browser, CefKeyEvent keyEvent, IntPtr os_event, out bool isKeyboardShortcut) {
18 | var handler = OwnerWebView.KeyPressed;
19 | if (handler != null && !browser.IsPopup) {
20 | handler(keyEvent, out var handled);
21 | isKeyboardShortcut = false;
22 | return handled;
23 | }
24 | return base.OnPreKeyEvent(browser, keyEvent, os_event, out isKeyboardShortcut);
25 | }
26 | }
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/WebViewControl/UrlHelper.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Diagnostics;
3 | using System.Runtime.InteropServices;
4 |
5 | namespace WebViewControl {
6 |
7 | internal static class UrlHelper {
8 |
9 | private const string ChromeInternalProtocol = "devtools:";
10 |
11 | public const string AboutBlankUrl = "about:blank";
12 |
13 | public static ResourceUrl DefaultLocalUrl = new ResourceUrl(ResourceUrl.LocalScheme, "index.html");
14 |
15 | public static bool IsChromeInternalUrl(string url) {
16 | return url != null && url.StartsWith(ChromeInternalProtocol, StringComparison.InvariantCultureIgnoreCase);
17 | }
18 |
19 | public static bool IsInternalUrl(string url) {
20 | return IsChromeInternalUrl(url) || url.StartsWith(DefaultLocalUrl.ToString(), StringComparison.InvariantCultureIgnoreCase);
21 | }
22 |
23 | public static void OpenInExternalBrowser(string url) {
24 | if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) {
25 | Process.Start("explorer", "\"" + url + "\"");
26 | } else {
27 | Process.Start("open", url);
28 | }
29 | }
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/Directory.Build.props:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | net8.0
5 | x64;ARM64
6 | 11.0.10
7 | 120.6099.207
8 |
9 |
10 |
11 | 2.0.0.0
12 | 2.0.0.0
13 |
14 | 3.120.11
15 | OutSystems
16 | WebViewControl
17 | Copyright © OutSystems 2023
18 | https://github.com/OutSystems/WebView
19 | $(MSBuildProjectDirectory)\..\nuget
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 | -ARM64
29 | .ARM64
30 |
31 |
32 |
--------------------------------------------------------------------------------
/WebViewControl/WebViewControl.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | $(TargetDotnetVersion)-windows
5 | WebViewControl WPF
6 | WebViewControl for WPF powered by CefGlue
7 |
8 |
9 | WebViewControl
10 | Copyright © 2023
11 | true
12 | true
13 | WebViewControl-WPF$(PackageSuffix)
14 | Debug;Release;ReleaseAvalonia;ReleaseWPF;ReleaseAvaloniaRemoteDebugSupport
15 | true
16 |
17 |
18 |
19 | true
20 |
21 |
22 |
23 |
24 |
25 | Designer
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
--------------------------------------------------------------------------------
/tests/Tests.WebView/LoadTests.cs:
--------------------------------------------------------------------------------
1 | using System.Threading.Tasks;
2 | using NUnit.Framework;
3 | using WebViewControl;
4 |
5 | namespace Tests.WebView {
6 |
7 | public class LoadTests : WebViewTestBase {
8 |
9 | protected override Task AfterInitializeView() => Task.CompletedTask;
10 |
11 | [Test(Description = "Custom schemes are loaded")]
12 | public async Task LoadCustomScheme() {
13 | await Run(async () => {
14 | var embeddedResourceUrl = new ResourceUrl(GetType().Assembly, "Resources", "EmbeddedHtml.html");
15 |
16 | var taskCompletionSource = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously);
17 |
18 | void OnNavigated(string url, string frameName) {
19 | if (url != UrlHelper.AboutBlankUrl) {
20 | TargetView.Navigated -= OnNavigated;
21 | taskCompletionSource.SetResult(true);
22 | }
23 | }
24 | TargetView.Navigated += OnNavigated;
25 | TargetView.LoadResource(embeddedResourceUrl);
26 | await taskCompletionSource.Task;
27 |
28 | var content = await TargetView.EvaluateScript("return document.documentElement.innerText");
29 | Assert.AreEqual("test", content);
30 | });
31 | }
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/WebViewControl/Request.cs:
--------------------------------------------------------------------------------
1 | using System.Collections.Specialized;
2 | using Xilium.CefGlue;
3 |
4 | namespace WebViewControl {
5 |
6 | public class Request {
7 |
8 | private CefRequest CefRequest { get; }
9 | private string UrlOverride { get; }
10 |
11 | internal Request(CefRequest request, string urlOverride) {
12 | CefRequest = request;
13 | UrlOverride = urlOverride;
14 | }
15 |
16 | public string Method {
17 | get { return CefRequest.Method; }
18 | }
19 |
20 | public string Url {
21 | get { return UrlOverride ?? CefRequest.Url; }
22 | }
23 |
24 | public virtual void Cancel() {
25 | Canceled = true;
26 | }
27 |
28 | public bool Canceled { get; private set; }
29 |
30 | internal bool IsMainFrame => CefRequest.ResourceType == CefResourceType.MainFrame;
31 |
32 | public NameValueCollection GetHeaderMap() =>
33 | CefRequest.GetHeaderMap();
34 |
35 | public void SetHeaderMap(NameValueCollection headers) =>
36 | CefRequest.SetHeaderMap(headers);
37 |
38 | public string GetHeaderByName(string name) =>
39 | CefRequest.GetHeaderByName(name);
40 |
41 | public void SetHeaderByName(string name, string value, bool overwrite) =>
42 | CefRequest.SetHeaderByName(name, value, overwrite);
43 | }
44 | }
--------------------------------------------------------------------------------
/WebViewControl/WebView.Extensions.cs:
--------------------------------------------------------------------------------
1 | using System.Linq;
2 | using Xilium.CefGlue;
3 |
4 | namespace WebViewControl {
5 |
6 | internal static class WebViewExtensions {
7 |
8 | public static string[] GetFrameNames(this WebView webview) {
9 | return webview.GetCefBrowser()?.GetFrameNames().Where(n => !webview.IsMainFrame(n)).ToArray() ?? new string[0];
10 | }
11 |
12 | internal static bool HasFrame(this WebView webview, string name) {
13 | return webview.GetFrame(name) != null;
14 | }
15 |
16 | internal static CefFrame GetFrame(this WebView webview, string frameName) {
17 | return webview.GetCefBrowser()?.GetFrame(frameName ?? "");
18 | }
19 |
20 | internal static bool IsMainFrame(this WebView webview, string frameName) {
21 | return string.IsNullOrEmpty(frameName);
22 | }
23 |
24 | internal static void SendKeyEvent(this WebView webview, CefKeyEvent keyEvent) {
25 | webview.GetCefBrowser()?.GetHost()?.SendKeyEvent(keyEvent);
26 | }
27 |
28 | internal static void SetAccessibilityState(this WebView webview, CefState state) {
29 | webview.GetCefBrowser()?.GetHost()?.SetAccessibilityState(state);
30 | }
31 |
32 | private static CefBrowser GetCefBrowser(this WebView webview) {
33 | return webview.UnderlyingBrowser.GetBrowser();
34 | }
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/WebViewControl/WebView.InternalDragHandler.cs:
--------------------------------------------------------------------------------
1 | using Xilium.CefGlue;
2 | using Xilium.CefGlue.Common.Handlers;
3 |
4 | namespace WebViewControl {
5 | partial class WebView {
6 | private class InternalDragHandler : DragHandler {
7 |
8 | private WebView OwnerWebView { get; }
9 |
10 | public InternalDragHandler(WebView owner) {
11 | OwnerWebView = owner;
12 | }
13 |
14 | protected override bool OnDragEnter(CefBrowser browser, CefDragData dragData, CefDragOperationsMask mask) {
15 | var filesDragging = OwnerWebView.FilesDragging;
16 | if (filesDragging != null) {
17 | var fileNames = dragData.GetFileNames();
18 | if (fileNames != null) {
19 | filesDragging(fileNames);
20 | }
21 | }
22 |
23 | var textDragging = OwnerWebView.TextDragging;
24 | if (textDragging != null) {
25 | var textContent = dragData.FragmentText;
26 | if (!string.IsNullOrEmpty(textContent)) {
27 | textDragging(textContent);
28 | }
29 | }
30 |
31 | return false;
32 | }
33 |
34 | protected override void OnDraggableRegionsChanged(CefBrowser browser, CefFrame frame, CefDraggableRegion[] regions) {
35 |
36 | }
37 | }
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/WebViewControl/WebView.InternalJsDialogHandler.cs:
--------------------------------------------------------------------------------
1 | using Xilium.CefGlue;
2 | using Xilium.CefGlue.Common.Handlers;
3 |
4 | namespace WebViewControl {
5 |
6 | partial class WebView {
7 |
8 | private class InternalJsDialogHandler : JSDialogHandler {
9 |
10 | private WebView OwnerWebView { get; }
11 |
12 | public InternalJsDialogHandler(WebView webView) {
13 | OwnerWebView = webView;
14 | }
15 |
16 | protected override bool OnJSDialog(CefBrowser browser, string originUrl, CefJSDialogType dialogType, string message_text, string default_prompt_text, CefJSDialogCallback callback, out bool suppress_message) {
17 | suppress_message = false;
18 |
19 | var javacriptDialogShown = OwnerWebView.JavacriptDialogShown;
20 | if (javacriptDialogShown == null) {
21 | return false;
22 | }
23 |
24 | void Close() {
25 | callback.Continue(true, "");
26 | callback.Dispose();
27 | }
28 |
29 | javacriptDialogShown.Invoke(message_text, Close);
30 | return true;
31 | }
32 |
33 | protected override bool OnBeforeUnloadDialog(CefBrowser browser, string messageText, bool isReload, CefJSDialogCallback callback) {
34 | return false; // use default
35 | }
36 |
37 | protected override void OnResetDialogState(CefBrowser browser) { }
38 |
39 | protected override void OnDialogClosed(CefBrowser browser) { }
40 | }
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/tests/Tests.WebView/IsolateCommonTests.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Diagnostics;
3 | using System.Threading.Tasks;
4 | using Avalonia.Controls;
5 | using NUnit.Framework;
6 | using WebViewControl;
7 |
8 | namespace Tests.WebView {
9 |
10 | public class IsolateCommonTests : WebViewTestBase {
11 |
12 | protected override Task SetUp() {
13 | return Task.CompletedTask;
14 | }
15 |
16 | protected override Task TearDown() {
17 | return Task.CompletedTask;
18 | }
19 |
20 | /*[Test(Description = "Tests that the webview is disposed when host window is not shown")]
21 | public async Task WebViewIsNotDisposedWhenUnloaded() {
22 | await Run(async () => {
23 | var taskCompletionSource = new TaskCompletionSource();
24 | var view = new WebViewControl.WebView();
25 | view.Disposed += delegate {
26 | taskCompletionSource.SetResult(true);
27 | };
28 |
29 | var window = new Window {
30 | Title = CurrentTestName,
31 | Content = view
32 | };
33 |
34 | try {
35 | window.Show();
36 |
37 | window.Content = null;
38 | Assert.IsFalse(taskCompletionSource.Task.IsCompleted);
39 |
40 | window.Content = view;
41 | } finally {
42 | window.Close();
43 | }
44 | var disposed = await taskCompletionSource.Task;
45 | Assert.IsTrue(disposed);
46 | });
47 | }*/
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/tests/Tests.WebView/SerializationTests.cs:
--------------------------------------------------------------------------------
1 | using System.Collections.Generic;
2 | using NUnit.Framework;
3 | using WebViewControl;
4 |
5 | namespace Tests.WebView {
6 |
7 | public class SerializationTests {
8 |
9 | private enum SerializationEnum {
10 | Value0,
11 | Value1
12 | }
13 |
14 | [Test(Description = "Tests that javascript data serialization works as expected")]
15 | public void JavascriptDataSerialization() {
16 | Assert.AreEqual("true", JavascriptSerializer.Serialize(true));
17 | Assert.AreEqual("false", JavascriptSerializer.Serialize(false));
18 | Assert.AreEqual("undefined", JavascriptSerializer.Serialize(JavascriptSerializer.Undefined));
19 |
20 | Assert.AreEqual("1", JavascriptSerializer.Serialize(1));
21 | Assert.AreEqual("-1", JavascriptSerializer.Serialize(-1));
22 | Assert.AreEqual("1.1", JavascriptSerializer.Serialize(1.1));
23 | Assert.AreEqual("-1.1", JavascriptSerializer.Serialize(-1.1));
24 |
25 | Assert.AreEqual("\"hello \\\"world\\\"\"", JavascriptSerializer.Serialize("hello \"world\""));
26 |
27 | Assert.AreEqual("1", JavascriptSerializer.Serialize(SerializationEnum.Value1));
28 |
29 | Assert.AreEqual("[1,2,3]", JavascriptSerializer.Serialize(new[] { 1, 2, 3 }));
30 |
31 | Assert.AreEqual("{\"prop-a\":\"value-a\",\"prop-b\":\"value-b\",\"prop-c\":1.1,\"prop-d\":undefined}",
32 | JavascriptSerializer.Serialize(new Dictionary() { { "prop-b", "value-b" }, { "prop-a", "value-a" }, { "prop-c", 1.1 }, { "prop-d", JavascriptSerializer.Undefined } }));
33 | }
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/WebViewControl/WebView.InternalDownloadHandler.cs:
--------------------------------------------------------------------------------
1 | using Xilium.CefGlue;
2 | using Xilium.CefGlue.Common.Handlers;
3 |
4 | namespace WebViewControl {
5 |
6 | partial class WebView {
7 |
8 | private class InternalDownloadHandler : DownloadHandler {
9 |
10 | private WebView OwnerWebView { get; }
11 |
12 | public InternalDownloadHandler(WebView owner) {
13 | OwnerWebView = owner;
14 | }
15 |
16 | protected override void OnBeforeDownload(CefBrowser browser, CefDownloadItem downloadItem, string suggestedName, CefBeforeDownloadCallback callback) {
17 | callback.Continue(downloadItem.SuggestedFileName, showDialog: true);
18 | }
19 |
20 | protected override void OnDownloadUpdated(CefBrowser browser, CefDownloadItem downloadItem, CefDownloadItemCallback callback) {
21 | if (downloadItem.IsComplete) {
22 | if (OwnerWebView.DownloadCompleted != null) {
23 | OwnerWebView.AsyncExecuteInUI(() => OwnerWebView.DownloadCompleted?.Invoke(downloadItem.FullPath));
24 | }
25 | } else if (downloadItem.IsCanceled) {
26 | if (OwnerWebView.DownloadCancelled != null) {
27 | OwnerWebView.AsyncExecuteInUI(() => OwnerWebView.DownloadCancelled?.Invoke(downloadItem.FullPath));
28 | }
29 | } else {
30 | if (OwnerWebView.DownloadProgressChanged != null) {
31 | OwnerWebView.AsyncExecuteInUI(() => OwnerWebView.DownloadProgressChanged?.Invoke(downloadItem.FullPath, downloadItem.ReceivedBytes, downloadItem.TotalBytes));
32 | }
33 | }
34 | }
35 | }
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/tests/Tests.WebView/Tests.WebView.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | $(TargetDotnetVersion)
5 | $(MSBuildProjectDirectory)\bin\
6 | true
7 | $(Platform)
8 | false
9 | Debug;Release;ReleaseAvalonia;ReleaseWPF;ReleaseAvaloniaRemoteDebugSupport
10 | win-x64;win-arm64
11 | Major
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 | MSBuild:Compile
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
--------------------------------------------------------------------------------
/Directory.Packages.props:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | true
5 |
6 |
7 |
8 |
13 |
14 | $(PrivateAssets);compile
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
--------------------------------------------------------------------------------
/WebViewControl/WebView.JavascriptException.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using Xilium.CefGlue.Common.Events;
5 |
6 | namespace WebViewControl {
7 |
8 | partial class WebView {
9 |
10 | public class JavascriptException : Exception {
11 |
12 | private JavascriptStackFrame[] JsStack { get; }
13 | private string InnerStack { get; }
14 |
15 | internal JavascriptException(string message, IEnumerable stack = null, string innerStackTrace = null)
16 | : base(message, null) {
17 | JsStack = stack?.ToArray() ?? new JavascriptStackFrame[0];
18 | InnerStack = innerStackTrace;
19 | }
20 |
21 | internal JavascriptException(string name, string message, IEnumerable stack = null, string baseStackTrace = null)
22 | : this((string.IsNullOrEmpty(name) ? "" : name + ": ") + message, stack, baseStackTrace) {
23 | }
24 |
25 | public override string StackTrace {
26 | get { return string.Join(Environment.NewLine, JsStack.Select(FormatStackFrame).Concat(new[] { BaseStackTrace })); }
27 | }
28 |
29 | private string BaseStackTrace => (InnerStack != null ? InnerStack + Environment.NewLine : "") + base.StackTrace;
30 |
31 | private static string FormatStackFrame(JavascriptStackFrame frame) {
32 | var functionName = string.IsNullOrEmpty(frame.FunctionName) ? "" : frame.FunctionName;
33 | var location = string.IsNullOrEmpty(frame.ScriptNameOrSourceUrl) ? "" : ($" in {frame.ScriptNameOrSourceUrl}:line {frame.LineNumber} {frame.Column}");
34 | return $" at {functionName}{location}";
35 | }
36 |
37 | public override string ToString() {
38 | return GetType().FullName + ": " + Message + Environment.NewLine + StackTrace;
39 | }
40 | }
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/WebViewControl/WebView.InternalLifeSpanHandler.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Diagnostics;
3 | using Xilium.CefGlue;
4 | using Xilium.CefGlue.Common.Handlers;
5 |
6 | namespace WebViewControl {
7 |
8 | partial class WebView {
9 |
10 | private class InternalLifeSpanHandler : LifeSpanHandler {
11 |
12 | private WebView OwnerWebView { get; }
13 |
14 | public InternalLifeSpanHandler(WebView webView) {
15 | OwnerWebView = webView;
16 | }
17 |
18 | protected override bool OnBeforePopup(CefBrowser browser, CefFrame frame, string targetUrl, string targetFrameName, CefWindowOpenDisposition targetDisposition, bool userGesture, CefPopupFeatures popupFeatures, CefWindowInfo windowInfo, ref CefClient client, CefBrowserSettings settings, ref CefDictionaryValue extraInfo, ref bool noJavascriptAccess) {
19 | if (UrlHelper.IsChromeInternalUrl(targetUrl)) {
20 | return false;
21 | }
22 |
23 | if (Uri.IsWellFormedUriString(targetUrl, UriKind.RelativeOrAbsolute)) {
24 | var uri = new Uri(targetUrl);
25 | if (!uri.IsAbsoluteUri) {
26 | // turning relative urls into full path to avoid that someone runs custom command lines
27 | targetUrl = new Uri(new Uri(frame.Url), uri).AbsoluteUri;
28 | }
29 | } else {
30 | return false; // if the url is not well formed let's use the browser to handle the things
31 | }
32 |
33 | try {
34 | var popupOpening = OwnerWebView.PopupOpening;
35 | if (popupOpening != null) {
36 | popupOpening(targetUrl);
37 | } else {
38 | UrlHelper.OpenInExternalBrowser(targetUrl);
39 | }
40 | } catch {
41 | // if we can't handle the command line let's continue the normal request with the popup
42 | // with this, will not blow in the users face
43 | return false;
44 | }
45 |
46 | return true;
47 | }
48 | }
49 | }
50 | }
51 |
--------------------------------------------------------------------------------
/SampleWebView.Avalonia/SampleWebView.Avalonia.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | WinExe
5 | $(TargetDotnetVersion)
6 | LatestMajor
7 | Debug;Release;ReleaseAvalonia;ReleaseWPF;ReleaseAvaloniaRemoteDebugSupport
8 | osx-x64;win-x64;osx-arm64;win-arm64
9 | false
10 | app.manifest
11 |
12 |
13 |
14 | SampleWebView.Avalonia
15 | SampleWebView
16 | com.example
17 | 1.0.0
18 | AAPL
19 | 4242
20 | DemoAvalonia
21 | SampleWebView.Avalonia
22 | AppName.icns
23 | NSApplication
24 | true
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 | MSBuild:Compile
36 |
37 |
38 | MSBuild:Compile
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
--------------------------------------------------------------------------------
/tests/Tests.WebView/CommonTests.cs:
--------------------------------------------------------------------------------
1 | using System.Threading.Tasks;
2 | using Avalonia.Controls;
3 | using NUnit.Framework;
4 | using WebViewControl;
5 |
6 | namespace Tests.WebView {
7 |
8 | public class CommonTests : WebViewTestBase {
9 |
10 | [Test(Description = "Before navigate hook is called")]
11 | public async Task BeforeNavigateHookCalled() {
12 | await Run(async () => {
13 | var taskCompletionSource = new TaskCompletionSource();
14 | TargetView.BeforeNavigate += (request) => {
15 | taskCompletionSource.SetResult(true);
16 | request.Cancel();
17 | };
18 | TargetView.Address = "https://www.google.com";
19 | var beforeNavigateCalled = await taskCompletionSource.Task;
20 | Assert.IsTrue(beforeNavigateCalled, "BeforeNavigate hook was not called!");
21 | });
22 | }
23 |
24 | [Test(Description = "Javascript evaluation on navigated event does not block")]
25 | public async Task JavascriptEvaluationOnNavigatedDoesNotBlock() {
26 | await Run(async () => {
27 | var taskCompletionSource = new TaskCompletionSource();
28 | TargetView.Navigated += delegate {
29 | TargetView.EvaluateScript("1+1");
30 | taskCompletionSource.SetResult(true);
31 | };
32 | await Load("");
33 | var navigatedCalled = await taskCompletionSource.Task;
34 | Assert.IsTrue(navigatedCalled, "JS evaluation on navigated event is blocked!");
35 | });
36 | }
37 |
38 | [Test(Description = "Tests that the webview is disposed when host window is not shown")]
39 | public async Task WebViewIsDisposedWhenHostWindowIsNotShown() {
40 | await Run(async () => {
41 | var taskCompletionSource = new TaskCompletionSource();
42 | var view = new WebViewControl.WebView();
43 | view.Disposed += delegate {
44 | taskCompletionSource.SetResult(true);
45 | };
46 |
47 | var window = new Window { Title = CurrentTestName };
48 |
49 | try {
50 | window.Content = view;
51 | window.Close();
52 |
53 | var disposed = await taskCompletionSource.Task;
54 | Assert.IsTrue(disposed);
55 | } finally {
56 | window.Close();
57 | }
58 | });
59 | }
60 | }
61 | }
62 |
--------------------------------------------------------------------------------
/WebViewControl/HttpResourceHandler.cs:
--------------------------------------------------------------------------------
1 | using System.IO;
2 | using System.Net;
3 | using System.Threading.Tasks;
4 | using Xilium.CefGlue;
5 | using Xilium.CefGlue.Common.Handlers;
6 |
7 | namespace WebViewControl {
8 |
9 | internal class HttpResourceHandler : DefaultResourceHandler {
10 |
11 | private const string AccessControlAllowOriginHeaderKey = "Access-Control-Allow-Origin";
12 |
13 | internal static readonly CefResourceType[] AcceptedResources = new CefResourceType[] {
14 | // These resources types need an "Access-Control-Allow-Origin" header response entry
15 | // to comply with CORS security restrictions.
16 | CefResourceType.SubFrame,
17 | CefResourceType.FontResource,
18 | CefResourceType.Stylesheet
19 | };
20 |
21 | protected override RequestHandlingFashion ProcessRequestAsync(CefRequest request, CefCallback callback) {
22 | Task.Run(async () => {
23 | try {
24 | var httpRequest = WebRequest.CreateHttp(request.Url);
25 | var headers = request.GetHeaderMap();
26 | foreach (var key in headers.AllKeys) {
27 | httpRequest.Headers.Add(key, headers[key]);
28 | }
29 |
30 | var response = (HttpWebResponse) await httpRequest.GetResponseAsync();
31 | Response = response.GetResponseStream();
32 | Headers = response.Headers;
33 |
34 | MimeType = response.ContentType;
35 | Status = (int) response.StatusCode;
36 | StatusText = response.StatusDescription;
37 |
38 | // we have to smash any existing value here
39 | Headers.Remove(AccessControlAllowOriginHeaderKey);
40 | Headers.Add(AccessControlAllowOriginHeaderKey, "*");
41 |
42 | } catch {
43 | // we should catch exceptions.. network errors cannot crash the app
44 | } finally {
45 | callback.Continue();
46 | }
47 |
48 | });
49 | return RequestHandlingFashion.ContinueAsync;
50 | }
51 |
52 | protected override bool Read(Stream outResponse, int bytesToRead, out int bytesRead, CefResourceReadCallback callback) {
53 | var buffer = new byte[bytesToRead];
54 | bytesRead = Response?.Read(buffer, 0, buffer.Length) ?? 0;
55 |
56 | if (bytesRead == 0) {
57 | return false;
58 | }
59 |
60 | outResponse.Write(buffer, 0, bytesRead);
61 | return bytesRead > 0;
62 | }
63 | }
64 | }
65 |
--------------------------------------------------------------------------------
/WebViewControl/WebView.ResourceHandler.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.IO;
3 | using System.Threading.Tasks;
4 | using Xilium.CefGlue;
5 |
6 | namespace WebViewControl {
7 |
8 | public sealed class ResourceHandler : Request {
9 |
10 | private bool isAsync;
11 |
12 | private readonly object syncRoot = new object();
13 |
14 | internal ResourceHandler(CefRequest request, string urlOverride)
15 | : base(request, urlOverride) {
16 | }
17 |
18 | internal AsyncResourceHandler Handler { get; private set; }
19 |
20 | public bool Handled { get; private set; }
21 |
22 | public Stream Response => Handler?.Response;
23 |
24 | private AsyncResourceHandler GetOrCreateCefResourceHandler() {
25 | if (Handler != null) {
26 | return Handler;
27 | }
28 |
29 | lock (syncRoot) {
30 | if (Handler != null) {
31 | return Handler;
32 | }
33 |
34 | var handler = new AsyncResourceHandler();
35 | handler.Headers.Add("cache-control", "public, max-age=315360000");
36 | Handler = handler;
37 | return handler;
38 | }
39 | }
40 |
41 | public void BeginAsyncResponse(Action handleResponse) {
42 | isAsync = true;
43 | var handler = GetOrCreateCefResourceHandler();
44 | Task.Run(() => {
45 | handleResponse();
46 | handler.Continue();
47 | });
48 | }
49 |
50 | private void Continue() {
51 | var handler = Handler;
52 | Handled = handler != null && (handler.Response != null || !string.IsNullOrEmpty(handler.RedirectUrl));
53 | if (isAsync || handler == null) {
54 | return;
55 | }
56 | handler.Continue();
57 | }
58 |
59 | public void RespondWith(string filename) {
60 | var fileStream = File.OpenRead(filename);
61 | GetOrCreateCefResourceHandler().SetResponse(fileStream, ResourcesManager.GetMimeType(filename), autoDisposeStream: true);
62 | Continue();
63 | }
64 |
65 | public void RespondWithText(string text) {
66 | GetOrCreateCefResourceHandler().SetResponse(text);
67 | Continue();
68 | }
69 |
70 | public void RespondWith(Stream stream, string extension = null) {
71 | GetOrCreateCefResourceHandler().SetResponse(stream, ResourcesManager.GetExtensionMimeType(extension));
72 | Continue();
73 | }
74 |
75 | public void Redirect(string url) {
76 | GetOrCreateCefResourceHandler().RedirectTo(url);
77 | Continue();
78 | }
79 | }
80 | }
81 |
--------------------------------------------------------------------------------
/WebViewControl/JavascriptSerializationHelper.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections;
3 | using System.Collections.Generic;
4 | using System.Globalization;
5 | using System.Linq;
6 | using System.Web;
7 | using JavascriptObject = System.Collections.Generic.IEnumerable>;
8 | using SerializationHandler = System.Func