Skip to content

About

A .NET MAUI WebView with extended JavaScript and Event Handling functionality

Resources

Stars

1 star

Watchers

1 watching

Forks

Latest commit

 

History

20 Commits

Folders and files

Repository files navigation

Extended WebView for .NET MAUI

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.

NuGet

Features

  • 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()

Supported Platforms

Platform Minimum OS Version
Android 21.0
iOS 12.2

Target Frameworks: net10.0-android, net10.0-ios (MAUI 10.0.41)

Installation

dotnet add package Com.Bnotech.ExtendedWebView

Setup

Register 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();
    }
}

Usage

Basic — Load a URL

<!-- 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" };

Execute JavaScript (C# → JS)

var request = new EvaluateJavaScriptAsyncRequest("document.title");
await webView.EvaluateJavaScriptAsync(request);

Receive JavaScript Calls (JS → C#)

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!");

Navigation Events

webView.Navigating += (sender, e) =>
{
    // Fires before navigation starts
};

webView.Navigated += (sender, e) =>
{
    // Fires after navigation completes
};

URL Change Detection (SPA/PWA)

Detects route changes in single-page applications that don't trigger traditional navigation events:

webView.UrlChanged += (sender, e) =>
{
    string newUrl = e.Url;
};

Cookie Management

// 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.

Native History and Application-Owned Boundaries

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.

iOS Native Swipe Gesture

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.

Refresh

Force a reload of the current source:

webView.Refresh();

API Reference

ExtWebView — Properties

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)

ExtWebView — Methods

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

ExtWebView — Events

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

License

See LICENSE.

About

A .NET MAUI WebView with extended JavaScript and Event Handling functionality

Resources

Stars

1 star

Watchers

1 watching

Forks

Releases

Packages

Used by

Contributors

Languages