Repository navigation
Route Android HybridWebView JS->.NET messages through HTTP intercept - #35850
Conversation
HybridWebView generally expects all hosted code to be local app content,
so this is general hardening -- it's just for correctness, not security
enforcement.
The Android handler used WebView.AddJavascriptInterface("hybridWebViewHost")
to expose HybridWebViewJavaScriptInterface.SendMessage to JavaScript.
addJavascriptInterface injects the bridge into every frame in the WebView
regardless of origin, and @JavascriptInterface does not surface the calling
frame to the C# side, so any non-app-origin sub-frame loaded by the page
can reach it.
Route JS->.NET messages through the same HTTP intercept channel that
__hwvInvokeDotNet already uses. The endpoint is gated by HasExpectedHeaders
(browser-set Origin/Referer) and the X-Maui-Invoke-Token custom header
that forces a CORS preflight on cross-origin callers, which we never
satisfy.
- Remove AddJavascriptInterface and HybridWebViewJavaScriptInterface
- Add __hwvSendMessage path handled in MauiHybridWebViewClient
- Extract TryValidateBridgeRequest helper shared with __hwvInvokeDotNet
- HybridWebView.ts/.js: send via fetch() chained on a single promise to
preserve FIFO ordering callers had with the synchronous bridge
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
|
🚀 Dogfood this PR with:
curl -fsSL https://raw.githubusercontent.com/dotnet/maui/main/eng/scripts/get-maui-pr.sh | bash -s -- 35850Or
iex "& { $(irm https://raw.githubusercontent.com/dotnet/maui/main/eng/scripts/get-maui-pr.ps1) } 35850" |
The TS lib used by the in-CI Microsoft.TypeScript.MSBuild target does not expose Promise as a runtime value, so 'let sendQueue: Promise<unknown> = Promise.resolve()' failed with TS2585. Initialize the queue lazily and seed it from the first fetch() call instead -- same FIFO behavior, no Promise constructor reference. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
|
/azp run maui-pr-devicetests |
|
/azp run maui-pr-uitests |
|
Azure Pipelines successfully started running 1 pipeline(s). |
|
/azp run maui-pr-uitests |
|
Azure Pipelines successfully started running 1 pipeline(s). |
|
/azp run maui-pr-devicetests |
|
Azure Pipelines successfully started running 1 pipeline(s). |
There was a problem hiding this comment.
Pull request overview
This PR hardens Android HybridWebView by removing the AddJavascriptInterface JavaScript bridge and routing JS → .NET messages through the existing HTTP interception mechanism, so requests can be validated (origin/referer + token headers) before being handled.
Changes:
- Add a dedicated
__hwvSendMessageintercept endpoint on Android and share request validation logic with__hwvInvokeDotNet. - Remove the Android
AddJavascriptInterface-based bridge (window.hybridWebViewHost) from the handler. - Update
HybridWebView.ts/ generatedHybridWebView.jsto send messages viafetch()with a FIFO promise chain.
Reviewed changes
Copilot reviewed 5 out of 5 changed files in this pull request and generated 3 comments.
Show a summary per file
| File | Description |
|---|---|
| src/Core/src/Platform/Android/MauiHybridWebViewClient.cs | Adds __hwvSendMessage handling and extracts shared request validation for bridge endpoints. |
| src/Core/src/Handlers/HybridWebView/HybridWebViewHandler.cs | Introduces SendMessagePath constant for the new Android bridge endpoint. |
| src/Core/src/Handlers/HybridWebView/HybridWebViewHandler.Android.cs | Removes AddJavascriptInterface bridge and relies on HTTP intercept for JS → .NET messaging. |
| src/Core/src/Handlers/HybridWebView/HybridWebView.ts | Switches Android message sending to fetch() and introduces endpoint constants. |
| src/Core/src/Handlers/HybridWebView/HybridWebView.js | Generated output reflecting the updated Android fetch()-based message sending. |
…essages for Android header transport - Remove dead 'window.hybridWebViewHost' interface declaration and the matching else-if branches in HostApp fixture HTMLs (the host JS interface was removed by this PR). - URL-encode the raw message string in HybridWebView.ts/js sendRawMessage and decode it in HybridWebViewHelper.ProcessRawMessage. Android's fetch transport puts the body in the X-Maui-Request-Body header, which rejects CR/LF/NUL bytes; encoding only the __RawMessage payload (not the __hwvInvokeDotNet wire format) keeps the fix targeted and symmetric across platforms. - Add SendRawMessageRoundTripsSpecialCharacters theory covering newline, CR/LF, NUL, '%', and non-ASCII / emoji to lock in the round-trip. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
|
/azp run maui-pr-uitests |
|
/azp run maui-pr-devicetests |
|
Azure Pipelines successfully started running 1 pipeline(s). |
1 similar comment
|
Azure Pipelines successfully started running 1 pipeline(s). |
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
Tests Failure Analysis
Test Failure Review: Needs human investigation - click to expandOverall verdict: Needs human investigation Four failures are pre-existing PublicAPI errors (RS0016/RS0017/RS0025) confirmed on the base branch, and the Android SDK Coverage: 49 checks · 6 passing/neutral · 43 failing · 0 pending · 0 inaccessible · 1 unmapped · 53 unexplained build legs · 0 unaccounted failing checks · 0 aborted failing checks · 0 canceled-build checks · 2 device-test unverified · 10 unattributed · 0 regressed-vs-base. Deterministic ceiling: Needs human investigation — 1 failing check has no inspectable AzDO build evidence (Build Analysis); 53 failed build legs produced no extractable failure (Integration Tests, Helix, Build Microsoft.Maui.sln); 10 failures could not be attributed deterministically; 2 device-test checks read GREEN but Failed==0 could not be positively confirmed.
Recommended actionA human should open the 53 unexplained build legs (Integration Tests, Helix, Build) and confirm whether they are pre-existing on the base branch. The 4 pre-existing PublicAPI failures (RS0016/RS0017/RS0025) and the Android SDK provisioning failures appear unrelated to this PR's HybridWebView Android bridge routing changes; if the unexplained legs and indeterminate failures are confirmed as pre-existing base-branch issues, the PR may be a merge candidate once those base issues are resolved. Evidence detailsPR builds: maui-pr 1483665 · maui-pr-devicetests 1483668 · maui-pr-uitests 1483667 Base builds used for comparison (all failed; base branch is actively broken):
Pre-existing failures confirmed on base (4 distinct test+platform entries):
Android SDK provisioning (indeterminate, 4 platform groups): The Windows device-test build errors (indeterminate): Unexplained build legs (53): Legs including Unverified device-test green checks: Unmapped check: Scope note: This PR touches HybridWebView Android bridge routing (11 changed files, 6 test files, Limitation: No AzDO authentication token was available; authenticated test-run APIs were skipped. Build metadata, timelines, and logs were queried as public. Baseline inspection was capped at the first 8 failed logs per build, so the baseline failure list may be incomplete. |
|
Note 🔍 AI-generated comment — this CI failure triage was produced by an AI agent (GitHub Copilot CLI) on request. Please verify before acting. CI failure triage — failures are unrelated to this PRAll three pipelines are red, but the redness is inherited from a broken
Evidence
Context
Bottom lineNo failure is attributable to this PR. The red checks are gated entirely by the broken base branch (the — Generated by GitHub Copilot CLI 🔍 |
…35850) > HybridWebView generally expects all hosted code to be local app content, so this is general hardening — it's just for correctness, not security enforcement. ## Background The Android handler used `WebView.AddJavascriptInterface("hybridWebViewHost")` to expose `HybridWebViewJavaScriptInterface.SendMessage` to JavaScript. `addJavascriptInterface` injects the bridge into **every frame** in the WebView regardless of origin, and `@JavascriptInterface` does not surface the calling frame to the C# side, so any non-app-origin sub-frame loaded by the page can reach it. ## Fix Route JS → .NET messages through the same HTTP intercept channel that `__hwvInvokeDotNet` already uses. The endpoint is gated by: - `HybridWebViewHandler.HasExpectedHeaders` (browser-set `Origin` / `Referer`), and - the `X-Maui-Invoke-Token` custom header, which forces a CORS preflight on cross-origin callers — and we never return permissive CORS headers. After this change, every JS → .NET call on Android flows through one validated entry point: | JS API | Endpoint | Validated by | |---|---|---| | `HybridWebView.SendRawMessage(...)` | `__hwvSendMessage` | `TryValidateBridgeRequest` | | `HybridWebView.InvokeDotNet(...)` | `__hwvInvokeDotNet` | `TryValidateBridgeRequest` | | `__InvokeJavaScriptCompleted` / `__InvokeJavaScriptFailed` callbacks | `__hwvSendMessage` | `TryValidateBridgeRequest` | `window.hybridWebViewHost` no longer exists. ## Changes - Remove `AddJavascriptInterface` and the `HybridWebViewJavaScriptInterface` inner class. - Add `__hwvSendMessage` path handled in `MauiHybridWebViewClient`. - Extract `TryValidateBridgeRequest` helper shared with `__hwvInvokeDotNet` (same 400/405 gate). - `HybridWebView.ts` / `HybridWebView.js`: send via `fetch()` chained on a single promise to preserve the FIFO ordering callers had with the synchronous bridge. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --------- Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…35850) > HybridWebView generally expects all hosted code to be local app content, so this is general hardening — it's just for correctness, not security enforcement. ## Background The Android handler used `WebView.AddJavascriptInterface("hybridWebViewHost")` to expose `HybridWebViewJavaScriptInterface.SendMessage` to JavaScript. `addJavascriptInterface` injects the bridge into **every frame** in the WebView regardless of origin, and `@JavascriptInterface` does not surface the calling frame to the C# side, so any non-app-origin sub-frame loaded by the page can reach it. ## Fix Route JS → .NET messages through the same HTTP intercept channel that `__hwvInvokeDotNet` already uses. The endpoint is gated by: - `HybridWebViewHandler.HasExpectedHeaders` (browser-set `Origin` / `Referer`), and - the `X-Maui-Invoke-Token` custom header, which forces a CORS preflight on cross-origin callers — and we never return permissive CORS headers. After this change, every JS → .NET call on Android flows through one validated entry point: | JS API | Endpoint | Validated by | |---|---|---| | `HybridWebView.SendRawMessage(...)` | `__hwvSendMessage` | `TryValidateBridgeRequest` | | `HybridWebView.InvokeDotNet(...)` | `__hwvInvokeDotNet` | `TryValidateBridgeRequest` | | `__InvokeJavaScriptCompleted` / `__InvokeJavaScriptFailed` callbacks | `__hwvSendMessage` | `TryValidateBridgeRequest` | `window.hybridWebViewHost` no longer exists. ## Changes - Remove `AddJavascriptInterface` and the `HybridWebViewJavaScriptInterface` inner class. - Add `__hwvSendMessage` path handled in `MauiHybridWebViewClient`. - Extract `TryValidateBridgeRequest` helper shared with `__hwvInvokeDotNet` (same 400/405 gate). - `HybridWebView.ts` / `HybridWebView.js`: send via `fetch()` chained on a single promise to preserve the FIFO ordering callers had with the synchronous bridge. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --------- Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…35850) > HybridWebView generally expects all hosted code to be local app content, so this is general hardening — it's just for correctness, not security enforcement. ## Background The Android handler used `WebView.AddJavascriptInterface("hybridWebViewHost")` to expose `HybridWebViewJavaScriptInterface.SendMessage` to JavaScript. `addJavascriptInterface` injects the bridge into **every frame** in the WebView regardless of origin, and `@JavascriptInterface` does not surface the calling frame to the C# side, so any non-app-origin sub-frame loaded by the page can reach it. ## Fix Route JS → .NET messages through the same HTTP intercept channel that `__hwvInvokeDotNet` already uses. The endpoint is gated by: - `HybridWebViewHandler.HasExpectedHeaders` (browser-set `Origin` / `Referer`), and - the `X-Maui-Invoke-Token` custom header, which forces a CORS preflight on cross-origin callers — and we never return permissive CORS headers. After this change, every JS → .NET call on Android flows through one validated entry point: | JS API | Endpoint | Validated by | |---|---|---| | `HybridWebView.SendRawMessage(...)` | `__hwvSendMessage` | `TryValidateBridgeRequest` | | `HybridWebView.InvokeDotNet(...)` | `__hwvInvokeDotNet` | `TryValidateBridgeRequest` | | `__InvokeJavaScriptCompleted` / `__InvokeJavaScriptFailed` callbacks | `__hwvSendMessage` | `TryValidateBridgeRequest` | `window.hybridWebViewHost` no longer exists. ## Changes - Remove `AddJavascriptInterface` and the `HybridWebViewJavaScriptInterface` inner class. - Add `__hwvSendMessage` path handled in `MauiHybridWebViewClient`. - Extract `TryValidateBridgeRequest` helper shared with `__hwvInvokeDotNet` (same 400/405 gate). - `HybridWebView.ts` / `HybridWebView.js`: send via `fetch()` chained on a single promise to preserve the FIFO ordering callers had with the synchronous bridge. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --------- Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…35850) > HybridWebView generally expects all hosted code to be local app content, so this is general hardening — it's just for correctness, not security enforcement. ## Background The Android handler used `WebView.AddJavascriptInterface("hybridWebViewHost")` to expose `HybridWebViewJavaScriptInterface.SendMessage` to JavaScript. `addJavascriptInterface` injects the bridge into **every frame** in the WebView regardless of origin, and `@JavascriptInterface` does not surface the calling frame to the C# side, so any non-app-origin sub-frame loaded by the page can reach it. ## Fix Route JS → .NET messages through the same HTTP intercept channel that `__hwvInvokeDotNet` already uses. The endpoint is gated by: - `HybridWebViewHandler.HasExpectedHeaders` (browser-set `Origin` / `Referer`), and - the `X-Maui-Invoke-Token` custom header, which forces a CORS preflight on cross-origin callers — and we never return permissive CORS headers. After this change, every JS → .NET call on Android flows through one validated entry point: | JS API | Endpoint | Validated by | |---|---|---| | `HybridWebView.SendRawMessage(...)` | `__hwvSendMessage` | `TryValidateBridgeRequest` | | `HybridWebView.InvokeDotNet(...)` | `__hwvInvokeDotNet` | `TryValidateBridgeRequest` | | `__InvokeJavaScriptCompleted` / `__InvokeJavaScriptFailed` callbacks | `__hwvSendMessage` | `TryValidateBridgeRequest` | `window.hybridWebViewHost` no longer exists. ## Changes - Remove `AddJavascriptInterface` and the `HybridWebViewJavaScriptInterface` inner class. - Add `__hwvSendMessage` path handled in `MauiHybridWebViewClient`. - Extract `TryValidateBridgeRequest` helper shared with `__hwvInvokeDotNet` (same 400/405 gate). - `HybridWebView.ts` / `HybridWebView.js`: send via `fetch()` chained on a single promise to preserve the FIFO ordering callers had with the synchronous bridge. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --------- Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…35850) > HybridWebView generally expects all hosted code to be local app content, so this is general hardening — it's just for correctness, not security enforcement. ## Background The Android handler used `WebView.AddJavascriptInterface("hybridWebViewHost")` to expose `HybridWebViewJavaScriptInterface.SendMessage` to JavaScript. `addJavascriptInterface` injects the bridge into **every frame** in the WebView regardless of origin, and `@JavascriptInterface` does not surface the calling frame to the C# side, so any non-app-origin sub-frame loaded by the page can reach it. ## Fix Route JS → .NET messages through the same HTTP intercept channel that `__hwvInvokeDotNet` already uses. The endpoint is gated by: - `HybridWebViewHandler.HasExpectedHeaders` (browser-set `Origin` / `Referer`), and - the `X-Maui-Invoke-Token` custom header, which forces a CORS preflight on cross-origin callers — and we never return permissive CORS headers. After this change, every JS → .NET call on Android flows through one validated entry point: | JS API | Endpoint | Validated by | |---|---|---| | `HybridWebView.SendRawMessage(...)` | `__hwvSendMessage` | `TryValidateBridgeRequest` | | `HybridWebView.InvokeDotNet(...)` | `__hwvInvokeDotNet` | `TryValidateBridgeRequest` | | `__InvokeJavaScriptCompleted` / `__InvokeJavaScriptFailed` callbacks | `__hwvSendMessage` | `TryValidateBridgeRequest` | `window.hybridWebViewHost` no longer exists. ## Changes - Remove `AddJavascriptInterface` and the `HybridWebViewJavaScriptInterface` inner class. - Add `__hwvSendMessage` path handled in `MauiHybridWebViewClient`. - Extract `TryValidateBridgeRequest` helper shared with `__hwvInvokeDotNet` (same 400/405 gate). - `HybridWebView.ts` / `HybridWebView.js`: send via `fetch()` chained on a single promise to preserve the FIFO ordering callers had with the synchronous bridge. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --------- Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…35850) > HybridWebView generally expects all hosted code to be local app content, so this is general hardening — it's just for correctness, not security enforcement. ## Background The Android handler used `WebView.AddJavascriptInterface("hybridWebViewHost")` to expose `HybridWebViewJavaScriptInterface.SendMessage` to JavaScript. `addJavascriptInterface` injects the bridge into **every frame** in the WebView regardless of origin, and `@JavascriptInterface` does not surface the calling frame to the C# side, so any non-app-origin sub-frame loaded by the page can reach it. ## Fix Route JS → .NET messages through the same HTTP intercept channel that `__hwvInvokeDotNet` already uses. The endpoint is gated by: - `HybridWebViewHandler.HasExpectedHeaders` (browser-set `Origin` / `Referer`), and - the `X-Maui-Invoke-Token` custom header, which forces a CORS preflight on cross-origin callers — and we never return permissive CORS headers. After this change, every JS → .NET call on Android flows through one validated entry point: | JS API | Endpoint | Validated by | |---|---|---| | `HybridWebView.SendRawMessage(...)` | `__hwvSendMessage` | `TryValidateBridgeRequest` | | `HybridWebView.InvokeDotNet(...)` | `__hwvInvokeDotNet` | `TryValidateBridgeRequest` | | `__InvokeJavaScriptCompleted` / `__InvokeJavaScriptFailed` callbacks | `__hwvSendMessage` | `TryValidateBridgeRequest` | `window.hybridWebViewHost` no longer exists. ## Changes - Remove `AddJavascriptInterface` and the `HybridWebViewJavaScriptInterface` inner class. - Add `__hwvSendMessage` path handled in `MauiHybridWebViewClient`. - Extract `TryValidateBridgeRequest` helper shared with `__hwvInvokeDotNet` (same 400/405 gate). - `HybridWebView.ts` / `HybridWebView.js`: send via `fetch()` chained on a single promise to preserve the FIFO ordering callers had with the synchronous bridge. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --------- Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…st interception order and Windows NUL-character handling (#36544) ### Failed Test ### Android: 1. RequestsCanBeInterceptedAndCustomDataReturned 2. RequestsCanBeInterceptedAndAsyncCustomDataReturned ### Windows: 1. SendRawMessageRoundTripsSpecialCharacters [InlineData("with\0nul")] ### Root Cause ### Android – Request interception regression - PR #35850 replaced the native `window.hybridWebViewHost.sendMessage` bridge with an HTTP `fetch()`-based transport that communicates through reserved framework endpoints (`__hwvSendMessage` and `__hwvInvokeDotNet`), which are processed by `MauiHybridWebViewClient.ShouldInterceptRequest`. - With the bridge now using standard HTTP requests, `ShouldInterceptRequest` invoked the application's `WebResourceRequested` interception before handling the framework's own bridge requests. As a result, internal framework requests that were previously invisible to application code became exposed to custom request handlers. - Applications that assumed every intercepted request contained application-specific query parameters could throw exceptions (for example, `KeyNotFoundException`). Because these exceptions propagated across the JNI boundary on the WebView thread, they could terminate the application. ### Windows – Existing WebView2 NUL-character limitation HybridWebView communicates through WebView2's `PostWebMessageAsString` and `TryGetWebMessageAsString` APIs, which internally marshal null-terminated native strings (`LPCWSTR`/`LPWSTR`). Consequently, an embedded NUL (`\0`) character is treated as the end of the string, truncating any remaining content. This limitation existed before PR #35850 and was not introduced by the Android changes. However, the newly added `SendRawMessageRoundTripsSpecialCharacters` device test—specifically the `"with\0nul"` test case—exposed the issue. Other special-character scenarios (Unicode, `%`, and newline) continued to work correctly. ### Description of Change ### Android The Android bridge was updated to clearly separate framework-internal bridge traffic from application-defined request interception. - **Corrected request routing:** `ShouldInterceptRequest` now checks `IsFrameworkInternalRequest` before invoking the application's `WebResourceRequested` callback. Framework bridge requests are handled entirely within the framework and are no longer exposed to application interception, restoring the behavior that existed before PR #35850. - **Stronger framework request validation:** Internal requests are identified using the reserved bridge endpoint paths together with the expected `X-Maui-Invoke-Token` header where applicable, preventing unrelated application requests from being treated as framework traffic. - **Framework exception isolation:** Only the framework's internal `Handler.MessageReceived` dispatch is wrapped in a `try/catch`, ensuring unexpected framework exceptions cannot escape across the JNI boundary and crash the WebView thread. Application-defined request interception remains unchanged, preserving existing application behavior. - Common URI parsing and application-relative path resolution logic was consolidated into a shared `TryGetAppRelativePath` helper, reducing duplication and simplifying request processing. ### Windows The Windows fix applies URL encoding at the WebView2 transport boundary so embedded NUL characters survive native string marshalling. - **.NET → JavaScript:** Messages are encoded with `Uri.EscapeDataString` before calling `PostWebMessageAsString`, and JavaScript decodes them using `decodeURIComponent`. - **JavaScript → .NET:** JavaScript encodes outgoing messages with `encodeURIComponent`, and `OnWebMessageReceived` decodes them using `Uri.UnescapeDataString` before dispatching the message. - **Shared raw-message path:** The existing shared raw-message pipeline continues to perform a single decode in `HybridWebViewHandler.MessageReceived` via `Uri.UnescapeDataString`, while `sendRawMessage` now URL-encodes its payload with `encodeURIComponent`. This preserves embedded NUL characters and other special characters without changing the shared handler logic or the overall JavaScript bridge design. ### Validated the behaviour in the following platforms - [x] Android - [x] Windows - [ ] iOS - [ ] Mac ### Issues Fixed: Fixes #36469 ### Screenshots | Before | After | |---------|--------| | <video src="https://github.com/user-attachments/assets/ceac9179-e177-4597-b98f-377b6c60d8c8"> | <video src="https://github.com/user-attachments/assets/a2b5387e-a0a4-4af4-9331-8c51d2f04178"> |
…35850) > HybridWebView generally expects all hosted code to be local app content, so this is general hardening — it's just for correctness, not security enforcement. ## Background The Android handler used `WebView.AddJavascriptInterface("hybridWebViewHost")` to expose `HybridWebViewJavaScriptInterface.SendMessage` to JavaScript. `addJavascriptInterface` injects the bridge into **every frame** in the WebView regardless of origin, and `@JavascriptInterface` does not surface the calling frame to the C# side, so any non-app-origin sub-frame loaded by the page can reach it. ## Fix Route JS → .NET messages through the same HTTP intercept channel that `__hwvInvokeDotNet` already uses. The endpoint is gated by: - `HybridWebViewHandler.HasExpectedHeaders` (browser-set `Origin` / `Referer`), and - the `X-Maui-Invoke-Token` custom header, which forces a CORS preflight on cross-origin callers — and we never return permissive CORS headers. After this change, every JS → .NET call on Android flows through one validated entry point: | JS API | Endpoint | Validated by | |---|---|---| | `HybridWebView.SendRawMessage(...)` | `__hwvSendMessage` | `TryValidateBridgeRequest` | | `HybridWebView.InvokeDotNet(...)` | `__hwvInvokeDotNet` | `TryValidateBridgeRequest` | | `__InvokeJavaScriptCompleted` / `__InvokeJavaScriptFailed` callbacks | `__hwvSendMessage` | `TryValidateBridgeRequest` | `window.hybridWebViewHost` no longer exists. ## Changes - Remove `AddJavascriptInterface` and the `HybridWebViewJavaScriptInterface` inner class. - Add `__hwvSendMessage` path handled in `MauiHybridWebViewClient`. - Extract `TryValidateBridgeRequest` helper shared with `__hwvInvokeDotNet` (same 400/405 gate). - `HybridWebView.ts` / `HybridWebView.js`: send via `fetch()` chained on a single promise to preserve the FIFO ordering callers had with the synchronous bridge. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --------- Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…35850) > HybridWebView generally expects all hosted code to be local app content, so this is general hardening — it's just for correctness, not security enforcement. ## Background The Android handler used `WebView.AddJavascriptInterface("hybridWebViewHost")` to expose `HybridWebViewJavaScriptInterface.SendMessage` to JavaScript. `addJavascriptInterface` injects the bridge into **every frame** in the WebView regardless of origin, and `@JavascriptInterface` does not surface the calling frame to the C# side, so any non-app-origin sub-frame loaded by the page can reach it. ## Fix Route JS → .NET messages through the same HTTP intercept channel that `__hwvInvokeDotNet` already uses. The endpoint is gated by: - `HybridWebViewHandler.HasExpectedHeaders` (browser-set `Origin` / `Referer`), and - the `X-Maui-Invoke-Token` custom header, which forces a CORS preflight on cross-origin callers — and we never return permissive CORS headers. After this change, every JS → .NET call on Android flows through one validated entry point: | JS API | Endpoint | Validated by | |---|---|---| | `HybridWebView.SendRawMessage(...)` | `__hwvSendMessage` | `TryValidateBridgeRequest` | | `HybridWebView.InvokeDotNet(...)` | `__hwvInvokeDotNet` | `TryValidateBridgeRequest` | | `__InvokeJavaScriptCompleted` / `__InvokeJavaScriptFailed` callbacks | `__hwvSendMessage` | `TryValidateBridgeRequest` | `window.hybridWebViewHost` no longer exists. ## Changes - Remove `AddJavascriptInterface` and the `HybridWebViewJavaScriptInterface` inner class. - Add `__hwvSendMessage` path handled in `MauiHybridWebViewClient`. - Extract `TryValidateBridgeRequest` helper shared with `__hwvInvokeDotNet` (same 400/405 gate). - `HybridWebView.ts` / `HybridWebView.js`: send via `fetch()` chained on a single promise to preserve the FIFO ordering callers had with the synchronous bridge. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --------- Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…35850) > HybridWebView generally expects all hosted code to be local app content, so this is general hardening — it's just for correctness, not security enforcement. ## Background The Android handler used `WebView.AddJavascriptInterface("hybridWebViewHost")` to expose `HybridWebViewJavaScriptInterface.SendMessage` to JavaScript. `addJavascriptInterface` injects the bridge into **every frame** in the WebView regardless of origin, and `@JavascriptInterface` does not surface the calling frame to the C# side, so any non-app-origin sub-frame loaded by the page can reach it. ## Fix Route JS → .NET messages through the same HTTP intercept channel that `__hwvInvokeDotNet` already uses. The endpoint is gated by: - `HybridWebViewHandler.HasExpectedHeaders` (browser-set `Origin` / `Referer`), and - the `X-Maui-Invoke-Token` custom header, which forces a CORS preflight on cross-origin callers — and we never return permissive CORS headers. After this change, every JS → .NET call on Android flows through one validated entry point: | JS API | Endpoint | Validated by | |---|---|---| | `HybridWebView.SendRawMessage(...)` | `__hwvSendMessage` | `TryValidateBridgeRequest` | | `HybridWebView.InvokeDotNet(...)` | `__hwvInvokeDotNet` | `TryValidateBridgeRequest` | | `__InvokeJavaScriptCompleted` / `__InvokeJavaScriptFailed` callbacks | `__hwvSendMessage` | `TryValidateBridgeRequest` | `window.hybridWebViewHost` no longer exists. ## Changes - Remove `AddJavascriptInterface` and the `HybridWebViewJavaScriptInterface` inner class. - Add `__hwvSendMessage` path handled in `MauiHybridWebViewClient`. - Extract `TryValidateBridgeRequest` helper shared with `__hwvInvokeDotNet` (same 400/405 gate). - `HybridWebView.ts` / `HybridWebView.js`: send via `fetch()` chained on a single promise to preserve the FIFO ordering callers had with the synchronous bridge. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --------- Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Background
The Android handler used
WebView.AddJavascriptInterface("hybridWebViewHost")to exposeHybridWebViewJavaScriptInterface.SendMessageto JavaScript.addJavascriptInterfaceinjects the bridge into every frame in the WebView regardless of origin, and@JavascriptInterfacedoes not surface the calling frame to the C# side, so any non-app-origin sub-frame loaded by the page can reach it.Fix
Route JS → .NET messages through the same HTTP intercept channel that
__hwvInvokeDotNetalready uses. The endpoint is gated by:HybridWebViewHandler.HasExpectedHeaders(browser-setOrigin/Referer), andX-Maui-Invoke-Tokencustom header, which forces a CORS preflight on cross-origin callers — and we never return permissive CORS headers.After this change, every JS → .NET call on Android flows through one validated entry point:
HybridWebView.SendRawMessage(...)__hwvSendMessageTryValidateBridgeRequestHybridWebView.InvokeDotNet(...)__hwvInvokeDotNetTryValidateBridgeRequest__InvokeJavaScriptCompleted/__InvokeJavaScriptFailedcallbacks__hwvSendMessageTryValidateBridgeRequestwindow.hybridWebViewHostno longer exists.Changes
AddJavascriptInterfaceand theHybridWebViewJavaScriptInterfaceinner class.__hwvSendMessagepath handled inMauiHybridWebViewClient.TryValidateBridgeRequesthelper shared with__hwvInvokeDotNet(same 400/405 gate).HybridWebView.ts/HybridWebView.js: send viafetch()chained on a single promise to preserve the FIFO ordering callers had with the synchronous bridge.Co-authored-by: Copilot 223556219+Copilot@users.noreply.github.com