JavaScript interop
The WebView2 control offers three complementary bridges between twinBASIC and the JavaScript running in the page:
- Host objects — publish a BASIC COM object to the page so JavaScript can call its methods and read its properties as if it were any other JS object.
- Messages — push a value (string, number, array, …) in either direction and listen for it on the other side.
- Scripted calls — call a named JavaScript function from BASIC and (optionally) wait for its return value.
This tutorial covers all three, with the matching JavaScript side shown next to each BASIC side. The worked code comes from Sample 0 — WebView2 Examples (form Example 2).
Bridge 1 — Host objects
AddObject publishes a BASIC class instance under chrome.webview.hostObjects.<Name>. Define a small class with public methods or properties:
Class MyCalculator
Public Function MultiplyByTen(ByVal Value As Long) As Long
Return Value * 10
End Function
End Class
Register it once the control is ready:
Private Sub WebView_Ready() Handles WebView.Ready
WebView.AddObject "myCalculator", New MyCalculator
End Sub
JavaScript can now call into it — but the proxy is asynchronous, so the call must be awaited inside an async function:
async function testHostCalculator() {
let value = Math.floor(Math.random() * 100000);
let result = await chrome.webview.hostObjects.myCalculator.MultiplyByTen(value);
alert(`BASIC said ${value} × 10 = ${result}`);
}
To trigger the JS function from BASIC, call ExecuteScript:
Private Sub btnTest_Click() Handles btnTest.Click
WebView.ExecuteScript("testHostCalculator()")
End Sub
Requires AreHostObjectsAllowed (default True). See Re-entrancy for the trade-off between synchronous calls (default) and the UseDeferredInvoke:=True variant.
Bridge 2 — Messages
Messages are values that travel in either direction. Use them for notifications and ad-hoc payloads where you don’t want to define a method signature ahead of time.
BASIC → page
PostWebMessage sends a value to the page; the page receives it through a message event on window.chrome.webview:
WebView.PostWebMessage "Hello from twinBASIC!"
window.chrome.webview.addEventListener('message', (e) => {
alert("Host sent: " + e.data);
});
Strings arrive as JavaScript strings; every other type is JSON-encoded before transit. If you already have serialised JSON, PostWebMessageJSON sends it through verbatim.
Page → BASIC
The page calls window.chrome.webview.postMessage(value); BASIC receives it as the JsMessage event:
function sendHostAMessage() {
window.chrome.webview.postMessage("This is a message from JavaScript.");
}
Private Sub WebView_JsMessage(ByVal Message As Variant) _
Handles WebView.JsMessage
Debug.Print "Page sent: "; Message
End Sub
Both directions require IsWebMessageEnabled (default True).
Bridge 3 — Scripted calls
When the page exposes named JS functions, BASIC can call them directly. There are three variants:
| Method | Returns | Use it when |
|---|---|---|
| JsRun | Variant, synchronously | You need the result inline and the JS is quick. |
| JsRunAsync | LongLong token; result via JsAsyncResult | The JS may take a while and you don’t want to block the UI. |
| ExecuteScript | nothing (fire-and-forget) | You just want to trigger something — no return value needed. |
JsRun (synchronous)
Given a page-side function:
function multiplyTheseNumbers(a, b) {
return a * b;
}
BASIC can call it and read the result on the same line:
Dim product As Long = WebView.JsRun("multiplyTheseNumbers", 5, 6)
Debug.Print product ' 30
The call blocks for up to JsCallTimeOutSeconds (default 0 — wait forever).
JsRunAsync (asynchronous)
Private Sub btnRun_Click() Handles btnRun.Click
WebView.JsRunAsync "multiplyTheseNumbers", 5, 6
End Sub
Private Sub WebView_JsAsyncResult( _
ByVal Result As Variant, Token As LongLong, ErrString As String) _
Handles WebView.JsAsyncResult
If LenB(ErrString) = 0 Then
Debug.Print "Async result: "; Result
Else
Debug.Print "Async error: "; ErrString
End If
End Sub
The return value of JsRunAsync is a token; the JsAsyncResult event carries the same token so a single handler can demultiplex multiple in-flight calls.
ExecuteScript (fire-and-forget)
WebView.ExecuteScript "startTimer()"
No return value, no event. The simplest way to nudge the page into doing something.
Re-entrancy
The Edge runtime forbids host code from calling back into the WebView2 object model while a host-object method is still executing — re-entry deadlocks the browser process. The control protects most events by deferring them through the BASIC message loop (UseDeferredEvents), but host-object method calls are synchronous by default.
The full discussion lives in the Re-entrancy tutorial; the short summary is:
AddObject(name, obj)— synchronous calls; the page can read return values but the BASIC method must not call back into the WebView2 control.AddObject(name, obj, UseDeferredInvoke:=True)— asynchronous calls; the BASIC method is free to call any WebView2 member but the page cannot read a return value.
Where next
- Hosting local web assets — bundle and serve the JavaScript that talks to the host.
- Driving Monaco from twinBASIC — a full case study using all three bridges.
- Re-entrancy — the deeper story behind UseDeferredInvoke.
- WebView2 reference — every property, method, and event.