diff --git a/eng/devices/windows.cake b/eng/devices/windows.cake
index 76741497c1e8..a64592128e97 100644
--- a/eng/devices/windows.cake
+++ b/eng/devices/windows.cake
@@ -205,7 +205,7 @@ Task("Test")
.IsDependentOn("SetupTestPaths")
.Does(() =>
{
- var waitForResultTimeoutInSeconds = 120;
+ var waitForResultTimeoutInSeconds = 240;
CleanDirectories(TEST_RESULTS);
diff --git a/src/Controls/samples/Controls.Sample/Pages/Controls/HybridWebViewPage.xaml b/src/Controls/samples/Controls.Sample/Pages/Controls/HybridWebViewPage.xaml
new file mode 100644
index 000000000000..78779691808a
--- /dev/null
+++ b/src/Controls/samples/Controls.Sample/Pages/Controls/HybridWebViewPage.xaml
@@ -0,0 +1,32 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/Controls/samples/Controls.Sample/Pages/Controls/HybridWebViewPage.xaml.cs b/src/Controls/samples/Controls.Sample/Pages/Controls/HybridWebViewPage.xaml.cs
new file mode 100644
index 000000000000..c25780f123b4
--- /dev/null
+++ b/src/Controls/samples/Controls.Sample/Pages/Controls/HybridWebViewPage.xaml.cs
@@ -0,0 +1,23 @@
+using System;
+using Microsoft.Maui.Controls;
+
+namespace Maui.Controls.Sample.Pages
+{
+ public partial class HybridWebViewPage
+ {
+ public HybridWebViewPage()
+ {
+ InitializeComponent();
+ }
+
+ private void SendMessageButton_Clicked(object sender, EventArgs e)
+ {
+ hwv.SendRawMessage("Hello from C#!");
+ }
+
+ private void hwv_RawMessageReceived(object sender, HybridWebViewRawMessageReceivedEventArgs e)
+ {
+ Dispatcher.Dispatch(() => statusLabel.Text += e.Message);
+ }
+ }
+}
diff --git a/src/Controls/samples/Controls.Sample/Resources/Raw/HybridSamplePage/docs/sample.pdf b/src/Controls/samples/Controls.Sample/Resources/Raw/HybridSamplePage/docs/sample.pdf
new file mode 100644
index 000000000000..f548cfbc6b3a
Binary files /dev/null and b/src/Controls/samples/Controls.Sample/Resources/Raw/HybridSamplePage/docs/sample.pdf differ
diff --git a/src/Controls/samples/Controls.Sample/Resources/Raw/HybridSamplePage/index.html b/src/Controls/samples/Controls.Sample/Resources/Raw/HybridSamplePage/index.html
new file mode 100644
index 000000000000..787d4dc3b9e0
--- /dev/null
+++ b/src/Controls/samples/Controls.Sample/Resources/Raw/HybridSamplePage/index.html
@@ -0,0 +1,32 @@
+
+
+
+
+
+
+
+
+
+
+
+
+ Hybrid sample!
+
+
+
+
+
+ Message from C#:
+
+
+
+
diff --git a/src/Controls/samples/Controls.Sample/Resources/Raw/HybridSamplePage/scripts/HybridWebView.js b/src/Controls/samples/Controls.Sample/Resources/Raw/HybridSamplePage/scripts/HybridWebView.js
new file mode 100644
index 000000000000..bf41fe0a160b
--- /dev/null
+++ b/src/Controls/samples/Controls.Sample/Resources/Raw/HybridSamplePage/scripts/HybridWebView.js
@@ -0,0 +1,48 @@
+function HybridWebViewInit() {
+
+ function DispatchHybridWebViewMessage(message) {
+ const event = new CustomEvent("HybridWebViewMessageReceived", { detail: { message: message } });
+ window.dispatchEvent(event);
+ }
+
+ if (window.chrome && window.chrome.webview) {
+ // Windows WebView2
+ window.chrome.webview.addEventListener('message', arg => {
+ DispatchHybridWebViewMessage(arg.data);
+ });
+ }
+ else if (window.webkit && window.webkit.messageHandlers && window.webkit.messageHandlers.webwindowinterop) {
+ // iOS and MacCatalyst WKWebView
+ window.external = {
+ "receiveMessage": message => {
+ DispatchHybridWebViewMessage(message);
+ }
+ };
+ }
+ else {
+ // Android WebView
+ window.addEventListener('message', arg => {
+ DispatchHybridWebViewMessage(arg.data);
+ });
+ }
+}
+
+window.HybridWebView = {
+ "SendRawMessage": function (message) {
+
+ if (window.chrome && window.chrome.webview) {
+ // Windows WebView2
+ window.chrome.webview.postMessage(message);
+ }
+ else if (window.webkit && window.webkit.messageHandlers && window.webkit.messageHandlers.webwindowinterop) {
+ // iOS and MacCatalyst WKWebView
+ window.webkit.messageHandlers.webwindowinterop.postMessage(message);
+ }
+ else {
+ // Android WebView
+ hybridWebViewHost.sendRawMessage(message);
+ }
+ }
+}
+
+HybridWebViewInit();
diff --git a/src/Controls/samples/Controls.Sample/Resources/Raw/HybridSamplePage/styles/app.css b/src/Controls/samples/Controls.Sample/Resources/Raw/HybridSamplePage/styles/app.css
new file mode 100644
index 000000000000..5e2f4852d638
--- /dev/null
+++ b/src/Controls/samples/Controls.Sample/Resources/Raw/HybridSamplePage/styles/app.css
@@ -0,0 +1,10 @@
+button
+{
+ background-color: dodgerblue;
+ border-radius: 5px;
+ border-width: 1px;
+ color: white;
+ cursor: pointer;
+ margin: 6px;
+ text-align: center;
+}
diff --git a/src/Controls/samples/Controls.Sample/ViewModels/ControlsViewModel.cs b/src/Controls/samples/Controls.Sample/ViewModels/ControlsViewModel.cs
index 2f5b80819267..a30d6ffb6c84 100644
--- a/src/Controls/samples/Controls.Sample/ViewModels/ControlsViewModel.cs
+++ b/src/Controls/samples/Controls.Sample/ViewModels/ControlsViewModel.cs
@@ -39,6 +39,9 @@ protected override IEnumerable CreateItems() => new[]
new SectionModel(typeof(EntryPage), "Entry",
"The Entry control is used for single-line text input."),
+ new SectionModel(typeof(HybridWebViewPage), "HybridWebView",
+ "The HybridWebView control embeds web content locally and natively in an app."),
+
new SectionModel(typeof(ImagePage), "Image",
"Displays an image."),
diff --git a/src/Controls/src/Core/HybridWebView/HybridWebView.cs b/src/Controls/src/Core/HybridWebView/HybridWebView.cs
new file mode 100644
index 000000000000..382ab5e7cc66
--- /dev/null
+++ b/src/Controls/src/Core/HybridWebView/HybridWebView.cs
@@ -0,0 +1,54 @@
+using System;
+
+namespace Microsoft.Maui.Controls
+{
+ ///
+ /// A that presents local HTML content in a web view and allows JavaScript and C# code to interop using messages.
+ ///
+ public class HybridWebView : View, IHybridWebView
+ {
+ /// Bindable property for .
+ public static readonly BindableProperty DefaultFileProperty =
+ BindableProperty.Create(nameof(DefaultFile), typeof(string), typeof(HybridWebView), defaultValue: "index.html");
+ /// Bindable property for .
+ public static readonly BindableProperty HybridRootProperty =
+ BindableProperty.Create(nameof(HybridRoot), typeof(string), typeof(HybridWebView), defaultValue: "wwwroot");
+
+
+ ///
+ /// Specifies the file within the that should be served as the default file. The
+ /// default value is index.html.
+ ///
+ public string? DefaultFile
+ {
+ get { return (string)GetValue(DefaultFileProperty); }
+ set { SetValue(DefaultFileProperty, value); }
+ }
+
+ ///
+ /// The path within the app's "Raw" asset resources that contain the web app's contents. For example, if the
+ /// files are located in [ProjectFolder]/Resources/Raw/hybrid_root, then set this property to "hybrid_root".
+ /// The default value is wwwroot, which maps to [ProjectFolder]/Resources/Raw/wwwroot.
+ ///
+ public string? HybridRoot
+ {
+ get { return (string)GetValue(HybridRootProperty); }
+ set { SetValue(HybridRootProperty, value); }
+ }
+
+ void IHybridWebView.RawMessageReceived(string rawMessage)
+ {
+ RawMessageReceived?.Invoke(this, new HybridWebViewRawMessageReceivedEventArgs(rawMessage));
+ }
+
+ ///
+ /// Raised when a raw message is received from the web view. Raw messages are strings that have no additional processing.
+ ///
+ public event EventHandler? RawMessageReceived;
+
+ public void SendRawMessage(string rawMessage)
+ {
+ Handler?.Invoke(nameof(IHybridWebView.SendRawMessage), rawMessage);
+ }
+ }
+}
diff --git a/src/Controls/src/Core/HybridWebView/HybridWebViewRawMessageReceivedEventArgs.cs b/src/Controls/src/Core/HybridWebView/HybridWebViewRawMessageReceivedEventArgs.cs
new file mode 100644
index 000000000000..d431303de8de
--- /dev/null
+++ b/src/Controls/src/Core/HybridWebView/HybridWebViewRawMessageReceivedEventArgs.cs
@@ -0,0 +1,14 @@
+using System;
+
+namespace Microsoft.Maui.Controls
+{
+ public class HybridWebViewRawMessageReceivedEventArgs : EventArgs
+ {
+ public HybridWebViewRawMessageReceivedEventArgs(string? message)
+ {
+ Message = message;
+ }
+
+ public string? Message { get; }
+ }
+}
diff --git a/src/Controls/src/Core/PublicAPI/net-android/PublicAPI.Unshipped.txt b/src/Controls/src/Core/PublicAPI/net-android/PublicAPI.Unshipped.txt
index 6ca2b95572bb..d463b309df62 100644
--- a/src/Controls/src/Core/PublicAPI/net-android/PublicAPI.Unshipped.txt
+++ b/src/Controls/src/Core/PublicAPI/net-android/PublicAPI.Unshipped.txt
@@ -1,4 +1,15 @@
#nullable enable
+Microsoft.Maui.Controls.HybridWebView
+Microsoft.Maui.Controls.HybridWebView.DefaultFile.get -> string?
+Microsoft.Maui.Controls.HybridWebView.DefaultFile.set -> void
+Microsoft.Maui.Controls.HybridWebView.HybridRoot.get -> string?
+Microsoft.Maui.Controls.HybridWebView.HybridRoot.set -> void
+Microsoft.Maui.Controls.HybridWebView.HybridWebView() -> void
+Microsoft.Maui.Controls.HybridWebView.RawMessageReceived -> System.EventHandler?
+Microsoft.Maui.Controls.HybridWebView.SendRawMessage(string! rawMessage) -> void
+Microsoft.Maui.Controls.HybridWebViewRawMessageReceivedEventArgs
+Microsoft.Maui.Controls.HybridWebViewRawMessageReceivedEventArgs.HybridWebViewRawMessageReceivedEventArgs(string? message) -> void
+Microsoft.Maui.Controls.HybridWebViewRawMessageReceivedEventArgs.Message.get -> string?
Microsoft.Maui.Controls.PlatformDragStartingEventArgs
Microsoft.Maui.Controls.PlatformDragStartingEventArgs.Sender.get -> Android.Views.View!
Microsoft.Maui.Controls.PlatformDragStartingEventArgs.MotionEvent.get -> Android.Views.MotionEvent!
@@ -73,6 +84,8 @@ Microsoft.Maui.Controls.TimeChangedEventArgs.NewTime.get -> System.TimeSpan
Microsoft.Maui.Controls.TimeChangedEventArgs.OldTime.get -> System.TimeSpan
Microsoft.Maui.Controls.TimeChangedEventArgs.TimeChangedEventArgs(System.TimeSpan oldTime, System.TimeSpan newTime) -> void
Microsoft.Maui.Controls.TimePicker.TimeSelected -> System.EventHandler
+static readonly Microsoft.Maui.Controls.HybridWebView.DefaultFileProperty -> Microsoft.Maui.Controls.BindableProperty!
+static readonly Microsoft.Maui.Controls.HybridWebView.HybridRootProperty -> Microsoft.Maui.Controls.BindableProperty!
static readonly Microsoft.Maui.Controls.KeyboardAccelerator.KeyProperty -> Microsoft.Maui.Controls.BindableProperty!
static readonly Microsoft.Maui.Controls.KeyboardAccelerator.ModifiersProperty -> Microsoft.Maui.Controls.BindableProperty!
static readonly Microsoft.Maui.Controls.DragGestureRecognizer.CanDragProperty -> Microsoft.Maui.Controls.BindableProperty!
diff --git a/src/Controls/src/Core/PublicAPI/net-ios/PublicAPI.Unshipped.txt b/src/Controls/src/Core/PublicAPI/net-ios/PublicAPI.Unshipped.txt
index 044e4e82b5c5..ab4745457ee3 100644
--- a/src/Controls/src/Core/PublicAPI/net-ios/PublicAPI.Unshipped.txt
+++ b/src/Controls/src/Core/PublicAPI/net-ios/PublicAPI.Unshipped.txt
@@ -1,4 +1,15 @@
#nullable enable
+Microsoft.Maui.Controls.HybridWebView
+Microsoft.Maui.Controls.HybridWebView.DefaultFile.get -> string?
+Microsoft.Maui.Controls.HybridWebView.DefaultFile.set -> void
+Microsoft.Maui.Controls.HybridWebView.HybridRoot.get -> string?
+Microsoft.Maui.Controls.HybridWebView.HybridRoot.set -> void
+Microsoft.Maui.Controls.HybridWebView.HybridWebView() -> void
+Microsoft.Maui.Controls.HybridWebView.RawMessageReceived -> System.EventHandler?
+Microsoft.Maui.Controls.HybridWebView.SendRawMessage(string! rawMessage) -> void
+Microsoft.Maui.Controls.HybridWebViewRawMessageReceivedEventArgs
+Microsoft.Maui.Controls.HybridWebViewRawMessageReceivedEventArgs.HybridWebViewRawMessageReceivedEventArgs(string? message) -> void
+Microsoft.Maui.Controls.HybridWebViewRawMessageReceivedEventArgs.Message.get -> string?
override Microsoft.Maui.Controls.Handlers.Compatibility.CellRenderer.Invoke(string! command, object? args) -> void
override Microsoft.Maui.Controls.Handlers.Compatibility.CellRenderer.UpdateValue(string! property) -> void
override Microsoft.Maui.Controls.Handlers.Compatibility.ShellRenderer.PreferredStatusBarUpdateAnimation.get -> UIKit.UIStatusBarAnimation
@@ -89,6 +100,8 @@ override Microsoft.Maui.Controls.Platform.Compatibility.ShellFlyoutRenderer.Pref
override Microsoft.Maui.Controls.Platform.Compatibility.ShellFlyoutRenderer.PrefersHomeIndicatorAutoHidden.get -> bool
override Microsoft.Maui.Controls.Platform.Compatibility.ShellFlyoutRenderer.PrefersStatusBarHidden() -> bool
override Microsoft.Maui.Controls.Platform.Compatibility.ShellSectionRenderer.ViewDidAppear(bool animated) -> void
+static readonly Microsoft.Maui.Controls.HybridWebView.DefaultFileProperty -> Microsoft.Maui.Controls.BindableProperty!
+static readonly Microsoft.Maui.Controls.HybridWebView.HybridRootProperty -> Microsoft.Maui.Controls.BindableProperty!
static readonly Microsoft.Maui.Controls.KeyboardAccelerator.KeyProperty -> Microsoft.Maui.Controls.BindableProperty!
static readonly Microsoft.Maui.Controls.KeyboardAccelerator.ModifiersProperty -> Microsoft.Maui.Controls.BindableProperty!
static readonly Microsoft.Maui.Controls.DragGestureRecognizer.CanDragProperty -> Microsoft.Maui.Controls.BindableProperty!
diff --git a/src/Controls/src/Core/PublicAPI/net-maccatalyst/PublicAPI.Unshipped.txt b/src/Controls/src/Core/PublicAPI/net-maccatalyst/PublicAPI.Unshipped.txt
index d1771227750c..39bf7cf0d753 100644
--- a/src/Controls/src/Core/PublicAPI/net-maccatalyst/PublicAPI.Unshipped.txt
+++ b/src/Controls/src/Core/PublicAPI/net-maccatalyst/PublicAPI.Unshipped.txt
@@ -1,4 +1,15 @@
#nullable enable
+Microsoft.Maui.Controls.HybridWebView
+Microsoft.Maui.Controls.HybridWebView.DefaultFile.get -> string?
+Microsoft.Maui.Controls.HybridWebView.DefaultFile.set -> void
+Microsoft.Maui.Controls.HybridWebView.HybridRoot.get -> string?
+Microsoft.Maui.Controls.HybridWebView.HybridRoot.set -> void
+Microsoft.Maui.Controls.HybridWebView.HybridWebView() -> void
+Microsoft.Maui.Controls.HybridWebView.RawMessageReceived -> System.EventHandler?
+Microsoft.Maui.Controls.HybridWebView.SendRawMessage(string! rawMessage) -> void
+Microsoft.Maui.Controls.HybridWebViewRawMessageReceivedEventArgs
+Microsoft.Maui.Controls.HybridWebViewRawMessageReceivedEventArgs.HybridWebViewRawMessageReceivedEventArgs(string? message) -> void
+Microsoft.Maui.Controls.HybridWebViewRawMessageReceivedEventArgs.Message.get -> string?
override Microsoft.Maui.Controls.Handlers.Compatibility.CellRenderer.Invoke(string! command, object? args) -> void
override Microsoft.Maui.Controls.Handlers.Compatibility.CellRenderer.UpdateValue(string! property) -> void
override Microsoft.Maui.Controls.GradientBrush.IsEmpty.get -> bool
@@ -83,6 +94,8 @@ Microsoft.Maui.Controls.TimeChangedEventArgs.OldTime.get -> System.TimeSpan
Microsoft.Maui.Controls.TimeChangedEventArgs.TimeChangedEventArgs(System.TimeSpan oldTime, System.TimeSpan newTime) -> void
Microsoft.Maui.Controls.TimePicker.TimeSelected -> System.EventHandler
override Microsoft.Maui.Controls.Handlers.Items.ItemsViewController.LoadView() -> void
+static readonly Microsoft.Maui.Controls.HybridWebView.DefaultFileProperty -> Microsoft.Maui.Controls.BindableProperty!
+static readonly Microsoft.Maui.Controls.HybridWebView.HybridRootProperty -> Microsoft.Maui.Controls.BindableProperty!
static readonly Microsoft.Maui.Controls.KeyboardAccelerator.KeyProperty -> Microsoft.Maui.Controls.BindableProperty!
static readonly Microsoft.Maui.Controls.KeyboardAccelerator.ModifiersProperty -> Microsoft.Maui.Controls.BindableProperty!
static readonly Microsoft.Maui.Controls.DragGestureRecognizer.CanDragProperty -> Microsoft.Maui.Controls.BindableProperty!
diff --git a/src/Controls/src/Core/PublicAPI/net-tizen/PublicAPI.Unshipped.txt b/src/Controls/src/Core/PublicAPI/net-tizen/PublicAPI.Unshipped.txt
index e32d6e8c2dc7..9e937760dacf 100644
--- a/src/Controls/src/Core/PublicAPI/net-tizen/PublicAPI.Unshipped.txt
+++ b/src/Controls/src/Core/PublicAPI/net-tizen/PublicAPI.Unshipped.txt
@@ -1,4 +1,15 @@
#nullable enable
+Microsoft.Maui.Controls.HybridWebView
+Microsoft.Maui.Controls.HybridWebView.DefaultFile.get -> string?
+Microsoft.Maui.Controls.HybridWebView.DefaultFile.set -> void
+Microsoft.Maui.Controls.HybridWebView.HybridRoot.get -> string?
+Microsoft.Maui.Controls.HybridWebView.HybridRoot.set -> void
+Microsoft.Maui.Controls.HybridWebView.HybridWebView() -> void
+Microsoft.Maui.Controls.HybridWebView.RawMessageReceived -> System.EventHandler?
+Microsoft.Maui.Controls.HybridWebView.SendRawMessage(string! rawMessage) -> void
+Microsoft.Maui.Controls.HybridWebViewRawMessageReceivedEventArgs
+Microsoft.Maui.Controls.HybridWebViewRawMessageReceivedEventArgs.HybridWebViewRawMessageReceivedEventArgs(string? message) -> void
+Microsoft.Maui.Controls.HybridWebViewRawMessageReceivedEventArgs.Message.get -> string?
Microsoft.Maui.Controls.PlatformDragStartingEventArgs
Microsoft.Maui.Controls.PlatformDropCompletedEventArgs
Microsoft.Maui.Controls.PlatformDragEventArgs
@@ -55,6 +66,8 @@ Microsoft.Maui.Controls.TimeChangedEventArgs.OldTime.get -> System.TimeSpan
Microsoft.Maui.Controls.TimeChangedEventArgs.TimeChangedEventArgs(System.TimeSpan oldTime, System.TimeSpan newTime) -> void
Microsoft.Maui.Controls.TimePicker.TimeSelected -> System.EventHandler
override Microsoft.Maui.Controls.GradientBrush.IsEmpty.get -> bool
+static readonly Microsoft.Maui.Controls.HybridWebView.DefaultFileProperty -> Microsoft.Maui.Controls.BindableProperty!
+static readonly Microsoft.Maui.Controls.HybridWebView.HybridRootProperty -> Microsoft.Maui.Controls.BindableProperty!
static readonly Microsoft.Maui.Controls.KeyboardAccelerator.KeyProperty -> Microsoft.Maui.Controls.BindableProperty!
static readonly Microsoft.Maui.Controls.KeyboardAccelerator.ModifiersProperty -> Microsoft.Maui.Controls.BindableProperty!
static readonly Microsoft.Maui.Controls.DragGestureRecognizer.CanDragProperty -> Microsoft.Maui.Controls.BindableProperty!
diff --git a/src/Controls/src/Core/PublicAPI/net-windows/PublicAPI.Unshipped.txt b/src/Controls/src/Core/PublicAPI/net-windows/PublicAPI.Unshipped.txt
index 2270d964161a..557e20477a14 100644
--- a/src/Controls/src/Core/PublicAPI/net-windows/PublicAPI.Unshipped.txt
+++ b/src/Controls/src/Core/PublicAPI/net-windows/PublicAPI.Unshipped.txt
@@ -1,5 +1,16 @@
#nullable enable
Microsoft.Maui.Controls.Handlers.Items.CarouselViewHandler.~CarouselViewHandler() -> void
+Microsoft.Maui.Controls.HybridWebView
+Microsoft.Maui.Controls.HybridWebView.DefaultFile.get -> string?
+Microsoft.Maui.Controls.HybridWebView.DefaultFile.set -> void
+Microsoft.Maui.Controls.HybridWebView.HybridRoot.get -> string?
+Microsoft.Maui.Controls.HybridWebView.HybridRoot.set -> void
+Microsoft.Maui.Controls.HybridWebView.HybridWebView() -> void
+Microsoft.Maui.Controls.HybridWebView.RawMessageReceived -> System.EventHandler?
+Microsoft.Maui.Controls.HybridWebView.SendRawMessage(string! rawMessage) -> void
+Microsoft.Maui.Controls.HybridWebViewRawMessageReceivedEventArgs
+Microsoft.Maui.Controls.HybridWebViewRawMessageReceivedEventArgs.HybridWebViewRawMessageReceivedEventArgs(string? message) -> void
+Microsoft.Maui.Controls.HybridWebViewRawMessageReceivedEventArgs.Message.get -> string?
Microsoft.Maui.Controls.PlatformDragStartingEventArgs
Microsoft.Maui.Controls.PlatformDragStartingEventArgs.Sender.get -> Microsoft.UI.Xaml.UIElement!
Microsoft.Maui.Controls.PlatformDragStartingEventArgs.DragStartingEventArgs.get -> Microsoft.UI.Xaml.DragStartingEventArgs!
@@ -80,6 +91,8 @@ Microsoft.Maui.Controls.TimeChangedEventArgs.NewTime.get -> System.TimeSpan
Microsoft.Maui.Controls.TimeChangedEventArgs.OldTime.get -> System.TimeSpan
Microsoft.Maui.Controls.TimeChangedEventArgs.TimeChangedEventArgs(System.TimeSpan oldTime, System.TimeSpan newTime) -> void
Microsoft.Maui.Controls.TimePicker.TimeSelected -> System.EventHandler
+static readonly Microsoft.Maui.Controls.HybridWebView.DefaultFileProperty -> Microsoft.Maui.Controls.BindableProperty!
+static readonly Microsoft.Maui.Controls.HybridWebView.HybridRootProperty -> Microsoft.Maui.Controls.BindableProperty!
static readonly Microsoft.Maui.Controls.KeyboardAccelerator.KeyProperty -> Microsoft.Maui.Controls.BindableProperty!
static readonly Microsoft.Maui.Controls.KeyboardAccelerator.ModifiersProperty -> Microsoft.Maui.Controls.BindableProperty!
static readonly Microsoft.Maui.Controls.DragGestureRecognizer.CanDragProperty -> Microsoft.Maui.Controls.BindableProperty!
diff --git a/src/Controls/src/Core/PublicAPI/net/PublicAPI.Unshipped.txt b/src/Controls/src/Core/PublicAPI/net/PublicAPI.Unshipped.txt
index 8420cbc99b5d..c19ed9b783d8 100644
--- a/src/Controls/src/Core/PublicAPI/net/PublicAPI.Unshipped.txt
+++ b/src/Controls/src/Core/PublicAPI/net/PublicAPI.Unshipped.txt
@@ -1,4 +1,15 @@
#nullable enable
+Microsoft.Maui.Controls.HybridWebView
+Microsoft.Maui.Controls.HybridWebView.DefaultFile.get -> string?
+Microsoft.Maui.Controls.HybridWebView.DefaultFile.set -> void
+Microsoft.Maui.Controls.HybridWebView.HybridRoot.get -> string?
+Microsoft.Maui.Controls.HybridWebView.HybridRoot.set -> void
+Microsoft.Maui.Controls.HybridWebView.HybridWebView() -> void
+Microsoft.Maui.Controls.HybridWebView.RawMessageReceived -> System.EventHandler?
+Microsoft.Maui.Controls.HybridWebView.SendRawMessage(string! rawMessage) -> void
+Microsoft.Maui.Controls.HybridWebViewRawMessageReceivedEventArgs
+Microsoft.Maui.Controls.HybridWebViewRawMessageReceivedEventArgs.HybridWebViewRawMessageReceivedEventArgs(string? message) -> void
+Microsoft.Maui.Controls.HybridWebViewRawMessageReceivedEventArgs.Message.get -> string?
Microsoft.Maui.Controls.PlatformDragStartingEventArgs
Microsoft.Maui.Controls.PlatformDropCompletedEventArgs
Microsoft.Maui.Controls.PlatformDragEventArgs
@@ -57,6 +68,8 @@ Microsoft.Maui.Controls.TimeChangedEventArgs.OldTime.get -> System.TimeSpan
Microsoft.Maui.Controls.TimeChangedEventArgs.TimeChangedEventArgs(System.TimeSpan oldTime, System.TimeSpan newTime) -> void
Microsoft.Maui.Controls.TimePicker.TimeSelected -> System.EventHandler
override Microsoft.Maui.Controls.GradientBrush.IsEmpty.get -> bool
+static readonly Microsoft.Maui.Controls.HybridWebView.DefaultFileProperty -> Microsoft.Maui.Controls.BindableProperty!
+static readonly Microsoft.Maui.Controls.HybridWebView.HybridRootProperty -> Microsoft.Maui.Controls.BindableProperty!
static readonly Microsoft.Maui.Controls.KeyboardAccelerator.KeyProperty -> Microsoft.Maui.Controls.BindableProperty!
static readonly Microsoft.Maui.Controls.KeyboardAccelerator.ModifiersProperty -> Microsoft.Maui.Controls.BindableProperty!
static readonly Microsoft.Maui.Controls.DragGestureRecognizer.CanDragProperty -> Microsoft.Maui.Controls.BindableProperty!
diff --git a/src/Controls/src/Core/PublicAPI/netstandard/PublicAPI.Unshipped.txt b/src/Controls/src/Core/PublicAPI/netstandard/PublicAPI.Unshipped.txt
index 585a0675a609..050dea94f92f 100644
--- a/src/Controls/src/Core/PublicAPI/netstandard/PublicAPI.Unshipped.txt
+++ b/src/Controls/src/Core/PublicAPI/netstandard/PublicAPI.Unshipped.txt
@@ -1,5 +1,16 @@
#nullable enable
Microsoft.Maui.Controls.DragEventArgs.PlatformArgs.get -> Microsoft.Maui.Controls.PlatformDragEventArgs?
+Microsoft.Maui.Controls.HybridWebView
+Microsoft.Maui.Controls.HybridWebView.DefaultFile.get -> string?
+Microsoft.Maui.Controls.HybridWebView.DefaultFile.set -> void
+Microsoft.Maui.Controls.HybridWebView.HybridRoot.get -> string?
+Microsoft.Maui.Controls.HybridWebView.HybridRoot.set -> void
+Microsoft.Maui.Controls.HybridWebView.HybridWebView() -> void
+Microsoft.Maui.Controls.HybridWebView.RawMessageReceived -> System.EventHandler?
+Microsoft.Maui.Controls.HybridWebView.SendRawMessage(string! rawMessage) -> void
+Microsoft.Maui.Controls.HybridWebViewRawMessageReceivedEventArgs
+Microsoft.Maui.Controls.HybridWebViewRawMessageReceivedEventArgs.HybridWebViewRawMessageReceivedEventArgs(string? message) -> void
+Microsoft.Maui.Controls.HybridWebViewRawMessageReceivedEventArgs.Message.get -> string?
Microsoft.Maui.Controls.PlatformDragStartingEventArgs
Microsoft.Maui.Controls.PlatformDropCompletedEventArgs
Microsoft.Maui.Controls.PlatformDragEventArgs
@@ -55,6 +66,8 @@ Microsoft.Maui.Controls.TimeChangedEventArgs.OldTime.get -> System.TimeSpan
Microsoft.Maui.Controls.TimeChangedEventArgs.TimeChangedEventArgs(System.TimeSpan oldTime, System.TimeSpan newTime) -> void
Microsoft.Maui.Controls.TimePicker.TimeSelected -> System.EventHandler
override Microsoft.Maui.Controls.GradientBrush.IsEmpty.get -> bool
+static readonly Microsoft.Maui.Controls.HybridWebView.DefaultFileProperty -> Microsoft.Maui.Controls.BindableProperty!
+static readonly Microsoft.Maui.Controls.HybridWebView.HybridRootProperty -> Microsoft.Maui.Controls.BindableProperty!
static readonly Microsoft.Maui.Controls.KeyboardAccelerator.KeyProperty -> Microsoft.Maui.Controls.BindableProperty!
static readonly Microsoft.Maui.Controls.KeyboardAccelerator.ModifiersProperty -> Microsoft.Maui.Controls.BindableProperty!
static readonly Microsoft.Maui.Controls.DragGestureRecognizer.CanDragProperty -> Microsoft.Maui.Controls.BindableProperty!
diff --git a/src/Controls/src/Xaml/Hosting/AppHostBuilderExtensions.cs b/src/Controls/src/Xaml/Hosting/AppHostBuilderExtensions.cs
index f5deafe55c61..ebbd595dffa2 100644
--- a/src/Controls/src/Xaml/Hosting/AppHostBuilderExtensions.cs
+++ b/src/Controls/src/Xaml/Hosting/AppHostBuilderExtensions.cs
@@ -86,6 +86,7 @@ public static IMauiHandlersCollection AddMauiControlsHandlers(this IMauiHandlers
handlersCollection.AddHandler();
handlersCollection.AddHandler();
handlersCollection.AddHandler();
+ handlersCollection.AddHandler();
handlersCollection.AddHandler();
handlersCollection.AddHandler();
handlersCollection.AddHandler();
diff --git a/src/Controls/tests/DeviceTests/Controls.DeviceTests.csproj b/src/Controls/tests/DeviceTests/Controls.DeviceTests.csproj
index b21cd0cc8e97..e051cd0a393f 100644
--- a/src/Controls/tests/DeviceTests/Controls.DeviceTests.csproj
+++ b/src/Controls/tests/DeviceTests/Controls.DeviceTests.csproj
@@ -26,6 +26,10 @@
+
+
+
+
diff --git a/src/Controls/tests/DeviceTests/Elements/HybridWebView/HybridWebViewTests.cs b/src/Controls/tests/DeviceTests/Elements/HybridWebView/HybridWebViewTests.cs
new file mode 100644
index 000000000000..7facd9f54a50
--- /dev/null
+++ b/src/Controls/tests/DeviceTests/Elements/HybridWebView/HybridWebViewTests.cs
@@ -0,0 +1,83 @@
+using System.Threading.Tasks;
+using Microsoft.Maui.Controls;
+using Microsoft.Maui.Handlers;
+using Microsoft.Maui.Hosting;
+using Xunit;
+
+namespace Microsoft.Maui.DeviceTests
+{
+ [Category(TestCategory.HybridWebView)]
+ public class HybridWebViewTests : ControlsHandlerTestBase
+ {
+ void SetupBuilder()
+ {
+ EnsureHandlerCreated(builder =>
+ {
+ builder.ConfigureMauiHandlers(handlers =>
+ {
+ handlers.AddHandler();
+ });
+ });
+ }
+
+ [Fact]
+ public async Task LoadsHtmlAndSendReceiveRawMessage()
+ {
+#if ANDROID
+ // NOTE: skip this test on older Android devices because it is not currently supported on these versions
+ if (!System.OperatingSystem.IsAndroidVersionAtLeast(24))
+ {
+ return;
+ }
+#endif
+
+ SetupBuilder();
+
+ await InvokeOnMainThreadAsync(async () =>
+ {
+ var hybridWebView = new HybridWebView()
+ {
+ WidthRequest = 100,
+ HeightRequest = 100,
+
+ HybridRoot = "HybridTestRoot",
+ };
+
+ var lastRawMessage = "";
+
+ hybridWebView.RawMessageReceived += (s, e) =>
+ {
+ lastRawMessage = e.Message;
+ };
+
+ var handler = CreateHandler(hybridWebView);
+
+ var platformView = handler.PlatformView;
+
+ // Setup the view to be displayed/parented and run our tests on it
+ await AttachAndRun(hybridWebView, async (handler) =>
+ {
+ await Task.Delay(5000);
+
+ const string TestRawMessage = "Hybrid\"\"'' {Test} with chars!";
+ hybridWebView.SendRawMessage(TestRawMessage);
+
+ var passed = false;
+
+ for (var i = 0; i < 10; i++)
+ {
+ if (lastRawMessage == "You said: " + TestRawMessage)
+ {
+ passed = true;
+ break;
+ }
+
+ await Task.Delay(1000);
+ }
+
+ Assert.True(passed, $"Waited for raw message response but it never arrived or didn't match (last message: {lastRawMessage})");
+ });
+ });
+ }
+ }
+}
diff --git a/src/Controls/tests/DeviceTests/Memory/MemoryTests.cs b/src/Controls/tests/DeviceTests/Memory/MemoryTests.cs
index e9ecca92794e..562d81752722 100644
--- a/src/Controls/tests/DeviceTests/Memory/MemoryTests.cs
+++ b/src/Controls/tests/DeviceTests/Memory/MemoryTests.cs
@@ -40,6 +40,7 @@ void SetupBuilder()
handlers.AddHandler();
#pragma warning restore CS0618 // Type or member is obsolete
handlers.AddHandler();
+ handlers.AddHandler();
handlers.AddHandler