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

21 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 and iOS
  • 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.

New Windows and Checkout Popups

User-initiated window.open(...) and target="_blank" requests open a separate native WebView: a dialog on Android and an overlay with an X button on iOS. For example, a checkout's payment button may open a bank's verification page this way, or submit a POST form with target="_blank". Ordinary links and redirects without a new-window target continue to use the existing window. Not every payment provider opens a popup; some use redirects, iframes, or a separate app instead.

HTTP(S) links and redirects inside a popup stay in the child WebView, including navigation to another domain where platform policy permits it. Opening and closing the popup does not replace or reload the checkout. On iOS, WebKit receives the child created with its supplied configuration and loads the original request itself, preserving POST bodies. Standard browser opener rules still apply: noopener, noreferrer, and browser defaults for target="_blank" can make window.opener unavailable. The library does not override these protections.

Close a popup with X or the page's window.close(); Android also supports dialog dismissal with the system back button. Nested popups are supported. Closing a parent popup closes its children, and disconnecting the handler releases all open popups. Cookies use the native shared store, subject to platform cookie policies. Popups receive no injected invokeCSharpAction function or native C# message handler on either platform. The main control's JavaScript API, history properties, and navigation/URL events continue to refer only to the main WebView. Normal web communication between windows remains subject to browser origin and opener rules.

Closing a popup is not a payment-success signal. The checkout and payment provider must determine the outcome through their normal verified flow; the library does not mark a payment successful or automatically reload the checkout.

Native popup-blocking defaults remain in place: scripts opening windows without a user gesture are not automatically allowed. iOS popups use a fresh content controller without the app bridge and do not enforce the main WebView's app-bound-domain restriction, allowing external payment-provider domains. The main WebView's restriction remains unchanged; any return URL loaded in the main window must still satisfy its policy. TLS, transport security, and other platform restrictions remain in effect. iOS displays the current host and main-document navigation failures in the popup. This support does not add handling for external-app URL schemes.

Device acceptance is still required for each payment provider on Android and iOS:

Scenario Expected result
Tap a target="_blank" link or a button calling window.open A visible child opens; the checkout remains unchanged underneath
Open a POST form with target="_blank" The child receives the submitted data
Follow a cross-domain HTTP(S) link and redirect in the child Content remains in the child, subject to platform domain policy
Open an external payment domain on iOS with app-bound domains configured for checkout The popup can navigate externally; the main window's policy is unchanged
Navigate to an unreachable URL or a site with an invalid certificate The failure is visible; certificate errors are not bypassed
Check invokeCSharpAction and the native bridge in the popup No library bridge is installed
Close via X or window.close() (also Android system back) The child disappears and the original checkout state is retained
Open nested or successive popups Each has working close handling; closing a parent releases its children
Leave the host page while a popup is open No popup or dialog remains after handler disconnection
Close or cancel verification without completing payment The library does not report payment success

These are acceptance criteria, not a claim of device/provider verification.

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