await webView.ExecuteScriptAsync("document.getElementById('test').style.backgroundColor = 'red';");
// The
is now red.
```
The method can also return a string result, with returned values being JSON-encoded:
```csharp
await webView.ExecuteScriptAsync("1 + 1"); // Returns a string containing 2
await webView.ExecuteScriptAsync($"(1 + 1).toString()"); // Returns a string containing "2"
await webView.ExecuteScriptAsync("eval({'test': 1})"); // Returns a string containing {"test":1}
```
## JavaScript to C# communication
`WebView2` enables sending web messages from JavaScript to C# on all supported targets. In your web page, include code that sends a message to the `WebView2` control if available. Since Uno Platform runs on multiple targets, you need to use the correct approach for each. We recommend creating a reusable function like the following:
```javascript
function postWebViewMessage(message){
try{
if (window.hasOwnProperty("chrome") && typeof chrome.webview !== undefined) {
// Windows
chrome.webview.postMessage(message);
} else if (window.hasOwnProperty("unoWebView")) {
// Android
unoWebView.postMessage(JSON.stringify(message));
} else if (window.hasOwnProperty("webkit") && typeof webkit.messageHandlers !== undefined) {
// iOS and macOS
webkit.messageHandlers.unoWebView.postMessage(JSON.stringify(message));
}
}
catch (ex){
alert("Error occurred: " + ex);
}
}
// Usage:
postWebViewMessage("hello world");
postWebViewMessage({"some": ['values',"in","json",1]});
```
> **Note:** Make sure not to omit the `JSON.stringify` calls for Android, iOS, and macOS as seen in the snippet above, as they are crucial to transfer data correctly.
To receive the message in C#, subscribe to the `WebMessageReceived` event:
```csharp
webView.WebMessageReceived += (s, e) =>
{
Debug.WriteLine(e.WebMessageAsJson);
};
```
The `WebMessageAsJson` property contains a JSON-encoded string of the data passed to `postWebViewMessage` above.
## Navigating to web content in the application package
To load local web content bundled with the application, you can use the `SetVirtualHostNameToFolderMapping` method. This allows you to set a virtual hostname that maps to a folder within the package, from which the web content will be loaded:
```csharp
await webView.EnsureCoreWebView2Async();
webView.CoreWebView2.SetVirtualHostNameToFolderMapping(
"UnoNativeAssets",
"WebContent",
CoreWebView2HostResourceAccessKind.Allow);
webView.CoreWebView2.Navigate("http://UnoNativeAssets/index.html");
```
This will navigate to the `index.html` file stored in the `WebContent` folder. This folder must be included in a platform-specific location on each platform:
- On Windows, it should be directly in the root of the `YourApp.Windows` project and all its contents should be set to `Content` build action
- On iOS, it should be inside the `Resources` folder and all its contents should be set to `BundleResource` build action
- On Android, it should be inside the `Assets` folder and all its contents should be set to `AndroidAsset` build action
To avoid duplication, you can put the files in a non-project-specific location and add them via linking, e.g.:
```xml
```
The web files can reference each other in a relative path fashion, for example, the following HTML file:
```html
...
```
Is referencing a `site.js` file inside the `js` subfolder.
## Enabling native developer tools
Set `Uno.UI.FeatureConfiguration.WebView2.EnableDevTools` during application startup, before any `WebView2` is materialized, to enable the platform-native developer tools for the underlying web engine:
```csharp
public App()
{
Uno.UI.FeatureConfiguration.WebView2.EnableDevTools = true;
this.InitializeComponent();
}
```
The flag defaults to `true` in `DEBUG` builds and `false` in `RELEASE` builds.
| Platform | What it enables | How to open |
| ---------- | ----------------- | ------------- |
| **Windows / Linux (Skia)** | Chromium DevTools | Right-click inside the WebView and choose **Inspect**, or press F12. |
| **iOS / Mac Catalyst / macOS** | Safari Web Inspector against the `WKWebView` (iOS 16.4+, macOS 13.3+) | In Safari, enable the **Develop** menu, then pick the device → page. See Apple's [Inspecting iOS](https://developer.apple.com/documentation/safari-developer-tools/inspecting-ios) guide. |
| **Android** | Chrome DevTools remote debugging | Open `chrome://inspect` in desktop Chrome with the device connected. |
| **WebAssembly** | N/A | Use the host browser's developer tools (F12). |
> [!IMPORTANT]
> On Apple platforms the OS gates inspection to apps signed with the get-task-allow entitlement (DEBUG / development builds). Setting the flag in a RELEASE build has no visible effect.
>
> [!NOTE]
> The legacy iOS-only `Uno.UI.FeatureConfiguration.WebView2.IsInspectable` property is now an obsolete alias for `EnableDevTools`.
## Linux specifics
In order to use WebView2 on Linux, you'll need to install `libwebkit2gtk` and `libgtk3-0`:
- On Ubuntu 22.04:
```bash
sudo apt install libwebkit2gtk-4.0-37
```
- On Ubuntu 24.04:
```bash
sudo apt install libgtk-3-0 libwebkit2gtk-4.1-dev
```
It's overall preferable to use libwebkit2gtk 4.1 whenever possible in order to get http headers support, if your environment allows for it.
### Wayland support
When running on a Wayland environment, the `WebView` control requires the environment variable `GDK_BACKEND` to be set to `x11` to function correctly.
```bash
export GDK_BACKEND=x11
dotnet run
```
## WebResourceRequested
The `WebResourceRequested` event allows you to intercept and modify HTTP requests made by the WebView. This is useful for scenarios like injecting custom headers, implementing authentication, or modifying request/response content.
### Basic usage
To use `WebResourceRequested`, you must first add a filter specifying which URLs should trigger the event, then subscribe to the event:
```csharp
await webView.EnsureCoreWebView2Async();
// Add a filter for all requests
webView.CoreWebView2.AddWebResourceRequestedFilter(
"*",
CoreWebView2WebResourceContext.All,
CoreWebView2WebResourceRequestSourceKinds.All);
// Subscribe to the event
webView.CoreWebView2.WebResourceRequested += (sender, args) =>
{
// Access request information
var uri = args.Request.Uri;
var method = args.Request.Method;
// Modify headers
args.Request.Headers.SetHeader("Authorization", "Bearer my-token");
args.Request.Headers.SetHeader("X-Custom-Header", "custom-value");
// Optionally provide a custom response
// args.Response = new CoreWebView2WebResourceResponse(...);
};
```
### Filter parameters
The `AddWebResourceRequestedFilter` method accepts three parameters:
- **uri**: A URI pattern with wildcard support (e.g., `"*"` for all URLs, `"https://api.example.com/*"` for specific domains)
- **resourceContext**: The type of resource to filter (`All`, `Document`, `Image`, `Script`, etc.)
- **requestSourceKinds**: The source of requests to filter (`All`, `Document`, etc.)
### Platform limitations
> [!IMPORTANT]
> `WebResourceRequested` has significant platform-specific limitations. Review the table below to understand what is supported on each platform.
| Platform | Support Level | Header Read | Header Modify | Custom Response | Notes |
| ---------- | -------------- | ------------- | --------------- | ----------------- | ------- |
| **Windows (Win32/WinAppSDK)** | ✅ Full | ✅ | ✅ | ✅ | Full WebView2 support |
| **Android** | ⚠️ Partial | ✅ | ⚠️ | ✅ | Header modification requires re-fetching the resource with HttpClient (only safe for GET/HEAD requests). Session cookies are automatically synchronized. POST request bodies cannot be reliably re-fetched and are not reissued by the implementation, so header changes for POST requests are unsupported. |
| **iOS** | ⚠️ Limited | ✅ | ⚠️ | ❌ | Navigation request headers cannot be modified. However, JavaScript-initiated requests (`fetch`/`XMLHttpRequest`) support custom header injection. Only fires for main document navigation, not sub-resources. |
| **macOS** | ⚠️ Limited | ✅ | ⚠️ | ❌ | Header injection is supported for new requests only. Cannot modify existing request headers. |
| **WebAssembly** | ⚠️ Limited | ✅ | ⚠️ | ❌ | Only `fetch`/`XMLHttpRequest` requests can be intercepted. Standard HTML elements (`img`, `script`, `link`, etc.) cannot have headers modified. Same-origin policy and CORS restrictions apply. May miss requests made during initial page load. |
| **Linux (X11)** | ❌ None | ❌ | ❌ | ❌ | Not implemented. |
### Platform-specific behavior
#### iOS/macOS (WKWebView)
The implementation uses two mechanisms:
1. **Navigation interception**: Fires `WebResourceRequested` for main document navigation (read-only headers)
2. **JavaScript injection**: Automatically injects a script that overrides `window.fetch()` and `XMLHttpRequest.prototype` to apply custom headers to AJAX requests
This means you can inject authentication tokens into API calls made via JavaScript:
```csharp
webView.CoreWebView2.WebResourceRequested += (sender, args) =>
{
// This will be applied to fetch() and XMLHttpRequest calls
args.Request.Headers.SetHeader("Authorization", "Bearer my-token");
};
```
#### Android
When headers are modified, the resource is re-fetched using `HttpClient`. The implementation includes:
- **Cookie synchronization**: Session cookies from the WebView are automatically included in re-fetched requests
- **Set-Cookie handling**: Response cookies are synchronized back to the WebView's `CookieManager`
This ensures authenticated sessions work correctly when using `WebResourceRequested`.
#### WebAssembly
For HTML element requests that cannot be intercepted:
- Use Service Workers for more comprehensive request interception
- Proxy requests through your server
- Use JavaScript-based loading for resources that need custom headers
## Accessing the underlying native control
In some advanced scenarios, you may need to access the platform-specific native web view control directly — for example, to configure settings not exposed by the Uno Platform abstraction.
The `WebView2` control template contains a single `ContentPresenter` named `WebViewTemplateRoot`. Each platform sets the `Content` of this presenter to its native web view control. You can retrieve it using `VisualTreeHelper`:
```csharp
await myWebView.EnsureCoreWebView2Async();
var presenter = (ContentPresenter)VisualTreeHelper.GetChild(myWebView, 0);
var nativeControl = presenter.Content;
```
The type of `nativeControl` varies per platform:
| Platform | Native Control Type | Notes |
| ---------- | ------------------- | ------- |
| **Android** | `Android.Webkit.WebView` | Standard Android WebView |
| **iOS** | `WebKit.WKWebView` | Via `UnoWKWebView`, which extends `WKWebView` |
| **macOS (Skia)** | `MacOSNativeWebView` | Internal wrapper using native WebKit via P/Invoke |
| **Windows (Win32/Skia)** | N/A | Uses a native HWND; not directly accessible via `Content` |
| **Linux (X11)** | N/A | Uses a GTK `WebKit.WebView` hosted in a separate window |
| **WebAssembly** | `BrowserHtmlElement` | An HTML `