A .NET MAUI class library providing an enhanced WebView control with JavaScript interop, navigation events, cookie management, and URL change detection for Android and iOS.
- Execute JavaScript from C# via
EvaluateJavaScriptAsync - Receive JavaScript calls in C# via
invokeCSharpAction(data)bridge - Navigation events:
Navigating,Navigated,WebViewNavigated - URL change detection for SPAs and PWAs via
UrlChanged(addresses dotnet/maui#19232) - Cookie management:
SetCookieAsync,UpdateCookieAsync,RemoveCookieAsync,RemoveAllCookiesAsync - Native back navigation:
CanGoBack,GoBack(),HistoryPosition - Multi-window / popup support on Android
- Force reload via
Refresh()
| Platform | Minimum OS Version |
|---|---|
| Android | 21.0 |
| iOS | 12.2 |
Target Frameworks: net10.0-android, net10.0-ios (MAUI 10.0.41)
dotnet add package Com.Bnotech.ExtendedWebViewRegister the handler in your MauiProgram.cs:
using Com.Bnotech.ExtendedWebView;
public static class MauiProgram
{
public static MauiApp CreateMauiApp()
{
var builder = MauiApp.CreateBuilder();
builder
.UseMauiApp<App>()
.UseExtendedWebView(); // <-- Add this
return builder.Build();
}
}<!-- In your XAML page -->
<controls:ExtWebView x:Name="webView"
Source="https://example.com" />using Com.Bnotech.ExtendedWebView;Or set the source in code:
webView.Source = new UrlWebViewSource { Url = "https://example.com" };var request = new EvaluateJavaScriptAsyncRequest("document.title");
await webView.EvaluateJavaScriptAsync(request);The control automatically injects a invokeCSharpAction(data) function into every page. Subscribe to the event in C#:
webView.JavaScriptAction += (sender, e) =>
{
string payload = e.Payload;
// Handle the data sent from JavaScript
};Then call it from your web page:
invokeCSharpAction("Hello from JavaScript!");webView.Navigating += (sender, e) =>
{
// Fires before navigation starts
};
webView.Navigated += (sender, e) =>
{
// Fires after navigation completes
};Detects route changes in single-page applications that don't trigger traditional navigation events:
webView.UrlChanged += (sender, e) =>
{
string newUrl = e.Url;
};// Set a cookie
await webView.SetCookieAsync(
name: "session",
value: "abc123",
domain: ".example.com",
path: "/",
expires: DateTimeOffset.UtcNow.AddDays(7),
isSecure: true,
isHttpOnly: true);
// Update a cookie (overwrites by name/domain/path)
await webView.UpdateCookieAsync(
name: "session",
value: "newvalue",
domain: ".example.com",
path: "/");
// Remove a specific cookie
await webView.RemoveCookieAsync("session", ".example.com", "/");
// Remove all cookies
await webView.RemoveAllCookiesAsync();On iOS, SetCookieAsync (and therefore UpdateCookieAsync) awaits the
WKHttpCookieStore set-cookie completion callback. RemoveAllCookiesAsync awaits
cookie enumeration and every native deletion callback, including the empty-store
case. Both methods also finish their corresponding NSHttpCookieStorage.SharedStorage
updates before returning. No delays are used.
Call these methods after the native handler is connected; a missing handler or UI synchronization context produces an exception rather than a successful no-op. Do not start login/navigation until initialization has completed, and do not run cookie initialization concurrently with navigation:
await webView.RemoveAllCookiesAsync();
await webView.SetCookieAsync("session", sessionToken, ".example.com", "/",
isSecure: true, isHttpOnly: true);
webView.Source = new UrlWebViewSource { Url = loginUrl };The iOS data store is shared: removing all cookies also affects other WebViews using
the default store. The new completion guarantee applies to the two methods above
on iOS, not to RemoveCookieAsync or Android cookie operations.
Read CanGoBack and HistoryPosition on the UI thread. On iOS they query
WKWebView.CanGoBack and WKWebView.BackForwardList directly, with no JavaScript
and no cached state. HistoryPosition is the number of entries in BackList:
the first entry is position 0, back navigation reduces it, forward navigation
increases it, and reload does not add an entry. It returns -1 if no native entry
is available (including before handler connection). Android uses its native
back/forward list index.
For completed document navigation, read the properties in Navigated with
WebNavigationResult.Success; the native history is already updated then.
GoBack() requests navigation asynchronously; do not read the destination position
immediately after calling it. It does nothing when the connected native WebView
cannot go back and throws if no handler is connected. URL observation can precede
a completed history update: UrlChanged alone is not a document-navigation
completion signal, and same-document/SPA history changes do not necessarily raise
Navigated.
An application can record HistoryPosition once, after it recognizes the
completed checkout entry navigation (after login and its redirects). For its back
button, it can use the following pattern:
// Application-owned state; set after the checkout entry has finished loading.
int? checkoutEntryPosition = null;
bool TryGoBackWithinCheckout()
{
if (checkoutEntryPosition is not int entry ||
webView.HistoryPosition <= entry || !webView.CanGoBack)
return false;
webView.GoBack();
return true;
}When this returns false, the application decides whether to close its MAUI page.
The library never closes pages or identifies checkout URLs. Serialize back requests
until navigation completes so repeated button presses cannot cross the boundary.
The position is a relative index, not a durable entry ID: if WebKit prunes history,
the application replaces the native WebView, or the site replaces history entries,
the application must reassess its boundary.
The application can enable WebKit's native back/forward swipe gesture through the
connected handler. Do not leave it enabled at the recorded entry: otherwise the
gesture can navigate to the preceding login page, bypassing the application back
button. For example, refresh this setting when recording the entry, after each
successful Navigated event, and after HandlerChanged:
void UpdateNativeNavigationGestures()
{
#if IOS
if (webView.Handler?.PlatformView is WebKit.WKWebView nativeWebView)
{
nativeWebView.AllowsBackForwardNavigationGestures =
checkoutEntryPosition is int entry &&
webView.HistoryPosition > entry &&
webView.CanGoBack;
}
#endif
}AllowsBackForwardNavigationGestures enables/disables both directions. At the
entry it must be disabled; a disabled WebKit gesture does not close the MAUI page.
Page closing (including any application-level back gesture) stays with the app.
For SPA changes, refresh the setting after the native history change has settled
as well. This toggle is not a native per-entry navigation policy: applications
requiring an unconditional boundary during overlapping/interactive navigations
should leave WebKit gestures disabled and route their own back action through the
boundary check.
For a genuinely new checkout, initialize the page/session anew and record a new
entry. A checkout page that is only temporarily covered should retain its WebView,
session, and recorded boundary; do not clear cookies or reset the entry on every
OnAppearing.
Force a reload of the current source:
webView.Refresh();| Property | Type | Description |
|---|---|---|
Source |
WebViewSource |
The URL or HTML content to display. Default: about:blank |
CanGoBack |
bool |
Whether the native WebView can navigate back (read on the UI thread) |
HistoryPosition |
int |
Zero-based native history index, or -1 if unavailable (read on the UI thread) |
| Method | Description |
|---|---|
EvaluateJavaScriptAsync(request) |
Executes JavaScript in the WebView |
SetCookieAsync(...) |
Sets a cookie in the platform cookie store |
UpdateCookieAsync(...) |
Updates (overwrites) an existing cookie |
RemoveCookieAsync(name, domain, path) |
Removes a specific cookie |
RemoveAllCookiesAsync() |
Removes all cookies |
Refresh() |
Forces a reload of the current source |
GoBack() |
Requests native back navigation; observe Navigated for completion |
| Event | EventArgs | Description |
|---|---|---|
JavaScriptAction |
JavaScriptActionEventArgs |
JS called invokeCSharpAction(data) |
Navigating |
WebNavigatingEventArgs |
Navigation is about to start |
Navigated |
WebNavigatedEventArgs |
Navigation completed |
UrlChanged |
UrlChangedEventArgs |
Displayed URL changed (SPA/PWA) |
SourceChanged |
SourceChangedEventArgs |
Source property was changed |
See LICENSE.