CreateLifecycle and Browser Creation
Create browser. Returns index (1, 2, 3…) or 0 on error.
idx = pool.Create(hwndParent, url, x, y, width, height)Dim idx As Long
idx = pool.Create(hwndParent, url, x, y, width, height)WebView2 browser control for Windows · VBA · VB6 · Access · Excel
Status: Hello from VBA!
m_lv.AddHostObjectToScript "vba", Mem_lv.ExecuteScript "...status..."LiteView2 is a WebView2-based ActiveX/COM control for VBA, VB6, Microsoft Access and Excel. It embeds a real Edge/Chromium browser inside a form, and lets your VBA code and the page's JavaScript call each other directly — in either a Registered ActiveX deployment or a Registration-Free pool.
This Cookbook has three parts:
New to LiteView2? Start with LiteView2 in 5 Minutes.
Part I — Learn LiteView2
Part II — Complete API Reference
Part III — Troubleshooting and Limits
Part I
LiteView2 puts a real web browser (WebView2, the same engine behind modern Edge) inside a VBA form, and lets your VBA code and the page's JavaScript call each other directly. This chapter builds the smallest complete example of that: an Access form that shows a local HTML page, pushes text into that page from VBA, and receives a callback from the page's JavaScript. By the end you will have both directions working.
A form named frmCustomerDashboard with a LiteView2Ctrl ActiveX control on it, showing a local HTML page called index.html. The page has one button. Clicking it calls into VBA; VBA responds by rewriting a line of text on the page.
Drop a LiteView2Ctrl ActiveX control on the form (its default name is LiteView2Ctrl1), then go to Tools → References and check "LiteView2 1.0 Type Library". In the form's code module:
Option Explicit
Private m_lv As LiteView2Lib.LiteView2Ctrl
Private Sub Form_Load()
Set m_lv = Me.LiteView2Ctrl1.Object
End SubMe.LiteView2Ctrl1.Object gives you a typed reference to the control that IntelliSense understands from here on.
Note
LiteView2 runs on an automatic trial with no license key required for anything in this chapter. Chapter 21 covers activation for production use.
Form_Load above only grabs the reference — it doesn't navigate anywhere. That's deliberate: the underlying browser isn't ready the instant the control is created. Add a handler for the control's WebViewReady event:
Private Sub LiteView2Ctrl1_WebViewReady()
End SubCaution
Everything that talks to the page — SetLocalContentRoot, Navigate, AddHostObjectToScript, and the rest — belongs inside WebViewReady (or later), never in Form_Load. Form_Load only guarantees you have a control reference, not a working browser. Two calls belong in Form_Load, after EnsureLicensed: EnableLvFacade and AddScriptToExecuteOnDocumentCreated. Before the browser exists they are queued and applied before the first page. Inside WebViewReady they fail at once with &H80040203.
SetLocalContentRoot maps a real folder on disk to a virtual hostname, lv2.example. Navigate then loads a page from that virtual host by URL:
Private Sub LiteView2Ctrl1_WebViewReady()
m_lv.SetLocalContentRoot CurrentProject.Path & "\html"
m_lv.Navigate "https://lv2.example/index.html"
End SubThis expects an html subfolder next to your database, containing index.html.
See Part II → Content & Navigation for the full set of navigation options.
Save this as html\index.html in your Access project folder:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Customer Dashboard</title>
</head>
<body>
<h1>Customer Dashboard</h1>
<p id="status">Waiting for VBA...</p>
<button onclick="notifyVBA()">Tell VBA hello</button>
<script>
// "vba" matches the first argument of AddHostObjectToScript in VBA.
// Calling sync.vba.<name>() invokes a Public Sub on the VBA form.
function notifyVBA() {
try {
window.chrome.webview.hostObjects.sync.vba.OnReady();
} catch (e) {
document.getElementById('status').textContent = 'Bridge error: ' + e.message;
}
}
</script>
</body>
</html>Run the form now (with just Steps 1-3 wired up) and you should see the "Customer Dashboard" heading and the "Waiting for VBA..." line. The button won't do anything yet — Step 6 wires it up. First, VBA needs a way to change what's on screen.
ExecuteScript runs a line of JavaScript inside the page and hands back the result as a JSON-encoded string. You can call it from ordinary code (a button, a timer) or from NavigationCompleted once the page has loaded. Do not call it inside WebViewReady, or inside a method the page is calling (Step 6): there it fails at once with error &H80040203. This one-line call reaches into the page and rewrites the status text:
m_lv.ExecuteScript "document.getElementById('status').textContent = 'Hello from VBA!';"We're calling it as a statement and ignoring the return value here, since we only care about the side effect on the page. (If you did capture the return value — say, from m_lv.ExecuteScript("document.title") — you'd get back "Customer Dashboard", quotes included, because the result is always JSON-encoded.)
In our example the page calls VBA when the button is clicked (Step 6). Inside such a call the page is waiting for VBA, so a synchronous ExecuteScript is refused there. Step 6 therefore sends the same line with ExecuteScriptAsync, which queues it and returns at once.
See Part II → Scripting & Data Exchange for ExecuteScript with functions, arguments, and structured return data.
Now the other direction: let JavaScript call VBA. Two lines make that possible — AreHostObjectsAllowed = True, and AddHostObjectToScript "vba", Me, which registers the form itself (Me) as a COM object the page can reach under the name "vba". Host objects only bind on the next navigation, so they must be registered before Navigate runs — add them to WebViewReady in this order (map the content root, then register the host object, then navigate):
Private Sub LiteView2Ctrl1_WebViewReady()
m_lv.SetLocalContentRoot CurrentProject.Path & "\html"
m_lv.AreHostObjectsAllowed = True
m_lv.AddHostObjectToScript "vba", Me
m_lv.Navigate "https://lv2.example/index.html"
End SubOn the page side, the button's notifyVBA() function (Step 4) already calls window.chrome.webview.hostObjects.sync.vba.OnReady(). "vba" is the name we just registered, and OnReady must be a Public Sub on the form:
Public Sub OnReady()
m_lv.ExecuteScriptAsync "document.getElementById('status').textContent = 'Hello from VBA!';"
End SubThat's the Step 5 line, sent with ExecuteScriptAsync: VBA is answering a call from the page, and ExecuteScript would fail there with &H80040203. It now runs every time the page tells VBA the button was clicked.
See Part II → Host Objects & the JavaScript Bridge for exposing more than one method, passing arguments, and returning values back to JavaScript.
The complete form module:
Option Explicit
Private m_lv As LiteView2Lib.LiteView2Ctrl
Private Sub Form_Load()
Set m_lv = Me.LiteView2Ctrl1.Object
End Sub
Private Sub LiteView2Ctrl1_WebViewReady()
m_lv.SetLocalContentRoot CurrentProject.Path & "\html"
m_lv.AreHostObjectsAllowed = True
m_lv.AddHostObjectToScript "vba", Me
m_lv.Navigate "https://lv2.example/index.html"
End Sub
Public Sub OnReady()
m_lv.ExecuteScriptAsync "document.getElementById('status').textContent = 'Hello from VBA!';"
End SubTogether with html\index.html from Step 4, that's the whole example.
Open frmCustomerDashboard. The page loads showing "Customer Dashboard" and "Waiting for VBA...". Click Tell VBA hello. The status line changes to "Hello from VBA!". Under the hood: the button's JavaScript called vba.OnReady() across the bridge, VBA's OnReady ran and sent the line with ExecuteScriptAsync; the page applies it as soon as OnReady returns.
You now have a local HTML page running inside an Access form, with VBA and JavaScript each able to call the other.
Registration-Free deployment skips the ActiveX control and drives the same browser through a pool object instead of m_lv; Chapter 17 covers that deployment model.
Chapter 1 got a LiteView2 control on a form and something on screen. This chapter turns that into a form you could actually ship: a typed reference so the VBA editor gives you IntelliSense, license activation in the right place, the ready-event ordering that real pages depend on, and a shutdown routine that doesn't leave anything behind.
The running example for this chapter is frmCustomerDashboard — an Access form with a LiteView2Ctrl1 control that loads a local HTML page, lets the user edit a customer's notes, and saves them back through VBA. Everything here is Registered Mode (the ActiveX control dropped straight onto the form). Registration-Free deployment uses the pool equivalent of every call shown in this chapter; Chapter 17 covers that model in full.
Me.LiteView2Ctrl1Type Me.LiteView2Ctrl1. in the VBA editor and Access's autocomplete shows you Name, Top, Left, Visible — the generic properties every ActiveX control has. It does not show Navigate, ActivateLicense, SetLocalContentRoot, or any of LiteView2's other members. This isn't a LiteView2 limitation — Access wraps every ActiveX control dropped on a form in a generic Control object. At runtime that wrapper forwards your calls to the real control through IDispatch::Invoke, but its type information doesn't include the control's actual members, so the editor has nothing to show you. The legacy WebBrowser control and the Microsoft DatePicker control have the exact same gap.
The fix is a module-level variable typed as the real control class, populated from .Object:
Option Explicit
' Typed reference — gives IntelliSense everywhere it's used.
Private m_lv As LiteView2Lib.LiteView2Ctrl
Private Sub Form_Load()
' .Object peels off Access's generic wrapper and returns the underlying
' ActiveX object, which is what the LiteView2Ctrl type expects. Without
' .Object the assignment raises "Type mismatch" — Access's wrapper is
' not QueryInterface-compatible with the LiteView2Ctrl coclass.
Set m_lv = Me.LiteView2Ctrl1.Object
End SubFrom this point on, m_lv. gives full IntelliSense — every method, property, and enum in the LiteView2 type library.
One-time setup, once per Access project:
liteview2-setup.zip from https://www.imranosoft.com/products/liteview2/#downloads); it installs and registers both the 32-bit and the 64-bit control. Setup can safely migrate earlier customer releases that were installed manually with regsvr32. For Registration-Free deployment there is nothing to register: put LiteView2_x64.ocx and LiteView2_x86.ocx beside the database or workbook (liteview2-regfree.zip); Chapter 17 covers that model.Late-bound still works without this. If you're distributing a database to users who may not have set up references, Private m_lv As Object instead of Private m_lv As LiteView2Lib.LiteView2Ctrl runs exactly the same way — m_lv.Navigate "..." still resolves through IDispatch::Invoke at runtime. You only lose IntelliSense while writing the code; nothing breaks for the end user.
The control's WebView2 engine starts up asynchronously — it isn't ready the instant the control is created, even though Form_Load has already run by then. Code that touches the page or the browser session before it's ready either does nothing, races the initialization, or leaves a stuck license watermark. So the rule is a hard split:
Form_Load does exactly two things: get the typed reference, and activate the license.LiteView2Ctrl1_WebViewReady — fired once the browser engine is actually up — does everything else: setting the content root, registering host objects, and the first Navigate.' ❌ WRONG — WebView2 isn't ready yet when Form_Load runs.
Private Sub Form_Load()
Set m_lv = Me.LiteView2Ctrl1.Object
m_lv.SetLocalContentRoot CurrentProject.Path ' too early
m_lv.AddHostObjectToScript "vba", Me ' too early
m_lv.Navigate "https://lv2.example/index.html" ' too early
End Sub' ✅ CORRECT — Form_Load only gets the reference and activates the license.
Private Sub Form_Load()
Set m_lv = Me.LiteView2Ctrl1.Object
EnsureLicensed m_lv
End Sub
Private Sub LiteView2Ctrl1_WebViewReady()
m_lv.SetLocalContentRoot CurrentProject.Path
m_lv.AreHostObjectsAllowed = True
m_lv.AddHostObjectToScript "vba", Me
m_lv.Navigate "https://lv2.example/customer_dashboard.html"
End SubForm_LoadPut the activation key in exactly one place — a shared module, never in per-form code — so rotating the key later means editing one file instead of every form in the database. Create a new standard module named modLV2License:
' ===== modLV2License — START =================================================
Option Explicit
' Edit LV2_COMPANY and LV2_KEY ONCE here. Forms call EnsureLicensed.
' Leave LV2_KEY empty to keep the 30-day Trial Period.
Private Const LV2_COMPANY As String = "Your Company"
Private Const LV2_KEY As String = "" ' "V1|..."
Private m_activated As Boolean
' Activate the runtime license against any LiteView2 control instance.
' Safe to call from every form's Form_Load — only the first call does
' the work; later calls are a no-op. Activation is process-wide.
Public Sub EnsureLicensed(ByVal ctrl As Object)
If m_activated Then Exit Sub
If ctrl Is Nothing Then Exit Sub
If Len(LV2_KEY) = 0 Then Exit Sub
ctrl.ActivateLicense LV2_COMPANY, LV2_KEY
m_activated = True
End Sub
' ===== modLV2License — END ===================================================Every form calls EnsureLicensed m_lv in Form_Load — before the first Navigate, never from WebViewReady. By the time WebViewReady fires, the control has already used the license state to decide how to navigate, and activating after Navigate risks leaving a trial watermark already rendered on the page. EnsureLicensed is idempotent — it checks its own flag — so calling it from every form's Form_Load is safe; only the first call in the process does anything. Full licensing detail — trial behavior, key formats, what happens if activation fails — is Chapter 21.
WebViewReady: content root, then host objects, then NavigateInside the ready handler, order matters: SetLocalContentRoot first, then AreHostObjectsAllowed / AddHostObjectToScript, then Navigate last. Any host object you want the page to call must be registered before the Navigate that loads the page which calls it — a page can only reach host objects that already existed at load time.
This is the full pattern the rest of Part I builds on. Everything above is assembled here in one place.
' ============================================================================
' Registered Mode — canonical lifecycle template (per form)
' ============================================================================
' Form prerequisite:
' - A LiteView2Ctrl ActiveX named LiteView2Ctrl1 (Access's default name)
'
' Project prerequisites:
' - modLV2License module added to the project (holds the activation key)
' - Tools -> References -> check "LiteView2 1.0 Type Library"
'
' Lifecycle rules this template follows:
' 1. Form_Load: the typed reference, EnsureLicensed, and (if you use
' them) EnableLvFacade / AddScriptToExecuteOnDocumentCreated —
' queued until the browser is ready.
' 2. SetLocalContentRoot, host objects, and the first Navigate go in
' the WebViewReady event.
' 3. Host objects must be added BEFORE the Navigate that uses them.
' 4. Set m_lv = Nothing in Form_Close. No explicit teardown needed.
'
' When you add buttons that the user may click while focus is inside the
' browser, mirror Click into MouseDown — see the button-click section
' below.
' ============================================================================
Option Compare Database
Option Explicit
' Typed reference. The "As LiteView2Lib.LiteView2Ctrl" gives IntelliSense
' on every m_lv.* call. ".Object" in Form_Load below peels Access's wrapper
' so the assignment is type-compatible; without it, Set raises Type mismatch.
Private m_lv As LiteView2Lib.LiteView2Ctrl
Private Sub Form_Load()
On Error GoTo ErrH
Set m_lv = Me.LiteView2Ctrl1.Object
' Single call -- activation key lives in modLV2License.bas.
' Safe to call from every form: only the first call does real work.
EnsureLicensed m_lv
Exit Sub
ErrH:
MsgBox "Form_Load: " & Err.Number & " " & Err.Description, vbCritical
End Sub
Private Sub LiteView2Ctrl1_WebViewReady()
On Error GoTo ErrH
' Order matters: content root first, then host objects, then Navigate.
m_lv.SetLocalContentRoot CurrentProject.Path
m_lv.AreHostObjectsAllowed = True
m_lv.AddHostObjectToScript "vba", Me
m_lv.Navigate "https://lv2.example/customer_dashboard.html"
Exit Sub
ErrH:
MsgBox "WebViewReady: " & Err.Number & " " & Err.Description, vbCritical
End Sub
' Called by the page via window.chrome.webview.hostObjects.sync.vba.OnReady()
' This runs INSIDE the page's call: the page waits until it returns. Calls that
' wait for the browser (ExecuteScript, GetCookies, CallScriptSync, ...) fail here
' at once with &H80040203. Note that the page is ready, or push data with
' ExecuteScriptAsync, and return. Post-load work that needs ExecuteScript belongs
' in LiteView2Ctrl1_NavigationCompleted, which runs after WebView2's callback.
Public Sub OnReady()
' Page is ready. Push the customer's notes with ExecuteScriptAsync, e.g.
' m_lv.ExecuteScriptAsync "loadNotes(" & notesJson & ");"
' (ExecuteScript would fail here with &H80040203: the page is waiting for this call.)
End Sub
' Called by the page's saveContent() via the same "vba" host object.
Public Sub SaveCustomer(ByVal CustomerName As String, ByVal Notes As String)
' TODO: write CustomerName / Notes back to tblCustomers.
End Sub
Private Sub Form_Close()
On Error Resume Next
If Not m_lv Is Nothing Then
m_lv.Stop ' halt any in-flight navigation before teardown
Set m_lv = Nothing ' release the reference; Access destroys the control next
End If
End SubWhat should happen: open frmCustomerDashboard in Access and the LiteView2 control area is blank for a moment while the browser engine starts, then customer_dashboard.html loads and renders. Nothing you typed in Form_Load or WebViewReady throws an error, the page can call back into OnReady, and closing the form leaves no error dialogs and no lingering process.
customer_dashboard.htmlThe smallest page that exercises the round trip — it announces itself ready, and exposes a saveContent() function that VBA triggers via ExecuteScriptAsync:
<!DOCTYPE html>
<html>
<head><meta charset="utf-8"></head>
<body>
<input id="txtName" placeholder="Customer name">
<textarea id="txtNotes" placeholder="Notes"></textarea>
<div id="divResults"></div>
<script>
function saveContent() {
const name = document.getElementById('txtName').value;
const notes = document.getElementById('txtNotes').value;
window.chrome.webview.hostObjects.sync.vba.SaveCustomer(name, notes);
document.getElementById('divResults').textContent = 'Saved.';
}
window.addEventListener('DOMContentLoaded', () => {
window.chrome.webview.hostObjects.sync.vba.OnReady();
});
</script>
</body>
</html>OnReady fires on page load through the vba host object registered in WebViewReady; SaveCustomer fires later, whenever the Access side calls m_lv.ExecuteScriptAsync "saveContent()" — see the button pattern next.
Form_Close above does two things and swallows errors on purpose:
m_lv.Stop halts any navigation still in flight. Without it, a page that's mid-load when the user closes the form can fire NavigationCompleted against a control that's already being torn down.Set m_lv = Nothing releases your reference; Access then destroys the ActiveX control as part of normal form teardown, and LiteView2's own destructor drains pending callbacks and releases the WebView2 engine. Registered Mode does not need any further teardown call — that's specific to the Reg-Free pool, covered in Chapter 17.On Error Resume Next is there because Form_Close can run while the form is already unloading due to an earlier error. A second error popping a MsgBox during shutdown produces a form the user can't cleanly dismiss. Genuine bugs are still caught earlier, by the On Error GoTo ErrH blocks in Form_Load and WebViewReady.Access moves keyboard focus to a button on the first click when focus was previously inside an embedded ActiveX control — the LiteView2 control, the legacy WebBrowser control, Microsoft DatePicker, all of them. On that first click, the button's Click event does not fire at all; the user has to click a second time before anything happens. This is long-standing Access behavior, not something LiteView2 does.
In practice: a user types into the page inside LiteView2, clicks a "Save" button on the Access form — nothing happens. They click again, and the save runs. Customers report this as "the button only works on the second click."
The fix is to mirror Click into MouseDown. MouseDown fires the instant the mouse button goes down regardless of focus state, so the action runs on the first physical click. Keep Click too, so keyboard activation (Enter / Spacebar) still works:
Private Sub cmdSave_Click()
DoSave ' the actual work
End Sub
Private Sub cmdSave_MouseDown(Button As Integer, Shift As Integer, _
X As Single, Y As Single)
If Button = 1 Then DoSave ' left mouse button only
End Sub
Private Sub DoSave()
If m_lv Is Nothing Then Exit Sub
m_lv.ExecuteScriptAsync "saveContent()"
End SubApply this pattern to any form button the user might click while focus is inside the LiteView2 control. Buttons triggered only from VBA code (for example cmdSave.Value = True) don't need it.
See Part II → Lifecycle & Teardown for the full Registered-vs-Reg-Free timeline comparison, and Part II → Licensing for every ActivateLicense edge case (failed activation, trial expiry behavior, multi-form timing).
Every recipe in this chapter answers the same underlying question: how do you get content into the LiteView2 control? There are three sources — a local file on disk, a remote URL, and an HTML string you build yourself in VBA — and a handful of methods for moving between pages once something is loaded.
The examples below assume m_lv is already a valid, initialized LiteView2Lib.LiteView2Ctrl reference on your form, as set up in Chapter 2. Registration-Free mode reaches the same operations through a pool object instead of a control reference — pool (As Object, from GetPool()) and idx (As Long, the browser index) in place of m_lv. Chapter 17 covers that deployment model in full; this chapter stays in Registered mode and flags the handful of places where the Reg-Free call shape genuinely differs.
file:// paths the way a browser add-ressbar does; you map a folder to an https:// hostname first (SetLocalContentRoot or SetVirtualHostNameToFolderMapping), then Navigate to a URL under that hostname. This keeps relative links, fetch/XHR calls, and script behavior working the same way they would on a real website.Navigate a normal https:// address directly; no mapping needed.NavigateToString loads HTML you assembled in code, with no file on disk at all. Useful for generated reports.The rest of this chapter works through each of these as a concrete scenario.
Say your Access application ships an html subfolder next to the database, containing a small dashboard that summarizes today's orders. SetLocalContentRoot maps a folder to the fixed virtual hostname https://lv2.example/; you then Navigate to a file under it.
Private Sub LiteView2Ctrl1_WebViewReady()
m_lv.SetLocalContentRoot CurrentProject.Path & "\html"
m_lv.Navigate "https://lv2.example/dashboard.html"
End SubAs established in Chapter 2, this belongs in WebViewReady, not Form_Load — Form_Load only guarantees a control reference, not a working browser.
CurrentProject.Path & "\html" becomes the URL root, so a file at ...\html\dashboard.html on disk is reached at https://lv2.example/dashboard.html — there is no /html/ segment in the URL, because the html folder itself is the root once mapped.
A minimal dashboard.html to go with it:
<!DOCTYPE html>
<html>
<head><meta charset="utf-8"><title>Order Dashboard</title></head>
<body>
<h1>Today's Orders</h1>
<div id="divResults-2">Loading…</div>
</body>
</html>What should happen: the control shows dashboard.html, and m_lv.DocumentTitle reads "Order Dashboard" once the page finishes loading.
The one detail worth internalizing here is that the mapping is one-to-one: whatever folder you pass to SetLocalContentRoot becomes the URL root, full stop.
| Disk path you pass | URL root | Example file on disk | URL to navigate to |
|---|---|---|---|
C:\Apps\MyDB\ | https://lv2.example/ | C:\Apps\MyDB\index.html | https://lv2.example/index.html |
C:\Apps\MyDB\ | https://lv2.example/ | C:\Apps\MyDB\pages\admin.html | https://lv2.example/pages/admin.html |
CurrentProject.Path | https://lv2.example/ | any file in that folder | https://lv2.example/<file> |
C:\Apps\ but your HTML actually lives in C:\Apps\MyDB\index.html. The URL then has to be https://lv2.example/MyDB/index.html, not /index.html. Map the folder that directly contains your HTML — don't map a parent folder "to be safe."CurrentProject.Path & "\html" and then navigate to https://lv2.example/html/index.html. That's wrong — the \html folder already became the root, so the correct URL is https://lv2.example/index.html with no html prefix. This is the single most common mistake with this call.https://lv2.example/index.html works; https://lv2.example\index.html does not. URLs use forward slashes, even though the folder you mapped uses backslashes.index.html doesn't actually exist in the mapped folder. WebView2 shows its own "page can't be reached" error, which looks like a LiteView2 problem but isn't — check the file exists before assuming the control is broken.Caution
all four mistakes above produce the same symptom — a blank control or a browser error page — with no LiteView2-specific error to point you at the cause. When a local page won't load, check the mapping and the file path before anything else.
Registration-Free has the same call on the pool — pool.SetLocalContentRoot idx, folder — with idx as the leading browser-index argument. Chapter 17 covers the pool workflow in full.
See Part II → Navigation for NavigateToLocalFile, a shorthand for navigating to a file already under your SetLocalContentRoot mapping without building the https://lv2.example/... URL yourself — you still call SetLocalContentRoot first (mentioned again in §3.9).
A multi-screen local UI is just more files under the same mapped root, with Navigate called again each time the user moves to a different screen. Extend the dashboard example with a Customers screen and an Orders screen in the same html folder:
Private Sub cmdCustomers_Click()
m_lv.Navigate "https://lv2.example/customers.html"
End Sub
Private Sub cmdOrders_Click()
m_lv.Navigate "https://lv2.example/orders.html"
End SubBecause customers.html and orders.html sit under the same root you mapped in WebViewReady, no re-mapping is needed — Navigate alone moves between them. Navigate doesn't care whether the target is local or remote; it's the same call either way, which is what makes switching between a local screen and a remote one (§3.4) a one-line change.
See Part II → Navigation for the full Navigate signature.
To load a hosted application — a CRM, an internal reporting portal — Navigate an https:// URL directly. No content-root mapping applies to remote addresses; that mechanism only exists to give local files an https://-shaped address to be loaded from.
Private Sub cmdOpenCRM_Click()
m_lv.Navigate "https://crm.mycompany.com/dashboard"
End SubWhat should happen: the control requests the remote page over the network and renders whatever it returns, exactly as a browser tab would.
SetVirtualHostNameToFolderMappingSetLocalContentRoot always maps to the fixed hostname lv2.example. If you want your own hostname instead — for example to keep a second, independent content area (say, exported report files) separate from your main app's lv2.example mapping — call SetVirtualHostNameToFolderMapping directly:
m_lv.SetVirtualHostNameToFolderMapping "myapp.local", _
CurrentProject.Path & "\reports", 1
m_lv.Navigate "https://myapp.local/quarterly-summary.html"The folder-to-URL mapping rules and the same four common mistakes from §3.2 apply here too — only the hostname changes. To remove a mapping later, ClearVirtualHostNameToFolderMapping exists (mentioned in §3.9; not demoed here).
Reg-Free:
pool.SetVirtualHostNameToFolderMapping idx, "myapp.local", _
CurrentProject.Path & "\reports", 1
pool.Navigate idx, "https://myapp.local/quarterly-summary.html"See Part II → Navigation for the meaning of the trailing 1 parameter and the rest of the signature.
NavigateToStringSometimes there's no HTML file at all — you want to generate a report on the fly from data already in your Access database and show it in the control without writing anything to disk. NavigateToString takes an HTML string and loads it directly.
Private Sub ShowOrderReport(customerID As Long)
' Call from a command button's Click event, e.g.:
' Private Sub cmdShowOrderReport_Click()
' ShowOrderReport Me.txtCustomerID.Value
' End Sub
Dim rs As DAO.Recordset
Dim html As String
Set rs = CurrentDb.OpenRecordset( _
"SELECT OrderID, OrderDate, Total FROM tblOrders " & _
"WHERE CustomerID = " & customerID)
html = "<html><body><h1>Order Report</h1><table border=""1"">" & _
"<tr><th>Order ID</th><th>Date</th><th>Total</th></tr>"
Do While Not rs.EOF
html = html & "<tr><td>" & rs!OrderID & "</td><td>" & _
rs!OrderDate & "</td><td>" & rs!Total & "</td></tr>"
rs.MoveNext
Loop
html = html & "</table></body></html>"
rs.Close
m_lv.NavigateToString html
End SubWhat should happen: the control immediately shows a formatted table of that customer's orders, built entirely in memory — no dashboard.html-style file, and no SetLocalContentRoot call needed for this particular screen.
Reg-Free: pool.NavigateToString idx, html — same string, same shape, no meaningful difference beyond the pool/idx pattern already noted.
See Part II → Navigation for NavigateToStringSync, the blocking variant (mentioned in §3.9).
Once a user has moved through a few pages — local or remote — the usual browser-style navigation controls are available: GoBack, GoForward, Refresh, Stop. GoBack and GoForward should be guarded by their corresponding CanGoBack / CanGoForward checks, since there may be nothing to go back or forward to.
Private Sub cmdBack_Click()
If m_lv.CanGoBack Then m_lv.GoBack
End Sub
Private Sub cmdForward_Click()
If m_lv.CanGoForward Then m_lv.GoForward
End Sub
Private Sub cmdRefresh_Click()
m_lv.Refresh
End Sub
Private Sub cmdStop_Click()
m_lv.Stop
End SubCaution
always check CanGoBack / CanGoForward before calling GoBack / GoForward. There's no history to go back to on the first page a user visits, or forward to until they've gone back at least once.
Reg-Free: the same four calls, indexed — pool.CanGoBack(idx), pool.GoBack idx, pool.Refresh idx, pool.Stop idx.
See Part II → Navigation for full details.
Url, DocumentTitle, IsLoadingThree read-oriented properties round out this chapter — useful for a status bar, a window caption, or a simple loading indicator.
' Update a status label and the form caption after navigating
lblStatus.Caption = "Viewing: " & m_lv.Url
Me.Caption = "MyApp — " & m_lv.DocumentTitle
' Check whether a page is still loading
If m_lv.IsLoading Then
Debug.Print "Still loading..."
End IfUrl is also a settable property in Registered mode — m_lv.Url = "https://example.com" navigates, the same as calling Navigate. DocumentTitle and IsLoading are read-only.
Reg-Free: pool.GetUrl(idx), pool.GetTitle(idx), and pool.IsLoading(idx) read the same three values. There is no Url property setter in the pool — call pool.Navigate idx, url instead of assigning to it.
See Part II → Navigation for the complete property list.
A few more navigation members exist in the API beyond what this chapter demos. They're worth knowing by name now; Part II → Navigation covers their full parameters and behavior:
NavigateSync / NavigateToStringSync — block the calling VBA code (messages keep pumping) until the page finishes loading or SynchronousTimeOut expires. Call them from ordinary code; inside WebViewReady, NavigationStarting or a host-object method they fail at once (&H80040203) and start no navigation. Known issue in 1.0.1.0: the wait can end when another navigation completes, so use Navigate + NavigationCompleted when navigations can overlap.NavigateToLocalFile — a shorthand for navigating to a file already under your SetLocalContentRoot mapping, without building the https://lv2.example/... URL yourself.ClearVirtualHostNameToFolderMapping — removes a hostname mapping created with SetVirtualHostNameToFolderMapping.AddCustomSchemeRegistration — registers a custom URI scheme (e.g. myapp://) rather than mapping a folder to https://.SynchronousTimeOut — the timeout, in milliseconds (default 30000), that ExecuteScript (with everything built on it), ExecuteScriptWithResult, the three *Sync navigation methods and WaitForElement (when you pass 0 for its timeoutMs) wait before giving up.Custom navigation with a specific HTTP method, POST body, or headers — NavigateWithWebResourceRequest, CreateWebResourceRequest, NavigateWithCustomRequest, and related members — also exists, but it's tied up with authentication scenarios and is covered in full in Chapter 14 (Authentication and Custom Web Requests) rather than here.
The LiteView2 control shows a web page, but the page is not just a display surface. Once it is loaded, your VBA code can run JavaScript inside it and read values back out of it. This chapter teaches the four calls that cover almost every real scenario — ExecuteScriptAsync, ExecuteScript, ExecuteScriptWithResult, and ExecuteScriptWithArgs — plus brief pointers to a handful of more specialized members. Part II → Script Execution has the complete, exhaustive reference for all of them.
All examples assume a form-level control declared and already navigated to a page:
Dim m_lv As LiteView2Lib.LiteView2CtrlThe running example is a Customers dashboard page, dashboard.html, loaded into m_lv. It has a status line, two KPI counters, a small form, and a placeholder chart object:
<!-- dashboard.html (excerpt) -->
<div id="divResults-3"></div>
<div id="kpiCustomers">0</div>
<div id="kpiOrders">0</div>
<input id="txtName-2" value="John Doe" />
<input id="txtEmail" value="john@example.com" />
<script>
function updateStatus(msg) {
document.getElementById('divResults').textContent = msg;
}
function loadKpis(customerCount, orderCount) {
document.getElementById('kpiCustomers').textContent = customerCount;
document.getElementById('kpiOrders').textContent = orderCount;
}
function getFormData() {
return {
name: document.getElementById('txtName').value,
email: document.getElementById('txtEmail').value
};
}
window.myChart = {
update: function (data) {
console.log('chart updated with', data);
}
};
</script>Every recipe below calls into this same page.
LiteView2 gives you three different ways to run JavaScript, and picking the right one matters:
ExecuteScriptAsync) — you tell the page to do something and do not wait for or care about a result. Cheapest option, use it for anything that just updates the DOM or calls a void function.ExecuteScript, ExecuteScriptWithResult) — you run an arbitrary JavaScript expression or statement and get a JSON-encoded string back, optionally with a success flag and an error message if the script threw. They wait for the page's answer, so inside WebViewReady, other inline events and host-object methods they fail at once with &H80040203; see "Getting a result where synchronous calls are refused" below.ExecuteScriptWithArgs) — instead of building a JavaScript string yourself, you name a function and let LiteView2 handle the calling convention; VBA values (like Array(100, 50)) are auto-serialized for you. Two more specialized, Registered-mode-only helpers with a similar goal (CallJsFunction, SetJsVariable) exist for a dot-path/JSON-string calling convention — mentioned briefly later in this chapter, covered in full in Part II.The rest of this chapter works through the four main tools in that order, then briefly names a few more members you'll meet later.
Use this when you want the page to do something and you do not need a result back — updating a status line, toggling a CSS class, calling a void JS function.
m_lv.ExecuteScriptAsync "updateStatus('Processing...');"
m_lv.ExecuteScriptAsync "document.getElementById('divResults').textContent = 'Updated';"What happens: the JavaScript runs in the page; your VBA code moves on immediately without waiting for it to finish and without receiving any value back.
Registration-Free uses the pool equivalent — pool.ExecuteScriptAsync idx, script — where pool is the object returned by GetPool() and idx is the browser index; Chapter 17 covers that deployment model.
See Part II → Script Execution for the full member reference.
Use this when you need a value out of the page — reading document.title, calling a function and reading its return value.
Dim result As String
result = m_lv.ExecuteScript("document.title")
Debug.Print "Title: " & result ' "My Page Title"
result = m_lv.ExecuteScript("getFormData()")
Debug.Print result ' {"name":"John Doe","email":"john@example.com"}What happens: the script runs synchronously and the JavaScript result is returned as a JSON-encoded string. Note the first example: the returned VBA string literally contains the surrounding quote characters, because a JavaScript string is JSON-encoded as a quoted string. If you only want the raw text, strip the quotes yourself or run the result through a JSON parser — LiteView2 hands you the JSON text, not a parsed value.
ExecuteScript waits at most SynchronousTimeOut ms (default 30000; 0 also means 30000). On a timeout it raises &H800705B4 (-2147023436) and sets LastErrorCode; the page may still run the script later. Inside WebViewReady, NavigationStarting, the other inline events and host-object methods it raises &H80040203 at once and runs nothing. Use ExecuteScriptAsync there. It works inside NavigationCompleted, DOMContentLoaded, WebMessageReceived and the other notification events. Before the browser exists it fails: it is not queued (ExecuteScriptAsync is).
Caution
ExecuteScript does not tell you whether the script threw an exception — a failed script and a script that legitimately returns an empty result can be hard to tell apart. If you need to know whether the call actually succeeded, use ExecuteScriptWithResult below.
See Part II → Script Execution for the full member reference.
ExecuteScript and ExecuteScriptWithResult cannot run inside WebViewReady, NavigationStarting and the other inline events, or inside a VBA method the page is calling: they fail there with &H80040203. If the page is calling you, have it pass the values as arguments. Otherwise send the script with ExecuteScriptAsync and let the page post the answer back:
' Inside an inline event:
m_lv.ExecuteScriptAsync "chrome.webview.postMessage(JSON.stringify({action:'title', value: document.title}));"
' The answer arrives here a moment later (Registered mode):
Private Sub LiteView2Ctrl1_WebMessageReceived(ByVal message As String, ByVal source As String)
If m_lv.JsonGetValue(message, "action") = "title" Then Debug.Print m_lv.JsonGetValue(message, "value")
End SubIn Reg-Free mode the answer goes to the macro you registered with pool.SetWebMessageCallback idx, Application, "MacroName". Everywhere else (buttons, timers, NavigationCompleted, WebMessageReceived) just use ExecuteScript.
Use this when the script might fail and you need to know about it — instead of just getting a string back, you get a result, a success flag, and an exception message.
Dim resultJson As String, succeeded As Boolean, errMsg As String
m_lv.ExecuteScriptWithResult "1 + 2", resultJson, succeeded, errMsg
If succeeded Then
Debug.Print "Result: " & resultJson ' "3"
Else
Debug.Print "Error: " & errMsg
End IfWhat happens: the script runs synchronously; resultJson receives the JSON-encoded result exactly as ExecuteScript would, succeeded tells you whether it ran without throwing, and errMsg carries the exception text when it did not.
ExecuteScriptWithResult waits at most SynchronousTimeOut (0 means 30000). A timeout raises no error. Instead it returns succeeded = False, puts "ExecuteScriptWithResult got no result from the page within … ms (SynchronousTimeOut)…" in errMsg, and sets LastErrorCode = &H800705B4. So succeeded = False does not always mean the script threw. Inside inline events and host-object methods it raises &H80040203 (it does not return succeeded = False) and runs nothing.
See Part II → Script Execution for the full member reference.
Use this when you want to call a specific JS function with values that already exist as VBA variables, without hand-building a JavaScript call string and worrying about quoting.
Dim result As String
result = m_lv.ExecuteScriptWithArgs("loadKpis", Array(100, 50))
' Calls loadKpis(100, 50) in the pageWhat happens: LiteView2 serializes the VBA array of arguments and calls loadKpis with them — loadKpis(100, 50), matching the function signature in dashboard.html. Like ExecuteScript, the returned value is the function's JSON-encoded return value.
See Part II → Script Execution for the full member reference.
A handful of more specialized script members exist beyond the four above. They're worth knowing by name now; Part II → Script Execution covers their full parameters, quote-doubling details, and behavior:
CallJsFunction (Registered-mode only) — calls a JavaScript function by dotted path (e.g. window.myChart.update, reaching a function hanging off a nested object) with a JSON-encoded arguments array you supply yourself as a string, rather than a VBA array. In Reg-Free mode there's no direct equivalent — build the call as a string and send it with ExecuteScriptAsync instead (pool.ExecuteScriptAsync idx, "updateStatus('Hello from VBA');").SetJsVariable (Registered-mode only) — sets a JavaScript variable, given as a dotted path, to a JSON-encoded value you supply as a string. Reg-Free falls back the same way, building the assignment as a script string for ExecuteScriptAsync.AddScriptToExecuteOnDocumentCreated / RemoveScriptToExecuteOnDocumentCreated — registers a script that runs automatically on every future page navigation in this browser, returning a scriptId you can pass back to un-register it.
Caution
it does not run against the page already on screen — only on navigations that happen after you register it. Registering the script is not a substitute for running one-time setup script against a page you already have open; run that once yourself with ExecuteScriptAsync in addition to registering it.
Registered. Register document scripts before the browser exists, in Form_Load / UserForm_Initialize. There the call is queued, and the script is registered before WebViewReady fires and before the first Navigate, so it also runs on the first page. A queued call returns an empty scriptId, so there is no id to pass to RemoveScriptToExecuteOnDocumentCreated.
Once the browser exists, the call waits (up to 5 s) for WebView2 to confirm and returns the id. That works from ordinary code and from notification events, and affects later navigations only. Inside WebViewReady, other inline events and host-object methods it fails at once with &H80040203 and registers nothing.
Reg-Free. Call pool.AddScriptToExecuteOnDocumentCreated idx, script inside OnBrowserReady, before pool.Navigate. The ready callback runs from the message loop, so the call completes there.
IsScriptEnabled (Property) — Boolean read/write; turns JavaScript on or off for the browser. Reg-Free exposes GetScriptEnabled/SetScriptEnabled method calls instead of a property.| Need | Use |
|---|---|
| Run something and don't care about the result | ExecuteScriptAsync |
| Run an expression/statement and read the result | ExecuteScript |
| Same, but you need to know if it failed and why | ExecuteScriptWithResult |
| Call a known JS function with VBA values as arguments | ExecuteScriptWithArgs |
| Call a function by dotted path with JSON args (Registered only) | CallJsFunction |
| Set a JS variable to a JSON value (Registered only) | SetJsVariable |
| Run setup script on every future page load | AddScriptToExecuteOnDocumentCreated |
| Turn JavaScript on/off for the browser | IsScriptEnabled |
The previous chapter showed VBA driving the page — calling into JavaScript from your VBA code. This chapter is the other direction: JavaScript running inside the embedded page calling back into a Public method on your VBA code, as if the browser could dial your Access form or class module directly.
This is what makes LiteView2 useful for anything beyond a static display: an HTML page can have a "Save" button, a form, a grid — and the actual work (writing to a table, running a query, updating other controls on your Access form) still happens in VBA, where you already know how to do it. The page just needs a way to ask VBA to do it.
That mechanism is called a host object.
A form, frmOrders, hosts a LiteView2 control (m_lv) showing an order-entry page. The page has an order total field and a Save Order button. When the user clicks Save, JavaScript reads the field, calls a VBA method with the order ID and total, VBA writes the row and returns a confirmation string, and JavaScript displays it — all without leaving the page.
Exposing VBA to JavaScript takes three calls, and they have to happen in your ready handler, in this order, before you call Navigate. In Registered Mode that ready handler is the WebViewReady control event; in Reg-Free Mode it's a callback method you register yourself with SetReadyCallback (commonly named OnBrowserReady in this Cookbook's examples, but any Public Sub name works). These are two different mechanisms — an event versus a named callback you wire up — that play the same conceptual role in each deployment model; Chapter 17 covers the Reg-Free version in full. This chapter stays in Registered Mode, on the same Access form and LiteView2Ctrl1 control used in Chapters 1-4:
Private Sub LiteView2Ctrl1_WebViewReady()
m_lv.SetLocalContentRoot CurrentProject.Path & "\html"
m_lv.AreHostObjectsAllowed = True
Dim helper As New clsJsBridge
m_lv.AddHostObjectToScript "vba", helper
m_lv.Navigate "https://lv2.example/orders.html"
End Sub
Caution
— host objects only bind on the next navigation. AddHostObjectToScript has to run before the Navigate call that loads the page which needs it. If you call it after the page has already loaded — or after some other navigation has already happened — the page you're looking at right now won't see the object; it only takes effect starting with the navigation that follows. This is the third of the three setup rules that trip up most customers, and it's the one specific to host objects: SetLocalContentRoot → AreHostObjectsAllowed → AddHostObjectToScript → Navigate, always in that order, always before the page loads.
If you ever need to swap out or disable the bridge — for example, before re-navigating to a page that shouldn't have VBA access — remove it explicitly:
m_lv.RemoveHostObjectFromScript "vba"
Registration-Free uses the pool equivalent (pool.RemoveHostObjectFromScript idx, "vba"); Chapter 17 covers that deployment model.
AddHostObjectToScript actually registersAddHostObjectToScript takes a name (this is the name JavaScript will use to reach it) and a VBA object. Two things can play the role of that object:
Me) — any Public Sub or Public Function on the form becomes callable from JavaScript.clsJsBridge — keeps your bridge methods separate from form/control-event code, which is usually cleaner once you have more than one or two callable methods.Both are legitimate; the example above uses a clsJsBridge instance. If you only need one or two simple callbacks, exposing Me directly and giving the form a Public Sub works just as well — see the sync.vba.OnReady() example later in this chapter, where Me is the exposed object.
Registration-Free uses pool.SetAreHostObjectsAllowed idx, True and pool.AddHostObjectToScript idx, "vba", helper; Chapter 17 covers that deployment model.
AreHostObjectsAllowed is a plain Boolean read/write property — set it True once, before the first AddHostObjectToScript call, as shown above. See Part II → Host Object Bridging for the full property reference and the pool equivalent (GetAreHostObjectsAllowed / SetAreHostObjectsAllowed).
' clsJsBridge (class module)
Public Function SaveOrder(ByVal OrderID As Long, ByVal OrderTotal As Currency) As String
Dim db As DAO.Database
Set db = CurrentDb
db.Execute "UPDATE tblOrders SET OrderTotal = " & OrderTotal & _
" WHERE OrderID = " & OrderID
SaveOrder = "Order " & OrderID & " saved: $" & Format(OrderTotal, "0.00")
End Function
Nothing about this method is LiteView2-specific — it's an ordinary Public Function that does an ordinary DAO update. The only requirement is that it's Public and lives on the object you passed to AddHostObjectToScript.
WebView2 host objects can be called two ways, and both are valid — pick whichever fits the call.
Async (recommended for anything that returns a value or does real work):
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Order Entry</title>
</head>
<body>
<h1>New Order</h1>
<input type="hidden" id="orderId" value="4521">
<input type="number" id="orderTotal" step="0.01" value="129.99">
<button onclick="saveOrder()">Save Order</button>
<p id="status-2"></p>
<script>
async function saveOrder() {
const vba = chrome.webview.hostObjects.vba;
const id = Number(document.getElementById('orderId').value);
const total = Number(document.getElementById('orderTotal').value);
try {
const result = await vba.SaveOrder(id, total);
document.getElementById('status').textContent = result;
} catch (e) {
document.getElementById('status').textContent = 'Save failed: ' + e.message;
}
}
</script>
</body>
</html>
chrome.webview.hostObjects.vba gives you a reference to the object registered under the name "vba"; calling a method on it returns a JavaScript Promise, so you await it to get the return value (SaveOrder's confirmation string, in this case).
Sync (fire-and-forget, or when you don't want to deal with a Promise):
window.chrome.webview.hostObjects.sync.vba.OnReady();
The .sync accessor calls the VBA method and returns its result immediately instead of a Promise — useful for a quick notification like "the page finished loading" where you don't need async/await machinery around it. AddHostObjectToScript "vba", Me in the ready handler is what makes the form itself answer to sync.vba.OnReady(); the form would need a matching Public Sub OnReady().
The user opens the order page, types or edits the total, and clicks Save Order. JavaScript reads the two field values, calls vba.SaveOrder(id, total), and waits. VBA's SaveOrder function runs the UPDATE, builds a confirmation string, and returns it. The await resolves, and the page's status paragraph shows Order 4521 saved: $129.99. If the DAO call throws (bad SQL, locked table, whatever), the catch block in the JavaScript shows Save failed: ... instead — the error crosses the bridge as a JavaScript exception.
AreHostObjectsAllowed property reference (including the Reg-Free Get/Set pair), and additional RemoveHostObjectFromScript scenarios (rebinding a different bridge object mid-session, disabling the bridge before navigating to untrusted content).So far you've called methods on m_lv directly and let JavaScript run inside the page. That covers the "VBA drives the page" direction. This chapter covers the other channel: web messaging, a loose, event-style pipe between VBA and JavaScript that either side can use to notify the other without a direct method call.
Web messaging is different from a host object (a VBA/COM object you expose into the page so JavaScript can call its methods directly, typed argument by typed argument). A host object is a phone call — JavaScript dials a specific method and gets a specific answer. Web messaging is more like a pager — one side posts a message, the other side is listening and reacts if it cares to. You'll use web messaging for things like "tell the page a new order arrived" or "tell VBA the user clicked Save on the page," where you don't need a return value and don't want the two sides tightly coupled.
Both directions share one JavaScript entry point on the page: chrome.webview.addEventListener('message', function(event) { ... }). Everything VBA posts to the page — JSON or plain string — arrives through that same listener, in event.data.
Suppose frmDashboard hosts m_lv showing a live Orders dashboard page. When a new order is saved in Access, you want the page to update without VBA reaching into the DOM itself.
Use this when the page needs structured data.
' In frmDashboard, after saving a new order:
m_lv.PostWebMessageAsJson "{""type"":""orderCount"",""value"":42}"// dashboard.html
chrome.webview.addEventListener('message', function(event) {
var data = event.data; // {type: "orderCount", value: 42}
if (data.type === 'orderCount') {
document.getElementById('divStatus').textContent = 'Orders today: ' + data.value;
}
});What should happen: the moment PostWebMessageAsJson runs, the listener fires and divStatus updates — no page reload, no re-navigation.
Reg-Free uses the pool equivalent: pool.PostWebMessageAsJson idx, "{""type"":""orderCount"",""value"":42}". Chapter 17 covers that deployment model.
Use this when you just need to get a plain string across — a notice, a log line, a short instruction — without building JSON.
m_lv.PostWebMessageAsString "New order received!"chrome.webview.addEventListener('message', function(event) {
console.log(event.data); // "New order received!"
});The same listener receives both kinds of messages; check typeof event.data or the shape of the value if a page needs to tell JSON messages apart from plain strings.
Reg-Free uses the pool equivalent: pool.PostWebMessageAsString idx, "New order received!". Chapter 17 covers that deployment model.
See Part II -> Web Messaging for the full parameter reference.
The page can post back to VBA with chrome.webview.postMessage(...). How VBA picks that up depends on the mode.
Events fire in Registered Mode only. There is no equivalent event in Reg-Free mode — see the callback pattern below for that.
Private Sub LiteView2Ctrl1_WebMessageReceived(ByVal message As String, ByVal source As String)
' message is whatever the page passed to chrome.webview.postMessage(...)
' Raised after WebView2's callback has returned: JsonGetValue, ExecuteScript
' and the other synchronous calls work here (see 6.5).
End SubOn the page:
// User clicks Save on the Orders form embedded in the page
chrome.webview.postMessage(JSON.stringify({ action: 'refreshOrders' }));What should happen: saving on the page fires LiteView2Ctrl1_WebMessageReceived in VBA with that JSON string as message. Section 6.5 covers the one rule you need to follow before you act on it.
Reg-Free has no events, so you register a Public Sub by name instead. Like SetReadyCallback (Chapter 17), SetWebMessageCallback only registers where a future message should be routed — it doesn't touch the page or wait for one — so it's safe alongside the rest of the pool/idx setup in Form_Load, before the browser is ready:
' In Form_Load, alongside CreateInControl / SetReadyCallback:
pool.SetWebMessageCallback idx, Application, "CB_frmDashboard"
' In modLV2Callbacks.bas:
Public Sub CB_frmDashboard(ByVal message As String, Optional ByVal source As String = "")
If IsFormLoaded("frmDashboard") Then Forms("frmDashboard").HandleMessage message
End Sub
' In frmDashboard's module:
Public Sub HandleMessage(ByVal message As String)
If InStr(message, """action"":""refreshOrders""") > 0 Then
' requery the Orders list, etc.
End If
End SubDetach the callback when the form is done with it (Form_Unload is a typical place):
pool.ClearWebMessageCallback idxThe Registered-mode equivalent of clearing is m_lv.ClearWebMessageCallback, though there's rarely a reason to call it — you'd normally just stop reacting to the event instead of detaching it.
See Part II -> Web Messaging for the exact callback macro signature and dispatch rules.
IsWebMessageEnabled is a read/write Boolean — messaging can be turned off entirely if a page shouldn't be allowed to talk back to VBA.
' Registered:
Debug.Print "Web messaging: " & m_lv.IsWebMessageEnabled ' Get
m_lv.IsWebMessageEnabled = True ' Enable' Reg-Free:
Debug.Print "Web messaging: " & pool.GetWebMessageEnabled(idx)
pool.SetWebMessageEnabled idx, TrueTurn this off if a page shouldn't be allowed to exchange messages with VBA at all.
Note
WebMessageReceived is raised after WebView2's own callback has returned, so handle the message right there: JsonGetValue, ExecuteScript and PushRecordset all work inside it. Synchronous calls fail (error &H80040203) in two places only: inside inline events such as WebViewReady / NavigationStarting, and inside VBA methods the page is calling through a host object. Inside a host method, use only calls that do not wait (ExecuteScriptAsync, PostWebMessageAsJson, the JSON helpers, DAO). See Critical Safety Warnings #1.
' Fine: WebMessageReceived runs after WebView2's callback has returned.
Private Sub LiteView2Ctrl1_WebMessageReceived(ByVal message As String, ByVal source As String)
Dim orderId As Variant
orderId = m_lv.JsonGetValue(message, "orderId")
' ... act on orderId; ExecuteScript and the other synchronous calls work here too ...
End SubLiteView2 also offers a window.lv JavaScript facade (lv.call/lv.on/lv.send/lv.bind, Registered Mode only) built on top of this same messaging model. It's introduced properly in Part II once you're comfortable with the underlying messaging model covered in this chapter.
PostWebMessageAsJson, PostWebMessageAsString, SetWebMessageCallback/ClearWebMessageCallback and IsWebMessageEnabled.AddHostObjectToScript: let the page call VBA methods directly (inside them, use only calls that do not wait).EnableLvFacade, IsLvFacadeEnabled, and lv.call/lv.on/lv.send/lv.bind.Once a page is loaded in LiteView2, you can read and change it from VBA the same way JavaScript running inside the page could: read what a user typed into a field, fill in a form before submitting it, swap a block of HTML, check whether an element exists yet, tick a checkbox, pick a dropdown option, move focus, scroll something into view, or pull the whole page's HTML or text back into VBA.
Every method in this chapter addresses an element one of three ways: by its id attribute, by its name attribute, or by a CSS selector. id-based methods are the ones you'll reach for first, because most forms you build already give their fields unique IDs and the calls read almost like plain English (GetElementValueById, SetElementValueById). CSS selectors come in when an element doesn't have a convenient ID of its own — a particular row in a table, or a button that only makes sense in the context of one specific form. name-based methods cover the older HTML convention of identifying form fields by name instead of id.
The examples below use m_lv (As LiteView2Lib.LiteView2Ctrl), the Registered-mode variable used throughout Part I. Registration-Free deployments call the same methods on the pool object instead — pool.SetElementValueById idx, "txtName", "Jane Smith" instead of m_lv.SetElementValueById "txtName", "Jane Smith" — the method name and arguments are identical, just prefixed with the browser index. Chapter 17 covers the Reg-Free pool model in full; nothing further in this chapter calls that difference out again.
All examples reference cookbook_test.html, the test page shipped with LiteView2. It's a small customer-contact page: a name/email form, a results panel, a data table, a checkbox pair, a country dropdown, and a plain contact form further down the page. The excerpt below shows the elements this chapter's recipes touch — the shipped page has more on it than this, but these are the pieces you'll see reused example to example.
<h2 id="pageTitle">Cookbook Test Page</h2>
<input type="text" id="txtName-3" data-role="primary" value="John Doe">
<input type="email" id="txtEmail-2" value="">
<button id="btnSubmit" class="primary">Submit</button>
<div id="divResults-4">Results will appear here</div>
<div id="divStatus">ready</div>
<div id="errorDiv" class="error" style="display:none">Please correct the errors below.</div>
<table id="tblData">
<tr><th>Order</th><th>Total</th></tr>
<tr><td>1001</td><td>42.00</td></tr>
<tr><td>1002</td><td>17.50</td></tr>
</table>
<form id="frmContact">
<input type="text" id="frmName">
<textarea id="frmMsg"></textarea>
<button type="submit">Send</button>
</form>
<input type="checkbox" id="chkAgree">
<input type="checkbox" id="chkNewsletter">
<select id="ddlCountry">
<option value="US">United States</option>
<option value="DE">Germany</option>
</select>
<div id="scrollTarget">You've reached the bottom section.</div>
<!-- a simpler form addressed by "name" instead of "id" -->
<form>
<input type="text" name="userName" value="John Doe">
<input type="email" name="userEmail" value="john@example.com">
<input type="checkbox" name="agree">
</form>Goal: read what a user (or the page itself) put into a text field, and fill fields in from VBA — the two operations you'll use constantly when a page's form doubles as your data-entry screen.
Dim custName As String
custName = m_lv.GetElementValueById("txtName")
Debug.Print "Name: " & custName ' "Name: John Doe"
m_lv.SetElementValueById "txtName", "Jane Smith"
m_lv.SetElementValueById "txtEmail", "jane@example.com"What happens: GetElementValueById returns the current value of an input, textarea, or select element as a string. SetElementValueById writes a new value into it, exactly as if the user had typed it (or, for a <select>, picked it) and left the field.
See Part II → DOM Manipulation Reference → Element Values for the full parameter and return-type details.
Goal: trigger a button the same way a user clicking it would, without re-implementing whatever the page's own JavaScript does when that button is clicked.
m_lv.SetElementValueById "txtName", "Jane Smith"
m_lv.SetElementValueById "txtEmail", "jane@example.com"
m_lv.ClickElementById "btnSubmit"What happens: ClickElementById dispatches a click on the element with that ID. If the page has a click handler wired to that button — validation, an AJAX call, whatever the page author built — it runs exactly as it would for a real mouse click.
See Part II → DOM Manipulation Reference → Clicking Elements.
Goal: push a chunk of markup or plain text into a results panel, and read or change the attributes a page uses to tag its elements — the tools you reach for once a single field value isn't the whole story.
' HTML — parsed as markup; use when you're deliberately building HTML
Dim html As String
html = m_lv.GetInnerHtmlById("divResults") ' "Results will appear here"
m_lv.SetInnerHtmlById "divResults", "<p>Found <b>42</b> items</p>"
' Plain text — never interpreted as markup; use for ordinary strings
Dim status As String
status = m_lv.GetElementTextById("divStatus") ' "ready"
m_lv.SetElementTextById "divStatus", "Status: processing..."
' Attributes — data-* tags, placeholder, href, class, or any other HTML attribute
Dim role As String
role = m_lv.GetElementAttributeById("txtName", "data-role") ' "primary"
m_lv.SetElementAttributeById "txtName", "data-role", "secondary"
m_lv.RemoveElementAttributeById "txtName", "data-role"What happens: GetInnerHtmlById/SetInnerHtmlById read and replace everything inside an element as an HTML string — <b>42</b> becomes a real bold element on the page, not literal text. GetElementTextById/SetElementTextById do the same job for plain text: reading strips all tags out, and writing never interprets <, >, or & as markup. GetElementAttributeById/SetElementAttributeById/RemoveElementAttributeById read, write, and delete any HTML attribute on an element, not just value.
Caution
this is the distinction to keep straight between the HTML calls and the text call above. Use SetInnerHtmlById only when you're deliberately building markup. Use SetElementTextById for ordinary text — especially anything from user input or an external data source — so a value like O'Brien & Sons <VIP> can never be misinterpreted as HTML.
See Part II → DOM Manipulation Reference for GetOuterHtmlById/SetOuterHtmlById (the same idea as the HTML calls above, but including the element's own tag) and the full parameter/return details for every method above.
Goal: guard code against an element that might not exist yet, and read or set the two other common form-control types besides plain text inputs.
' Guard before acting on an element that might not be there yet
If m_lv.ElementExistsById("errorDiv") Then
Debug.Print "Error div found"
End If
' Checkboxes — a plain Boolean instead of an attribute or value string
m_lv.SetCheckboxById "chkAgree", True
If m_lv.GetCheckboxById("chkAgree") Then Debug.Print "User agreed"
' Dropdowns — matched by the option's underlying value, not its visible text
m_lv.SelectOptionById "ddlCountry", "DE" ' selects "Germany"
Dim country As String
country = m_lv.GetSelectedOptionById("ddlCountry") ' "DE"What happens: ElementExistsById returns True/False without raising an error, so it's safe to call as a guard before any Get/Set-by-ID method. SetCheckboxById/GetCheckboxById check or uncheck a box and read its state as a Boolean. SelectOptionById matches against the <option> elements' value attribute, not their displayed text; GetSelectedOptionById returns that same value for whichever option is currently selected.
See Part II → DOM Manipulation Reference for the full parameter details.
Goal: move keyboard focus to a field after a validation failure, scroll a section into view, and submit a form once its fields are filled in from VBA.
m_lv.FocusElementById "txtEmail"
m_lv.ScrollToElementById "scrollTarget"
m_lv.SetElementValueById "frmName", "John Doe"
m_lv.SetElementValueById "frmMsg", "Hello"
m_lv.SubmitFormById "frmContact"What happens: FocusElementById sets keyboard focus the same as a click or Tab would. ScrollToElementById scrolls the element into view without changing focus. SubmitFormById submits the form the same way clicking its Submit button would — any client-side validation or onsubmit handling the page defines still runs; this triggers the page's own logic rather than bypassing it.
See Part II → DOM Manipulation Reference for the full parameter details.
Goal: capture the entire page for logging, diagnostics, or exporting a rendered report, rather than one element at a time.
Dim fullHtml As String, plainText As String
fullHtml = m_lv.GetPageHtml
plainText = m_lv.GetPageTextWhat happens: GetPageHtml returns the complete HTML of the loaded page as one string. GetPageText returns the same page's visible text with markup stripped — the same HTML-vs-text distinction as section 7.3, applied to the whole document.
Every recipe so far has assumed the element you want already has a convenient id. Real pages don't always cooperate — a table row usually doesn't have its own ID, and a button inside one form might share a class with buttons in three other forms. A CSS selector lets you describe where an element sits in the page instead of relying on it having a unique name.
A selector is the same kind of pattern used in a stylesheet: #id matches an ID, .class matches a class, tag matches an element type, and selectors can be chained to narrow down to one specific spot — table#tblData tr:nth-child(2) means "the second row inside the table whose ID is tblData"; form#frmContact button[type='submit'] means "the submit button inside the form whose ID is frmContact," even if other submit buttons exist elsewhere on the page.
m_lv.ClickElementBySelector "#btnSubmit"
m_lv.ClickElementBySelector "button.primary"
m_lv.ClickElementBySelector "form#frmContact button[type='submit']"If m_lv.ElementExistsBySelector("table#tblData tr:nth-child(2)") Then
Debug.Print "Table has at least 2 rows"
End IfWhat happens: ClickElementBySelector clicks the first element matching the given CSS selector; ElementExistsBySelector checks whether any element matches, returning a Boolean the same way ElementExistsById does. Both take any valid CSS selector, so they cover cases plain ID-based methods can't reach.
See Part II → DOM Manipulation Reference → CSS Selectors.
Two smaller families of DOM methods round out the catalog. You won't reach for these as often as the ID-based recipes above, so this section just shows the shape of them; Part II documents every one in full.
By name attribute, for pages built around HTML's older name-based form fields instead of id:
Dim email As String
email = m_lv.GetElementValueByName("userEmail")
Debug.Print "Email: " & email ' "john@example.com"
m_lv.SetElementValueByName "userEmail", "new@example.com"
m_lv.ClickElementByName "agree"These work exactly like their ...ById counterparts (section 7.1), just matching on the element's name attribute instead of its id.
OuterHTML, which replaces an element including its own tag — not just its contents:
m_lv.SetOuterHtmlById "divResults", "<div id=""divResults""><b>Replaced!</b></div>"GetOuterHtmlById / SetOuterHtmlById and their ...ByName counterparts follow the same Get/Set pattern as GetInnerHtmlById / SetInnerHtmlById from section 7.3, except the tag itself is included in what's read or replaced.
See Part II → DOM Manipulation Reference → Name-Based Addressing and → Outer HTML for the complete parameter lists, including GetOuterHtmlByName / SetOuterHtmlByName.
The previous chapters got a page loaded into the control and let VBA call into it. This chapter is about the part most Access and Excel developers actually came for: getting real business data — recordsets, totals, lookups — out of VBA and onto the page, in a shape JavaScript can use directly.
The running example is a sales dashboard: an Access form (frmDashboard) hosts the LiteView2Ctrl (m_lv), and its page shows a monthly sales summary pulled from tblSales. We assume the page has already finished loading, as covered in earlier chapters — every call below runs from code that fires after that point (a button click, a form event, and so on).
Three members do the work in this chapter:
PushRecordset — hands an entire DAO or ADO recordset to a named JavaScript function, as an array of row objects.BuildJson — converts a single VBA value (string, number, array) to a JSON string, for the smaller cases where you don't have a whole recordset to send.FireJsEvent — dispatches a browser CustomEvent into the page, so JavaScript can react through addEventListener instead of waiting on a function call.What we're trying to accomplish
The dashboard page needs a table of monthly sales totals. Rather than building that table row-by-row from VBA (string-concatenating HTML, or making dozens of small BuildJson calls), we run one aggregate query in Access and hand the whole recordset to PushRecordset. It serializes the recordset to JSON and calls a JavaScript function of our choosing with the result.
Code
Dim rs As DAO.Recordset
Set rs = CurrentDb.OpenRecordset( _
"SELECT Year(SaleDate) AS Yr, Month(SaleDate) AS Mo, " & _
" Sum(Quantity * UnitPrice) AS Total " & _
"FROM tblSales " & _
"GROUP BY Year(SaleDate), Month(SaleDate)", dbOpenSnapshot)
rs.MoveLast
rs.MoveFirst
m_lv.PushRecordset rs, "onSalesData"
rs.CloseJavaScript side
function onSalesData(rows) {
// rows = [{Yr: 2024, Mo: 1, Total: 15234.50}, ...]
rows.forEach(r => console.log(r.Yr + "-" + r.Mo + ": $" + r.Total));
}onSalesData must already be defined in the page (a <script> block loaded with the page, same as the JS functions VBA calls in earlier chapters) — PushRecordset calls it by name, it does not define it for you.
What should happen
The page's onSalesData function runs once, with rows as a plain JavaScript array of objects — one object per recordset row, keyed by the query's column aliases (Yr, Mo, Total). From there it's ordinary JavaScript: render a table, feed a chart library, filter in memory, whatever the dashboard needs.
Cautions
dbOpenSnapshot when the query is a GROUP BY or JOIN, as above — this matches the query in the example and is what the source recommends for aggregate results.rs.MoveLast followed by rs.MoveFirst before calling PushRecordset. A freshly opened DAO recordset doesn't know its own row count until something forces it to walk every row; MoveLast does that walk, and MoveFirst puts the cursor back at the start so PushRecordset serializes from row one. Skipping this step is the single most common mistake with this call.PushRecordset caps out at 100,000 rows per call. For anything larger, page the query in Access (add a date range or TOP n filter) and push in batches, or aggregate further before pushing.rs.Close) once PushRecordset returns — it has already read what it needs.Registration-Free mode uses the pool equivalent — pool.PushRecordset idx, rs, "onSalesData" — with the same dbOpenSnapshot / MoveLast+MoveFirst / 100,000-row rules. Chapter 17 covers the pool/idx deployment model in full.
See Part II → Data Bridge & JSON for the complete member reference.
What we're trying to accomplish
Not everything is a full recordset. Sometimes you just need one VBA value — a string, a number, an array of names — turned into a JSON string: to log it, to build a payload for FireJsEvent (next section), or to hand-assemble a small piece of a larger script call. BuildJson does that conversion without touching the page at all.
Code
Dim json As String
json = m_lv.BuildJson("Hello World") ' Returns: "Hello World"
json = m_lv.BuildJson(42) ' Returns: 42
json = m_lv.BuildJson(Array(1, 2, 3)) ' Returns: [1,2,3]
Debug.Print jsonWhat should happen
Each call returns a JSON-formatted string for the value passed in — a quoted string, a bare number, or a bracketed array, matching the VBA type given. No page navigation or JavaScript call happens; the conversion is pure VBA-side work.
That last point is worth pausing on: BuildJson works even without a WebView2 browser present. The control is doing JSON conversion as a service, independent of anything on screen. Chapter 9 goes into this in full — BuildJson is one entry point into a standalone JSON toolkit (JsonToArray, JsonToRowArray, and friends) that doesn't require a live browser to use.
Registration-Free mode calls the same operation through the pool, with the browser index as the first argument: pool.BuildJson(idx, Array("a", "b", "c")). Chapter 17 covers the pool/idx deployment model in full.
See Part II → Data Bridge & JSON for the complete member reference.
What we're trying to accomplish
PushRecordset is for handing over data. FireJsEvent is for handing over a signal — "something happened, here's a small detail payload" — that the page can listen for the standard browser way, with document.addEventListener, rather than exposing a specific callback function name for VBA to call. This suits things like "the dashboard just refreshed" or "a new order was saved," where several independent pieces of page script might all want to react.
Code
Dim detail As String
detail = m_lv.BuildJson("dataReady") ' or build a richer object by hand, see below
m_lv.FireJsEvent "dataReady", "{""count"":42,""source"":""VBA""}"The event detail is a JSON string you supply — here it's written out by hand (note the doubled quotes to embed " inside a VBA string literal); for anything more than a couple of fields, building it with BuildJson from an array or a dictionary-like structure is less error-prone than hand-quoting JSON in VBA.
JavaScript side
document.addEventListener('dataReady', function(e) {
console.log(e.detail); // {count: 42, source: "VBA"}
});What should happen
Any listener already registered on the page fires with e.detail set to the parsed object from the JSON string VBA supplied. Unlike PushRecordset's named-function call, any number of listeners can react to the same event, and the page doesn't need to expose a function VBA knows about in advance.
Caution — no Reg-Free equivalent
Registration-Free mode has no direct FireJsEvent call. It falls back to ExecuteScriptAsync, dispatching the event manually with a hand-built JavaScript string:
' Use ExecuteScriptAsync to dispatch event manually
pool.ExecuteScriptAsync idx, _
"document.dispatchEvent(new CustomEvent('dataReady'," & _
"{detail:{count:42,source:'VBA'}}));"This is more than the usual Reg-Free variable-naming difference, so it's called out here rather than folded into a one-line aside: in Registered mode you get a dedicated method; in Reg-Free mode you construct the equivalent JavaScript yourself. Chapter 17 covers the pool/idx deployment model, and ExecuteScriptAsync itself, in full.
See Part II → Data Bridge & JSON for the complete member reference.
JsonToArray and JsonToRowArray, and more of what BuildJson belongs to as a standalone capability.If your project pulls data from a REST API, reads a config blob, or exchanges structured data with a page loaded in your browser control, you eventually need to pick fields out of a JSON string and, just as often, build one. VBA has no native JSON support, so most projects either write a hand-rolled parser or import a third-party module like JsonConverter.bas.
LiteView2 ships its own JSON engine — pure native C++, not a JavaScript library running in the browser — and exposes it as plain VBA function calls. You pass in a JSON string, you get a value, an array, or a modified JSON string back. No browser navigation is required for any of it, and as you'll see at the end of this chapter, you don't even need a browser control on the form to use it.
This chapter uses a recurring example: your VBA code has just received a JSON response describing a customer record and a small sales report, and you need to read fields out of it, build/modify JSON of your own, and turn a JSON array into something you can loop over or drop into an Excel range.
JsonGetValue pulls a single value out of a JSON string using a path expression. Use a dot to walk into nested objects.
Dim json As String
json = "{""name"":""John"",""age"":30,""address"":{""city"":""NYC""}}"
Debug.Print m_lv.JsonGetValue(json, "name") ' "John"
Debug.Print m_lv.JsonGetValue(json, "age") ' 30
Debug.Print m_lv.JsonGetValue(json, "address.city") ' "NYC"Registration-Free uses the pool equivalent — same method name, with the browser index (idx) as an extra leading argument; Chapter 17 covers that deployment model in full.
Note
WebMessageReceived is raised after WebView2's own callback has returned, so handle the message right there: JsonGetValue, ExecuteScript and PushRecordset all work inside it. Synchronous calls fail (error &H80040203) in two places only: inside inline events such as WebViewReady / NavigationStarting, and inside VBA methods the page is calling through a host object. Inside a host method, use only calls that do not wait (ExecuteScriptAsync, PostWebMessageAsJson, the JSON helpers, DAO). See Critical Safety Warnings #1.
Dim json As String
json = "{""items"":[10,20,30,40]}"
Dim count As Long
count = m_lv.JsonGetArrayLength(json, "items")
Debug.Print "Array has " & count & " items" ' 4JsonGetArrayItem fetches one element by index. Pass an empty path when the array is the JSON value itself, not a key inside an object:
Dim json As String
json = "[{""name"":""Alpha""},{""name"":""Beta""}]"
Dim item As String
item = m_lv.JsonGetArrayItem(json, "", 0)
Debug.Print item ' {"name":"Alpha"}Returns the keys of an object at a path, as a JSON array string. An empty path means the root object.
Dim json As String
json = "{""name"":""John"",""age"":30,""city"":""NYC""}"
Dim keys As String
keys = m_lv.JsonGetKeys(json, "")
Debug.Print keys ' ["name","age","city"]Checks whether a string parses as JSON at all — useful before you trust an API response or a value a user typed into a text box.
Debug.Print m_lv.JsonIsValid("{""valid"":true}") ' True
Debug.Print m_lv.JsonIsValid("{bad json}") ' FalseSee Part II → JSON Helper for the complete signature list, including every Reg-Free form.
The mutation methods below are pure native C++ — they don't touch the WebView2 browser at all, so they work even before a page has finished loading. Each one accepts a JSON string and returns the modified JSON string; on failure the original JSON comes back unchanged, so always assign the return value back.
Dim json As String
json = m_lv.JsonCreateObject() ' {}Dim json As String
json = m_lv.JsonCreateArray() ' []Sets a value at a path, creating any intermediate objects or arrays that don't exist yet. Array elements use name[index] syntax; gaps are padded with null.
Dim json As String
json = m_lv.JsonCreateObject()
' Simple values
json = m_lv.JsonSetValue(json, "name", "Imran")
json = m_lv.JsonSetValue(json, "age", 30)
json = m_lv.JsonSetValue(json, "active", True)
' Auto-creates the nested "address" object
json = m_lv.JsonSetValue(json, "address.city", "London")
json = m_lv.JsonSetValue(json, "address.country", "UK")
' Overwrite an existing value
json = m_lv.JsonSetValue(json, "name", "Ali")
' Array elements — auto-creates "scores" as an array
json = m_lv.JsonSetValue(json, "scores[0]", 95)
json = m_lv.JsonSetValue(json, "scores[1]", 88)
Debug.Print m_lv.JsonPrettyPrint(json)Checks whether a path is present before you read or overwrite it.
Dim json As String
json = "{""user"":{""name"":""Imran""}}"
If m_lv.JsonExists(json, "user.name") Then Debug.Print "Found"
If Not m_lv.JsonExists(json, "user.email") Then
json = m_lv.JsonSetValue(json, "user.email", "imran@example.com")
End IfAppends a value to the end of an array at a path, creating the array if it isn't there yet.
Dim json As String
json = m_lv.JsonCreateObject()
json = m_lv.JsonAppend(json, "tags", "vba")
json = m_lv.JsonAppend(json, "tags", "com")
json = m_lv.JsonAppend(json, "tags", "webview2")
Debug.Print json ' {"tags":["vba","com","webview2"]}Combines two JSON objects. The third argument controls shallow vs. deep merge, and the difference only shows up when a key on both sides is itself an object:
Dim base As String, overlay As String, json As String
base = "{""name"":""Imran"",""address"":{""city"":""London"",""zip"":""SW1""}}"
overlay = "{""age"":30,""address"":{""city"":""Manchester""}}"
' Shallow merge (False) — overlay's "address" REPLACES base's "address" entirely
json = m_lv.JsonMerge(base, overlay, False)
' {"name":"Imran","address":{"city":"Manchester"},"age":30} <- zip is GONE
' Deep merge (True) — overlay's "address" is merged key-by-key into base's
json = m_lv.JsonMerge(base, overlay, True)
' {"name":"Imran","address":{"city":"Manchester","zip":"SW1"},"age":30} <- zip survivesIf you're merging a partial update (like a form that only edits address.city) into a full record and want to keep the fields the update didn't touch, use deep merge. Shallow merge is closer to a plain key-for-key overwrite.
JsonRemoveKey, JsonInsert, JsonCount, JsonClear, JsonPrettyPrint, JsonFlatten, and JsonUnflatten round out the mutation API — removing a key or array element, inserting into the middle of an array, counting keys/elements, emptying a container, pretty-printing for debugging, and flattening/unflattening nested JSON to and from dot-notation keys. They follow the same "pass JSON string in, get JSON string back" shape as the methods above. Part II → JSON Helper documents each one in full, along with a complete worked example that builds a report document from JsonCreateObject() through JsonSetValue, JsonAppend, JsonExists, JsonCount, and JsonPrettyPrint in one pass.
Caution
On the Reg-Free pool, JsonSetValue, JsonRemoveKey, and JsonToRowArray are exposed as JsonSetValue2, JsonRemoveKey2, and JsonToRowArray2 — the 2 suffix just avoids a COM name collision on those three methods. Everything else keeps its Registered-mode name.
Reading values one path at a time is fine for a handful of fields. When you have an array of records — a customer list, an order history, a report's line items — and you want to loop over it or dump it into an Excel range, converting the whole array to a VBA array in one call is faster and far less code.
Converts a JSON array to a plain 2D VBA array. Suppose an API call handed you a small table of daily figures:
Dim json As String
json = "[{""Date"":""2024-01-01"",""Open"":100,""Close"":105}," & _
"{""Date"":""2024-01-02"",""Open"":105,""Close"":110}]"
Dim arr As Variant
arr = m_lv.JsonToArray(json)
' arr is a 2D array: arr(1,1)="2024-01-01", arr(1,2)=100, arr(1,3)=105, ...JsonToArray gives you raw values, indexed from arr(1,1), with no header row.
JsonToRowArray does the same job but adds a header row, and it types each cell in VBA terms rather than leaving everything as Variant/string — which matters when you're writing straight into an Excel range or comparing values later in code. Row 1 holds the column headers (taken from the JSON keys); rows 2+ hold the data.
Dim json As String
json = "[{""Name"":""Imran"",""Age"":30,""Active"":true}," & _
"{""Name"":""Ali"",""Age"":25,""Active"":false}]"
Dim arr As Variant
arr = m_lv.JsonToRowArray(json)
' arr(1,1)="Name" arr(1,2)="Age" arr(1,3)="Active" <- headers
' arr(2,1)="Imran" arr(2,2)=30 arr(2,3)=True <- data
' arr(3,1)="Ali" arr(3,2)=25 arr(3,3)=False
Dim r As Long, c As Long
For r = 2 To UBound(arr, 1)
For c = 1 To UBound(arr, 2)
Debug.Print arr(r, c);
Next c
Debug.Print
Next rThat array is also exactly the shape Range.Value = arr expects in Excel — headers in row 1, data below — so writing a JSON array straight into a worksheet is usually a one-line assignment once you have arr.
Type mapping (JSON type → VBA type in the returned array):
| JSON Type | VBA Type |
|---|---|
| string | vbString |
| number (integer) | vbLong |
| number (decimal) | vbDouble |
| boolean | vbBoolean |
| null | vbEmpty |
| object / array | vbString (serialized JSON) |
A nested object or array inside a row comes back as a JSON string in that cell, not expanded into more columns — feed it back through JsonGetValue or JsonToRowArray if you need to drill into it.
Reg-Free note: use JsonToRowArray2 here (see the caution at the end of the previous section).
JsonToArrayMaxSize is a Registered-control property (Long, read/write) that caps how large a JSON string JsonToArray and JsonToRowArray will accept, in bytes. Default is 52428800 (50MB).
Debug.Print m_lv.JsonToArrayMaxSize ' 52428800
m_lv.JsonToArrayMaxSize = 104857600 ' raise the cap to 100MBThe Reg-Free pool doesn't expose this as a settable property — it always applies the built-in default.
Everything above assumed you had a m_lv control (or a pool/idx pair) already on a form. The JSON engine is also reachable as a fully standalone COM object that has nothing to do with WebView2 or a browser control:
Dim json As Object
Set json = CreateObject("LiteView2.Json")
Dim result As String
result = json.Append(json, path, value)Dim json As Object
Set json = CreateObject("LiteView2.Json")
Dim result As String
result = json.Clear(json, path)This matters for a project that has no on-screen use for a browser at all — an Access module that calls a REST API and needs to pick apart the response, a scheduled Excel macro that reshapes a JSON export, a class module in a service-style VB6 app. You get the same JSON engine, same method names, without adding a LiteView2Ctrl to any form and without a pool or browser index anywhere in the code.
The standalone JSON object also documents a MaxSize property — the byte cap applied to Parse/ToArray/ToRowArray calls. Default is 50MB; documented range is 1KB–500MB.
Dim value As Long
pool.GetMaxSize idx, valuepool.SetMaxSize idx, newValue ' newValue As LongNote the calling shape here is different from the read methods earlier in this chapter: GetMaxSize/SetMaxSize are Sub calls that take the value as a ByRef output/input parameter (GetMaxSize idx, value), not functions that return the value directly.
Note
The reference entry this is drawn from tags MaxSize as Mode: ILV2Json (standalone object), but the Get/Set code it documents is the Reg-Free pool form — pool.GetMaxSize idx, value / pool.SetMaxSize idx, newValue — not a call through the standalone json object shown above for Append/Clear. The documentation doesn't show a json.MaxSize form. Treat the pool form above as what's actually documented for this property, and see Part II → JSON Helper for the complete, unresolved picture of how MaxSize is meant to be reached from each mode.
By now you've navigated a page, called ExecuteScript to run a line of JavaScript, exposed a host object so the page can call back into VBA, and passed a web message across the boundary. All of that still works, and none of it goes away. This chapter is not a repeat of Chapter 4 — it introduces a different tool that sits on top of what you already know: the Two-Way Script Bridge.
Once a page has grown past a handful of ExecuteScript calls, you start feeling two problems:
"window.PageController.Title = '" & txtName.Text & "'" — which is fragile and, if the value contains a quote character, wrong.ExecuteScript gives you back a string (or nothing). If the page returns a number, a boolean, or an array, you're parsing it yourself.The bridge fixes both. You bind an alias once to a JavaScript object on the page, and from then on you read and write its properties or call its methods almost like it was a native form control:
m_lv.SetScriptProperty "page", "Title", "Customers"
total = m_lv.CallScriptSync("page", "computeTotal", 2, 3)No string concatenation, and total comes back as a real typed Variant — a number, in this case — not text you have to CLng() yourself.
The examples in this chapter assume the page exposes a JavaScript object named window.PageController. Here is a self-contained page you can paste into any of the navigation techniques from earlier chapters:
<script>
window.PageController = {
Title: "Untitled",
computeTotal: function (a, b) { return (a || 0) + (b || 0); },
showToast: function (msg) { document.title = msg; }
};
</script>Bind once, after the page that defines the object has loaded: in NavigationCompleted (Registered), or later from ordinary code. A bind without LV2_BIND_VALIDATE also works in WebViewReady / OnBrowserReady and is resolved lazily. Inside WebViewReady, other inline events and host-object methods, a validating bind fails at once with &H80040203 and changes no binding. Every method below refers back to that binding by its alias.
Registration-Free code follows the pool equivalent (pool/idx instead of m_lv); Chapter 17 covers that deployment model in full. It is shown once per method here for completeness, but the rest of the chapter's prose sticks to Registered mode.
BindScriptObject binds a short alias to a JavaScript object expression. Every other bridge call — CallScript, CallScriptSync, GetScriptProperty, SetScriptProperty — refers to the object by that alias instead of re-stating the JavaScript path each time.
m_lv.BindScriptObject "page", "window.PageController"Reg-Free: pool.BindScriptObject idx, "page", "window.PageController".
By default (flag 0), the object is resolved lazily — the bind call itself always succeeds, and the first bridge call that actually touches the object is where a "not found" error would surface. Pass a flag as the optional third argument to change that:
' Fail immediately if window.PageController doesn't exist yet:
m_lv.BindScriptObject "page", "window.PageController", 1 ' ValidateLV2_BIND_FLAGS)Flags are combined with +.
| Flag | Value | Meaning |
|---|---|---|
| (none) | 0 | Default — resolve lazily; the first call surfaces any resolution error. |
| Validate | 1 | Resolve the object at bind time; return an error immediately if it's missing. |
| Transient | 2 | Drop the binding on the next navigation instead of re-resolving it. |
| RawExpression | 4 | Accept an arbitrary JavaScript expression instead of a simple object path. |
| Async | 8 | Reserved — accepted but has no effect in this release. |
| FailIfBound | 16 | Return an error instead of silently replacing an existing binding. |
| FollowResolution | 32 | Re-evaluate the expression on every call (tracks reassignment) instead of capturing the object once at bind time. |
Caution
RawExpression accepts an arbitrary JavaScript expression as the bind target instead of a restricted object-path form. By passing it, you accept the injection risk yourself — only combine it with strings you built, not with anything that came from a user, a grid cell, or an external file.
To combine flags, add the values — e.g. 1 + 16 validates immediately and refuses to silently replace an existing binding.
Releases a binding. Idempotent — unbinding an alias that was never bound, or was already unbound, simply returns success.
m_lv.UnbindScriptObject "page"Reg-Free: pool.UnbindScriptObject idx, "page".
Calls a method on the bound object without waiting for a result.
m_lv.CallScript "page", "showToast", "Saved!"Reg-Free: pool.CallScript idx, "page", "showToast", "Saved!".
Up to 8 arguments are supported; trailing omitted arguments are ignored.
Caution
a JavaScript exception thrown during a fire-and-forget CallScript is not reported back to VBA — the call still returns as if it succeeded. If you need to know whether the call actually worked, use CallScriptSync instead.
Calls a method and blocks until it returns, delivering the result as a typed Variant.
Dim total As Variant
total = m_lv.CallScriptSync("page", "computeTotal", 2, 3)
Debug.Print total ' 5 (a real number, not a string)Reg-Free: total = pool.CallScriptSync(idx, "page", "computeTotal", 2, 3).
Caution
if the JavaScript method returns a Promise, CallScriptSync does not wait for it — it fails with LV2_BRIDGE_PROMISE_RETURNED. CallScriptSync is for synchronous JavaScript functions only; a method that returns a Promise needs a different approach (see Part II under the bridge reference for the full picture).
Read or write a property of the bound object as a typed Variant.
Dim caption As Variant
caption = m_lv.GetScriptProperty("page", "Title")
m_lv.SetScriptProperty "page", "Title", "Customers"Reg-Free: pool.GetScriptProperty(idx, "page", "Title") and pool.SetScriptProperty idx, "page", "Title", "Customers".
Reading a property whose value is a function returns the error LV2_BRIDGE_PROPERTY_IS_FUNCTION — you can use that deliberately as a "does this method exist on the page?" probe before calling it.
Caution
a successful SetScriptProperty call does not guarantee the value stuck. Assigning to a read-only JavaScript property is silently ignored by the page — no error, no exception, the call just returns success. If the write matters (a title the user needs to see change, a setting a chart reads before redrawing), read the property back with GetScriptProperty afterward to confirm it actually took.
Retrieves the last bridge error for this browser as a numeric code (an HRESULT) plus a message. The slot is overwritten on every error and is never auto-cleared, so check it right after the call that might have failed.
On Error Resume Next
Dim v As Variant
v = m_lv.CallScriptSync("page", "noSuchMethod")
If Err.Number <> 0 Then
Dim code As Long, msg As String
m_lv.GetLastBridgeError code, msg
Debug.Print Hex(code) & " - " & msg ' 80040503 - ...
End If
On Error GoTo 0Reg-Free: pool.GetLastBridgeError idx, code, msg.
LV2_BRIDGE_ERR)These surface as Err.Number and are also returned by GetLastBridgeError.
| Constant | Hex | When |
|---|---|---|
| LV2_BRIDGE_ALIAS_NOT_FOUND | &H80040501 | Call on an alias that was never bound. |
| LV2_BRIDGE_OBJECT_NOT_RESOLVED | &H80040502 | The bound expression evaluates to nothing on the page. |
| LV2_BRIDGE_METHOD_NOT_FOUND | &H80040503 | No method of that name on the object. |
| LV2_BRIDGE_PROPERTY_NOT_FOUND | &H80040504 | No property of that name on the object. |
| LV2_BRIDGE_JS_EXCEPTION | &H80040505 | The JavaScript threw — see the message. |
| LV2_BRIDGE_PROMISE_RETURNED | &H80040506 | A sync call received a Promise; use a synchronous method. |
| LV2_BRIDGE_TIMEOUT | &H80040507 | The call did not complete within 30 seconds. |
| LV2_BRIDGE_NOT_READY | &H80040508 | The browser isn't ready yet. |
| LV2_BRIDGE_INVALID_ARG | &H80040509 | Bad alias/member name, or a value that can't be passed. |
| LV2_BRIDGE_INVALID_EXPRESSION | &H8004050A | The bind expression isn't a safe object path (use RawExpression to override). |
| LV2_BRIDGE_REENTRY_BLOCKED | &H8004050B | Too many nested synchronous calls. |
| LV2_BRIDGE_ALIAS_ALREADY_BOUND | &H8004050C | Rebind blocked because FailIfBound was set. |
| LV2_BRIDGE_PROPERTY_IS_FUNCTION | &H8004050D | GetScriptProperty target is a function. |
| (no constant) E_LV2_SYNC_REENTRANT | &H80040203 | A synchronous bridge call (CallScriptSync, GetScriptProperty, SetScriptProperty, BindScriptObject with LV2_BIND_VALIDATE) was made inside an inline event or a host-object call. Nothing was sent. Use CallScript / ExecuteScriptAsync there, or call after the handler returns. |
See Part II → Two-Way Script Bridge Reference for the complete member list and every argument variant.
This is the pattern that makes the bridge worth the extra setup step: bind once, then push new data and pull the user's selection back, all with typed values instead of ad-hoc strings.
' 1. load a page that defines window.Dashboard, then bind it
m_lv.NavigateToStringSync DashboardHtml() ' your page (defines window.Dashboard)
m_lv.BindScriptObject "chart", "window.Dashboard"
' 2. drive the chart from VBA
m_lv.SetScriptProperty "chart", "Title", "Sales by Category"
m_lv.CallScript "chart", "setData", Array(120, 340, 80) ' array arrives in JS as [120,340,80]
' 3. read the user's selection back as a typed value
Dim picked As Variant
picked = m_lv.CallScriptSync("chart", "getSelectedCategory")
If Len(CStr(picked)) > 0 Then
Me.Filter = "Category='" & picked & "'"
Me.FilterOn = True
End IfThe Access form here re-filters itself the moment the user clicks a slice of a chart living inside the WebView2 page — the same round trip you'd use to refresh an Orders or Inventory dashboard from a live query.
By this chapter you have four ways to move data or trigger behavior across the VBA ↔ JavaScript boundary. They are not competitors — most real screens use two or three of them together.
| Mechanism | Direction | What you get back | Best for |
|---|---|---|---|
ExecuteScript | VBA → JS, one-shot | A string result you parse yourself (if you need one at all) | Running a single line of script once — no repeated access to the same object needed |
| Host object | JS → VBA | The page calls a VBA method directly | Letting the page-side code trigger VBA logic on its own initiative (a button click inside the page, a JS-side validation failure) |
| Web message | VBA ↔ JS, message-based | A message your handler parses | Loosely-coupled, event-style notifications where you don't need a typed round trip |
| Two-Way Script Bridge (this chapter) | VBA ↔ JS, bound alias | A typed Variant from every call/read | Repeated, ongoing access to one known JavaScript object — reading and writing its properties and calling its methods as if it were a native control |
Reach for the bridge when you find yourself binding to the same JavaScript object over and over — a chart, a form controller, a report viewer — rather than firing one-off ExecuteScript strings at it. Reach for ExecuteScript, a host object, or a web message when the interaction really is a one-shot script, a page-initiated callback, or a loose notification, respectively — Chapter 4 covers those in depth.
See Part II → Two-Way Script Bridge Reference for the exhaustive member list, and Chapter 17 for the Reg-Free (pool/idx) deployment model referenced throughout this chapter.
WebView2 is a full browser engine, and by default it behaves like one: right-click brings up a context menu with "Inspect", Ctrl+scroll changes the zoom level, and a new window from a link opens as a real popup window. For a public-facing consumer app that's usually fine. For an internal line-of-business screen — an Orders dashboard embedded in an Access form, a Customer report opened from Excel — it's usually not what you want, and it's not always obvious how to turn it off.
This chapter walks through the browser-chrome and UX settings you're most likely to touch on a real internal application: context menus, DevTools, the status bar, zoom, popup handling, and find-in-page. All of these are simple Boolean, numeric, or string properties (plus a handful of find-in-page methods) — there's no async callback or DOM work involved, so this is a good chapter to work through even if you're still getting comfortable with the rest of the control.
Every example below uses the Registered ActiveX control, m_lv As LiteView2Lib.LiteView2Ctrl, which is the default for the rest of Part I. Registration-Free deployments expose the same behavior through pool-style calls (for example pool.SetZoom idx, 1.5 instead of m_lv.ZoomFactor = 1.5) — Chapter 17 covers that deployment model and its calling convention in full. As with any property on the control, set these after the browser has finished initializing, not immediately after Set m_lv = ... — the ready-state events were covered earlier in Part I.
The scenario used throughout: frmDashboard, an Access form that hosts m_lv and shows an internal Orders/Customers report page.
The two most common lockdown settings for an internal app: should right-click show the browser's context menu, and should end users be able to open DevTools (F12) and start poking at your page's HTML, JavaScript, and network traffic?
Boolean, read/write. Turns the browser's built-in right-click context menu on or off.
Debug.Print m_lv.AreDefaultContextMenusEnabled
m_lv.AreDefaultContextMenusEnabled = False ' Disable right-click menuBoolean, read/write. Turns F12 DevTools on or off.
m_lv.AreDevToolsEnabled = False ' Disable F12For a shipped internal tool, a common pattern is to disable both in the production build and leave them enabled in a debug build, so you (and your support desk) can still inspect the page when something goes wrong:
m_lv.AreDefaultContextMenusEnabled = Not gblnProductionBuild
m_lv.AreDevToolsEnabled = Not gblnProductionBuildSee Part II -> Settings and Browser Behavior for the complete property reference, including the Reg-Free equivalents.
IsStatusBarEnabled turns the browser's status bar overlay on or off. StatusBarText is the read-only string currently shown in it — in a normal browser, this is typically the target URL when the mouse is hovering over a hyperlink.
m_lv.IsStatusBarEnabled = True
' Elsewhere, e.g. on a timer or a mouse-move handler:
Debug.Print m_lv.StatusBarTextStatusBarText is read-only — you read what the browser is currently showing, you don't set it. If you want your own custom status display in an Access form (a label showing the hovered link, for example), read StatusBarText and write it into your own control rather than relying on the browser's built-in overlay.
See Part II -> Settings and Browser Behavior for the complete property reference.
ZoomFactor (Double, read/write) is the current zoom level, where 1.0 is 100%. IsZoomControlEnabled (Boolean, read/write) turns end-user Ctrl+scroll zooming on or off.
Debug.Print "Zoom: " & m_lv.ZoomFactor * 100 & "%"
m_lv.ZoomFactor = 1.5 ' 150%, e.g. for a low-vision user on the Orders report
m_lv.IsZoomControlEnabled = False ' Users can't Ctrl+scroll to change it themselvesA typical combination for a kiosk-style dashboard: set ZoomFactor once to a layout that fits your form, then set IsZoomControlEnabled = False so an accidental Ctrl+scroll during scrolling doesn't resize the page.
See Part II -> Settings and Browser Behavior for the complete property reference.
PopupMode (type LV2_POPUP_MODE, read/write) controls what happens when the page tries to open a new window — for example a link with target="_blank", or a window.open() call. It's a numeric enum: 0 = Allow, 1 = Block, 2 = SameWindow.
m_lv.PopupMode = 1 ' Block all popups — good default for a locked-down LOB screenOpenPopupsInSameWindow (Boolean, read/write) is a separate setting that also affects how popups are navigated, forcing them to load into the same window rather than a new one. The exact interaction between the two when both are set is covered in Part II -> Settings and Browser Behavior — for most internal apps you'll only need one of them: PopupMode = 1 if you never want popups, or PopupMode = 2 / OpenPopupsInSameWindow = True if a report page opens links you want to keep inside your existing view instead of spawning a separate window.
A minimal page-side trigger, for context — an Orders report with a "Print invoice" link that would normally pop a new window:
<a href="/orders/invoice.html?id=4821" target="_blank">Print invoice</a>With PopupMode = 1 that link simply does nothing when clicked; with PopupMode = 2 (or OpenPopupsInSameWindow = True) it navigates the existing m_lv view to the invoice page instead of opening a new window.
See Part II -> Settings and Browser Behavior for the complete property reference.
Every WebView2-based control can search the currently loaded page's text, the same way Ctrl+F works in a normal browser. This is useful for an internal report page that's just a long HTML table — a Customers or Orders listing rendered as HTML rather than paged through your own UI — where you want "find the row for Contoso" without building your own search box logic against the DOM.
' Start a find session for whatever the user typed into txtSearch on frmDashboard
m_lv.StartFind txtSearch.Text, False, False ' case-insensitive, partial match
' Move between matches, e.g. from "Next" / "Previous" buttons
m_lv.FindNext
m_lv.FindPrevious
' End the session when the user closes the search bar
m_lv.StopFind False ' False = keep the current selection, True = clear itCaution
StopFind's parameter is easy to get backwards — False keeps the last-found selection highlighted on the page, True clears it. Decide up front which behavior your "close search" button should have.
Debug.Print "Matches: " & m_lv.FindMatchCount
Debug.Print "Active: " & m_lv.FindActiveMatchIndex
Debug.Print "Active session: " & m_lv.IsFindActive
m_lv.SuppressFindUI = True ' Hide WebView2's built-in find toolbar
m_lv.FindHighlightAllMatches = True ' Highlight every match, not just the current oneFindMatchCount and FindActiveMatchIndex are what you'd use to drive your own "Match X of Y" label next to a custom search box on frmDashboard, instead of relying on the browser's own find bar:
Private Sub txtSearch_Change()
m_lv.StartFind txtSearch.Text, False, False
End Sub
Private Sub UpdateMatchLabel()
If m_lv.FindMatchCount > 0 Then
lblMatches.Caption = "Match " & m_lv.FindActiveMatchIndex & _
" of " & m_lv.FindMatchCount
Else
lblMatches.Caption = "No matches"
End If
End SubWhether FindActiveMatchIndex counts from 0 or from 1 isn't spelled out in the property reference — check it against your build before assuming an offset; see Part II -> Settings and Browser Behavior for the full property reference.
SuppressFindUI = True is what makes this pattern work cleanly — without it, WebView2's own find toolbar appears on top of the page alongside whatever search UI you've built into the Access form.
See Part II -> Settings and Browser Behavior for the complete property and method reference, including Reg-Free calling conventions.
The properties above cover the settings an internal LOB application typically needs to touch. LiteView2 exposes a much larger set of browser-behavior settings that this chapter deliberately skips — they're documented in full in Part II -> Settings and Browser Behavior:
AdditionalBrowserArgumentsProxyServer / ProxyBypassListUserAgent overrideLanguageIsReputationCheckingRequiredNonClientRegionSupportEnabledSmartResizeMemoryUsageTargetLevelEventsUseHexadecimalAutoRecoverOnCrashDefaultScriptDialogsEnabledIsBuiltInErrorPageEnabledPreferredColorSchemeRequiredMinimumVersionIsDragDropEnabledBrowserExecutableFolderHiddenPdfToolbarItemsIf you're deciding whether to disable something for a hardened deployment, start there — it's an exhaustive listing, not just the handful covered above.
This chapter covers three things that are easy to lump together but are managed through completely separate LiteView2 calls:
AllowDownloads, DefaultDownloadFolderPath, HiddenDownloadUI, the download dialog methods, and PauseDownload / ResumeDownload / CancelDownload.GetCookies, SetCookie, ClearCookies, ClearProfileCookies.ClearCache, ClearBrowsingData, and ClearBrowsingDataInTimeRange. Note that cookies are also one of the data kinds you can clear this way — ClearBrowsingData and the two dedicated cookie-clearing calls overlap on purpose, and the section below explains when to reach for which. Known issue in 1.0.1.0: ClearBrowsingDataInTimeRange returns without an error but clears nothing. Use ClearBrowsingData (all time) or ClearCookies instead.All examples below use Registered mode (m_lv As LiteView2Lib.LiteView2Ctrl), the running example for Part I. Registration-Free deployments call the same operations on the pool object instead (pool.MethodName idx, ...); Chapter 17 covers that calling convention and the pool / idx variable names used from there on.
Say frmOrders has an Export Report button that opens a server-rendered report page, and that page offers the export as a normal download link:
<!-- served from https://reports.internal.example.com/orders/export -->
<h1>Orders Report</h1>
<p>Choose a format to download:</p>
<a id="lnkExportCsv" href="/orders/export.csv">Download CSV</a>
<a id="lnkExportPdf" href="/orders/export.pdf">Download PDF</a>There is nothing LiteView2-specific about that HTML — it is a page that offers ordinary download links. What LiteView2-specific behavior determines is what happens when the user clicks one: whether downloads are allowed at all, where the file lands, and whether a download dialog interrupts the user.
Like the rest of the calls in this chapter, these talk to the browser, so set them up once inside WebViewReady (or later) — not Form_Load:
' Called from LiteView2Ctrl1_WebViewReady
Private Sub SetupDownloads()
With m_lv
.AllowDownloads = True
.DefaultDownloadFolderPath = "C:\LiteViewReports\"
.HiddenDownloadUI = True ' no browser download prompt/dialog
End With
End Sub
Private Sub cmdExportReport_Click()
m_lv.Navigate "https://reports.internal.example.com/orders/export"
End SubWhat should happen: when the user clicks a download link on the page, the file is saved directly into C:\LiteViewReports\ with no download-progress popup, because HiddenDownloadUI is True. Setting AllowDownloads = False instead blocks every download attempt outright — useful for a kiosk-style form where you never want a file leaving the machine through the browser.
If you would rather show the browser's own download manager (progress, history, "show in folder") instead of running silently, leave HiddenDownloadUI at its default and drive the dialog yourself:
Private Sub cmdShowDownloads_Click()
If Not m_lv.IsDownloadDialogOpen Then
m_lv.OpenDownloadDialog
Else
m_lv.CloseDownloadDialog
End If
End SubIsDownloadDialogOpen is read-only — use it to avoid opening a second copy of the dialog, or to decide whether you need to close it before doing something else with the control.
Caution
if you turn on silent downloads (HiddenDownloadUI = True), the user has no browser-provided indication that anything happened. Build your own confirmation (a status label, a "Report saved" message box) rather than relying on the browser to tell them.
See Part II → Downloads & Cookies for the full property list and reference details.
If you do show the built-in download dialog, DefaultDownloadDialogCornerAlignment, DefaultDownloadDialogMarginX, and DefaultDownloadDialogMarginY control which corner it anchors to and its offset in pixels:
m_lv.DefaultDownloadDialogCornerAlignment = 3 ' LV2_DOWNLOAD_CORNER_BOTTOM_RIGHT
m_lv.DefaultDownloadDialogMarginX = 20
m_lv.DefaultDownloadDialogMarginY = 20These are cosmetic settings, not covered further here — see Part II → Downloads & Cookies for the corner-alignment constants.
A large export can take a while, and you may want a Pause/Resume/Cancel row on frmOrders next to the Export button. PauseDownload, ResumeDownload, and CancelDownload each take a downloadId — the same ID that LiteView2's Download* events hand you as a download starts and progresses. This chapter doesn't cover those events in detail; see Part II → Events for their exact names and parameters. What matters here is the pattern: capture the ID from the event, then use it with these three calls.
Private m_lastDownloadId As String ' captured in a Download* event handler; see Part II -> Events
Private Sub cmdPauseDownload_Click()
If Len(m_lastDownloadId) > 0 Then m_lv.PauseDownload m_lastDownloadId
End Sub
Private Sub cmdResumeDownload_Click()
If Len(m_lastDownloadId) > 0 Then m_lv.ResumeDownload m_lastDownloadId
End Sub
Private Sub cmdCancelDownload_Click()
If Len(m_lastDownloadId) > 0 Then m_lv.CancelDownload m_lastDownloadId
End SubWhat should happen: calling PauseDownload with a valid, still-active ID suspends that transfer; ResumeDownload continues it; CancelDownload stops it and discards the partial file. All three are no-ops (or errors) if the ID doesn't correspond to a download that's still in flight — which is another reason to capture the ID from the event rather than guessing at it.
See Part II → Downloads & Cookies for the method reference, and Part II → Events for how to capture downloadId in the first place.
Cookies are how a site remembers who's logged in. If a Customers portal your Access form embeds via LiteView2 seems to randomly "forget" that the user is signed in — or won't let a second user switch accounts — cookies and cached browsing data are where to look. All of the calls below talk to the browser and its profile, so call them once the browser is ready — from a button handler, a timer or NavigationCompleted. GetCookies and ClearBrowsingDataInTimeRange wait for the browser and fail inside WebViewReady (&H80040203).
Inspect and seed cookies for a URL:
Dim cookiesJson As String
cookiesJson = m_lv.GetCookies("https://portal.example.com") ' JSON array: name, value, attributes
m_lv.SetCookie "sessionId", "abc123", ".example.com", "/" ' pre-seed a session before navigatingFor a "Reset Session" / "Switch User" button, these calls cover the range from a full reset down to a minimal one:
' Broadest: every cookie, or every cookie for the current profile
m_lv.ClearCookies
m_lv.ClearProfileCookies
' Targeted: a bitmask of LiteView2BrowsingDataKinds — e.g. cache + cookies together
m_lv.ClearBrowsingData 256 + 64 ' LV2_DATA_DISK_CACHE + LV2_DATA_COOKIES
' Cookies only (all time), leaving the cache and the rest of the profile alone
m_lv.ClearBrowsingData 64 ' LV2_DATA_COOKIESKnown issue in 1.0.1.0
ClearBrowsingDataInTimeRange returns without an error but clears nothing. Use ClearBrowsingData (all time) or ClearCookies instead.
What should happen: after any of these, the next navigation to a site that was tracking a session via cookies treats the browser as a fresh, logged-out visitor — exactly what a "Reset Session" button needs. ClearCache alone (m_lv.ClearCache) clears only cached page resources, leaving cookies and everything else in the profile untouched — reach for it when the goal is freeing disk space, not resetting a login. ClearBrowsingData 16384 (LV2_DATA_ALL_PROFILE) wipes the entire profile — use it deliberately, not as a casual session reset; for a stuck login, ClearCookies/ClearProfileCookies or the targeted bitmask above is the better tool.
Note
The source material doesn't spell out the exact scope difference between ClearCookies and ClearProfileCookies beyond the wording above — see Part II → Downloads & Cookies for the precise distinction, the full LiteView2BrowsingDataKinds bitmask table, and every other data-kind constant.
A quick summary of which call to reach for:
| Situation | Call |
|---|---|
| Stop the user downloading files at all | AllowDownloads = False |
| Save reports without a download popup | HiddenDownloadUI = True + DefaultDownloadFolderPath |
| Let the user manage downloads themselves | OpenDownloadDialog / CloseDownloadDialog / IsDownloadDialogOpen |
| Pause/resume/cancel one specific download | PauseDownload / ResumeDownload / CancelDownload downloadId |
| Inspect what a site has stored for a login | GetCookies |
| Pre-seed a session cookie | SetCookie |
| Reset a stuck login, minimal blast radius | ClearCookies / ClearProfileCookies or ClearBrowsingData with LV2_DATA_COOKIES |
| Free disk space used by cached pages | ClearCache |
| Full profile reset | ClearBrowsingData LV2_DATA_ALL_PROFILE |
This chapter covers two things Access and VBA developers ask for constantly: saving whatever LiteView2 is currently showing as a PDF, and grabbing a screenshot of it. From there it covers letting the user print through the normal system dialogs, driving print output from code without a dialog, and checking ahead of time whether the runtime on a given machine even supports these features.
Everything here operates on the page currently loaded in the control — there is no separate "report generator." If m_lv is showing a sales report page, PrintToPdf turns that page into a PDF. If it's showing a dashboard, CaptureScreenshot turns that into a PNG.
Say frmSalesReport hosts m_lv, which is displaying a sales report page — a plain HTML table driven by tblSales, with an on-screen toolbar you don't want in the printed output:
<!-- SalesReport.html — loaded into the LiteView2 control on frmSalesReport -->
<style>
.toolbar { margin-bottom: 12px; }
@media print {
.toolbar { display: none; } /* hide the on-screen buttons in print/PDF output */
}
</style>
<div class="toolbar">
<button onclick="location.reload()">Refresh</button>
</div>
<table id="reportTable">
<thead><tr><th>Order ID</th><th>Customer</th><th>Total</th></tr></thead>
<tbody>
<!-- rows populated from tblSales -->
</tbody>
</table>A command button on the form saves that report to a timestamped PDF on the desktop:
Private Sub cmdSaveReportPdf_Click()
Dim path As String
path = Environ("USERPROFILE") & "\Desktop\SalesReport_" & _
Format(Now(), "yyyymmdd_hhnnss") & ".pdf"
m_lv.PrintToPdf path
End Sub
Private Sub LiteView2Ctrl1_PrintToPdfCompleted(ByVal filePath As String, ByVal success As Boolean)
If success Then MsgBox "Report saved.", vbInformation
End SubWhat should happen: LiteView2 renders the currently loaded page to the PDF at path, and once the file is written the PrintToPdfCompleted event fires on m_lv so you know it's safe to tell the user the report is ready.
Caution
Reg-Free mode has no completion event. pool.PrintToPdf idx, path starts the same PDF write, but it is fire-and-forget: there is no PrintToPdfCompleted to catch. Check Dir(path) <> "" the next time the user does something (closes the form, clicks a "Done" button, etc.), or, if you need a real result signal, wrap the call so the browser process can call back into a host object you supply. Chapter 17 covers the Reg-Free pool/idx pattern in full.
' Reg-Free — no completion event, so poll for the file on the next action:
pool.PrintToPdf idx, path
' ... later, e.g. in the button that closes the report form:
If Dir(path) <> "" Then
MsgBox "Report saved."
End IfSee Part II -> Print & Capture for the full PrintToPdf reference entry.
The same idea applies to a screenshot. Say frmDashboard hosts m_lv showing a live dashboard page:
<!-- Dashboard.html — loaded into the LiteView2 control on frmDashboard -->
<div id="dashboard">
<div class="kpi-card">Total Sales<br><strong>$482,910</strong></div>
<div class="kpi-card">Open Orders<br><strong>37</strong></div>
</div>Private Sub cmdCaptureDashboard_Click()
Dim path As String
path = Environ("USERPROFILE") & "\Desktop\Dashboard_" & _
Format(Now(), "yyyymmdd_hhnnss") & ".png"
m_lv.CaptureScreenshot path
End Sub
Private Sub LiteView2Ctrl1_CaptureScreenshotCompleted(ByVal filePath As String, ByVal success As Boolean)
If success Then MsgBox "Dashboard captured.", vbInformation
End SubWhat should happen: a PNG of the dashboard as currently rendered appears at path, and CaptureScreenshotCompleted fires on m_lv when it's done.
The Reg-Free caveat from 13.1 applies here too, with no differences: pool.CaptureScreenshot idx, path is fire-and-forget, so check Dir(path) <> "" on the next user action rather than waiting for an event.
See Part II -> Print & Capture for the full CaptureScreenshot reference entry.
For a straight "print this" or "save this" button, three members hand off to Windows' own dialogs instead of doing the work silently.
Private Sub cmdPrintReport_Click()
m_lv.PrintPage
End SubThis sends the current page straight to the default printer with no dialog. Registration-Free mode has no equivalent silent call — its documented substitute is ShowPrintDialog (pool.ShowPrintDialog idx), so a Reg-Free "print" button always shows the picker rather than printing silently. Chapter 17 covers the pool/idx pattern.
Private Sub cmdPrint_Click()
m_lv.ShowPrintDialog
End SubThis opens the normal Windows print dialog for the current page, letting the user pick a printer, copies, and so on. Reg-Free: pool.ShowPrintDialog idx.
Private Sub cmdSaveAs_Click()
m_lv.ShowSaveAsDialog
End SubThis opens the standard Save As dialog for the current page. Reg-Free: pool.ShowSaveAsDialog idx.
See Part II -> Print & Capture for the reference entries on all three.
PrintWithSettings and PrintToPdfWithSettings skip the dialog entirely: you set the properties you care about on m_lv, then call the method. Every print-related property behaves the same way — read it like any other property, set it, and it stays in effect until you change it again.
Landscape, background graphics included, straight to the printer:
Private Sub cmdPrintReportLandscape_Click()
m_lv.PrintOrientation = 1 ' LV2_PRINT_ORIENTATION: 0=Portrait, 1=Landscape
m_lv.PrintShouldPrintBackgrounds = True
m_lv.PrintWithSettings
End SubLandscape with tight margins, straight to a PDF file:
Private Sub cmdSaveReportPdfNarrow_Click()
m_lv.PrintOrientation = 1
m_lv.PrintMarginTop = 0.25
m_lv.PrintMarginBottom = 0.25
m_lv.PrintToPdfWithSettings Environ("USERPROFILE") & "\Desktop\Report.pdf"
End SubNeither call shows a dialog, and the source doesn't document a completion event for either one in Registered mode the way it does for PrintToPdf and CaptureScreenshot — treat the call as returning once printing/writing has been kicked off.
Reg-Free code sets and reads the same properties through paired Get<Name>/Set<Name> calls that take the browser index instead of plain property syntax, e.g. pool.SetPrintOrientation idx, 1 and pool.GetPrintOrientation idx, orient. Chapter 17 covers that pattern; the shape is identical for every property below.
You are not limited to the two properties shown above. All of these follow the same set-property-then-call pattern and are read/write on m_lv:
| Property | Type | Notes |
|---|---|---|
PrintOrientation | LV2_PRINT_ORIENTATION | 0 = Portrait, 1 = Landscape |
PrintScaleFactor | Double | default 1.0 |
PrintMarginTop / PrintMarginBottom / PrintMarginLeft / PrintMarginRight | Double | inches |
PrintPageWidth / PrintPageHeight | Double | inches |
PrintShouldPrintBackgrounds | Boolean | print background graphics |
PrintShouldPrintSelectionOnly | Boolean | print only the current selection |
PrintShouldPrintHeaderAndFooter | Boolean | print header/footer |
PrintHeaderTitle | String | custom header title |
PrintFooterUri | String | custom footer URI |
PrintCopies | Long | number of copies |
PrintPagesPerSide | Long | N-up printing |
PrintPageRanges | String | e.g. "1-3,5,7" |
See Part II -> Print & Capture for the full reference entry — type, default, and both calling forms — for each of these properties, plus PrintWithSettings and PrintToPdfWithSettings themselves.
Not every runtime a customer has installed supports every capability. HasPrintToPdf and HasCapturePreview let you check before you show a "Save as PDF" or "Capture" button at all, rather than letting the user click it and get nothing.
If m_lv.HasPrintToPdf Then
cmdSaveReportPdf.Enabled = True
Else
cmdSaveReportPdf.Enabled = False
End If
Debug.Print "Screenshot support: " & m_lv.HasCapturePreviewBoth are read-only Boolean properties. Reg-Free reads them the same way as the other properties in this chapter, but as a function call that takes the index: pool.HasPrintToPdf(idx) and pool.HasCapturePreview(idx).
Run this check once when the form loads (or the control finishes initializing — see Chapter 4) and use the result to enable or disable the relevant buttons, rather than checking it on every click.
OpenTaskManagerWindow opens the browser's own task manager window — useful while you're troubleshooting a page that's hanging or eating memory, not something you'd wire up to end-user UI.
m_lv.OpenTaskManagerWindowReg-Free: pool.OpenTaskManagerWindow idx.
Every navigation so far in this book has been a plain GET: m_lv.Navigate "https://...". Two situations come up in real applications that a plain GET doesn't cover. First, you sometimes need to send data with the navigation itself — a POST body and headers, the way an HTML form submits. Second, some sites you navigate to are protected by HTTP authentication rather than a login page, and you need to supply credentials instead of waiting for a browser prompt. A third, more advanced capability — watching or answering the page's own requests before they reach the network — is covered at the end of this chapter, after the two common cases.
Say frmCustomers collects a new customer's name and email, and instead of writing to a local table you need to submit that data to an internal web API and show whatever confirmation page it returns.
Dim postBody As String
postBody = "name=" & Me.txtCustomerName & "&email=" & Me.txtCustomerEmail
m_lv.NavigateWithWebResourceRequest _
"https://api.example.com/submit", _
"POST", _
postBody, _
"Content-Type: application/x-www-form-urlencoded"What should happen: LiteView2 issues the POST with the body and header you supplied, and navigates to whatever page the server sends back — the same end result as if an HTML <form method="post"> on the page had been submitted, except you built it entirely from VBA.
Registration-Free calls the same operation through the pool object: pool.NavigateWithWebResourceRequest idx, url, method, postData, headers. Chapter 17 covers the pool/idx pattern used for Reg-Free code throughout this book.
See Part II → Web Resource Requests for the full parameter reference.
NavigateWithWebResourceRequest builds and navigates in a single call. CreateWebResourceRequest splits that into two steps: build a request object first — getting back a Long handle — then navigate with it separately. Reach for this form when the request needs to be assembled in one place and fired from another, for example approving an order from a toolbar button after the request body was already put together when the order loaded.
Dim hReq As Long
hReq = m_lv.CreateWebResourceRequest("POST", "https://api.example.com/submit", _
"Content-Type: application/json", "{""OrderID"":1042,""Status"":""Approved""}")
m_lv.NavigateWithCustomRequest hReqCreateWebResourceRequest takes method, URL, header string, and POST body, in that order, and returns the handle. The same call works for a GET carrying only a header, with no body:
Dim hReq As Long
hReq = m_lv.CreateWebResourceRequest("GET", "https://api.example.com/data", _
"Authorization: Bearer token123", "")Registration-Free: pool.CreateWebResourceRequest(idx, method, url, headers, postData) returns the handle, and pool.NavigateWithCustomRequest idx, hReq performs the navigation.
Both forms above return immediately — the navigation happens asynchronously, and you'd normally learn it finished from the NavigationCompleted event. When your code needs the result before it can continue — a report routine that submits a request and then immediately has to read the page that comes back — use the synchronous variant with a handle from CreateWebResourceRequest:
Dim hReq As Long
hReq = m_lv.CreateWebResourceRequest("POST", "https://api.example.com/data", _
"Content-Type: application/json", "{""key"":""value""}")
m_lv.NavigateWithWebResourceRequestSync hReq
' Execution resumes here when the next navigation completes (see the caution below).Caution
this call blocks your VBA code until NavigationCompleted or SynchronousTimeOut (messages keep pumping, so the UI stays responsive). Use it for short, predictable internal requests — not for navigating to arbitrary external pages where load time isn't under your control. Call it from ordinary code (a button, a timer) or from a notification event such as NavigationCompleted; inside WebViewReady, NavigationStarting, the other inline events and host-object methods it fails at once with &H80040203 and starts no navigation (use NavigateWithCustomRequest there). Known issue in 1.0.1.0: the wait ends at the next NavigationCompleted, whichever navigation it belongs to. If a navigation is already in progress, the call can return when that other navigation completes, before its own page has loaded. Use Navigate + NavigationCompleted when navigations can overlap.
Registration-Free: pool.NavigateWithWebResourceRequestSync idx, hReq.
See Part II → Web Resource Requests for more on the synchronous variant and the request handle's lifetime.
The most common case: some internal sites — a reporting server, an internal dashboard — sit behind HTTP Basic Authentication rather than a login form. Rather than waiting for the browser to prompt, check support and register credentials for a URI pattern before you navigate:
If m_lv.HasAuthenticationSupport Then
m_lv.SetBasicAuthCredentials "https://api.example.com/*", "username", "password"
End If
m_lv.Navigate "https://api.example.com/dashboard"
' Later, to remove the stored credentials:
m_lv.ClearBasicAuthCredentialsWhat should happen: any request whose URL matches the pattern is authenticated with the stored credentials automatically; no prompt appears. HasAuthenticationSupport is a capability check worth making first, since it's read-only and tells you whether the rest of this section applies to the current environment at all.
Registration-Free: the same three calls, indexed — pool.SetBasicAuthCredentials idx, uriPattern, username, password, pool.ClearBasicAuthCredentials idx, pool.HasAuthenticationSupport(idx).
A couple of related mechanisms exist for less common cases, covered in full in Part II → Authentication: the BasicAuthenticationRequested event lets you react at the moment a site actually challenges for credentials, instead of pre-registering them; AllowSingleSignOnUsingOSPrimaryAccount = True lets an intranet site that supports Windows Integrated Authentication sign the user in silently with their Windows account, with no credential prompt at all.
Everything up to this point handles requests your code initiates. The page itself also makes requests — for its own scripts, images, and API calls — and LiteView2 lets you see, block, or answer those before they reach the network. Most applications never need this; reach for it when you're building something like a tracker/ad blocker, a request auditor, or a page that must be prevented from calling out to certain domains.
A filter is a URL pattern plus a resource-type code. A request only becomes visible to interception once it matches an active filter — matching by itself does not block anything (see the next section).
' 0 = all resource types
m_lv.AddWebResourceRequestedFilter "*.doubleclick.net*", 0
m_lv.AddWebResourceRequestedFilter "*.googlesyndication.com*", 0
' 3 = Image — block only images from this domain
m_lv.AddWebResourceRequestedFilter "*.ads.example.com*", 3Remove one filter, or clear all of them:
m_lv.RemoveWebResourceRequestedFilter "*.doubleclick.net*", 0
m_lv.ClearAllWebResourceRequestedFiltersRegistration-Free: pool.AddWebResourceRequestedFilter idx, pattern, resourceType, pool.RemoveWebResourceRequestedFilter idx, pattern, resourceType, pool.ClearAllWebResourceRequestedFilters idx.
See Part II → Web Resource Interception for the full resource-type code list.
Filters alone only mark which requests are intercepted. To actually block the matches, enable blocking:
m_lv.AddWebResourceRequestedFilter "*.ads.example.com*", 0
m_lv.SetWebResourceBlocking TrueCheck whether blocking is currently on, and see which filters are active, as a JSON array:
Debug.Print "Blocking enabled: " & m_lv.IsWebResourceBlockingEnabled
Debug.Print m_lv.GetWebResourceRequestedFiltersRegistration-Free: pool.SetWebResourceBlocking idx, True, pool.IsWebResourceBlockingEnabled(idx), pool.GetWebResourceRequestedFilters(idx).
For a dashboard tile showing how much has been blocked in the current session:
Debug.Print "Blocked: " & m_lv.BlockedRequestCount
m_lv.ResetBlockedRequestCountRegistration-Free: the property becomes a method — pool.GetBlockedRequestCount(idx) — and resetting is pool.ResetBlockedRequestCount idx.
Intercepting a request doesn't limit you to blocking it silently — you can hand back your own response body, status code, and headers. This is how you'd serve an actual "403 Forbidden" page instead of a broken image or a blank result:
Dim hResp As Long
hResp = m_lv.CreateWebResourceResponse("<h1>Blocked</h1>", 403, "Forbidden", _
"Content-Type: text/html")This is meant to be built inside a WebResourceRequested handler (next section) — that's where you supply the response object for the request you're answering.
Registration-Free: pool.CreateWebResourceResponse(idx, body, statusCode, statusText, headers).
Two events cover interception at the request and response stages:
WebResourceRequested fires for each request that matches an active filter — this is where you inspect the request and, if you choose, hand back a CreateWebResourceResponse result to answer it yourself.WebResourceResponseReceived fires after a response comes back, but only once tracking is turned on:m_lv.EnableWebResourceResponseTracking TrueRegistration-Free: pool.EnableWebResourceResponseTracking idx, True.
See Part II → Events for both handler signatures, and Part II → Web Resource Interception for how the request and response objects are represented.
Three related properties round out this chapter. ProxyServer and ProxyBypassList (both String, read/write, and both pre-init only — set before the control initializes, not after) configure an outbound proxy and its bypass list. IgnoreCertificateErrors (Boolean, read/write) suppresses SSL certificate validation and is documented for development and testing only; leaving it True in a production deployment defeats certificate checking entirely. Registration-Free exposes the same settings as pool.SetProxyServer/pool.GetProxyServer, pool.SetProxyBypassList, and pool.SetIgnoreCertificateErrors, each taking idx. See Part II → Proxy and Network Settings for the full configuration reference.
Every example so far has used one LiteView2Ctrl on one form. That covers a large share of real Access and VB6 applications — a single embedded browser showing a report, a dashboard, or a document viewer. This chapter is for the cases where one browser is not enough: comparing two customer sessions side by side, keeping a login isolated from the rest of the app, or managing a bank of report panels that get created and torn down as the user navigates.
Say this plainly, because it trips people up: Registered Mode is one ActiveX control instance per form. There is no multi-browser pool concept in Registered Mode. m_lv is one browser. If you drop a second LiteView2Ctrl on the form, you get a second, completely independent control — that works, but it does not scale past a handful of fixed, designed-at-build-time panels, and it does not give you dynamic profiles, parking, or runtime-created instances.
If what you actually need is several independent browser instances — created and destroyed at runtime, each with its own login, each addressable by an index — that is Reg-Free Mode. Reg-Free gives you a pool object (obtained from GetPool(), per the shared-module pattern used throughout this book) and every browser you create through it comes back as an integer index. The rest of this chapter previews the pool members you need for the multi-browser, multi-profile, multi-panel scenario. Chapter 17 covers Reg-Free deployment — registration, the DLL layout, and the pool lifecycle — in full.
For the rest of this chapter, pool (As Object, from GetPool()) and idx (As Long, the index returned by a create call) are the variable names used throughout, matching the convention used from Chapter 17 onward.
Suppose frmDashboard in a support-desk Access application needs two browsers open at once: one showing an internal orders report, one showing the external customer portal. Each needs to exist, resize, and close independently of the other — that's the case for two pool-created instances.
Dim pool As Object
Dim idxReport As Long
Dim idxPortal As Long
Set pool = GetPool() ' shared module: handles ActivateLicense + pool setup, see Chapter 17
' A browser in its own raw window, positioned and sized explicitly
idxReport = pool.Create(Me.hWnd, "https://intranet.example.com/orders/report", 10, 10, 800, 600)
' A second, independent browser embedded directly inside a Frame control on the form
idxPortal = pool.CreateInControl("about:blank", Me.FraPortal)Create opens a browser positioned against a window handle you supply (Me.hWnd here, so it's a child window of the Access form) at the given x, y, width, and height. CreateInControl skips the manual positioning and embeds the browser directly inside a Frame control you've already placed on the form — the same way m_lv sits inside the form in Registered Mode, except now you can have as many of them as you want, and each one hands back its own idx.
Both calls return Long — the index for that browser. Most other pool methods in this chapter that act on a specific browser take that index as their first (or only) parameter to say which browser it applies to; a few — like DestroyAllParked, GetParkedBrowserCount, GetBrowserCount, and the runtime-detection calls in 15.6 — apply to the pool as a whole and take no index at all.
What should happen: two browsers appear — one in the raw window area, one inside FraPortal — each navigable and destroyable independently through idxReport and idxPortal.
See Part II → Pool-Only: Create / CreateInControl for the full parameter reference.
An index alone gives you separate browser instances, but by default they can still share the same underlying browsing data. When two panels need genuinely isolated sessions — for example, a support supervisor comparing what two different customer accounts see when logged into the same portal — you create each browser against a named profile.
Dim idxCustomerA As Long
Dim idxCustomerB As Long
idxCustomerA = pool.CreateWithProfile(Me.hWnd, "https://portal.example.com/login", 0, 0, 800, 600, "CustomerA")
idxCustomerB = pool.CreateInControlWithProfile("https://portal.example.com/login", Me.FraCustomerB, "CustomerB")
' A private profile - use for a one-off check you don't want persisted to disk under any profile name
Dim idxAudit As Long
idxAudit = pool.CreateWithProfileInPrivate(Me.hWnd, "https://portal.example.com/login", 0, 0, 800, 600, "AuditCheck")CreateWithProfile is the raw-window form of Create with a profile name added as the last argument; CreateInControlWithProfile is the same idea for CreateInControl. Browsers created against the same profile name share cookies, cache, and login state; browsers on different profile names do not — that's what lets idxCustomerA and idxCustomerB stay logged in as two different customers at the same time without one login stomping the other. CreateWithProfileInPrivate still takes a profile name but creates the session as a private/in-private browsing context.
Once a browser exists, you can look its profile back up, and clear that profile's stored browsing data:
Debug.Print pool.GetProfileName(idxCustomerA) ' "CustomerA"
Debug.Print pool.GetProfilePath(idxCustomerA) ' folder on disk backing that profile
pool.ClearProfileBrowsingData idxCustomerA, 16384 ' 16384 is a data-category flag - see Part II for the full flag list
pool.ClearProfileBrowsingDataAll idxCustomerA ' clear everything for this profileWhat should happen: idxCustomerA and idxCustomerB can be logged into the customer portal as two different accounts simultaneously, and GetProfileName/GetProfilePath let you confirm which on-disk profile a given index is actually using before you clear anything.
See Part II → Pool-Only: Profile Creation and Pool-Only: Profile Management for the full parameter and flag reference.
A dashboard often needs to move a browser between panels at runtime — collapsing one panel and expanding another, or promoting an embedded panel out into its own popup window. In Reg-Free Mode this is direct pool manipulation by index:
pool.Resize idxPortal, 0, 0, 1024, 768
pool.EmbedInControl idxPortal, Me.FraOtherPanel
pool.Reparent idxPortal, newHwnd, 0, 0, 800, 600Resize changes the browser's width and height in place. EmbedInControl moves an existing browser (already created, identified by idx) into a different Frame control — useful when the user switches which tab or panel should host it. Reparent moves it to a different raw window handle entirely, which is how you'd promote an embedded panel into its own top-level window.
Registered Mode has no Reparent/EmbedInControl, because there's only ever one browser (m_lv) to move — but that one browser can still be relocated into an external window handle, resized, moved, and closed:
m_lv.EmbedInHwnd CLngLng(hwndParent), 800, 600
m_lv.EmbedInHwndByLong CLng(hwndParent), 800, 600 ' VB6 compat
m_lv.ResizeEmbedded 1024, 768
m_lv.MoveEmbedded 100, 50
m_lv.CloseEmbeddedEmbedInHwnd takes a raw Win32 window handle rather than a Frame control — useful when the target isn't a VB6/Access control at all, just a window handle you obtained some other way. EmbedInHwndByLong is the same call with a Long handle parameter for VB6 compatibility. ResizeEmbedded, MoveEmbedded, and CloseEmbedded then manage that one embedded browser. The distinction to keep straight: these Registered-Mode calls relocate and resize the single existing browser — they do not create a second one.
See Part II → Pool-Only: Resize / EmbedInControl / Reparent and → EmbedInHwnd / EmbedInHwndByLong for the full reference.
A dashboard that creates report panels on demand also needs to close them without leaking browser processes. The pool gives you three ways to remove a browser, plus bulk cleanup and counters:
pool.Destroy idxPortal ' Async destroy - returns immediately
pool.DestroySync idxAudit ' Sync destroy - blocks until fully torn down
pool.Park idxCustomerA ' Hide for reuse, instead of destroying
pool.DestroyAllParked ' Clean up every parked browser at once
Debug.Print pool.GetParkedBrowserCount
Debug.Print pool.GetBrowserCountDestroy tears a browser down asynchronously — the call returns before teardown necessarily finishes. DestroySync blocks until the browser is fully gone, which matters if you need certainty before proceeding (for example, right before the Access application closes). Park is the cheaper alternative to destroying: it hides the browser instead of tearing it down, so if the user comes back to that panel you can reuse it instead of paying browser-startup cost again. DestroyAllParked reclaims every parked browser in one call. GetParkedBrowserCount and GetBrowserCount let you track how many browsers are parked versus live at any point — useful for a diagnostic screen or before deciding whether to park or destroy.
Caution
DestroySync blocks the calling thread until teardown completes. Call it from an ordinary event handler (a button click, Form_Unload) the way the examples in this book do — not from inside a callback the browser's own message pump is waiting on (a WebMessageReceived-style handler or similar). If you're unsure whether a given call site qualifies, prefer the async Destroy and confirm completion via GetBrowserCount instead.
What should happen: after DestroyAllParked, GetParkedBrowserCount returns 0; GetBrowserCount reflects only the browsers still actually live.
See Part II → Pool-Only: Destroy / DestroySync / Park for the full reference.
Before creating any browser on a machine of unknown configuration, check that the WebView2 Runtime is actually present:
If pool.IsWebView2RuntimeInstalled Then
Debug.Print "Version: " & pool.GetWebView2RuntimeVersion
Debug.Print "Path: " & pool.GetWebView2RuntimePath
Else
MsgBox "The WebView2 Runtime is not installed on this machine.", vbExclamation
End IfIsWebView2RuntimeInstalled is the guard to run first — every Create/CreateInControl/CreateWithProfile call in this chapter depends on that runtime being present. GetWebView2RuntimeVersion and GetWebView2RuntimePath are useful once you know it's installed, for logging or for diagnosing a version-specific issue on a deployed machine.
See Part II → Pool-Only: Runtime Detection for the full reference.
For a given browser index, you can list the extensions currently loaded in that browser's profile:
Debug.Print pool.GetBrowserExtensionList(idxCustomerA)See Part II → Pool-Only: GetBrowserExtensionList for the full reference.
One thing you do not need to do per browser: activate the license. ActivateLicense is process-wide — one successful call activates every LiteView2 browser created afterwards in that Access (or Office) session, so idxReport, idxPortal, idxCustomerA, and idxCustomerB all inherit the same activation with no extra work.
See Chapter 21 for the full licensing story, including trial state and activation failure handling.
The previous chapters each covered one piece of LiteView2 in isolation: loading a page, pushing data into it, calling from JavaScript back into VBA, and closing down cleanly. This chapter puts those pieces together into one screen you could actually ship — an Access form that shows open orders in an HTML dashboard, lets the user mark an order shipped from the page, and updates the real Access data when they do.
The example uses Registered Mode (m_lv As LiteView2Lib.LiteView2Ctrl) throughout, since that's this cookbook's default. One section near the end shows the single documented wiring difference for VB6 and Excel UserForms.
tblOrders (OrderID, CustomerName, OrderDate, Status).frmCustomers, an Access form with a LiteView2Ctrl1 control docked into it, showing a "Customer Orders" dashboard.customers_dashboard.html, loaded through the virtual host mapping you set up in Chapter 3, rendering the open orders into a table inside <div id="divResults-5">.PushRecordset); the page renders them and gives each row a "Mark Shipped" button; clicking it calls back into VBA through a host object (Chapter 5); VBA updates tblOrders with DAO; VBA confirms the change back to the page (Chapter 4/6's ExecuteScriptAsync or PostWebMessageAsJson) so the row updates without a full reload; the form closes using the exit lifecycle from Chapter 2.Each step below shows the piece in isolation with what should happen when it runs. Section 16.9 gives the complete, consolidated listing.
Option Compare Database
Option Explicit
Private m_lv As LiteView2Lib.LiteView2Ctrl
Private Sub Form_Load()
Set m_lv = Me.LiteView2Ctrl1.Object
End Sub
Private Sub LiteView2Ctrl1_WebViewReady()
m_lv.SetLocalContentRoot CurrentProject.Path
m_lv.Navigate "https://lv2.example/customers_dashboard.html"
End SubThis is the same virtual-host Navigate pattern from Chapter 2/3 — nothing new here. Opening frmCustomers should show a blank dashboard page with an empty orders table; the data arrives in the next step, once the page has actually finished loading.
We don't push data immediately after calling Navigate — the page hasn't loaded yet. We wait for the control's NavigationCompleted event, which Access wires up automatically because the control on the form is named LiteView2Ctrl1:
Private Sub LiteView2Ctrl1_NavigationCompleted(ByVal url As String, ByVal success As Boolean)
PushOrdersToPage
End Sub
Private Sub PushOrdersToPage()
Dim rs As DAO.Recordset
On Error GoTo ErrH
Set rs = CurrentDb.OpenRecordset( _
"SELECT OrderID, CustomerName, OrderDate, Status " & _
"FROM tblOrders WHERE Status <> 'Shipped'", dbOpenSnapshot)
m_lv.PushRecordset rs, "renderOrders"
rs.Close
Set rs = Nothing
Exit Sub
ErrH:
MsgBox "Could not load orders: " & Err.Description, vbExclamation
End SubPushRecordset (Chapter 8) hands the open-order rows to the page; the JavaScript side you register there is what actually turns them into HTML. Opening the form should populate the table within a moment of the page appearing, with one row per open order.
See Part II → Recordset Transfer for every PushRecordset overload and column-mapping option.
The page side is plain HTML and JavaScript — the same table-building code you'd write for any local page:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Customer Orders</title>
</head>
<body>
<h1>Open Orders</h1>
<div id="divResults-6"></div>
<script>
function renderOrders(rows) {
var container = document.getElementById('divResults');
var html = '<table border="1" cellpadding="4">' +
'<thead><tr><th>Order</th><th>Customer</th><th>Date</th><th>Status</th><th></th></tr></thead><tbody>';
rows.forEach(function (r) {
html += '<tr data-order-id="' + r.OrderID + '">' +
'<td>' + r.OrderID + '</td>' +
'<td>' + r.CustomerName + '</td>' +
'<td>' + r.OrderDate + '</td>' +
'<td class="status">' + r.Status + '</td>' +
'<td><button onclick="shipOrder(' + r.OrderID + ')">Mark Shipped</button></td>' +
'</tr>';
});
html += '</tbody></table>';
container.innerHTML = html;
}
</script>
</body>
</html>renderOrders is the receiving function on the divResults target that Chapter 8 has you register against PushRecordset; once it runs, the page shows one table row per open order with a "Mark Shipped" button in the last column.
Clicking the button needs to reach VBA with the OrderID of the row that was clicked. That's the host object pattern from Chapter 5: a VBA class exposing a method, registered with the control so the page can call it directly.
' Class module: CHostBridge
Option Explicit
Private m_lv As LiteView2Lib.LiteView2Ctrl
Public Sub Init(ByVal ctrl As LiteView2Lib.LiteView2Ctrl)
Set m_lv = ctrl
End Sub
Public Sub MarkOrderShipped(ByVal OrderID As Long)
' business logic lives here — see 16.6
End Sub' Form module: extends Form_Load and WebViewReady from 16.2
Private m_HostBridge As CHostBridge
Private Sub Form_Load()
Set m_lv = Me.LiteView2Ctrl1.Object
Set m_HostBridge = New CHostBridge
m_HostBridge.Init m_lv
End Sub
Private Sub LiteView2Ctrl1_WebViewReady()
m_lv.SetLocalContentRoot CurrentProject.Path
m_lv.AreHostObjectsAllowed = True
m_lv.AddHostObjectToScript "hostBridge", m_HostBridge
m_lv.Navigate "https://lv2.example/customers_dashboard.html"
End Sub<script>
function shipOrder(orderId) {
window.chrome.webview.hostObjects.hostBridge.MarkOrderShipped(orderId);
}
</script>Clicking "Mark Shipped" should call MarkOrderShipped in VBA with the order's ID, with no page navigation and no round trip through Access UI. Host object calls from JavaScript are asynchronous from the page's point of view, so don't assume the confirmation is back before the click handler returns — that's what step 16.6 sends explicitly.
See Part II → Host Objects & Script Bridging for the full AddHostObjectToScript registration contract.
MarkOrderShipped does the real work: update tblOrders with DAO, then tell the page the row changed so it can update itself without a fresh PushRecordset.
Public Sub MarkOrderShipped(ByVal OrderID As Long)
Dim rs As DAO.Recordset
On Error GoTo ErrH
Set rs = CurrentDb.OpenRecordset( _
"SELECT OrderID, Status FROM tblOrders WHERE OrderID = " & OrderID, _
dbOpenDynaset)
If Not rs.EOF Then
rs.Edit
rs!Status = "Shipped"
rs.Update
End If
rs.Close
Set rs = Nothing
m_lv.ExecuteScriptAsync "updateOrderRow(" & OrderID & ", 'Shipped');"
Exit Sub
ErrH:
' swallow here — a failed confirmation shouldn't crash the page's click handler
End Sub<script>
function updateOrderRow(orderId, status) {
var row = document.querySelector('tr[data-order-id="' + orderId + '"]');
if (row) {
row.querySelector('.status').textContent = status;
}
}
</script>ExecuteScriptAsync (Chapter 4) runs updateOrderRow(...) directly in the page. After clicking "Mark Shipped", the row's Status cell should change to "Shipped" in place, and tblOrders on disk now reflects it too.
PostWebMessageAsJson (Chapter 6) is the other option for this same step: post {"type":"orderShipped","orderId":123} from VBA and have the page listen with window.chrome.webview.addEventListener('message', ...) instead of exposing a named JS function. Either gets the confirmation to the page; ExecuteScriptAsync is simpler when you already know which function to call, PostWebMessageAsJson is preferable when several unrelated pieces of the page need to react to the same event.
See Part II → Script Execution & Messaging for both APIs in full, including error handling on the JS side.
WithEventsEverything above assumes an Access form, where dropping LiteView2Ctrl1 onto the form gives you Private Sub LiteView2Ctrl1_NavigationCompleted(ByVal url As String, ByVal success As Boolean) and the rest of the design-time event stubs for free, and where Me.LiteView2Ctrl1.Object is the documented way to unwrap Access's own control wrapper into a typed reference (Chapter 2 explains why .Object is needed there).
The Cookbook also documents a second, explicit event-wiring pattern that works with LiteView2 as a plain COM object, independent of any particular host: declare the reference with WithEvents, and VBA generates Private Sub <VariableName>_<EventName> handlers keyed to that variable name instead of a control's instance name.
Private WithEvents m_lv As LiteView2Lib.LiteView2Ctrl
Private Sub m_lv_NavigationCompleted(ByVal url As String, ByVal success As Boolean)
If success Then PushOrdersToPage
End SubThis is a real, working pattern for the control's events. What this source material does not establish is whether a design-time-placed LiteView2 control on a VB6 Form or an Excel UserForm also generates instance-named event stubs the way Access does, or whether WithEvents is the only route to its events in those hosts — that is host-specific IDE behavior this Cookbook doesn't test or document either way. The same caution applies to acquiring the reference (whether .Object is needed outside Access) and to the project-path equivalent of CurrentProject.Path in each host (VB6: App.Path; Excel: ThisWorkbook.Path) — do not assume Access's syntax carries over unchanged. Treat WithEvents as a documented, portable event-wiring option, not a proven requirement for any specific non-Access host, and verify the exact acquisition/wiring syntax against your actual VB6 or Excel project before shipping it.
The LiteView2 method calls themselves — PushRecordset, AddHostObjectToScript, ExecuteScriptAsync, PostWebMessageAsJson, the exit-lifecycle calls in 16.8 — are ordinary calls on the m_lv object and do not depend on which event-wiring style the host uses.
See Part II → Events Reference for the full list — all 63 events from _DLiteView2CtrlEvents (DISPIDs 1-48, 60-64, 600-609) — available through either wiring style.
Private Sub Form_Close()
On Error Resume Next
If Not m_lv Is Nothing Then
m_lv.Stop ' halt any in-flight navigation before teardown
Set m_lv = Nothing ' release the reference; Access destroys the control next
End If
End Subm_lv.Stop halts a navigation that might still be in flight when the user closes the form — without it, the page can fire NavigationCompleted while the control is being destroyed. Set m_lv = Nothing releases the VBA reference; Access then destroys the ActiveX control as part of normal form teardown, and LiteView2's own destructor drains any pending callbacks and releases the WebView2 underneath it.
On Error Resume Next matters here because the form may be closing due to an earlier error elsewhere; a second error popping a MsgBox during shutdown produces a half-stuck form. Any genuine bug is already caught by the On Error GoTo ErrH blocks in Form_Load and PushOrdersToPage above — teardown just needs to finish quietly.
Registration-Free mode needs the pool equivalent — a synchronous teardown before releasing the pool reference, or the WebView2 host process keeps Access alive after the form closes; Chapter 17 covers that deployment model in full.
frmCustomers (Access form module)
Option Compare Database
Option Explicit
Private m_lv As LiteView2Lib.LiteView2Ctrl
Private m_HostBridge As CHostBridge
Private Sub Form_Load()
Set m_lv = Me.LiteView2Ctrl1.Object
Set m_HostBridge = New CHostBridge
m_HostBridge.Init m_lv
End Sub
Private Sub LiteView2Ctrl1_WebViewReady()
m_lv.SetLocalContentRoot CurrentProject.Path
m_lv.AreHostObjectsAllowed = True
m_lv.AddHostObjectToScript "hostBridge", m_HostBridge
m_lv.Navigate "https://lv2.example/customers_dashboard.html"
End Sub
Private Sub LiteView2Ctrl1_NavigationCompleted(ByVal url As String, ByVal success As Boolean)
PushOrdersToPage
End Sub
Private Sub PushOrdersToPage()
Dim rs As DAO.Recordset
On Error GoTo ErrH
Set rs = CurrentDb.OpenRecordset( _
"SELECT OrderID, CustomerName, OrderDate, Status " & _
"FROM tblOrders WHERE Status <> 'Shipped'", dbOpenSnapshot)
m_lv.PushRecordset rs, "renderOrders"
rs.Close
Set rs = Nothing
Exit Sub
ErrH:
MsgBox "Could not load orders: " & Err.Description, vbExclamation
End Sub
Private Sub Form_Close()
On Error Resume Next
If Not m_lv Is Nothing Then
m_lv.Stop
Set m_lv = Nothing
End If
End SubCHostBridge (class module)
Option Explicit
Private m_lv As LiteView2Lib.LiteView2Ctrl
Public Sub Init(ByVal ctrl As LiteView2Lib.LiteView2Ctrl)
Set m_lv = ctrl
End Sub
Public Sub MarkOrderShipped(ByVal OrderID As Long)
Dim rs As DAO.Recordset
On Error GoTo ErrH
Set rs = CurrentDb.OpenRecordset( _
"SELECT OrderID, Status FROM tblOrders WHERE OrderID = " & OrderID, _
dbOpenDynaset)
If Not rs.EOF Then
rs.Edit
rs!Status = "Shipped"
rs.Update
End If
rs.Close
Set rs = Nothing
m_lv.ExecuteScriptAsync "updateOrderRow(" & OrderID & ", 'Shipped');"
Exit Sub
ErrH:
' swallow here — a failed confirmation shouldn't crash the page's click handler
End Subcustomers_dashboard.html
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Customer Orders</title>
</head>
<body>
<h1>Open Orders</h1>
<div id="divResults-7"></div>
<script>
function renderOrders(rows) {
var container = document.getElementById('divResults');
var html = '<table border="1" cellpadding="4">' +
'<thead><tr><th>Order</th><th>Customer</th><th>Date</th><th>Status</th><th></th></tr></thead><tbody>';
rows.forEach(function (r) {
html += '<tr data-order-id="' + r.OrderID + '-2">' +
'<td>' + r.OrderID + '</td>' +
'<td>' + r.CustomerName + '</td>' +
'<td>' + r.OrderDate + '</td>' +
'<td class="status">' + r.Status + '</td>' +
'<td><button onclick="shipOrder(' + r.OrderID + ')">Mark Shipped</button></td>' +
'</tr>';
});
html += '</tbody></table>';
container.innerHTML = html;
}
function shipOrder(orderId) {
window.chrome.webview.hostObjects.hostBridge.MarkOrderShipped(orderId);
}
function updateOrderRow(orderId, status) {
var row = document.querySelector('tr[data-order-id="' + orderId + '-2"]');
if (row) {
row.querySelector('.status').textContent = status;
}
}
</script>
</body>
</html>Walking through it end to end: opening frmCustomers navigates to the dashboard; NavigationCompleted fires and pushes the open orders in; renderOrders draws the table; clicking "Mark Shipped" calls hostBridge.MarkOrderShipped with the row's OrderID; VBA updates tblOrders and calls back with ExecuteScriptAsync; updateOrderRow flips that one row's status text; closing the form runs Stop then releases m_lv cleanly.
For VB6 or an Excel UserForm, the only change is the two lines from 16.7 — WithEvents on the declaration and m_lv_NavigationCompleted instead of LiteView2Ctrl1_NavigationCompleted.
The core browser capabilities you learned in Chapters 1-16 — navigating pages, passing data between VBA and JavaScript, host objects, the Customers/Orders/Inventory examples — remain available in Registration-Free ("Reg-Free") mode. But the two deployment models are not perfectly identical API surfaces: what changes is not only the mechanics around the browser (how you create it, how you address one once several might be open, how you receive callbacks from it instead of handling events, and how you destroy it correctly), but also that some Registered-only convenience methods, host-object plumbing, and Office/document features have a different pool equivalent or no direct equivalent at all — each one called out where it comes up in this chapter. Part II remains authoritative for exactly which members are Registered-only, Reg-Free-only, or available in both — check there before assuming a method you liked in Chapters 1-16 has a pool counterpart.
This chapter covers the four lifecycle mechanics end to end, and flags the genuine API differences along the way; it does not attempt to make Reg-Free sound like a separate product — it's the same engine and the same core workflows, addressed differently.
LiteView2Ctrl ActiveX control on the form at design time and talk to it as m_lv. The control is a COM class registered on the machine.GetPool() — creates and owns one or more browsers at runtime. Each browser is identified by a Long index, conventionally named idx. You call methods on the pool (pool.Navigate idx, ..., pool.SetLocalContentRoot idx, ..., and so on), passing idx to say which browser you mean.If you've been reading a Reg-Free snippet in an earlier chapter and wondering what pool and idx were — this is where they're explained properly.
modLV2Pool is the bootstrap module that ships with LiteView2 and already exports GetPool(). You don't add a new module for Reg-Free mode — you make two small edits to the one you already have.
Addition 1 — near the top of the module, above the #If Win64 Then / #Else declare blocks:
Private Const LV2_COMPANY As String = "Your Company"
Private Const LV2_KEY As String = "" ' "V1|..." (leave empty for trial)Addition 2 — inside GetPool(), immediately after the existing line Set m_Pool = LiteView2_CreateBrowserPool() and before m_Initialized = True:
If Len(LV2_KEY) > 0 Then m_Pool.ActivateLicense LV2_COMPANY, LV2_KEYThat's the whole edit — everything else in modLV2Pool stays as it was.
Because GetPool() already guards its own creation with an m_Initialized flag, this activation runs exactly once per process, no matter how many forms call GetPool(). Activation is a process-wide event, not something each form does for itself — a form's Form_Load never calls ActivateLicense directly.
This is the template every Reg-Free form in this book is built from. It assumes:
fraBrowser on the form.modLV2Pool already edited per the diff above.' ============================================================================
' Reg-Free Mode — canonical lifecycle template (per form)
' ============================================================================
' Lifecycle rules this template follows:
' 1. The activation key lives in modLV2Pool; GetPool() activates it
' automatically on the first call in the process. Forms never call
' ActivateLicense themselves.
' 2. CreateInControl with "about:blank" is the safe starting point.
' The real Navigate happens in OnBrowserReady, after the virtual
' host is mapped.
' 3. Inside OnBrowserReady the order is: SetLocalContentRoot ->
' SetAreHostObjectsAllowed -> AddHostObjectToScript -> Navigate.
' 4. Form_Unload MUST call ClearReadyCallback and DestroyBrowserSync.
' Without DestroyBrowserSync, the browser process keeps Access
' alive after the form closes.
' ============================================================================
Option Compare Database
Option Explicit
Private pool As Object
Private idx As Long
Private Sub Form_Load()
On Error GoTo ErrH
If Me.CurrentView = 0 Then Exit Sub ' Skip in design view
' GetPool() handles license activation once per process — see modLV2Pool.bas.
Set pool = GetPool()
idx = pool.CreateInControl("about:blank", Me.fraBrowser)
pool.SetReadyCallback idx, Me, "OnBrowserReady"
Exit Sub
ErrH:
MsgBox "Form_Load: " & Err.Number & " " & Err.Description, vbCritical
End Sub
' Public — invoked by the pool by name when the browser is ready.
Public Sub OnBrowserReady()
On Error GoTo ErrH
pool.SetLocalContentRoot idx, CurrentProject.Path
pool.SetAreHostObjectsAllowed idx, True
pool.AddHostObjectToScript idx, "vba", Me
pool.Navigate idx, "https://lv2.example/index.html"
Exit Sub
ErrH:
MsgBox "OnBrowserReady: " & Err.Number & " " & Err.Description, vbCritical
End Sub
' Called by the page via window.chrome.webview.hostObjects.sync.vba.OnReady()
' Runs INSIDE the page's call: synchronous pool calls (pool.ExecuteScript, ...)
' fail here (error &H80004005; pool.GetLastErrorCode(idx) = &H80040203). Use
' pool.ExecuteScriptAsync here, or note that the page is ready and do the
' synchronous work after this call has returned (for example from Form_Timer).
Public Sub OnReady()
End Sub
Private Sub Form_Unload(Cancel As Integer)
On Error Resume Next
If Not pool Is Nothing And idx <> 0 Then
pool.Stop idx ' halt any in-flight navigation
pool.ClearReadyCallback idx ' detach our callback so it can't fire after teardown
pool.DestroyBrowserSync idx, 2000 ' synchronous browser teardown (max 2 s wait)
idx = 0
End If
Set pool = Nothing ' release the pool reference
End SubWhat should happen: the form opens with a blank frame, the pool calls your OnBrowserReady once the WebView2 process is up, the page navigates and loads, and closing the form tears the browser down synchronously before the form finishes unloading.
Caution
Why DestroyBrowserSync matters more here than anywhere else in this book: in Reg-Free mode, the browser is a child process (msedgewebview2.exe) that the pool spawned on your host process's behalf. If you close the form without destroying the browser synchronously, that child process can keep the host process alive — MSACCESS.EXE (or your VB6 .exe) sits in Task Manager after every window looks closed, because the browser process it owns hasn't actually gone away yet. DestroyBrowserSync idx, 2000 blocks until teardown finishes (or 2 seconds pass) specifically to prevent that. This is the single most important safety rule in this chapter — everything else in Reg-Free mode is convenience; this one is correctness.
Note
Migrating from the old Form_Timer + IsReady() polling pattern? Delete the timer. Replace the body of Form_Load with a single pool.SetReadyCallback idx, Me, "OnBrowserReady" call. Move whatever ran inside the If m_Pool.IsReady(...) Then block into a public OnBrowserReady() sub on the same form. That's the entire migration.
| Stage | Registered (Ch. 1-16) | Reg-Free |
|---|---|---|
| Get a browser | Drop LiteView2Ctrl on the form at design time; reference it as m_lv | Set pool = GetPool() then idx = pool.CreateInControl("about:blank", Me.fraBrowser) |
| Know it's ready | Handle the control's own ready event (WithEvents) — see Chapter 2 | pool.SetReadyCallback idx, Me, "OnBrowserReady" invokes a Public Sub you name |
| Load content | m_lv.Navigate ..., typically inside the ready event | pool.Navigate idx, ..., inside OnBrowserReady |
| Receive messages from JS | Handle the control's message event | pool.SetWebMessageCallback idx, Application, "CB_frmX" invokes a Public Sub you name in a standard module |
| Clean up | The control is torn down along with the form; little explicit teardown | Form_Unload must call pool.Stop, pool.ClearReadyCallback, pool.DestroyBrowserSync idx, 2000, then Set pool = Nothing |
The right-hand column is everything the rest of this chapter walks through in detail.
Registered mode uses COM events (WithEvents). Reg-Free mode has no control instance to raise events from — instead, the pool calls a Public Sub you name, on an object you supply, when something happens. You already used this once above: SetReadyCallback.
SetReadyCallback / ClearReadyCallbackCovered in full above. SetReadyCallback idx, Me, "OnBrowserReady" registers the sub the pool calls once the browser is ready (or immediately, if it's already ready when you register). ClearReadyCallback idx detaches it — always call this in Form_Unload before destroying the browser, so a late callback can't fire into a form that's going away.
SetWebMessageCallback / ClearWebMessageCallbackThis is the Reg-Free equivalent of the message-received event you'd handle in Registered mode. Register it the same way you registered readiness:
pool.SetWebMessageCallback idx, Application, "CB_frmX"
...
pool.ClearWebMessageCallback idx ' pair with ClearReadyCallback in Form_Unload
' In a standard module:
Public Sub CB_frmX(ByVal message As String, Optional ByVal source As String = "")
' route the message to the form, e.g. Forms("frmX").HandleMessage message
End SubCB_frmX is a Public Sub in a standard module, not on the form: the pool runs it with Application.Run, passing the message and, when it is known, the page URL as source. Chapter 6.3 shows the complete routing pattern. See Part II -> Messaging for the exact parameters LiteView2 passes into it.
On the page side, sending a message is the standard WebView2 call:
<!-- index.html -->
<script>
window.chrome.webview.postMessage(JSON.stringify({ action: "getData", id: 42 }));
</script>Design tip: if more than one Reg-Free form can be open at once (e.g., both frmCustomers and frmOrders embed a browser), don't register SetWebMessageCallback against a sub on each form in isolation without thinking about it — consider pointing it at a Public Sub in a shared standard module instead, and have that one dispatcher look at which idx the callback fired for (keeping your own idx -> form lookup) to forward the message to the correct open form. Otherwise you end up duplicating dispatch logic per form.
RegisterVbaCallback / UnregisterVbaCallbackWhere SetWebMessageCallback gives you one catch-all inbox for anything the page posts, RegisterVbaCallback lets you register distinct, named endpoints that JavaScript can invoke directly:
pool.RegisterVbaCallback idx, "getData", callbackObj, "GetData"
pool.UnregisterVbaCallback idxThis maps the name "getData" to the GetData method on callbackObj for browser idx. Instead of parsing one big message string in OnWebMessage to figure out what the page wants, you register a callback per operation ("getData", "saveRecord", and so on) and let the pool route directly to the matching method.
A small set of helper calls control which VBA procedures a page is allowed to reach, and whether matching calls are dispatched automatically:
pool.SetAllowedVBAMacros idx, "CB_frmMyForm,CB_frmOther"
pool.ClearAllowedVBAMacros idx
pool.EnableAutoDispatch idx, True
Debug.Print pool.IsAutoDispatchEnabled(idx)SetAllowedVBAMacros takes a comma-separated allow-list of procedure names the page may call for that browser index; ClearAllowedVBAMacros empties it. EnableAutoDispatch idx, True turns on automatic dispatch of allowed calls, and IsAutoDispatchEnabled(idx) reads that state back. See Part II -> VBA Callbacks for exactly how auto-dispatch relates to RegisterVbaCallback.
Caution
keep the allow-list narrow. Only list the VBA procedures you actually intend the page to invoke — this is a security boundary, not a convenience list.
The lifecycle template above uses CreateInControl, which anchors a browser inside a Frame control (fraBrowser) and lets LiteView2 manage its size within that frame. There's also a plain Create, which positions the browser as a child window against an hWnd you supply, with explicit coordinates:
Dim idx As Long
idx = pool.Create(Me.hWnd, "https://example.com", 10, 10, 800, 600)
' Or embedded in a control frame, as used in the template above:
idx = pool.CreateInControl("about:blank", Me.fraBrowser)For destroying a browser, the lifecycle template's Form_Unload uses:
pool.DestroyBrowserSync idx, 2000 ' synchronous, max 2 s wait — use thisNote
the source material documents synchronous teardown two different ways in two different places — as DestroyBrowserSync idx, 2000 in the canonical lifecycle template, and elsewhere as a separate DestroySync idx (no timeout parameter shown) alongside an async Destroy idx. This chapter uses DestroyBrowserSync idx, 2000, matching the lifecycle template above; see Part II -> Pool Lifecycle for the authoritative signature.
The pool also supports parking a browser instead of destroying it — useful if a form is opened and closed repeatedly and recreating the browser each time is wasteful:
pool.Park idx ' Hide for reuse instead of destroying
pool.DestroyAllParked ' Clean up everything parked
Debug.Print pool.GetParkedBrowserCount
Debug.Print pool.GetBrowserCountSee Part II -> Pool Lifecycle for the full create/park/destroy reference.
The following, all covered earlier in this book, exist only on the ActiveX control (m_lv). There is no pool equivalent for any of them — leave them out of a Reg-Free design entirely:
CallJsFunctionSetJsVariableEnableLvFacade / IsLvFacadeEnabledJsonToArrayMaxSize as a settable propertyNotifyParentWindowPositionChangedGetDiagnosticsOpenOfficeDocumentWithEvents with the callback model described above (SetReadyCallback, SetWebMessageCallback, RegisterVbaCallback).If a design depends heavily on these, Registered mode is the right deployment choice for it.
ActivateLicense reference and process-wide activation semantics.Create / CreateInControl / Destroy / DestroySync / DestroyBrowserSync / Park / DestroyAllParked in full.SetWebMessageCallback parameter reference.RegisterVbaCallback, UnregisterVbaCallback, and the VBA Callback Helpers in full.Chapter 7 showed you how to read and write individual elements: SetElementValueById, ClickElementBySelector, ElementExistsBySelector, and the rest of the DOM toolkit. Those calls work, but a real page rarely finishes rendering the instant Navigate returns. A login page may swap in its form fields after a script runs. A dashboard may not populate until an AJAX call comes back. If you call ClickElementBySelector a moment too early, the element isn't there yet and the call does nothing useful.
The usual fix in hand-written automation code is a polling loop: check whether the element exists, and if not, wait a bit and check again, until either the element shows up or you give up. That loop is easy to get wrong in VBA — sleep too long and the UI freezes, sleep too short and you burn CPU, forget the timeout and a broken page hangs your procedure forever.
WaitForElement and GetTextBySelector are convenience wrappers built on top of the same DOM primitives from Chapter 7. They don't do anything you couldn't write yourself with ElementExistsBySelector and a loop — they just do it correctly, every time, so you don't have to.
WaitForElement waits for an element matching a CSS selector to appear in the DOM. It returns True if the element showed up before the timeout, False if it didn't.
Under the hood it polls every 50ms using ElementExistsBySelector, and it pumps Windows messages between each check — so your form stays responsive (no frozen UI, no "Not Responding" title bar) while the wait is in progress.
m_lv.Navigate "https://example.com/login"
' Wait up to 10 seconds for the login form
If m_lv.WaitForElement("#loginForm", 10000) Then
Debug.Print "Form appeared!"
Else
Debug.Print "Timed out waiting for form"
End IfIt's just as useful after an action that triggers dynamic content, not just after a fresh navigation:
m_lv.ClickElementBySelector ".load-more-btn"
If m_lv.WaitForElement(".new-results", 5000) Then
Debug.Print "Results loaded"
End IfRegistration-Free uses the pool equivalent — pool.WaitForElement(idx, selector, timeoutMs), with the browser index as the first argument. Chapter 17 covers that deployment model in full.
Caution
— the timeout parameter. If you pass 0 for timeoutMs, WaitForElement does not wait forever and does not return immediately. It uses whatever the control's SynchronousTimeOut property is currently set to (30 seconds by default). Pass an explicit millisecond value when you want a specific wait, and remember that the UI stays responsive throughout — the message pump between polls is what makes that possible.
See Part II → Automation API for the full parameter and return-value reference.
GetTextBySelector returns the text content of the first element matching a CSS selector — the same idea as ElementExistsBySelector, but it hands you the rendered text instead of a yes/no.
Dim welcome As String
welcome = m_lv.GetTextBySelector(".welcome-message")
Debug.Print welcome
Dim price As String
price = m_lv.GetTextBySelector("#product-price .amount")
Debug.Print "Price: " & priceRegistration-Free follows the same pattern as WaitForElement — pool.GetTextBySelector(idx, selector) — again covered in Chapter 17.
See Part II → Automation API for the full reference.
This is the pattern these two helpers exist for: navigate, wait for something to render, interact with it using the Chapter 7 element setters, wait for the result of that interaction, then read the result back out — with no fixed Sleep calls anywhere.
Assume the login page looks roughly like this on the page side:
<form id="loginForm">
<input type="text" id="email" name="email">
<input type="password" id="password" name="password">
<button type="submit">Log In</button>
</form>And the page it redirects to after a successful login contains:
<div class="dashboard-panel">
<p class="welcome-message">Welcome back, Jamie!</p>
</div>The VBA side, in an Access form or Excel module holding m_lv As LiteView2Lib.LiteView2Ctrl:
' Navigate to the login page
m_lv.Navigate "https://example.com/login"
' Wait for the login form to render
If m_lv.WaitForElement("#loginForm", 10000) Then
' Fill the fields using the Chapter 7 ID-based setters
m_lv.SetElementValueById "email", "user@example.com"
m_lv.SetElementValueById "password", "secret123"
' Submit via a CSS selector
m_lv.ClickElementBySelector "#loginForm button[type='submit']"
' Wait for the resulting dashboard to appear
If m_lv.WaitForElement(".dashboard-panel", 15000) Then
Dim msg As String
msg = m_lv.GetTextBySelector(".welcome-message")
Debug.Print "Logged in: " & msg
Else
Debug.Print "Dashboard never appeared — login may have failed"
End If
Else
Debug.Print "Login form never appeared"
End IfWhat should happen: the form navigates, the Immediate window prints "Form appeared!"-equivalent progress (or in this combined version, silently proceeds), the two fields get filled, the submit button click fires the page's own login logic, and once the server responds and the dashboard markup lands in the DOM, GetTextBySelector reads back something like Logged in: Welcome back, Jamie!. All of that happens without your code ever guessing how long the login redirect will take — WaitForElement finds out for you.
If either WaitForElement call returns False, you know exactly where the page failed to progress — the login form never rendered, or the dashboard never showed up after submit — instead of your code marching ahead and calling GetTextBySelector against a page that isn't ready, which would return an empty or stale result.
WaitForElement and GetTextBySelector cover the two most common waits: "did an element show up" and "what does an element say." For the full method signatures, additional parameters, and the Registration-Free calling convention in detail, see Part II → Automation API. For everything else on the DOM side — setting values, clicking, checking existence — see Chapter 7. For deploying this same login workflow from a Reg-Free pool instead of the registered control, see Chapter 17.
Everything in this chapter is optional. None of it is needed to build an ordinary Customers/Orders/Inventory front end with LiteView2 — the earlier chapters already cover navigation, the JS bridge, and document display. Come back to this chapter when you specifically need to: debug a page you're hosting using Chrome's own developer tools, edit an Office document in place inside the control, embed the control into a window you don't own, or automate a page that actively tries to detect and block automated browsers.
LiteView2 hosts a real Chromium browser, and Chromium ships with the same Chrome DevTools Protocol (CDP) that powers the DevTools window you get from pressing F12 in a normal Chrome tab. CDP is a JSON-based protocol for controlling and inspecting the browser: capturing screenshots, watching network requests, and so on. LiteView2 exposes two ways to use it: open the interactive DevTools window yourself, or call CDP methods directly from VBA without any window appearing at all.
Use this when you want to interactively inspect the page you're hosting — for example, the login/session page or the dashboard page from earlier chapters — the same way you'd inspect any web page in Chrome.
m_lv.OpenDevToolsWindowRegistration-Free uses the pool equivalent, pool.OpenDevToolsWindow idx; Chapter 17 covers that calling convention.
What should happen: a separate Chrome DevTools window opens, showing the Elements/Console/Network/etc. panels for the page currently loaded in the control. This is a normal DevTools window — closing it does not close LiteView2.
See Part II -> DevTools & CDP for the full member list.
CallDevToolsProtocolMethod sends one CDP method call and returns its JSON result, without opening any window. This is useful for things like grabbing a screenshot of a page (for example, a dashboard page you want to drop into a nightly report) from a script that never shows the DevTools UI.
Dim result As String
result = m_lv.CallDevToolsProtocolMethod("Page.captureScreenshot", "{""format"":""png""}")
Debug.Print resultRegistration-Free: result = pool.CallDevToolsProtocolMethod(idx, "Page.captureScreenshot", "{""format"":""png""}").
What should happen: result holds the JSON response CDP returns for that method — for Page.captureScreenshot, that's a JSON object containing the image data. The method name and its parameters JSON are whatever the Chrome DevTools Protocol itself defines for that command; LiteView2 is a pass-through here, not a wrapper around individual CDP commands.
CallDevToolsProtocolMethod waits for the browser's answer (fixed 30 s). It works from ordinary code and inside DevToolsProtocolEventReceived, which is how you answer a CDP event. Inside WebViewReady, other inline events and host-object methods it fails at once with &H80040203 and sends nothing. Reg-Free: a refused call raises &H80004005; GetLastErrorCode(idx) returns &H80040203.
See Part II -> DevTools & CDP for more CallDevToolsProtocolMethod examples.
Some CDP data only shows up as events rather than as the result of a method call — network requests being sent is a typical example. Subscribe with AddDevToolsProtocolEventHandler, and drop the subscription with RemoveDevToolsProtocolEventHandler when you're done.
m_lv.AddDevToolsProtocolEventHandler "Network.requestWillBeSent"
' ... later, in the DevToolsProtocolEventReceived event handler, each matching CDP event arrives
m_lv.RemoveDevToolsProtocolEventHandler "Network.requestWillBeSent"Registration-Free: the pool accepts pool.AddDevToolsProtocolEventHandler idx, …, but the pool raises no events, so no CDP event ever arrives there. CDP events are Registered-mode only.
What should happen (Registered): once subscribed, every matching CDP event raises DevToolsProtocolEventReceived a moment later, in arrival order. See Part II -> Events for that handler's exact signature and parameters.
You can check whether CDP is available at all with the read-only HasDevToolsProtocol property:
Debug.Print "CDP available: " & m_lv.HasDevToolsProtocolRegistration-Free: Debug.Print "CDP available: " & pool.HasDevToolsProtocol(idx).
ToggleBridgeInspector shows or hides a heads-up display of live JS-to-VBA message traffic — the same bridge traffic from the chapters on calling VBA from JS and JS from VBA. It's a debugging aid for exactly that traffic, not a general CDP feature.
m_lv.ToggleBridgeInspectorRegistration-Free: pool.ToggleBridgeInspector idx.
What should happen: a HUD overlay toggles on (or off, if already showing) over the browser, listing bridge messages as they're sent and received. Reach for this when a call you registered earlier in the bridge chapters doesn't seem to be firing, to see whether the message is actually crossing the JS/VBA boundary.
This entire group — opening, saving, and inspecting an Office document in place inside the control — is Registered mode only. The source material for OpenOfficeDocument says so explicitly ("Not available in pool - use ShowDocumentPreview"), and no Reg-Free form is documented for any other member in this group either. If you need to show an Office file from a Reg-Free deployment, use ShowDocumentPreview (section 19.4) instead.
m_lv.OpenOfficeDocument "C:\Documents\Report.docx"What should happen: the control loads and embeds the Office document itself (not a preview) so it can be edited in place.
m_lv.SaveOfficeDocument
m_lv.SaveOfficeDocumentAs "C:\Documents\Report_v2.docx"
m_lv.CloseOfficeDocumentSaveOfficeDocument saves in place; SaveOfficeDocumentAs saves to a new path; CloseOfficeDocument closes the currently open Office document.
If m_lv.IsOfficeDocumentOpen Then
Debug.Print "Path: " & m_lv.OfficeDocumentPath
Debug.Print "Type: " & m_lv.OfficeDocumentType
Dim wdApp As Object: Set wdApp = m_lv.WordApplication
Dim wdDoc As Object: Set wdDoc = m_lv.WordDocument
End IfIsOfficeDocumentOpen reports whether an Office document is currently open; OfficeDocumentPath and OfficeDocumentType report its path and type. WordApplication and WordDocument hand back the underlying Word Application/Document objects, letting you drive them directly with normal Word object model calls once a document is open.
Caution
— close it before the form/host unloads. An Office document opened with OpenOfficeDocument is a real, embedded Office application instance. If the form closes (or the control is destroyed) while a document is still open, nothing in the source material documents automatic cleanup of that instance — call SaveOfficeDocument/CloseOfficeDocument (guarded by IsOfficeDocumentOpen) in your exit lifecycle, the same place you already call Stop/Set m_lv = Nothing (Chapter 2), so you don't leave a stranded WINWORD.EXE (or equivalent) process running after the host application appears to have closed.
See Part II -> Office Document Integration for the full member list.
OpenDocument is the general-purpose sibling of OpenOfficeDocument: it opens any file type — images, PDFs, videos, or Office documents — and is available in both Registered and Reg-Free modes.
m_lv.OpenDocument "C:\Photos\image.png", 0 ' 0=Auto
m_lv.OpenDocument "C:\Docs\report.pdf", 0
Debug.Print m_lv.CurrentDocumentPath
Debug.Print m_lv.CurrentDocumentTypeRegistration-Free: pool.OpenDocument idx, "C:\Photos\image.png", 0.
The second parameter selects a document type; passing 0 requests automatic detection from the file's extension. After the call, CurrentDocumentPath and CurrentDocumentType report what's currently loaded.
What should happen: the control displays the file directly — an image or video plays inline, a PDF renders, and so on — without any separate viewer window.
See Part II -> Document Viewing for the type-code reference.
ShowDocumentPreview, ShowDocumentPreviewInBrowserFrame, and CloseDocumentPreview show a document as a preview rather than opening it as the control's main content — useful when you want to pop up a quick look at a file (a vendor invoice PDF attached to an order, for example) without disturbing whatever the control is currently showing.
m_lv.ShowDocumentPreview "C:\Docs\file.pdf", m_lv.EmbeddedParentHwnd
m_lv.ShowDocumentPreviewInBrowserFrame "C:\Docs\file.pdf"
m_lv.CloseDocumentPreviewShowDocumentPreview shows the preview against a given parent window handle; ShowDocumentPreviewInBrowserFrame shows it inside the control's own browser frame instead. CloseDocumentPreview closes whichever preview is open.
Registration-Free: pool.ShowDocumentPreview idx, "C:\Docs\file.pdf" and pool.CloseDocumentPreview idx.
See Part II -> Document Viewing for the full member list.
These members embed the browser into a window handle you supply, rather than the control managing its own window the usual way. Only Registered-mode examples are documented for this group.
Debug.Print "Parent HWND: " & m_lv.EmbeddedParentHwnd
m_lv.EmbedInHwnd CLngLng(hwndParent), 800, 600
m_lv.EmbedInHwndByLong CLng(hwndParent), 800, 600 ' VB6 compat
m_lv.ResizeEmbedded 1024, 768
m_lv.MoveEmbedded 100, 50
m_lv.CloseEmbeddedEmbeddedParentHwnd (read-only) reports the window handle of the embedded parent frame. EmbedInHwnd embeds the browser into an external window handle at a given size; EmbedInHwndByLong is the same operation taking the handle as a Long rather than a LongLong, for VB6 compatibility. Once embedded, ResizeEmbedded and MoveEmbedded adjust the embedded browser's size and position, and CloseEmbedded tears it down.
Caution
— call CloseEmbedded before the parent window is destroyed. Because EmbedInHwnd hands the browser a window handle you own rather than one the control manages itself, destroying that parent window while the browser is still embedded in it is not something the source material documents as safe or automatically handled. Call CloseEmbedded first, in your exit lifecycle, the same as the Office Document Integration caution above.
See Part II -> Embedding for the full member list.
This is a separate, distinctly advanced/optional capability set from everything else in this chapter: controls for reducing how detectable the browser is as automation, and for detecting and solving CAPTCHAs. Both are available in Registered and Reg-Free modes.
EnableStealthMode bundles all of the individual hides listed below into one call.
m_lv.EnableStealthMode TrueRegistration-Free: pool.EnableStealthMode idx, True.
Each of these can also be toggled on its own:
m_lv.HideWebDriver True ' removes navigator.webdriver
m_lv.SpoofWebGL "Intel Inc.", "Intel Iris OpenGL Engine" ' spoofs WebGL vendor/renderer strings
m_lv.RandomizeCanvasFingerprint True ' adds noise to the canvas fingerprint
m_lv.SpoofScreenSize 1920, 1080, 24 ' spoofs screen width, height, color depth
m_lv.DisableWebRTC True ' disables WebRTC (prevents IP leaks)
m_lv.EnableMouseNoise True ' adds human-like noise to mouse movement
m_lv.EnableTimingNoise 50, 200 ' 50-200ms random delays on actions
Debug.Print "Stealth: " & m_lv.IsStealthEnabled ' read-onlyEach does exactly what its inline comment says — reach for EnableStealthMode unless you need only one or two of these individually. Registration-Free uses the pool equivalents with idx as the first parameter, e.g. pool.HideWebDriver idx, True, pool.IsStealthEnabled(idx), and so on for the rest of this group; Chapter 17 covers that calling convention.
See Part II -> Stealth & Anti-Detection for the full member list.
m_lv.SetCaptchaService "2captcha", "YOUR_API_KEY_HERE"
Dim info As String
info = m_lv.DetectCaptcha
Debug.Print "CAPTCHA info: " & info
Dim solved As Boolean
solved = m_lv.SolveCaptcha(30000) ' 30 second timeout
If solved Then Debug.Print "CAPTCHA solved!"
Dim token As String
token = m_lv.SolveRecaptchaV2("6Le-SITE-KEY", "https://www.example.com/form")
token = m_lv.SolveHCaptcha("SITE-KEY", "https://www.example.com/form")
m_lv.SubmitCaptchaToken tokenSetCaptchaService configures the solving service once (name and API key); SolveCaptcha runs the full detect-solve-submit sequence in one call, while DetectCaptcha, SolveRecaptchaV2/SolveHCaptcha, and SubmitCaptchaToken let you do those steps individually when you need more control (e.g. you already know the site key and just want the token back before deciding whether to submit it).
Registration-Free uses the pool equivalents with idx as the first parameter, e.g. pool.SetCaptchaService idx, "2captcha", "YOUR_API_KEY_HERE", solved = pool.SolveCaptcha(idx, 30000); Chapter 17 covers that calling convention.
The CaptchaDetected and CaptchaSolved events fire alongside DetectCaptcha/SolveCaptcha activity. See Part II -> Events for their handler signatures.
See Part II -> CAPTCHA Handling for the full member list.
A LiteView2 panel that felt fast when you built the form can feel slow months later, once real data volumes, more open forms, or a second monitor are involved. This chapter is a checklist for two situations: a panel that is sitting idle but still costing CPU or memory, and a panel you need to inspect before you can describe what went wrong in a bug report.
This chapter assumes the control is already created and navigated to a page, as covered in Chapter 2, and does not repeat that startup sequence here.
The running example is frmDashboard, an Access form with a tab strip for Customers, Orders, and Inventory. The Orders tab hosts the LiteView2 control (m_lv As LiteView2Lib.LiteView2Ctrl) showing an HTML sales report built from tblSales; the other two tabs use ordinary Access controls.
TrySuspend, Resume, and IsSuspended let you cut CPU and memory use for a panel the user isn't currently looking at, without tearing it down and losing its state.
Private Sub TabStrip1_Change()
Select Case TabStrip1.SelectedItem.Index
Case 2 ' the Orders tab, which hosts m_lv
If m_lv.IsSuspended Then m_lv.Resume
Case Else
If Not m_lv.IsSuspended Then m_lv.TrySuspend
End Select
End SubWhen the user switches to Customers or Inventory, the Orders report suspends; switching back resumes it. IsSuspended is a read-only Boolean, so guard both calls with it rather than calling TrySuspend/Resume unconditionally on every tab change.
Registration-Free uses the pool equivalent (pool.TrySuspend idx, pool.Resume idx, pool.IsSuspended(idx)); Chapter 17 covers that deployment model.
See Part II -> Lifecycle & Performance Reference for the full suspend/resume state machine.
GetMemoryUsage returns the browser's approximate memory footprint in bytes. ForceGarbageCollection asks the page's JavaScript engine to collect garbage immediately, instead of waiting for its own schedule.
Debug.Print "Before load: " & Format(m_lv.GetMemoryUsage / 1048576, "0.0") & " MB"
m_lv.Navigate "https://intranet/reports/orders_full.html"
' ... after NavigationCompleted fires ...
Debug.Print "After load: " & Format(m_lv.GetMemoryUsage / 1048576, "0.0") & " MB"
m_lv.ForceGarbageCollection
Debug.Print "After GC: " & Format(m_lv.GetMemoryUsage / 1048576, "0.0") & " MB"Use this pair when a report feels heavier than it should: read the memory figure before and after loading a page, and again after forcing collection, to see whether the page is holding on to objects it no longer needs (detached chart instances, large arrays from a previous filter, and so on).
Registration-Free uses the pool equivalent (pool.GetMemoryUsage(idx), pool.ForceGarbageCollection idx); Chapter 17 covers that deployment model.
See Part II -> Lifecycle & Performance Reference for further detail.
Dispose explicitly releases the WebView resources held by the control. CloseBrowserProcess forcibly terminates the underlying browser subprocess. Use Dispose for ordinary, planned shutdown; reach for CloseBrowserProcess when a panel has stopped responding and you need to force it closed rather than wait on it.
Private Sub Form_Unload(Cancel As Integer)
m_lv.Dispose
Set m_lv = Nothing
End Sub' The Orders panel has stopped responding to Navigate or JS calls -
' force the browser subprocess closed rather than waiting on it.
m_lv.CloseBrowserProcessCaution
After calling Dispose, always set the variable to Nothing (as shown above). The control has released its underlying resources at that point, so continuing to call methods on the old reference is not safe.
Registration-Free uses the pool equivalent (pool.Dispose idx, pool.CloseBrowserProcess idx); Chapter 17 covers that deployment model.
See Part II -> Lifecycle & Performance Reference for the complete shutdown sequence.
MoveFocus moves keyboard focus into or out of the WebView content. The source documents two direction values:
m_lv.MoveFocus 0 ' LV2_FOCUS_TABFORWARD
m_lv.MoveFocus 2 ' LV2_FOCUS_PROGRAMMATICA typical use is returning focus to the report after the user closes a modal Customer Detail popup opened from the Orders grid:
Private Sub frmCustomerDetail_Unload()
m_lv.MoveFocus 2 ' put focus back into the report
End SubRegistration-Free uses the pool equivalent (pool.MoveFocus idx, 0); Chapter 17 covers that deployment model.
See Part II -> Lifecycle & Performance Reference for the complete list of LV2_FOCUS_* constants.
When the host form moves — dragged to a second monitor with different DPI scaling, for example — the WebView's rendering can fall out of sync with its actual screen position. NotifyParentWindowPositionChanged tells the control to re-sync.
Private Sub Form_Move()
m_lv.NotifyParentWindowPositionChanged
End SubThis member is Registered-mode only. The source is explicit that there is no pool equivalent — Registration-Free handles this internally, so no call is needed there.
See Part II -> Lifecycle & Performance Reference for further detail.
GetDiagnostics returns a diagnostic dump of the WebView's current state. It is Registered-mode only; the source notes that Registration-Free has no equivalent call and directs you to GetLastError/GetLastErrorCode instead (covered below, in the read-only property group).
Debug.Print m_lv.GetDiagnosticsCombine it with the read-only properties below to build a single report before filing a bug — see that section for a complete example.
See Part II -> Diagnostics Reference for the full contents of the diagnostics dump.
IsVisible is a read/write Boolean that shows or hides the WebView area without destroying it — useful, for example, while a long Customers import is running and you don't want the Orders report repainting underneath it.
m_lv.IsVisible = False ' hide while the import runs
' ... long-running Customers import ...
m_lv.IsVisible = True ' show it againThe page and its state are preserved while hidden; nothing needs to be reloaded when you show it again.
Note
The source shows two different Reg-Free calls for this same property — pool.SetVisible idx, False in one place and pool.SetIsVisible idx, True in another — without reconciling them. Of the two, SetIsVisible matches the naming pattern used for the other Reg-Free boolean setters in this chapter (SetIsMuted, for instance), so treat pool.SetIsVisible idx, <value> as the primary form; Part II carries the full picture on this member. Chapter 17 covers Registration-Free in general.
See Part II -> Lifecycle & Performance Reference for further detail.
The properties below don't change anything — they tell you what's currently true about the browser. They're most useful bundled together, for example behind a "Report a Problem" button on frmDashboard:
Private Sub BuildDiagnosticsReport()
Dim s As String
s = "=== LiteView2 Diagnostics (Orders panel) ===" & vbCrLf
s = s & "Control version: " & m_lv.ControlVersion & vbCrLf
s = s & "WebView2 runtime: " & m_lv.BrowserVersionString & vbCrLf
s = s & "WebView created: " & m_lv.WebViewCreated & vbCrLf
s = s & "Browser PID: " & m_lv.BrowserProcessId & vbCrLf
s = s & "User data folder: " & m_lv.UserDataFolder & vbCrLf
s = s & "InPrivate mode: " & m_lv.InPrivateMode & vbCrLf
s = s & "Fullscreen element active: " & m_lv.ContainsFullScreenElement & vbCrLf
s = s & "Status bar text: " & m_lv.StatusBarText & vbCrLf
s = s & "Favicon: " & m_lv.FaviconUri & vbCrLf
s = s & "Last error: " & m_lv.LastError & " (code " & m_lv.LastErrorCode & ")" & vbCrLf
s = s & vbCrLf & "--- Full diagnostics dump ---" & vbCrLf
s = s & m_lv.GetDiagnostics
Debug.Print s
' Or write s to a text file and attach it to the bug report.
End SubBrowserProcessIDLong is the same process ID as BrowserProcessId — the source documents both under one entry as Long; use whichever name is already in your codebase.
Registration-Free exposes each of these as its own indexed call on the pool instead of a single combined report — pool.GetBrowserProcessId(idx), pool.GetStatusBarText(idx), pool.GetLastError(idx)/GetLastErrorCode(idx), and so on, one indexed getter per property above (pool.ControlVersion is the one exception — no index, since the control version isn't per-browser). Reg-Free also has no GetDiagnostics call; use GetLastError/GetLastErrorCode there instead. Chapter 17 covers Registration-Free in general; see Part II -> Diagnostics Reference for the full property-by-property table (every Reg-Free getter name and type) and exhaustive detail on every field, including what GetDiagnostics returns.
IsDocumentPlayingAudio and IsMuted track whether the current page is playing audio (an embedded video on a Document-report screen, for example) and whether it's muted. The two modes expose this differently, and it's a real capability gap, not just a naming difference:
Registered mode has no direct get/set properties for these. Use the IsDocumentPlayingAudioChanged and IsMutedChanged events instead, and track the state yourself:
' On the form declaring "WithEvents m_lv As LiteView2Lib.LiteView2Ctrl":
Private Sub m_lv_IsMutedChanged(...)
' update your own tracking variable here
End Sub
Private Sub m_lv_IsDocumentPlayingAudioChanged(...)
' same pattern
End SubSee Part II -> Events Reference for the exact event signatures.
Registration-Free has direct Get/Set methods, including a setter to mute the page:
Debug.Print "Playing: " & pool.GetIsDocumentPlayingAudio(idx)
Debug.Print "Muted: " & pool.GetIsMuted(idx)
pool.SetIsMuted idx, True ' muteChapter 17 covers Registration-Free in general. See Part II -> Diagnostics Reference and Part II -> Events Reference for further detail.
Chapter 2 got a license activated with the minimum needed to run correctly: one call, EnsureLicensed m_lv, in Form_Load, backed by the modLV2License.bas shared module. Chapter 17 does the equivalent for Reg-Free deployment with modLV2Pool.bas and GetPool(). Neither chapter re-derives licensing from first principles — they just tell you where the call goes and move on. This chapter is that missing detail: what the 30-day trial actually is, exactly what ActivateLicense takes and where its effect lives, why it has to run on every launch, and every property and method for reading license state back out at runtime.
If you haven't read Chapter 2's "License activation: one shared module, called from Form_Load" section yet, read that first — it's the pattern this chapter assumes.
If the activation key constant in modLV2License.bas (Registered) or modLV2Pool.bas (Reg-Free) is left empty, LiteView2 does not fail to start and does not throw an error. It runs under a 30-day Trial Period automatically — no code required to get it, and no code required to detect that it's happening by default.
' modLV2License.bas
Private Const LV2_COMPANY As String = "Your Company"
Private Const LV2_KEY As String = "" ' empty = 30-day Trial Period
While the key is empty, EnsureLicensed skips the ActivateLicense call entirely (Chapter 2's implementation exits early when Len(LV2_KEY) = 0), and the control runs under the trial on its own. Section 21.3 below covers how to detect trial state — LicenseStatus, LicenseType, and TrialDaysRemaining — and what changes once the 30 days run out.
ActivateLicense takes two arguments: a company name, and an opaque V1 activation string — the key you were issued.
m_lv.ActivateLicense "My Company", "V1|...|..."
The runtime validates the key and enforces any expiry timestamp encoded inside it. Activation is in-memory only — no registry write, no file on disk, no network call. Which also means it is session-only: nothing about a successful ActivateLicense call survives past the current process. Close Access and reopen it, and the license state is gone until something calls ActivateLicense again. That's why the call belongs in Form_Load / UserForm_Initialize on every launch, not behind a "first run only" check of your own — EnsureLicensed (Registered) and GetPool (Reg-Free) already are that check, run every time, and are idempotent: EnsureLicensed tests its own flag and exits early on every call after the first, and GetPool only initializes once. Calling either from ten forms in the same session is harmless.
Caution
Never call ActivateLicense after the first Navigate, and never from inside WebViewReady / OnBrowserReady. By the time WebViewReady fires, the trial watermark may already have rendered and the navigation decision has already used whatever license state existed at that point — activating afterward doesn't retroactively fix either. Form_Load (Registered) / the shared GetPool() call (Reg-Free) is the only place this belongs, exactly as templated in Chapters 2 and 17.
Registered — reading state back after activating:
m_lv.ActivateLicense "My Company", "V1|...|..."
Debug.Print "Status: " & m_lv.LicenseStatus
Debug.Print "Type: " & m_lv.LicenseType
Debug.Print "State: " & m_lv.LicenseState
Debug.Print "Days remaining: " & m_lv.TrialDaysRemaining
Debug.Print "Expiry: " & m_lv.LicenseExpiry
Debug.Print "Version: " & m_lv.LicenseVersion
Debug.Print "Info JSON: " & m_lv.GetLicenseInfo()
Reg-Free — same idea, called on pool:
pool.ActivateLicense "My Company", "V1|...|..."
Debug.Print "Status: " & pool.LicenseStatus
Debug.Print "Type: " & pool.LicenseType
Debug.Print "State: " & pool.LicenseState
Debug.Print "Trial days: " & pool.TrialDaysRemaining
Debug.Print "Info JSON: " & pool.GetLicenseInfo()
What should happen: immediately after ActivateLicense returns, the properties above reflect the activated license — LicenseStatus moves off TRIAL, LicenseType moves off trial, and so on. Section 21.3 covers what each of these values means.
ActivateLicense activates the whole process, not the form or control it was called on:
ActivateLicense call activates every LiteView2 browser created afterward in that Access (or Office) session — including ones you haven't created yet.CreateInControl call automatically. You do not register the license per browser index.ActivateLicense once — which is exactly the job EnsureLicensed and GetPool do for you.The recommended pattern — already the one Chapters 2 and 17 template — is to keep the key in exactly one shared module, never duplicated in per-form code. Whichever form or routine runs first in the process triggers the activation; everything opened later in that same session inherits the licensed state through the same shared module. Rotating a key means editing one file, not searching every form in the database.
Once activated (or running under the default trial), the properties below let you read license state back at runtime — for a startup banner, a support diagnostic screen, or a guard before letting a screen open at all.
LicenseStatus (String) — one of TRIAL, EXPIRED, PRO, ENTERPRISE, EVAL. An EVAL license returns EVAL while unexpired, and reverts to EXPIRED once its expiry timestamp passes.
LicenseType (String, lowercase) — one of trial, eval, pro, enterprise, none. The eval value marks an active Extended Evaluation License (EULA Section 2.3) — issued at the Licensor's discretion to MVPs, educators, reviewers, and community contributors, not something your code requests or triggers itself.
LicenseState (enum LV2_LICENSE_STATE: Valid, Trial, Expired, Invalid) — the coarse, code-branchable version of the two strings above. PRO, Enterprise, and an unexpired Extended Evaluation license all report Valid (value 0) — there is no separate EVAL member at the enum level. Once an EVAL key passes its expiry timestamp, LicenseState reverts to Expired (value 2).
' Registered
Dim status As String, kind As String, state As LV2_LICENSE_STATE
status = m_lv.LicenseStatus
kind = m_lv.LicenseType
state = m_lv.LicenseState
' Reg-Free — index-free, process-wide (matches ActivateLicense above)
Dim status As String, kind As String, state As LV2_LICENSE_STATE
status = pool.LicenseStatus
kind = pool.LicenseType
state = pool.LicenseState
Note
Documented two ways: the source reference for these three properties shows Reg-Free access twice, in two different forms. Right beside ActivateLicense, they're index-free process-wide pool properties — pool.LicenseStatus, pool.LicenseType, pool.LicenseState, no browser index passed — which matches the process-wide activation semantics described above: one activation, one license state, for the whole pool. Separately, the auto-generated property reference documents the same three (and, per that same reference, TrialDaysRemaining) as indexed Get-methods instead: pool.GetLicenseStatus browserIndex, value, pool.GetLicenseType browserIndex, value, pool.GetLicenseState browserIndex, value. Both forms are shown in the source as written; this chapter teaches the index-free form as primary because it's the one consistent with "activation is process-wide, not per-browser." See Part II → Licensing for the complete picture, including which form is current.
The one other property the dashboard scenario below actually uses: TrialDaysRemaining reports how many days are left, read the same way as the properties above (m_lv.TrialDaysRemaining Registered; pool.TrialDaysRemaining Reg-Free, index-free — see the Note above, since the same index-free-vs-indexed inconsistency is documented for this property too, via pool.GetTrialDaysRemaining browserIndex, value).
A few more license members exist beyond what this dashboard scenario needs — LicenseExpiry (a DATE, 0 for perpetual licenses), LicenseVersion, and GetLicenseInfo() (a small JSON status document — schema-versioned, no credential material, useful for a support-ticket dump) all follow the same read pattern shown above. See Part II → Licensing for their full reference, signatures, and example payloads.
Putting 21.2 and 21.3 together, a typical use is a small status line on frmDashboard so a support call — "why does this say Trial?" — is answered by looking at the screen instead of digging through code:
Private Sub Form_Load()
Set m_lv = Me.LiteView2Ctrl1.Object
EnsureLicensed m_lv
End Sub
Private Sub LiteView2Ctrl1_WebViewReady()
m_lv.SetLocalContentRoot CurrentProject.Path
m_lv.Navigate "https://lv2.example/dashboard.html"
Select Case m_lv.LicenseStatus
Case "TRIAL"
Me.lblLicense.Caption = "Trial — " & m_lv.TrialDaysRemaining & " day(s) remaining"
Case "EXPIRED"
Me.lblLicense.Caption = "License expired"
Case Else
Me.lblLicense.Caption = m_lv.LicenseType & " license active"
End Select
End Sub
Reading LicenseStatus here — after Navigate, inside WebViewReady — is fine; the caution in 21.2 is specifically about calling ActivateLicense at that point, not about reading the status properties, which are safe to read anywhere once the control exists.
What should happen: on a machine with an empty key constant, lblLicense reads something like "Trial — 12 day(s) remaining" and counts down across the 30-day window; after activating a PRO key it reads "pro license active" instead.
See Part II → Licensing for the exhaustive property/method reference, every enum value, and activation-failure handling not covered here.
Part II
This part is organized for lookup, not sequential reading. Every method, property and event documented anywhere in the source Cookbook is preserved here.
modLV2License.bas exports EnsureLicensed(ctrl); each form calls it once in Form_Load. Reg-Free Mode: modLV2Pool.bas already activates the license inside GetPool() on the first call. Rotate the key in one place and every form picks it up.SetLocalContentRoot, host-object registration, and the first Navigate belong in LiteView2Ctrl1_WebViewReady (Registered) or OnBrowserReady (Reg-Free — a callback you register yourself with SetReadyCallback, not a fixed event name). Calling them in Form_Load is a silent no-op or fails. Two calls belong in Form_Load, after EnsureLicensed: EnableLvFacade and AddScriptToExecuteOnDocumentCreated. Before the browser exists they are queued and applied before the first page. Inside WebViewReady they fail at once with &H80040203.SetLocalContentRoot → AreHostObjectsAllowed → AddHostObjectToScript → Navigate.MouseDown mirror when focus may be inside the browser. Keep Click for keyboard activation; add MouseDown so the first physical mouse click also fires the action. Access uses the first click on an external button to move focus, swallowing the Click event.Form_Unload must call ClearReadyCallback, DestroyBrowserSync idx, 2000, then Set pool = Nothing. Registered Mode only needs Set m_lv = Nothing in Form_Close.These are template helper routines the Cookbook's boilerplate ships in a shared module — not methods on LiteView2Ctrl or the pool object itself — but the lifecycle rules above depend on them, so their contract is documented here.
EnsureLicensed(ctrl) — lives in modLV2License.bas (Registered Mode). Activates the license once per process; called from every form's Form_Load. Idempotent: checks an internal flag and exits early on subsequent calls, so calling it twice (e.g. a form re-imported into another project) is harmless.
GetPool() — lives in modLV2Pool.bas (Reg-Free Mode). Returns the shared pool object and activates the license internally on its first call in the process. Also idempotent — only initializes once.
Do/don't for the underlying ActivateLicense call these helpers wrap (full ActivateLicense parameter/return documentation lives in the Licensing reference; the facts below are the ones that affect when you may call it):
Const in modLV2License.bas; forms call EnsureLicensed m_lv in Form_Load.Const in modLV2Pool.bas; GetPool() activates automatically on the first call in the process.ActivateLicense AFTER the first Navigate — the watermark may already have rendered.ActivateLicense inside WebViewReady / OnBrowserReady — by the time those fire, the navigation decision has already used the license state.ActivateLicense is process-wide, not per-form and not per-browser:
ActivateLicense call activates every LiteView2 browser created afterwards in the same Access (or Office) session.CreateInControl call automatically — you do not register the license per browser-index.ActivateLicense once — that's what EnsureLicensed and GetPool handle for you.Recommended pattern: the activation key lives in a shared module, never duplicated across forms. Whichever form opens first in the process triggers the activation; every later form inherits the licensed state via the same shared module. You change the key once, in one file.
Form_Load' ❌ WRONG — Form_Load runs BEFORE WebView2 is ready.
' These calls silently no-op, race with init, or cause a stuck watermark.
Private Sub Form_Load()
Set m_lv = Me.LiteView2Ctrl1.Object
m_lv.SetLocalContentRoot CurrentProject.Path ' ❌ wait for WebViewReady
m_lv.AddHostObjectToScript "vba", Me ' ❌ wait for WebViewReady
m_lv.Navigate "https://lv2.example/index.html" ' ❌ wait for WebViewReady
End Sub' ✅ CORRECT — Form_Load only acquires the typed reference + activates the license.
' Activation key lives in modLV2License.bas, not duplicated here.
Private Sub Form_Load()
Set m_lv = Me.LiteView2Ctrl1.Object
EnsureLicensed m_lv ' from modLV2License — once per process
End Sub
Private Sub LiteView2Ctrl1_WebViewReady()
m_lv.SetLocalContentRoot CurrentProject.Path
m_lv.AreHostObjectsAllowed = True
m_lv.AddHostObjectToScript "vba", Me
m_lv.Navigate "https://lv2.example/index.html"
End SubSame anti-pattern applies to Reg-Free Mode: do Set pool = GetPool() (which activates the license internally) + CreateInControl + SetReadyCallback in Form_Load; move SetLocalContentRoot / SetAreHostObjectsAllowed / AddHostObjectToScript / Navigate into the OnBrowserReady callback.
Registered Mode:
TIME EVENT WHAT YOUR CODE DOES HERE
──── ──────────────────────────────── ───────────────────────────────────────────
t0 Form_Load Set m_lv = Me.LiteView2Ctrl1.Object
EnsureLicensed m_lv (from modLV2License)
↓
(WebView2 starts up — async) (you do nothing; message loop runs)
↓
t1 LiteView2Ctrl1_WebViewReady m_lv.SetLocalContentRoot CurrentProject.Path
m_lv.AreHostObjectsAllowed = True
m_lv.AddHostObjectToScript "vba", Me
m_lv.Navigate "https://lv2.example/index.html"
↓
t2 Page loads → DOMContentLoaded JavaScript runs in the page
Page calls window.chrome.webview
.hostObjects.sync.vba.OnReady()
↓
t3 Public Sub OnReady() in VBA Note "page ready" / push data with ExecuteScriptAsync.
Runs inside the page's call: calls that wait for
the browser fail here (&H80040203)
↓
t4 LiteView2Ctrl1_NavigationCompleted
Post-load work that needs ExecuteScript and other
synchronous calls runs here
↓
(user interacts...)
↓
tN Form_Close Set m_lv = NothingReg-Free Mode:
TIME EVENT WHAT YOUR CODE DOES HERE
──── ──────────────────────────────── ───────────────────────────────────────────
t0 Form_Load Set pool = GetPool() (modLV2Pool activates
the license internally)
idx = pool.CreateInControl("about:blank", fra)
pool.SetReadyCallback idx, Me, "OnBrowserReady"
↓
(WebView2 starts up — async) (you do nothing)
↓
t1 Public Sub OnBrowserReady pool.SetLocalContentRoot idx, CurrentProject.Path
pool.SetAreHostObjectsAllowed idx, True
pool.AddHostObjectToScript idx, "vba", Me
pool.Navigate idx, "https://lv2.example/index.html"
↓
t2 Page loads → DOMContentLoaded JavaScript runs in the page
Page calls window.chrome.webview
.hostObjects.sync.vba.OnReady()
↓
t3 Public Sub OnReady() in VBA Note "page ready" / push data with ExecuteScriptAsync.
Runs inside the page's call: calls that wait for
the browser fail here (&H80040203)
↓
t4 Form_Timer Post-load work that needs ExecuteScript and other
synchronous calls runs here
↓
(user interacts...)
↓
tN Form_Unload pool.Stop idx
pool.ClearReadyCallback idx
pool.DestroyBrowserSync idx, 2000
Set pool = NothingThe loading sequence has a clear order; the unloading sequence does too. Following it cleanly prevents three real problems:
NavigationCompleted against a half-destroyed control.msedgewebview2.exe) keeps Access alive after the form closes if it isn't explicitly torn down.Private Sub Form_Close()
On Error Resume Next
If Not m_lv Is Nothing Then
m_lv.Stop ' halt any in-flight navigation before teardown
Set m_lv = Nothing ' release the reference; Access destroys the control next
End If
End Subm_lv.Stop — halts a navigation that might still be loading when the user closes the form. Without it, the page can fire NavigationCompleted while the control is being destroyed.Set m_lv = Nothing — releases your VBA reference. Access then destroys the ActiveX control as part of normal form teardown; LiteView2's internal destructor drains pending callbacks and releases the WebView2.On Error Resume Next — if the form is closing because of an earlier error, we still want the teardown attempt to complete; we don't want a second error to pop a second MsgBox during shutdown.Registered Mode does NOT need DestroyBrowserSync — that's a pool-only API. The control's own destructor handles the equivalent work when Access tears the form down.
Private Sub Form_Unload(Cancel As Integer)
On Error Resume Next
If Not pool Is Nothing And idx <> 0 Then
pool.Stop idx ' halt any in-flight navigation
pool.ClearReadyCallback idx ' detach our callback so it can't fire after teardown
pool.DestroyBrowserSync idx, 2000 ' synchronous browser teardown (max 2 s wait)
idx = 0
End If
Set pool = Nothing ' release the pool reference
End Subpool.Stop idx — same as the Registered Mode Stop; halts in-flight navigation before teardown.pool.ClearReadyCallback idx — removes the OnBrowserReady callback so it cannot fire on a form that's already unloading. Without this, an async ready callback can call back into VBA after the form's references are gone (Type mismatch / Object required).pool.DestroyBrowserSync idx, 2000 — the critical one for Reg-Free Mode. Tears down the browser instance synchronously, waiting up to 2 seconds for in-process cleanup to complete. Without this call, the WebView2 host process keeps running and Access cannot exit cleanly — Task Manager will show a lingering msedgewebview2.exe and MSACCESS.EXE until the OS finally kills them.idx = 0 — sentinel so a stray callback hitting this form sees an invalid handle and exits cleanly.Set pool = Nothing — releases your VBA reference to the pool singleton.On Error Resume Next in the teardownBy the time Form_Close / Form_Unload fires, the form may be unloading because of an earlier error elsewhere. A second error popping a MsgBox during shutdown produces a half-stuck form customers struggle to dismiss. On Error Resume Next swallows teardown errors so the form can finish unloading — any genuine bug is caught earlier by the On Error GoTo ErrH blocks in Form_Load and OnBrowserReady.
Create browser. Returns index (1, 2, 3…) or 0 on error.
idx = pool.Create(hwndParent, url, x, y, width, height)Dim idx As Long
idx = pool.Create(hwndParent, url, x, y, width, height)Create browser and embed in control — one-liner convenience method.
idx = pool.CreateInControl(url, control)Dim idx As Long
idx = pool.CreateInControl(url, control)Create browser with a named profile for session isolation.
idx = pool.CreateWithProfile(hwndParent, url, x, y, width, height, profileName)Dim idx As Long
idx = pool.CreateWithProfile(hwndParent, url, x, y, width, height, profileName)Create browser with profile and embed in a VBA control.
idx = pool.CreateInControlWithProfile(url, control, profileName)Dim idx As Long
idx = pool.CreateInControlWithProfile(url, control, profileName)Create browser with a named profile in InPrivate mode.
idx = pool.CreateWithProfileInPrivate(hwndParent, url, x, y, width, height, profileName)Dim idx As Long
idx = pool.CreateWithProfileInPrivate(hwndParent, url, x, y, width, height, profileName)Embed browser into a VBA control (Frame, Image, etc.).
pool.EmbedInControl idx, controlpool.EmbedInControl idx, controlEmbed browser in an external window handle for reg-free usage.
m_lv.EmbedInHwnd hwndParent, width, heightm_lv.EmbedInHwnd hwndParent, width, heightGotchas
ctrl.-based signature (m_lv.EmbedInHwnd) both indicate Registered Only, but the description text says "for reg-free usage" — this wording mismatch is in the source itself and is preserved here rather than silently resolved.Embed browser using LONG parent handle (VB6/VFP compatibility).
m_lv.EmbedInHwndByLong hwndParent, width, heightm_lv.EmbedInHwndByLong hwndParent, width, heightCheck if WebView2 is ready.
result = m_lv.IsReady() / result = pool.IsReady(idx)' Registered
Dim result As Boolean
result = m_lv.IsReady()' Reg-Free
Dim result As Boolean
result = pool.IsReady(idx)Register a COM callback object/method to invoke when the browser is fully ready.
callbackObject must be STA-affine (VBA form, VB6 class, etc.). Fires immediately if the browser is already ready at registration time. Fires exactly once per registration. Dispatched on the LiteView2 UI thread via PostMessage — never on WebView2 callback threads.
pool.SetReadyCallback idx, callbackObject, methodNamepool.SetReadyCallback idx, callbackObject, methodNameUnregister the ready callback and allow re-registration.
pool.ClearReadyCallback idxpool.ClearReadyCallback idxResize browser.
pool.Resize idx, x, y, width, heightpool.Resize idx, x, y, width, heightResize the embedded browser to new dimensions.
m_lv.ResizeEmbedded width, heightm_lv.ResizeEmbedded width, heightReparent browser to new window.
pool.Reparent idx, hwndNewParent, x, y, width, heightpool.Reparent idx, hwndNewParent, x, y, width, heightPark and ParkBrowser are documented separately in the source with near-identical descriptions ("Park browser (hide) by index" vs. "Park browser (hide without destroying)") and identical signatures — presented here as they appear, not merged, since the source does not state they are the same method.
Park browser (hide) by index.
pool.Park idxpool.Park idxGotchas
ParkBrowser, which has a near-identical description ("Park browser (hide without destroying)") and an identical signature; the source does not state whether the two are the same underlying method.See alsoParkBrowser
Park browser (hide without destroying).
pool.ParkBrowser idxpool.ParkBrowser idxGotchas
Park, which has a near-identical description ("Park browser (hide) by index") and an identical signature; the source does not state whether the two are the same underlying method.See alsoPark
Get count of currently parked browsers.
result = pool.GetParkedBrowserCount()Dim result As Long
result = pool.GetParkedBrowserCount()Get count of active browsers.
result = pool.GetBrowserCount()Dim result As Long
result = pool.GetBrowserCount()IsValidHandle and IsValidIndex are likewise documented as two separate members with essentially identical descriptions ("Check if browser index is valid" vs. "Check if index is valid") and identical signatures. Presented separately below as documented.
Check if browser index is valid.
result = pool.IsValidHandle(idx)Dim result As Boolean
result = pool.IsValidHandle(idx)Gotchas
IsValidIndex, which has an essentially identical description ("Check if index is valid") and an identical signature.See alsoIsValidIndex
Check if index is valid.
result = pool.IsValidIndex(idx)Dim result As Boolean
result = pool.IsValidIndex(idx)Gotchas
IsValidHandle, which has an essentially identical description ("Check if browser index is valid") and an identical signature.See alsoIsValidHandle
Four distinct destroy-family members are documented in the source. Destroy and DestroyBrowser have separate, near-identical descriptions ("Destroy browser by index" vs. "Destroy browser instance"); DestroySync and DestroyBrowserSync are genuinely different signatures (one takes no timeout, one does) and are NOT interchangeable — the canonical Reg-Free teardown template (see Exit Lifecycle above) uses DestroyBrowserSync specifically, not DestroySync.
Destroy browser by index.
pool.Destroy idxpool.Destroy idxGotchas
DestroyBrowser, which has a near-identical description ("Destroy browser instance").See alsoDestroyBrowser
Destroy browser instance.
pool.DestroyBrowser idxpool.DestroyBrowser idxGotchas
Destroy, which has a near-identical description ("Destroy browser by index").See alsoDestroy
Destroy browser SYNCHRONOUSLY — pumps messages until WebView2 cleanup completes.
No timeout parameter.
pool.DestroySync idxpool.DestroySync idxGotchas
DestroyBrowserSync — the two have genuinely different signatures (this one takes no timeout parameter); the canonical Reg-Free Form_Unload teardown pattern uses DestroyBrowserSync specifically, not this method.See alsoDestroyBrowserSync
Destroy browser synchronously with timeout.
This is the call used in the canonical Form_Unload teardown pattern — waits up to the given millisecond timeout for in-process cleanup to complete. Without calling this during teardown, the WebView2 host process (msedgewebview2.exe) keeps running and Access cannot exit cleanly.
pool.DestroyBrowserSync idx, timeoutMspool.DestroyBrowserSync idx, timeoutMs
' Canonical usage:
pool.DestroyBrowserSync idx, 2000See alsoDestroySync
Destroy all parked browsers — cleanup resources.
result = pool.DestroyAllParked()Dim result As Long
result = pool.DestroyAllParked()TrySuspend and SuspendBrowser are explicitly documented as aliases ("SuspendBrowser ... same as TrySuspend"). Resume and ResumeBrowser are documented the same way (the TrySuspend entry itself lists SuspendBrowser as an "Or:" alternative, and the Resume entry lists ResumeBrowser as an "Or:" alternative).
Suspend the browser to reduce CPU/memory usage.
m_lv.TrySuspend / pool.TrySuspend idx' Registered
m_lv.TrySuspend' Reg-Free
pool.TrySuspend idx
' Or: pool.SuspendBrowser idxGotchas
SuspendBrowser is documented as an alias for this method in Reg-Free Mode — pool.SuspendBrowser idx is shown as an "Or:" alternative to pool.TrySuspend idx.See alsoSuspendBrowser
Suspend browser to reduce CPU/memory (same as TrySuspend).
Use with ResumeBrowser.
pool.SuspendBrowser idxpool.SuspendBrowser idxSee alsoTrySuspend, ResumeBrowser
Resume a suspended browser.
m_lv.Resume / pool.Resume idx' Registered
m_lv.Resume' Reg-Free
pool.Resume idx
' Or: pool.ResumeBrowser idxGotchas
ResumeBrowser is documented as an alias for this method in Reg-Free Mode — pool.ResumeBrowser idx is shown as an "Or:" alternative to pool.Resume idx.See alsoResumeBrowser
Resume browser.
pool.ResumeBrowser idxpool.ResumeBrowser idxSee alsoResume
Check if the browser is suspended.
Type: Boolean. Read-only.
m_lv.IsSuspended / pool.IsSuspended(idx)' Registered
If m_lv.IsSuspended Then m_lv.Resume' Reg-Free
If pool.IsSuspended(idx) Then pool.Resume idxExplicitly release all WebView resources.
m_lv.Dispose / pool.Dispose idx' Registered
m_lv.Dispose
Set m_lv = Nothing' Reg-Free
pool.Dispose idxForce close the browser process.
m_lv.CloseBrowserProcess / pool.CloseBrowserProcess idx' Registered
m_lv.CloseBrowserProcess' Reg-Free
pool.CloseBrowserProcess idxGet approximate memory usage in bytes.
m_lv.GetMemoryUsage / pool.GetMemoryUsage(idx)' Registered
Debug.Print "Memory: " & Format(m_lv.GetMemoryUsage / 1048576, "0.0") & " MB"' Reg-Free
Debug.Print "Memory: " & Format(pool.GetMemoryUsage(idx) / 1048576, "0.0") & " MB"Force JavaScript garbage collection.
m_lv.ForceGarbageCollection / pool.ForceGarbageCollection idx' Registered
m_lv.ForceGarbageCollection' Reg-Free
pool.ForceGarbageCollection idxMove focus into or out of the WebView.
m_lv.MoveFocus 0 / pool.MoveFocus idx, 0' Registered
m_lv.MoveFocus 0 ' LV2_FOCUS_TABFORWARD
m_lv.MoveFocus 2 ' LV2_FOCUS_PROGRAMMATIC' Reg-Free
pool.MoveFocus idx, 0Notify WebView that the host window has moved (fixes DPI/rendering).
m_lv.NotifyParentWindowPositionChanged' Registered
m_lv.NotifyParentWindowPositionChanged' Reg-Free
' Not available in pool - handled internallyGotchas
Toggle Bridge Inspector HUD showing live JS <-> VBA message traffic.
m_lv.ToggleBridgeInspector / pool.ToggleBridgeInspector idx' Registered
m_lv.ToggleBridgeInspector' Reg-Free
pool.ToggleBridgeInspector idxThe following read-only/diagnostic members were confirmed present in the source but not captured under any category during the first drafting pass; added here during the completeness audit so Part II remains an exhaustive reference. All are documented with the Reg-Free forms shown exactly as the source gives them.
Get diagnostic information about the WebView state.
m_lv.GetDiagnostics' Registered
Debug.Print m_lv.GetDiagnostics' Reg-Free
' Not available in pool - use GetLastError insteadGotchas
GetLastError / GetLastErrorCode instead.See alsoLastError, LastErrorCode
Get or set WebView visibility.
Type: Boolean. R/W.
m_lv.IsVisible = True / pool.SetIsVisible idx, True' Registered
m_lv.IsVisible = False ' Hide
m_lv.IsVisible = True ' Show' Reg-Free
pool.SetVisible idx, False
pool.SetIsVisible idx, True
Dim visible As Boolean
visible = pool.GetIsVisible(idx)Gotchas
SetVisible and SetIsVisible, side by side in the same example, with no explanation of which is authoritative or whether they're aliases. Both are preserved here exactly as documented; verify against your installed type library before relying on either.Get the browser subprocess process ID.
Type: Long. Read-only. BrowserProcessIDLong is a VB6-friendly alias for BrowserProcessId.
m_lv.BrowserProcessId / pool.GetBrowserProcessId(idx)' Registered
Debug.Print "PID: " & m_lv.BrowserProcessId
Debug.Print "PID (Long): " & m_lv.BrowserProcessIDLong' Reg-Free
Debug.Print "PID: " & pool.GetBrowserProcessId(idx)Gotchas
Long-suffixed) form, pool.GetBrowserProcessId(idx) — there is no Reg-Free equivalent to the VB6-friendly BrowserProcessIDLong alias.Whether the page has a fullscreen element.
Type: Boolean. Read-only.
m_lv.ContainsFullScreenElement / pool.GetContainsFullScreenElement(idx)' Registered
If m_lv.ContainsFullScreenElement Then Debug.Print "Fullscreen active"' Reg-Free
If pool.GetContainsFullScreenElement(idx) Then Debug.Print "Fullscreen"See alsoContainsFullScreenElementChanged (event, Events — Registered Mode)
Current status bar text.
Type: String. Read-only.
m_lv.StatusBarText / pool.GetStatusBarText(idx)' Registered
Debug.Print m_lv.StatusBarText' Reg-Free
Debug.Print pool.GetStatusBarText(idx)See alsoStatusBarTextChanged (event, Events — Registered Mode)
Get the last error message and code.
Read-only.
LastErrorCode (Long, read-only) holds the HRESULT of the last failure that records one: WebView2 creation failures, an ExecuteScript / ExecuteScriptWithResult timeout (&H800705B4), or a call refused inside an event or host-object call (&H80040203). It is not reset by later successful calls, and many other failures don't set it. Handle Err at the call site and read LastErrorCode / LastError right after a failing call; don't poll it.
m_lv.LastError / pool.GetLastError(idx)' Registered
Debug.Print "Error: " & m_lv.LastError
Debug.Print "Code: " & m_lv.LastErrorCode' Reg-Free
Debug.Print "Error: " & pool.GetLastError(idx)
Debug.Print "Code: " & pool.GetLastErrorCode(idx)Whether the WebView has been created.
Type: Boolean. Read-only.
m_lv.WebViewCreated / pool.GetWebViewCreated(idx)' Registered
Debug.Print "Created: " & m_lv.WebViewCreated' Reg-Free
Debug.Print "Created: " & pool.GetWebViewCreated(idx)Favicon URI of the current page.
Type: String. Read-only.
m_lv.FaviconUri / pool.GetFaviconUri(idx)' Registered
Debug.Print "Favicon: " & m_lv.FaviconUri' Reg-Free
Debug.Print "Favicon: " & pool.GetFaviconUri(idx)See alsoFaviconChanged (event, Events — Registered Mode)
Get the control version string.
Type: String. Read-only.
m_lv.ControlVersion / pool.ControlVersion' Registered
Debug.Print "Version: " & m_lv.ControlVersion' Reg-Free
Debug.Print "Version: " & pool.ControlVersionGotchas
pool.ControlVersion, a bare property with no browser index — unlike the other members in this group, which all take idx. Preserved exactly as documented.Get the WebView2 runtime version.
Type: String. Read-only.
m_lv.BrowserVersionString / pool.GetBrowserVersionString(idx)' Registered
Debug.Print "Runtime: " & m_lv.BrowserVersionString' Reg-Free
Debug.Print "Runtime: " & pool.GetBrowserVersionString(idx)Get the user data folder path.
Type: String. Read-only.
m_lv.UserDataFolder / pool.GetUserDataFolder(idx)' Registered
Debug.Print "UDF: " & m_lv.UserDataFolder' Reg-Free
Debug.Print "UDF: " & pool.GetUserDataFolder(idx)Whether the browser is in InPrivate mode.
Type: Boolean. Read-only.
m_lv.InPrivateMode / pool.IsInPrivateMode(idx)' Registered
Debug.Print "InPrivate: " & m_lv.InPrivateMode' Reg-Free
Debug.Print "InPrivate: " & pool.IsInPrivateMode(idx)Gotchas
IsInPrivateMode, not the Get-prefixed GetInPrivateMode pattern used by sibling properties in this group.Reg-Free only for direct get/set.
In Registered Mode, the source documents no direct properties for these — use the IsDocumentPlayingAudioChanged and IsMutedChanged events instead (see Events (Registered Mode)).
pool.GetIsDocumentPlayingAudio(idx) / pool.GetIsMuted(idx) / pool.SetIsMuted idx, True' Reg-Free
Debug.Print "Playing: " & pool.GetIsDocumentPlayingAudio(idx)
Debug.Print "Muted: " & pool.GetIsMuted(idx)
pool.SetIsMuted idx, True ' MuteSee alsoIsDocumentPlayingAudioChanged, IsMutedChanged (events, Events — Registered Mode)
Quick initialization in a Frame control (handles DPI automatically).
m_lv.QuickInitInFrame Me, Me.FraBrowser / pool.QuickInitInFrame idx, Me, Me.FraBrowser' Registered
m_lv.QuickInitInFrame Me, Me.FraBrowser' Reg-Free
pool.QuickInitInFrame idx, Me, Me.FraBrowserGet the default OCX file path for the current Office application.
m_lv.GetDefaultOcxPath / pool.GetDefaultOcxPath(idx)' Registered
Debug.Print m_lv.GetDefaultOcxPath' Reg-Free
Debug.Print pool.GetDefaultOcxPath(idx)Convert points to pixels for DPI awareness.
px = m_lv.PointsToPixels(100, True) / px = pool.PointsToPixels(idx, 100, True)' Registered
Dim px As Long
px = m_lv.PointsToPixels(100, True) ' 100 points, horizontal axis
Debug.Print "100 points = " & px & " pixels"' Reg-Free
Dim px As Long
px = pool.PointsToPixels(idx, 100, True)Navigate to a URL.
m_lv.Navigate urlRegistered:
m_lv.Navigate "https://www.example.com"Reg-Free:
pool.Navigate idx, "https://www.example.com"Navigate to a URL and block until load completes or timeout.
The UI remains responsive during the wait (message pump). Timeout is governed by SynchronousTimeOut (see below) — if the timeout elapses before load completes, the wait ends anyway.
Call it from ordinary code (a button, a timer) or from a notification event such as NavigationCompleted. Inside WebViewReady, NavigationStarting, the other inline events and host-object methods it fails at once with &H80040203 and starts no navigation: use Navigate there and react in NavigationCompleted. With SynchronousTimeOut set to 0 it does not wait: it returns the timeout error &H800705B4 at once while the navigation goes on.
Known issue in 1.0.1.0
The wait ends at the next NavigationCompleted, whichever navigation it belongs to. If a navigation is already in progress, the call can return when that other navigation completes, before its own page has loaded. Use Navigate + NavigationCompleted when navigations can overlap.
m_lv.NavigateSync urlRegistered:
m_lv.NavigateSync "https://www.example.com"
' Execution pauses until the next navigation completes or SynchronousTimeOut expires
Debug.Print "Page loaded: " & m_lv.DocumentTitleReg-Free:
pool.NavigateSync idx, "https://www.example.com"
Debug.Print "Page loaded: " & pool.GetTitle(idx)See alsoSynchronousTimeOut
Load HTML content directly from a string, instead of navigating to a URL.
m_lv.NavigateToString htmlRegistered:
m_lv.NavigateToString "<html><body><h1>Hello from VBA</h1></body></html>"Reg-Free:
pool.NavigateToString idx, "<html><body><h1>Hello from VBA</h1></body></html>"Load an HTML string and block until load completes.
Same synchronous-wait behavior as NavigateSync (message pump stays responsive; governed by SynchronousTimeOut): inside inline events and host-object methods it fails at once with &H80040203 (use NavigateToString there), and in 1.0.1.0 its wait ends at the next NavigationCompleted (known issue, see NavigateSync).
m_lv.NavigateToStringSync htmlRegistered:
m_lv.NavigateToStringSync "<html><body>Sync loaded</body></html>"
Debug.Print m_lv.GetPageTextReg-Free:
pool.NavigateToStringSync idx, "<html><body>Sync loaded</body></html>"
Debug.Print pool.GetPageText(idx)See alsoNavigateSync, SynchronousTimeOut
Navigate to a file under the folder mapped by SetLocalContentRoot — a shorthand so you don't have to build the https://lv2.example/... URL yourself.
Verified against the LiteView2 IDL and its C++ implementation: IBrowserPool.NavigateToLocalFile(browserIndex, filename) is a first-class pool method, not a Registered-only convenience.
m_lv.NavigateToLocalFile filenameRegistered:
m_lv.SetLocalContentRoot CurrentProject.Path & "\html"
m_lv.NavigateToLocalFile "dashboard.html"
' Equivalent to: m_lv.Navigate "https://lv2.example/dashboard.html"Reg-Free:
pool.SetLocalContentRoot idx, CurrentProject.Path & "\html"
pool.NavigateToLocalFile idx, "dashboard.html"
' Equivalent to: pool.Navigate idx, "https://lv2.example/dashboard.html"See alsoSetLocalContentRoot
Map a local folder to the fixed lv2.example virtual hostname, so local HTML/JS/CSS files can be loaded via https://lv2.example/... URLs instead of file://.
SetLocalContentRoot is a first-class IBrowserPool method (verified against the LiteView2 IDL and its C++ implementation) — it is not a Registered-only convenience. Internally it wraps SetVirtualHostNameToFolderMapping with the fixed lv2.example hostname; calling SetVirtualHostNameToFolderMapping directly does the same thing and remains available if you want a different hostname.
m_lv.SetLocalContentRoot folderRegistered:
m_lv.SetLocalContentRoot CurrentProject.Path & "\html"
m_lv.Navigate "https://lv2.example/index.html"Reg-Free:
pool.SetLocalContentRoot idx, CurrentProject.Path & "\html"
pool.Navigate idx, "https://lv2.example/index.html"Gotchas
SetLocalContentRoot must be called BEFORE the first Navigate — and before AddHostObjectToScript if host objects are used.LiteView2Ctrl1_WebViewReady in Registered Mode, OnBrowserReady in Reg-Free Mode), not in Form_Load. Calling it in Form_Load is a silent no-op or fails.SetLocalContentRoot → AreHostObjectsAllowed → AddHostObjectToScript → Navigate.See alsoSetVirtualHostNameToFolderMapping, NavigateToLocalFile
SetLocalContentRoot folder maps a folder on disk to the https://lv2.example/ virtual host. The mapping is one-to-one: the folder you pass becomes the URL root.
| Disk path you pass | URL root | Example file on disk | URL to navigate to |
|---|---|---|---|
C:\Apps\MyDB\ | https://lv2.example/ | C:\Apps\MyDB\index.html | https://lv2.example/index.html |
C:\Apps\MyDB\ | https://lv2.example/ | C:\Apps\MyDB\pages\admin.html | https://lv2.example/pages/admin.html |
CurrentProject.Path (e.g. C:\Apps\MyDB) | https://lv2.example/ | the form's folder | https://lv2.example/<any-file-in-that-folder> |
Gotchas
C:\Apps\ but your HTML lives in C:\Apps\MyDB\index.html. The URL must then be https://lv2.example/MyDB/index.html, not /index.html. Pick the folder that DIRECTLY contains your HTML; do not over-shoot to a parent.CurrentProject.Path & "\html" and then navigate to https://lv2.example/html/index.html. The \html part already became the new root — the correct URL is https://lv2.example/index.html, with no html prefix.https://lv2.example/index.html works; https://lv2.example\index.html does not. Use forward slashes in URLs only.index.html doesn't actually exist in the mapped folder. WebView2 shows its own "page can't be reached" error — looks like a LiteView2 problem but isn't. Verify the file is there before blaming the control.See alsoSetLocalContentRoot
Map a local folder to a custom (caller-chosen) virtual hostname — the general-purpose form of SetLocalContentRoot, which is hardcoded to the lv2.example hostname.
Use this instead of SetLocalContentRoot when you specifically need a hostname other than lv2.example.
m_lv.SetVirtualHostNameToFolderMapping hostName, folderPath, accessKindRegistered:
m_lv.SetVirtualHostNameToFolderMapping "myapp.local", _
CurrentProject.Path & "\html", 1
m_lv.Navigate "https://myapp.local/dashboard.html"Reg-Free:
pool.SetVirtualHostNameToFolderMapping idx, "myapp.local", _
CurrentProject.Path & "\html", 1
pool.Navigate idx, "https://myapp.local/dashboard.html"See alsoSetLocalContentRoot, NavigateToLocalFile
Remove a virtual hostname mapping previously created with SetVirtualHostNameToFolderMapping.
m_lv.ClearVirtualHostNameToFolderMapping hostNameRegistered:
m_lv.ClearVirtualHostNameToFolderMapping "myapp.local"Reg-Free:
pool.ClearVirtualHostNameToFolderMapping idx, "myapp.local"See alsoSetVirtualHostNameToFolderMapping
Register a custom URI scheme (e.g. myapp://).
Second parameter is treatAsSecure (Boolean).
m_lv.AddCustomSchemeRegistration schemeName, treatAsSecureRegistered:
m_lv.AddCustomSchemeRegistration "myapp", True ' treatAsSecureReg-Free:
pool.AddCustomSchemeRegistration idx, "myapp", TrueNavigate with a custom HTTP method, POST data, and headers, passed inline as parameters (method, body, headers) rather than via a pre-built request handle.
m_lv.NavigateWithWebResourceRequest url, method, postData, headersRegistered:
m_lv.NavigateWithWebResourceRequest _
"https://api.example.com/submit", _
"POST", _
"name=John&email=john@example.com", _
"Content-Type: application/x-www-form-urlencoded"Reg-Free:
pool.NavigateWithWebResourceRequest idx, _
"https://api.example.com/submit", _
"POST", _
"name=John&email=john@example.com", _
"Content-Type: application/x-www-form-urlencoded"Navigate with a custom request (built via CreateWebResourceRequest, passed as a handle) and block until load completes. Same synchronous-wait behavior as NavigateSync: inside inline events and host-object methods it fails at once with &H80040203 (use NavigateWithCustomRequest there), and in 1.0.1.0 its wait ends at the next NavigationCompleted (known issue, see NavigateSync).
m_lv.NavigateWithWebResourceRequestSync hReqRegistered:
Dim hReq As Long
hReq = m_lv.CreateWebResourceRequest("POST", "https://api.example.com/data", _
"Content-Type: application/json", "{""key"":""value""}")
m_lv.NavigateWithWebResourceRequestSync hReqReg-Free:
Dim hReq As Long
hReq = pool.CreateWebResourceRequest(idx, "POST", "https://api.example.com/data", _
"Content-Type: application/json", "{""key"":""value""}")
pool.NavigateWithWebResourceRequestSync idx, hReqSee alsoCreateWebResourceRequest
Create a custom HTTP request object.
Returns a handle (Long) for use with NavigateWithCustomRequest / NavigateWithWebResourceRequestSync.
hReq = m_lv.CreateWebResourceRequest(method, url, headers, postData)Registered:
Dim hReq As Long
hReq = m_lv.CreateWebResourceRequest("GET", "https://api.example.com/data", _
"Authorization: Bearer token123", "")
Debug.Print "Request handle: " & hReqReg-Free:
Dim hReq As Long
hReq = pool.CreateWebResourceRequest(idx, "GET", "https://api.example.com/data", _
"Authorization: Bearer token123", "")See alsoNavigateWithCustomRequest, NavigateWithWebResourceRequestSync
Create a custom HTTP response object, for use inside a WebResourceRequested event handler.
hResp = m_lv.CreateWebResourceResponse(content, statusCode, reasonPhrase, headers)Registered:
Dim hResp As Long
hResp = m_lv.CreateWebResourceResponse("<h1>Blocked</h1>", 403, "Forbidden", _
"Content-Type: text/html")Reg-Free:
Dim hResp As Long
hResp = pool.CreateWebResourceResponse(idx, "<h1>Blocked</h1>", 403, "Forbidden", _
"Content-Type: text/html")See alsoWebResourceRequested
Navigate using a request handle previously obtained from CreateWebResourceRequest.
m_lv.NavigateWithCustomRequest hReqRegistered:
Dim hReq As Long
hReq = m_lv.CreateWebResourceRequest("POST", "https://api.example.com/submit", _
"Content-Type: application/json", "{""data"":1}")
m_lv.NavigateWithCustomRequest hReqReg-Free:
Dim hReq As Long
hReq = pool.CreateWebResourceRequest(idx, "POST", "https://api.example.com/submit", _
"Content-Type: application/json", "{""data"":1}")
pool.NavigateWithCustomRequest idx, hReqSee alsoCreateWebResourceRequest
GoBack navigates back in history. CanGoBack (Property, Boolean, read-only) reports whether a back-navigation is currently possible.
If m_lv.CanGoBack Then m_lv.GoBackRegistered:
If m_lv.CanGoBack Then m_lv.GoBack
Debug.Print "Can go back: " & m_lv.CanGoBackReg-Free:
If pool.CanGoBack(idx) Then pool.GoBack idx
Debug.Print "Can go back: " & pool.CanGoBack(idx)GoForward navigates forward in history. CanGoForward (Property, Boolean, read-only) reports whether a forward-navigation is currently possible.
If m_lv.CanGoForward Then m_lv.GoForwardRegistered:
If m_lv.CanGoForward Then m_lv.GoForward
Debug.Print "Can go forward: " & m_lv.CanGoForwardReg-Free:
If pool.CanGoForward(idx) Then pool.GoForward idx
Debug.Print "Can go forward: " & pool.CanGoForward(idx)Reload the current page.
m_lv.RefreshRegistered:
m_lv.RefreshReg-Free:
pool.Refresh idxStop loading the current page.
m_lv.StopRegistered:
m_lv.Stop ' halt any in-flight navigation before teardownReg-Free:
pool.Stop idx ' halt any in-flight navigation before teardownGotchas
Form_Unload to halt any in-flight navigation before the control/pool slot is destroyed.NavigationCompleted while the control is being torn down.See alsoNavigationCompleted
Get or set the current URL.
Type: String. Registered Mode: read/write (setting it navigates, same as calling Navigate). Reg-Free Mode: the pool exposes only a getter (GetUrl) — there is no Url property setter in the pool object; use Navigate to change the URL instead.
m_lv.Url [= url]Registered:
Debug.Print m_lv.Url ' Get current URL
m_lv.Url = "https://example.com" ' Navigate (same as Navigate)Reg-Free:
Debug.Print pool.GetUrl(idx) ' Get current URL
pool.Navigate idx, "https://example.com" ' No Url property setter in poolGotchas
GetUrl is separately documented in the auto-synced API reference as Mode: Reg-Free Only — "Get current URL" — result = pool.GetUrl(idx) — consistent with the Registered/Reg-Free pairing shown here.See alsoGetUrl, Navigate
Get the current page title.
Type: String. Read-only in both modes. In Registered Mode this is the DocumentTitle property; in Reg-Free Mode there is no DocumentTitle property on the pool — use the GetTitle method instead.
m_lv.DocumentTitleRegistered:
Debug.Print "Title: " & m_lv.DocumentTitleReg-Free:
Debug.Print "Title: " & pool.GetTitle(idx)Gotchas
GetTitle is separately documented in the auto-synced API reference as Mode: Reg-Free Only — "Get page title" — result = pool.GetTitle(idx) — consistent with the pairing shown here.See alsoGetTitle
Check if the page is currently loading.
Type: Boolean. Read-only.
m_lv.IsLoadingRegistered:
If m_lv.IsLoading Then Debug.Print "Still loading..."Reg-Free:
If pool.IsLoading(idx) Then Debug.Print "Still loading..."SynchronousTimeOut is the timeout, in milliseconds (default 30000), that ExecuteScript (with everything built on it), ExecuteScriptWithResult, the three *Sync navigation methods and WaitForElement (when you pass 0 for its timeoutMs) wait before giving up.
Type: Long. Read/write. The UI remains responsive during the wait (message pump).
Use a value greater than 0.
ExecuteScript and ExecuteScriptWithResult treat 0 as 30000.*Sync navigation methods treat 0 as "don't wait": they return the timeout error &H800705B4 at once, while the navigation goes on.On a timeout, ExecuteScript and the *Sync methods raise &H800705B4, and ExecuteScriptWithResult returns succeeded = False.
Other waiting calls have fixed limits and ignore this property:
| Call | Fixed limit |
|---|---|
CallScriptSync, GetScriptProperty, SetScriptProperty, BindScriptObject with Validate | 30 s (LV2_BRIDGE_TIMEOUT, &H80040507) |
CallDevToolsProtocolMethod, ClearBrowsingDataInTimeRange | 30 s |
GetBrowserExtensionList, DetectCaptcha | 10 s |
GetCookies, AddScriptToExecuteOnDocumentCreated | 5 s |
Inside inline events and host-object calls no wait starts at all: the call fails at once with &H80040203. A negative value raises E_INVALIDARG.
m_lv.SynchronousTimeOut [= ms]Registered:
Debug.Print m_lv.SynchronousTimeOut ' Get (default 30000)
m_lv.SynchronousTimeOut = 60000 ' Set to 60 secondsReg-Free:
Debug.Print pool.SynchronousTimeOut(idx)
pool.SynchronousTimeOut(idx) = 60000See alsoNavigateSync, NavigateToStringSync
Maps a folder on disk to the reserved https://lv2.example/ virtual host.
The mapping is one-to-one: the folder you pass becomes the URL root. Verified against the LiteView2 IDL and its C++ implementation: IBrowserPool.SetLocalContentRoot(browserIndex, folderPath) is a first-class pool method, not a Registered-only convenience — internally it wraps SetVirtualHostNameToFolderMapping with the fixed lv2.example hostname on either side.
SetLocalContentRoot([idx,] folderPath)m_lv.SetLocalContentRoot CurrentProject.Path & "\html"
m_lv.Navigate "https://lv2.example/index.html"pool.SetLocalContentRoot idx, CurrentProject.Path & "\html"
pool.Navigate idx, "https://lv2.example/index.html"Path-mapping examples
| Disk path you pass | URL root | Example file on disk | URL to navigate to |
|---|---|---|---|
C:\Apps\MyDB\ | https://lv2.example/ | C:\Apps\MyDB\index.html | https://lv2.example/index.html |
C:\Apps\MyDB\ | https://lv2.example/ | C:\Apps\MyDB\pages\admin.html | https://lv2.example/pages/admin.html |
CurrentProject.Path (e.g. C:\Apps\MyDB) | https://lv2.example/ | the form's folder | https://lv2.example/<any-file-in-that-folder> |
Gotchas
C:\Apps\ but your HTML lives in C:\Apps\MyDB\index.html. The URL must then be https://lv2.example/MyDB/index.html, not /index.html. Pick the folder that DIRECTLY contains your HTML; do not over-shoot to a parent.CurrentProject.Path & "\html" and then navigate to https://lv2.example/html/index.html. The \html part already became the new root — the correct URL is https://lv2.example/index.html, with no html prefix.https://lv2.example/index.html works; https://lv2.example\index.html does not. Use forward slashes in URLs only.index.html doesn't actually exist in the mapped folder. WebView2 shows its own "page can't be reached" error — looks like a LiteView2 problem but isn't. Verify the file is there before blaming the control.See alsoSetVirtualHostNameToFolderMapping
Maps a local folder to a custom virtual hostname (any hostname you choose, not limited to lv2.example).
SetVirtualHostNameToFolderMapping([idx,] hostName, folderPath, 1)m_lv.SetVirtualHostNameToFolderMapping "myapp.local", _
CurrentProject.Path & "\html", 1
m_lv.Navigate "https://myapp.local/dashboard.html"pool.SetVirtualHostNameToFolderMapping idx, "myapp.local", _
CurrentProject.Path & "\html", 1
pool.Navigate idx, "https://myapp.local/dashboard.html"Gotchas
idx as the leading browser-index argument; Registered has no such argument, since m_lv addresses a single control instance directly.1 as the final argument, exactly as shown in the source examples; the reviewed source ranges do not define what that argument represents.See alsoSetLocalContentRoot
Removes a virtual hostname mapping previously set with SetVirtualHostNameToFolderMapping (or SetLocalContentRoot).
ClearVirtualHostNameToFolderMapping([idx,] hostName)m_lv.ClearVirtualHostNameToFolderMapping "myapp.local"pool.ClearVirtualHostNameToFolderMapping idx, "myapp.local"See alsoSetVirtualHostNameToFolderMapping, SetLocalContentRoot
Registers a custom URI scheme (e.g., myapp://).
AddCustomSchemeRegistration([idx,] schemeName, treatAsSecure)m_lv.AddCustomSchemeRegistration "myapp", True ' treatAsSecurepool.AddCustomSchemeRegistration idx, "myapp", TrueSynchronous calls and events (LiteView2 1.0.1.0)
Which calls wait. Some methods wait for the browser's answer before they return:
ExecuteScript and everything built on it: ExecuteScriptWithArgs, the DOM helpers such as GetElementValueById, GetPageHtml, GetPageText and GetTextBySelectorExecuteScriptWithResult, WaitForElement, GetCookies, GetBrowserExtensionList, ClearBrowsingDataInTimeRange, CallDevToolsProtocolMethod, DetectCaptcha, SolveCaptchaNavigateSync, NavigateToStringSync, NavigateWithWebResourceRequestSyncCallScriptSync, GetScriptProperty, SetScriptProperty, and BindScriptObject with LV2_BIND_VALIDATEAddScriptToExecuteOnDocumentCreated and EnableLvFacadeWhere they work. In ordinary code (a button, a timer), and inside these events, which LiteView2 raises after WebView2's own callback has returned:
NavigationCompleted, NavigationCompletedHexSourceChanged, ContentLoading, ContentLoadingHex, DOMContentLoadedDocumentTitleChanged, HistoryChangedWebMessageReceived, DevToolsProtocolEventReceivedWhere they fail. Everywhere else they do not wait. They fail at once with error &H80040203 (-2147220989), start nothing, and the error text says what to use instead. That applies:
WebViewReady, CreateWebViewCompleted, NavigationStarting and every other event not listed above;chrome.webview.hostObjects.<name> or .sync.<name>) or RegisterVbaCallback.WaitForElement returns False there instead of raising. The rule is per thread: inside one control's event, the other LiteView2 controls on the form refuse as well.
What to use in those places. Calls that do not wait: Navigate, NavigateToString, ExecuteScriptAsync, PostWebMessageAsJson / PostWebMessageAsString, CallScript, PushRecordset, FireJsEvent, CallJsFunction, SetJsVariable, AddHostObjectToScript, property assignments, and the JSON helpers. To get a value back, do one of these:
chrome.webview.postMessage(...) and read the answer in WebMessageReceived;Application.OnTime in Excel or Word, a Timer control in VB6.Reg-Free (BrowserPool). The pool raises no events. Its ready callback (SetReadyCallback) and message macro (SetWebMessageCallback) run from the message loop, where synchronous pool calls work. The exception is when your own code is pumping messages (MsgBox, DoEvents) inside an event or host-object method at that moment. Inside a VBA method the page is calling, a synchronous pool call fails with &H80004005; pool.GetLastErrorCode(idx) returns &H80040203 and pool.GetLastError(idx) gives the reason.
Execute JavaScript synchronously and return the result as a JSON-encoded string.
Waits at most SynchronousTimeOut ms (default 30000; 0 also means 30000). On a timeout it raises &H800705B4 (-2147023436) and sets LastErrorCode; the page may still run the script later. Inside WebViewReady, NavigationStarting, the other inline events and host-object methods it raises &H80040203 at once and runs nothing. Use ExecuteScriptAsync there. It works inside NavigationCompleted, DOMContentLoaded, WebMessageReceived and the other notification events. Before the browser exists it fails: it is not queued (ExecuteScriptAsync is).
Reg-Free: a failing call raises &H80004005; pool.GetLastErrorCode(idx) returns &H80040203 or &H800705B4.
result = m_lv.ExecuteScript(script)Dim result As String
result = m_lv.ExecuteScript("document.title")
Debug.Print "Title: " & result ' Returns JSON-encoded: "My Page Title"
' Call a function
result = m_lv.ExecuteScript("getFormData()")
Debug.Print result ' Returns: {"name":"John Doe","email":"john@example.com"}Dim result As String
result = pool.ExecuteScript(idx, "document.title")
Debug.Print "Title: " & resultExecute JavaScript asynchronously (fire and forget). No return value.
m_lv.ExecuteScriptAsync scriptm_lv.ExecuteScriptAsync "updateStatus('Processing...');"
m_lv.ExecuteScriptAsync "document.getElementById('divResults').textContent = 'Updated';"pool.ExecuteScriptAsync idx, "updateStatus('Processing...');"Execute script and get result + success flag + exception info.
Waits at most SynchronousTimeOut (0 means 30000). A timeout raises no error. Instead it returns succeeded = False, puts "ExecuteScriptWithResult got no result from the page within … ms (SynchronousTimeOut)…" in errMsg, and sets LastErrorCode = &H800705B4. So succeeded = False does not always mean the script threw. Inside inline events and host-object methods it raises &H80040203 (it does not return succeeded = False) and runs nothing.
m_lv.ExecuteScriptWithResult script, resultJson, succeeded, errMsgDim resultJson As String, succeeded As Boolean, errMsg As String
m_lv.ExecuteScriptWithResult "1 + 2", resultJson, succeeded, errMsg
If succeeded Then
Debug.Print "Result: " & resultJson ' "3"
Else
Debug.Print "Error: " & errMsg
End IfDim resultJson As String, succeeded As Boolean, errMsg As String
pool.ExecuteScriptWithResult idx, "1 + 2", resultJson, succeeded, errMsg
If succeeded Then Debug.Print "Result: " & resultJsonExecute a JavaScript function with VBA arguments (auto-serialized).
result = m_lv.ExecuteScriptWithArgs(funcName, argsArray)Dim result As String
result = m_lv.ExecuteScriptWithArgs("loadKpis", Array(100, 50))
' Calls: loadKpis(100, 50) in the pageDim result As String
result = pool.ExecuteScriptWithArgs(idx, "loadKpis", Array(100, 50))Call a JavaScript function by dot-path with a JSON-encoded arguments array.
m_lv.CallJsFunction path, jsonArgsm_lv.CallJsFunction "window.myChart.update", "[1, 2, 3]"
m_lv.CallJsFunction "updateStatus", "[""Hello from VBA""]"' Use ExecuteScriptAsync with manual call
pool.ExecuteScriptAsync idx, "updateStatus('Hello from VBA');"Gotchas
CallJsFunction; the Reg-Free workaround shown in the source is to build the call manually with ExecuteScriptAsync, which is not a real equivalent."[""Hello from VBA""]" evaluates at runtime to the JSON text ["Hello from VBA"] — a one-element array containing the string Hello from VBA.SetJsVariable.See alsoSetJsVariable, ExecuteScriptAsync
Set a JavaScript variable to a JSON-encoded value.
m_lv.SetJsVariable path, jsonValuem_lv.SetJsVariable "window.userName", """Alice"""
m_lv.SetJsVariable "window.config", "{""debug"":true,""timeout"":5000}"pool.ExecuteScriptAsync idx, "window.userName = 'Alice';"Gotchas
SetJsVariable; the Reg-Free workaround shown in the source is to assign the variable manually with ExecuteScriptAsync, which is not a real equivalent."""Alice""" evaluates at runtime to the 7-character JSON text "Alice" (a quoted JSON string — the doubled quotes collapse down to the single pair that wraps the value).{"debug":true,"timeout":5000}, a JSON object.CallJsFunction.See alsoCallJsFunction, ExecuteScriptAsync
Register a script that runs automatically on every new page load. Returns a scriptId for removal.
Registered. Register document scripts before the browser exists, in Form_Load / UserForm_Initialize. There the call is queued, and the script is registered before WebViewReady fires and before the first Navigate, so it also runs on the first page. A queued call returns an empty scriptId, so there is no id to pass to RemoveScriptToExecuteOnDocumentCreated.
Once the browser exists, the call waits (up to 5 s) for WebView2 to confirm and returns the id. That works from ordinary code and from notification events, and affects later navigations only. Inside WebViewReady, other inline events and host-object methods it fails at once with &H80040203 and registers nothing.
Reg-Free. Call pool.AddScriptToExecuteOnDocumentCreated idx, script inside OnBrowserReady, before pool.Navigate. The ready callback runs from the message loop, so the call completes there.
scriptId = m_lv.AddScriptToExecuteOnDocumentCreated(script)Dim scriptId As String
scriptId = m_lv.AddScriptToExecuteOnDocumentCreated( _
"window._appReady = true; console.log('LiteView2 injected');")
Debug.Print "Script ID: " & scriptIdDim scriptId As String
scriptId = pool.AddScriptToExecuteOnDocumentCreated(idx, _
"window._appReady = true; console.log('LiteView2 injected');")See alsoRemoveScriptToExecuteOnDocumentCreated
Remove a previously registered auto-execute script (by the scriptId returned from AddScriptToExecuteOnDocumentCreated).
m_lv.RemoveScriptToExecuteOnDocumentCreated scriptIdm_lv.RemoveScriptToExecuteOnDocumentCreated scriptIdpool.RemoveScriptToExecuteOnDocumentCreated idx, scriptIdSee alsoAddScriptToExecuteOnDocumentCreated
Get or set whether JavaScript is enabled.
Type: Boolean. Read/write.
IsScriptEnabled As Boolean · GetScriptEnabled(idx) As Boolean / SetScriptEnabled idx, valueDebug.Print "JS enabled: " & m_lv.IsScriptEnabled ' Get
m_lv.IsScriptEnabled = False ' Disable JSDim result As Boolean
result = pool.GetScriptEnabled(idx)
Debug.Print "JS enabled: " & result
pool.SetScriptEnabled idx, FalseGotchas
IsScriptEnabled.GetScriptEnabled / SetScriptEnabled, rather than one R/W property.GetScriptEnabled and SetScriptEnabled is independently confirmed "Reg-Free Only" in the auto-synced reference, which also types the getter's return as Boolean.Expose a VBA COM object to JavaScript.
m_lv.AddHostObjectToScript "vba", helperRegistered:
m_lv.AreHostObjectsAllowed = True
Dim helper As New clsJsBridge
m_lv.AddHostObjectToScript "vba", helperJavaScript side (either mode):
const vba = chrome.webview.hostObjects.vba;
const result = await vba.GetCustomerName(123);Reg-Free:
pool.SetAreHostObjectsAllowed idx, True
Dim helper As New clsJsBridge
pool.AddHostObjectToScript idx, "vba", helperRemove a previously exposed host object.
m_lv.RemoveHostObjectFromScript "vba"As RemoveHostObjectFromScript (Cookbook usage entry):
Registered:
m_lv.RemoveHostObjectFromScript "vba"Reg-Free:
pool.RemoveHostObjectFromScript idx, "vba"As RemoveHostObjectToScript (auto-synced-from-IDL entry):
Registered:
m_lv.RemoveHostObjectToScript nameReg-Free:
pool.RemoveHostObjectToScript browserIndex, nameGotchas
RemoveHostObjectFromScript.RemoveHostObjectToScript (same one-line description — "Remove host object from script" — same parameter shape, same mode availability).Enable/disable host objects.
Type: Boolean. R/W.
m_lv.AreHostObjectsAllowed = TrueRegistered — true property, get and set:
m_lv.AreHostObjectsAllowed = TrueReg-Free — exposed as two explicit accessor methods, not a property, since the pool object is indexed by browser:
pool.SetAreHostObjectsAllowed idx, True
Debug.Print pool.GetAreHostObjectsAllowed(idx)Gotchas
GetAreHostObjectsAllowed and SetAreHostObjectsAllowed, because the pool object is indexed by browser.See alsoGetAreHostObjectsAllowed, SetAreHostObjectsAllowed
Get whether host objects are allowed in script.
result = pool.GetAreHostObjectsAllowed(browserIndex)Dim result As Boolean
result = pool.GetAreHostObjectsAllowed(browserIndex)See alsoAreHostObjectsAllowed, SetAreHostObjectsAllowed
Set whether host objects are allowed in script.
pool.SetAreHostObjectsAllowed browserIndex, allowedpool.SetAreHostObjectsAllowed browserIndex, allowedSee alsoAreHostObjectsAllowed, GetAreHostObjectsAllowed
Send a JSON message from VBA to JavaScript.
PostWebMessageAsJson(json As String)m_lv.PostWebMessageAsJson "{""type"":""update"",""value"":42}"chrome.webview.addEventListener('message', function(event) {
var data = event.data; // {type: "update", value: 42}
document.getElementById('divStatus').textContent = JSON.stringify(data);
});pool.PostWebMessageAsJson idx, "{""type"":""update"",""value"":42}"Send a plain string message from VBA to JavaScript.
PostWebMessageAsString(message As String)m_lv.PostWebMessageAsString "Hello from VBA!"chrome.webview.addEventListener('message', function(event) {
console.log(event.data); // "Hello from VBA!"
});pool.PostWebMessageAsString idx, "Hello from VBA!"Register a VBA macro to receive messages sent from JavaScript.
SetWebMessageCallback(context As Object, procName As String)' In Form_Load:
pool.SetWebMessageCallback idx, Application, "CB_frmMyForm"
' In modLV2Callbacks.bas:
Public Sub CB_frmMyForm(ByVal message As String, Optional ByVal source As String = "")
If IsFormLoaded("frmMyForm") Then Forms("frmMyForm").HandleMessage message
End Sub
' In the form module:
Public Sub HandleMessage(ByVal message As String)
If InStr(message, """action"":""save""") > 0 Then
' handle save action
End If
End SubGotchas
WebMessageReceived event instead.See alsoWebMessageReceived
Clear the callback macro registered via SetWebMessageCallback.
ClearWebMessageCallback()m_lv.ClearWebMessageCallbackpool.ClearWebMessageCallback idxSee alsoSetWebMessageCallback
Inject the window.lv global JavaScript facade into every page loaded afterwards. Call it in Form_Load (queued until the browser is ready, so it covers the first page) or later from ordinary code. Inside WebViewReady and other inline events it fails at once with &H80040203. EnableLvFacade False only clears IsLvFacadeEnabled: the facade script stays registered and is still injected into later pages.
Provides lv.call(), lv.on(), lv.send(), and lv.bind().
EnableLvFacade(enable As Boolean)m_lv.EnableLvFacade True
' JavaScript can now use:
' lv.send({action: "save", data: {...}})
' lv.on("dataReady", function(data) { ... })' Not available in pool - use PostWebMessageAsJson/ExecuteScript directlyGotchas
PostWebMessageAsJson/ExecuteScript directly instead.See alsoPostWebMessageAsJson, ExecuteScript
Check whether the window.lv facade is active. Type: Boolean. Read-only.
IsLvFacadeEnabled As Boolean ' Read-onlyDebug.Print "LV Facade enabled: " & m_lv.IsLvFacadeEnabledSee alsoEnableLvFacade
Get or set whether web messaging is enabled. Type: Boolean. Read/write.
Exposed differently per mode: a single R/W property in Registered mode, and a pair of separate get/set methods in Reg-Free mode.
IsWebMessageEnabled As Boolean ' Read/writeDebug.Print "Web messaging: " & m_lv.IsWebMessageEnabled ' Get
m_lv.IsWebMessageEnabled = True ' EnableDebug.Print "Web messaging: " & pool.GetWebMessageEnabled(idx)
pool.SetWebMessageEnabled idx, TrueGotchas
GetWebMessageEnabled — "Get web message enabled." — and SetWebMessageEnabled — "Set web message enabled."Dim result As Boolean
result = pool.GetWebMessageEnabled(idx)pool.SetWebMessageEnabled idx, enabledAll code examples below reference elements in cookbook_test.html. Every member in this category is available in both Registered Mode and Reg-Free Mode; no member here is ILV2Json-standalone. Registered Mode calls the member directly on the control instance (m_lv); Reg-Free Mode calls it on the pool object (pool), passing the browser index (idx) as the first argument.
Get or set the value of an input element by its ID.
m_lv.GetElementValueById(id) / m_lv.SetElementValueById id, valueDim name As String
name = m_lv.GetElementValueById("txtName")
Debug.Print "Name: " & name ' "John Doe"
m_lv.SetElementValueById "txtName", "Jane Smith"
m_lv.SetElementValueById "txtEmail", "jane@example.com"Dim name As String
name = pool.GetElementValueById(idx, "txtName")
Debug.Print "Name: " & name
pool.SetElementValueById idx, "txtName", "Jane Smith"
pool.SetElementValueById idx, "txtEmail", "jane@example.com"Get or set an element's value by its name attribute (as opposed to its ID).
m_lv.GetElementValueByName(name) / m_lv.SetElementValueByName name, valueDim email As String
email = m_lv.GetElementValueByName("userEmail")
Debug.Print "Email: " & email ' "john@example.com"
m_lv.SetElementValueByName "userEmail", "new@example.com"Dim email As String
email = pool.GetElementValueByName(idx, "userEmail")
pool.SetElementValueByName idx, "userEmail", "new@example.com"Click an element by its ID.
m_lv.ClickElementById idm_lv.ClickElementById "btnSubmit"pool.ClickElementById idx, "btnSubmit"Click an element by its name attribute.
m_lv.ClickElementByName namem_lv.ClickElementByName "agree"pool.ClickElementByName idx, "agree"Click an element identified by a CSS selector.
m_lv.ClickElementBySelector selectorm_lv.ClickElementBySelector "#btnSubmit"
m_lv.ClickElementBySelector "button.primary"
m_lv.ClickElementBySelector "form#frmContact button[type='submit']"pool.ClickElementBySelector idx, "#btnSubmit"Get or set the innerHTML of an element by ID.
m_lv.GetInnerHtmlById(id) / m_lv.SetInnerHtmlById id, htmlDim html As String
html = m_lv.GetInnerHtmlById("divResults")
Debug.Print "Inner HTML: " & html ' "Results will appear here"
m_lv.SetInnerHtmlById "divResults", "<p>Found <b>42</b> items</p>"Dim html As String
html = pool.GetInnerHtmlById(idx, "divResults")
pool.SetInnerHtmlById idx, "divResults", "<p>Found <b>42</b> items</p>"Get the outerHTML of an element by ID, or replace an element entirely (including its own tag) by ID.
m_lv.GetOuterHtmlById(id) / m_lv.SetOuterHtmlById id, htmlDim outer As String
outer = m_lv.GetOuterHtmlById("pageTitle")
Debug.Print outer ' "<h2 id=""pageTitle"">Cookbook Test Page</h2>"
m_lv.SetOuterHtmlById "divResults", "<div id=""divResults""><b>Replaced!</b></div>"Dim outer As String
outer = pool.GetOuterHtmlById(idx, "pageTitle")
pool.SetOuterHtmlById idx, "divResults", "<div id=""divResults""><b>Replaced!</b></div>"Get or set the outerHTML of the first element matching a given name attribute.
m_lv.GetOuterHtmlByName(name) / m_lv.SetOuterHtmlByName name, htmlDim html As String
html = m_lv.GetOuterHtmlByName("userName")
Debug.Print html
m_lv.SetOuterHtmlByName "userName", "<input type=""text"" name=""userName"" value=""New"">"Dim html As String
html = pool.GetOuterHtmlByName(idx, "userName")
pool.SetOuterHtmlByName idx, "userName", _
"<input type=""text"" name=""userName"" value=""New"">"Get or set the text content (not HTML) of an element by ID.
m_lv.GetElementTextById(id) / m_lv.SetElementTextById id, textDim txt As String
txt = m_lv.GetElementTextById("divStatus")
Debug.Print "Status: " & txt ' "Status: ready"
m_lv.SetElementTextById "divStatus", "Status: processing..."Dim txt As String
txt = pool.GetElementTextById(idx, "divStatus")
pool.SetElementTextById idx, "divStatus", "Status: processing..."Get, set, or remove an arbitrary attribute value on an element by ID.
m_lv.GetElementAttributeById(id, name) / m_lv.SetElementAttributeById id, name, value / m_lv.RemoveElementAttributeById id, nameDim role As String
role = m_lv.GetElementAttributeById("txtName", "data-role")
Debug.Print "Role: " & role ' "primary"
m_lv.SetElementAttributeById "txtName", "placeholder", "Enter your name"
m_lv.SetElementAttributeById "txtName", "data-role", "secondary"
m_lv.RemoveElementAttributeById "txtName", "data-role"Dim role As String
role = pool.GetElementAttributeById(idx, "txtName", "data-role")
pool.SetElementAttributeById idx, "txtName", "placeholder", "Enter your name"
pool.RemoveElementAttributeById idx, "txtName", "data-role"Get the complete HTML of the current page.
m_lv.GetPageHtml As StringDim fullHtml As String
fullHtml = m_lv.GetPageHtml
Debug.Print "HTML length: " & Len(fullHtml)Dim fullHtml As String
fullHtml = pool.GetPageHtml(idx)Get the plain text content of the page (no HTML tags).
m_lv.GetPageText As StringDim plainText As String
plainText = m_lv.GetPageText
Debug.Print plainTextDim plainText As String
plainText = pool.GetPageText(idx)Check whether an element exists, by its ID.
Returns a Boolean.
m_lv.ElementExistsById(id) As BooleanIf m_lv.ElementExistsById("errorDiv") Then
Debug.Print "Error div found"
End IfIf pool.ElementExistsById(idx, "errorDiv") Then
Debug.Print "Error div found"
End IfCheck whether an element exists, by CSS selector.
Returns a Boolean.
m_lv.ElementExistsBySelector(selector) As BooleanIf m_lv.ElementExistsBySelector("table#tblData tr:nth-child(2)") Then
Debug.Print "Table has at least 2 rows"
End IfIf pool.ElementExistsBySelector(idx, "table#tblData tr:nth-child(2)") Then
Debug.Print "Table has at least 2 rows"
End IfSubmit a form by its ID.
m_lv.SubmitFormById idm_lv.SetElementValueById "frmName", "John Doe"
m_lv.SetElementValueById "frmMsg", "Hello"
m_lv.SubmitFormById "frmContact"pool.SetElementValueById idx, "frmName", "John Doe"
pool.SetElementValueById idx, "frmMsg", "Hello"
pool.SubmitFormById idx, "frmContact"Set or get a checkbox's checked state by element ID.
m_lv.SetCheckboxById id, checked / m_lv.GetCheckboxById(id) As Booleanm_lv.SetCheckboxById "chkAgree", True
m_lv.SetCheckboxById "chkNewsletter", False
If m_lv.GetCheckboxById("chkAgree") Then
Debug.Print "User agreed"
End Ifpool.SetCheckboxById idx, "chkAgree", True
pool.SetCheckboxById idx, "chkNewsletter", False
If pool.GetCheckboxById(idx, "chkAgree") Then
Debug.Print "User agreed"
End IfSelect a <select> dropdown option by element ID and option value, or read back the currently selected option's value.
m_lv.SelectOptionById id, value / m_lv.GetSelectedOptionById(id) As Stringm_lv.SelectOptionById "ddlCountry", "DE" ' Select Germany
Dim country As String
country = m_lv.GetSelectedOptionById("ddlCountry")
Debug.Print "Selected country: " & country ' "US"pool.SelectOptionById idx, "ddlCountry", "DE"
Dim country As String
country = pool.GetSelectedOptionById(idx, "ddlCountry")Set keyboard focus to an element by its ID.
m_lv.FocusElementById idm_lv.FocusElementById "txtEmail"pool.FocusElementById idx, "txtEmail"Scroll an element into view by its ID.
m_lv.ScrollToElementById idm_lv.ScrollToElementById "scrollTarget"pool.ScrollToElementById idx, "scrollTarget"Serialize a DAO or ADO recordset to JSON and call a JavaScript function with the data.
m_lv.PushRecordset rs, "onSalesData"Dim rs As DAO.Recordset
Set rs = CurrentDb.OpenRecordset( _
"SELECT Year(SaleDate) AS Yr, Month(SaleDate) AS Mo, " & _
" Sum(Quantity * UnitPrice) AS Total " & _
"FROM tblSales " & _
"GROUP BY Year(SaleDate), Month(SaleDate)", dbOpenSnapshot)
rs.MoveLast
rs.MoveFirst
m_lv.PushRecordset rs, "onSalesData"
rs.Closefunction onSalesData(rows) {
// rows = [{Yr:2024, Mo:1, Total:15234.50}, ...]
rows.forEach(r => console.log(r.Yr + "-" + r.Mo + ": $" + r.Total));
}pool.PushRecordset idx, rs, "onSalesData"Gotchas
dbOpenSnapshot for GROUP BY/JOIN queries.rs.MoveLast : rs.MoveFirst before PushRecordset.Convert any VBA data (string, number, array, etc.) to a valid JSON string.
Works without WebView2.
json = m_lv.BuildJson(value)Dim json As String
json = m_lv.BuildJson("Hello World") ' Returns: "Hello World"
json = m_lv.BuildJson(42) ' Returns: 42
json = m_lv.BuildJson(Array(1, 2, 3)) ' Returns: [1,2,3]
Debug.Print jsonDim json As String
json = pool.BuildJson(idx, Array("a", "b", "c"))Fire a CustomEvent in the page document.
JS can listen with addEventListener or window.lv.on().
m_lv.FireJsEvent "eventName", "jsonDetail"m_lv.FireJsEvent "dataReady", "{""count"":42,""source"":""VBA""}"document.addEventListener('dataReady', function(e) {
console.log(e.detail); // {count: 42, source: "VBA"}
});' Use ExecuteScriptAsync to dispatch the event manually
pool.ExecuteScriptAsync idx, _
"document.dispatchEvent(new CustomEvent('dataReady'," & _
"{detail:{count:42,source:'VBA'}}));"Gotchas
FireJsEvent method exists on the pool object in Reg-Free mode.ExecuteScriptAsync instead of calling FireJsEvent directly.See alsoExecuteScriptAsync
Bind an alias to a JavaScript object once, then read/write its properties and call its methods from VBA as if it were a native form control. This is the typed, two-way successor to hand-built ExecuteScript strings: instead of concatenating JavaScript by hand, you write m_lv.SetScriptProperty "page", "Title", "Customers" and total = m_lv.CallScriptSync("page", "computeTotal", 2, 3).
All members below assume the page exposes a JavaScript object, e.g. window.PageController:
<script>
window.PageController = {
Title: "Untitled",
computeTotal: function (a, b) { return (a || 0) + (b || 0); },
showToast: function (msg) { document.title = msg; }
};
</script>Bind once, after the page that defines the object has loaded: in NavigationCompleted (Registered), or later from ordinary code. A bind without LV2_BIND_VALIDATE also works in WebViewReady / OnBrowserReady and is resolved lazily. Inside WebViewReady, other inline events and host-object methods, a validating bind fails at once with &H80040203 and changes no binding. Then use the members below to drive it.
Mode availability: every member in this category is available in both Registered and Reg-Free mode. There is no standalone-ILV2Json form.
Bind an alias to a JavaScript object expression.
Every other bridge call refers to the object by this alias. An optional flag (3rd argument — see LV2_BIND_FLAGS below) controls validation and lifetime; combine flags with +.
m_lv.BindScriptObject alias, jsExpression [, flags]m_lv.BindScriptObject "page", "window.PageController"
' with an optional flag (validate immediately; error if not present):
m_lv.BindScriptObject "page", "window.PageController", 1pool.BindScriptObject idx, "page", "window.PageController"See alsoLV2_BIND_FLAGS
Release a binding.
Idempotent — unbinding an alias that isn't bound simply returns success.
m_lv.UnbindScriptObject aliasm_lv.UnbindScriptObject "page"pool.UnbindScriptObject idx, "page"Call a method on the bound object, fire-and-forget.
Returns immediately without waiting for a result.
m_lv.CallScript alias, methodName [, arg1, arg2, ...]m_lv.CallScript "page", "showToast", "Saved!"pool.CallScript idx, "page", "showToast", "Saved!"Gotchas
CallScriptSync if you need to detect failures.See alsoCallScriptSync
Call a method and block until it returns, delivering the result as a typed Variant (number, string, boolean, date, array, …).
result = m_lv.CallScriptSync(alias, methodName [, arg1, arg2, ...])Dim total As Variant
total = m_lv.CallScriptSync("page", "computeTotal", 2, 3)
Debug.Print total ' 5 (a real number, not a string)Dim total As Variant
total = pool.CallScriptSync(idx, "page", "computeTotal", 2, 3)Gotchas
LV2_BRIDGE_PROMISE_RETURNED rather than blocking — use a synchronous method with CallScriptSync.See alsoLV2_BRIDGE_ERR
Read a property of the bound object as a typed Variant.
value = m_lv.GetScriptProperty(alias, propertyName)Dim caption As Variant
caption = m_lv.GetScriptProperty("page", "Title")Dim caption As Variant
caption = pool.GetScriptProperty(idx, "page", "Title")Gotchas
LV2_BRIDGE_PROPERTY_IS_FUNCTION — useful as a "does this method exist?" probe.See alsoLV2_BRIDGE_ERR
Write a property of the bound object.
m_lv.SetScriptProperty alias, propertyName, valuem_lv.SetScriptProperty "page", "Title", "Customers"pool.SetScriptProperty idx, "page", "Title", "Customers"Gotchas
GetScriptProperty to confirm.See alsoGetScriptProperty
Retrieve the last bridge error for this browser: a numeric code (an HRESULT) and a message, returned via two ByRef output arguments.
The slot is overwritten on each error and is never auto-cleared.
m_lv.GetLastBridgeError code, msgOn Error Resume Next
Dim v As Variant
v = m_lv.CallScriptSync("page", "noSuchMethod")
If Err.Number <> 0 Then
Dim code As Long, msg As String
m_lv.GetLastBridgeError code, msg
Debug.Print Hex(code) & " - " & msg ' 80040503 - ...
End If
On Error GoTo 0Dim code As Long, msg As String
pool.GetLastBridgeError idx, code, msgSee alsoLV2_BRIDGE_ERR
Pass as the optional last argument of BindScriptObject (combine with +).
LV2_BIND_FLAGS As Long ' bit flags, combine with +| Flag | Value | Meaning |
|---|---|---|
| (none) | 0 | Default — resolve lazily; the first call surfaces any resolution error. |
| Validate | 1 | Resolve the object at bind time; return an error immediately if it's missing. |
| Transient | 2 | Drop the binding on the next navigation instead of re-resolving it. |
| RawExpression | 4 | Accept an arbitrary JavaScript expression instead of a simple object path. You accept the injection risk — use only with trusted strings. |
| Async | 8 | Reserved — accepted but has no effect in this release. |
| FailIfBound | 16 | Return an error instead of silently replacing an existing binding. |
| FollowResolution | 32 | Re-evaluate the expression on every call (tracks reassignment) instead of capturing the object once at bind time. |
See alsoBindScriptObject
Surfaced as Err.Number and via GetLastBridgeError.
LV2_BRIDGE_ERR ' HRESULT-coded constants, &H8004xxxx| Constant | Hex | When |
|---|---|---|
| LV2_BRIDGE_ALIAS_NOT_FOUND | &H80040501 | Call on an alias that was never bound. |
| LV2_BRIDGE_OBJECT_NOT_RESOLVED | &H80040502 | The bound expression evaluates to nothing on the page. |
| LV2_BRIDGE_METHOD_NOT_FOUND | &H80040503 | No method of that name on the object. |
| LV2_BRIDGE_PROPERTY_NOT_FOUND | &H80040504 | No property of that name on the object. |
| LV2_BRIDGE_JS_EXCEPTION | &H80040505 | The JavaScript threw — see the message. |
| LV2_BRIDGE_PROMISE_RETURNED | &H80040506 | A sync call received a Promise; use a synchronous method. |
| LV2_BRIDGE_TIMEOUT | &H80040507 | The call did not complete within 30 seconds. |
| LV2_BRIDGE_NOT_READY | &H80040508 | The browser isn't ready yet. |
| LV2_BRIDGE_INVALID_ARG | &H80040509 | Bad alias/member name, or a value that can't be passed. |
| LV2_BRIDGE_INVALID_EXPRESSION | &H8004050A | The bind expression isn't a safe object path (use RawExpression to override). |
| LV2_BRIDGE_REENTRY_BLOCKED | &H8004050B | Too many nested synchronous calls. |
| LV2_BRIDGE_ALIAS_ALREADY_BOUND | &H8004050C | Rebind blocked because FailIfBound was set. |
| LV2_BRIDGE_PROPERTY_IS_FUNCTION | &H8004050D | GetScriptProperty target is a function. |
| (no constant) E_LV2_SYNC_REENTRANT | &H80040203 | A synchronous bridge call (CallScriptSync, GetScriptProperty, SetScriptProperty, BindScriptObject with LV2_BIND_VALIDATE) was made inside an inline event or a host-object call. Nothing was sent. Use CallScript / ExecuteScriptAsync there, or call after the handler returns. |
See alsoGetLastBridgeError
A worked example that chains the members above — binding an object, writing a property, calling a method fire-and-forget, and calling a method synchronously to read a result back — into one end-to-end flow that drives a chart from a query.
' 1. load a page that defines window.Dashboard, then bind it
m_lv.NavigateToStringSync DashboardHtml() ' your page (defines window.Dashboard)
m_lv.BindScriptObject "chart", "window.Dashboard"
' 2. drive the chart from VBA
m_lv.SetScriptProperty "chart", "Title", "Sales by Category"
m_lv.CallScript "chart", "setData", Array(120, 340, 80) ' array arrives in JS as [120,340,80]
' 3. read the user's selection back as a typed value
Dim picked As Variant
picked = m_lv.CallScriptSync("chart", "getSelectedCategory")
If Len(CStr(picked)) > 0 Then
Me.Filter = "Category='" & picked & "'"
Me.FilterOn = True
End IfSee alsoBindScriptObject, SetScriptProperty, CallScript, CallScriptSync
LiteView2 exposes JSON functionality through three distinct call surfaces:
Dim m_lv As LiteView2Lib.LiteView2Ctrl, called as m_lv.JsonXxx(...).Dim pool As Object, Dim idx As Long, called as pool.JsonXxx(idx, ...). Where a Reg-Free method name would collide with a COM name already used elsewhere in the pool's dispatch interface, it is exposed under a 2-suffixed name instead (JsonSetValue2, JsonRemoveKey2, JsonToRowArray2) — this is called out per-member below.ILV2Json object — a free-standing JSON engine with no dependency on a control or browser instance at all: Dim lvj As Object, Set lvj = CreateObject("LiteView2.Json"), called as lvj.MethodName(...). Method names on this object are not Json-prefixed (e.g. lvj.Append, not lvj.JsonAppend). This object additionally exposes handle-based fast-path variants (...H, ...HP suffixes — see "Parse / CompilePath" below) that the Registered and Reg-Free surfaces do not have.Note
On the source's own ILV2Json code samples: every auto-generated example for the standalone object declares its object variable as Dim json As Object / Set json = CreateObject("LiteView2.Json"), and then — for methods that take the JSON text as an argument — passes that same variable name as the argument too, e.g. result = json.Append(json, path, value). Read literally this means "pass the object to itself," which is not what the argument is: it is a JSON string to operate on. This is almost certainly a naming collision in the doc generator's template (confirmed by the Merge example, which needs two JSON-string arguments and uses json1/json2 instead of reusing the object's name). To avoid repeating that ambiguity, this reference names the object variable lvj and keeps a separate json string variable for the data being operated on. The method names, parameter order, and return types below are reproduced exactly as documented.
Note
JsonGetValue and the other JSON helpers never talk to the browser. They are safe in any handler, including WebMessageReceived.
Extract a value from a JSON string using a path expression.
Note
WebMessageReceived is raised after WebView2's own callback has returned, so handle the message right there: JsonGetValue, ExecuteScript and PushRecordset all work inside it. Synchronous calls fail (error &H80040203) in two places only: inside inline events such as WebViewReady / NavigationStarting, and inside VBA methods the page is calling through a host object. Inside a host method, use only calls that do not wait (ExecuteScriptAsync, PostWebMessageAsJson, the JSON helpers, DAO). See Critical Safety Warnings #1.
result = m_lv.JsonGetValue(json, path)Dim json As String
json = "{""name"":""John"",""age"":30,""address"":{""city"":""NYC""}}"
Debug.Print m_lv.JsonGetValue(json, "name") ' "John"
Debug.Print m_lv.JsonGetValue(json, "age") ' 30
Debug.Print m_lv.JsonGetValue(json, "address.city") ' "NYC"Debug.Print pool.JsonGetValue(idx, json, "name")The standalone ILV2Json object exposes this as GetValue / GetValueH / GetValueHP.
Dim lvj As Object
Set lvj = CreateObject("LiteView2.Json")
Dim result As Variant
result = lvj.GetValue(json, path) ' plain path lookup
result = lvj.GetValueH(handle, path) ' handle = a Parse() result; "Get value from parsed JSON handle by path."
result = lvj.GetValueHP(jsonHandle, pathHandle) ' "Maximum performance" — both JSON and path pre-parsed; see Parse/CompilePathGotchas
GetValue "Returns VT_EMPTY if not found, VT_NULL for null" — this return-type distinction is documented only for the standalone form, not restated for Registered/Reg-Free JsonGetValue.Get multiple values at once.
pathsArray is an array of BSTR paths. Returns a SAFEARRAY of VARIANTs. No Registered or Reg-Free equivalent is documented; this exists only on the standalone ILV2Json object.
result = lvj.GetValues(json, pathsArray)Dim lvj As Object
Set lvj = CreateObject("LiteView2.Json")
Dim result As Variant
result = lvj.GetValues(json, pathsArray) ' "Get multiple values at once."
result = lvj.GetValuesH(handle, pathsArray) ' "Get multiple values from parsed JSON handle."
result = lvj.GetValuesHP(jsonHandle, pathHandlesArray) ' "Get multiple values using parsed JSON + compiled path handles. Fastest bulk API."Get the length of a JSON array at a given path.
count = m_lv.JsonGetArrayLength(json, path)Dim json As String
json = "{""items"":[10,20,30,40]}"
Dim count As Long
count = m_lv.JsonGetArrayLength(json, "items")
Debug.Print "Array has " & count & " items" ' 4Dim count As Long
count = pool.JsonGetArrayLength(idx, json, "items")Gotchas
ILV2Json equivalent named "ArrayLength" is documented; on the standalone object, Count doubles as the array-length check ("Count array elements or object keys at path").See also JsonCount
Get a specific item from a JSON array by index.
item = m_lv.JsonGetArrayItem(json, path, index)Dim json As String
json = "[{""name"":""Alpha""},{""name"":""Beta""}]"
Dim item As String
item = m_lv.JsonGetArrayItem(json, "", 0)
Debug.Print item ' {"name":"Alpha"}Dim item As String
item = pool.JsonGetArrayItem(idx, json, "", 0)The standalone ILV2Json object exposes this as GetItem / GetItemH / GetItemHP: "Get array element as string by index. Empty string if out of range."
Dim lvj As Object
Set lvj = CreateObject("LiteView2.Json")
Dim result As String
result = lvj.GetItem(json, path, index)
result = lvj.GetItemH(handle, path, index) ' "Get array item from parsed JSON handle."
result = lvj.GetItemHP(jsonHandle, pathHandle, index) ' "Get array item using pre-parsed JSON handle and compiled path."Get all keys from a JSON object at a given path.
keys = m_lv.JsonGetKeys(json, path)Dim json As String
json = "{""name"":""John"",""age"":30,""city"":""NYC""}"
Dim keys As String
keys = m_lv.JsonGetKeys(json, "")
Debug.Print keys ' ["name","age","city"]Dim keys As String
keys = pool.JsonGetKeys(idx, json, "")The standalone ILV2Json object exposes this as GetKeys / GetKeysH: "Get all keys from object at path as JSON array string."
Dim lvj As Object
Set lvj = CreateObject("LiteView2.Json")
Dim result As String
result = lvj.GetKeys(json, path)
result = lvj.GetKeysH(handle, path) ' "Get keys from parsed JSON handle."Gotchas
Count, Exists, GetItem, and GetValue, GetKeys has no GetKeysHP (compiled-path) variant documented.See also JsonCount, JsonExists, JsonGetArrayItem, JsonGetValue
Check if a path exists in a JSON string.
m_lv.JsonExists(json, path)Dim json As String
json = "{""user"":{""name"":""Imran""}}"
If m_lv.JsonExists(json, "user.name") Then Debug.Print "Found"
If Not m_lv.JsonExists(json, "user.email") Then
json = m_lv.JsonSetValue(json, "user.email", "imran@example.com")
End IfIf pool.JsonExists(idx, json, "user.name") Then Debug.Print "Found"The standalone ILV2Json object exposes this as Exists / ExistsH / ExistsHP: "Check if a path exists in JSON."
Dim lvj As Object
Set lvj = CreateObject("LiteView2.Json")
Dim result As Boolean
result = lvj.Exists(json, path)
result = lvj.ExistsH(handle, path) ' "Check path existence in parsed JSON handle."
result = lvj.ExistsHP(jsonHandle, pathHandle) ' "Check path existence using pre-parsed JSON handle and compiled path."See also JsonSetValue
Count the number of keys in an object or elements in an array at a path.
n = m_lv.JsonCount(json, path)Dim n As Long
n = m_lv.JsonCount("{""a"":1,""b"":2,""c"":3}", "") ' 3 (object keys)
n = m_lv.JsonCount("{""items"":[10,20,30]}", "items") ' 3 (array elements)Dim n As Long
n = pool.JsonCount(idx, json, "items")The standalone ILV2Json object exposes this as Count / CountH / CountHP.
Dim lvj As Object
Set lvj = CreateObject("LiteView2.Json")
Dim result As Long
result = lvj.Count(json, path)
result = lvj.CountH(handle, path) ' "Count from parsed JSON handle."
result = lvj.CountHP(jsonHandle, pathHandle) ' "Count using pre-parsed JSON handle and compiled path."Gotchas
Count returns -1 if the target is not a container — this behavior is documented only for the standalone form, not restated for Registered/Reg-Free JsonCount.See also JsonGetArrayLength
Check if a string is valid JSON.
m_lv.JsonIsValid(json)Debug.Print m_lv.JsonIsValid("{""valid"":true}") ' True
Debug.Print m_lv.JsonIsValid("{bad json}") ' FalseDebug.Print pool.JsonIsValid(idx, "{""valid"":true}")The standalone ILV2Json object exposes this as IsValid: "Validate JSON string syntax."
Dim lvj As Object
Set lvj = CreateObject("LiteView2.Json")
Dim result As Boolean
result = lvj.IsValid(json)Gotchas
IsValidH/IsValidHP variant is documented — this is a validity check on a raw JSON string, not something meaningfully done from an already-parsed handle.Handle-based fast access, standalone ILV2Json object only. Parse parses JSON into a DOM tree and returns a handle for fast repeated access; CompilePath pre-compiles a path for repeated use and returns a path handle.
No Registered or Reg-Free equivalent is documented. These two methods produce the handle and pathHandle values consumed by every ...H and ...HP variant documented above (GetValueH/HP, GetValuesH/HP, GetItemH/HP, GetKeysH, ExistsH/HP, CountH/HP).
handle = lvj.Parse(json)Parse — Parse JSON into DOM tree. Returns a handle for fast repeated access.
Dim lvj As Object
Set lvj = CreateObject("LiteView2.Json")
Dim handle As Long
handle = lvj.Parse(json)CompilePath — Pre-compile a path for repeated use. Returns a path handle.
Dim pathHandle As Long
pathHandle = lvj.CompilePath(path)Combined ("HP" = handle + compiled path, the documented fastest form):
Dim handle As Long, pathHandle As Long
handle = lvj.Parse(json)
pathHandle = lvj.CompilePath("user.address.city")
Dim result As Variant
result = lvj.GetValueHP(handle, pathHandle)Gotchas
ReleaseHandle/ReleasePath when a handle is no longer needed — the source does not document automatic cleanup for these handles.See also JsonGetValue, GetValues, JsonGetArrayItem, JsonGetKeys, JsonExists, JsonCount, ReleaseHandle
Release the DOM-tree and compiled-path handles returned by Parse/CompilePath.
No Registered or Reg-Free equivalent is documented — these exist only on the standalone ILV2Json object.
lvj.ReleaseHandle handleReleaseHandle — Release a parsed JSON handle (from Parse). Safe to call twice.
Dim lvj As Object
Set lvj = CreateObject("LiteView2.Json")
lvj.ReleaseHandle handleReleasePath — Release a compiled path handle (from CompilePath). Safe to call twice.
lvj.ReleasePath pathHandleReleaseAll — Release every JSON and path handle held by this ILV2Json instance in one call.
lvj.ReleaseAllSee also Parse
Create an empty JSON object.
Starting point for building JSON from scratch.
json = m_lv.JsonCreateObject()Dim json As String
json = m_lv.JsonCreateObject() ' Returns: {}Dim json As String
json = pool.JsonCreateObject(idx)The standalone ILV2Json object exposes this as CreateObject: "Create empty JSON object string."
Dim lvj As Object
Set lvj = CreateObject("LiteView2.Json") ' <- this CreateObject is the VBA built-in, instantiating lvj
Dim result As String
result = lvj.CreateObject() ' <- this CreateObject is the JSON Helper method, on lvjGotchas
CreateObject, identical to the VBA built-in function used to instantiate the object in the first place. lvj.CreateObject() (called on an existing instance) invokes the JSON Helper method; the bare CreateObject("LiteView2.Json") call invokes the VBA built-in. Not a defect, but a naming collision worth being deliberate about.Create an empty JSON array.
json = m_lv.JsonCreateArray()Dim json As String
json = m_lv.JsonCreateArray() ' Returns: []Dim json As String
json = pool.JsonCreateArray(idx)The standalone ILV2Json object exposes this as CreateArray: "Create empty JSON array string."
Dim lvj As Object
Set lvj = CreateObject("LiteView2.Json")
Dim result As String
result = lvj.CreateArray()The block of members below (JsonSetValue through JsonToRowArray, plus JsonCreateObject/JsonCreateArray above) is documented in the source under the heading "JSON MUTATION (Building & Modifying)" with the following blanket statement, which applies to all of them on the Registered/Reg-Free surfaces:
Note
All JSON mutation methods are pure native C++ — no WebView2 round-trips. They accept a JSON string and return the modified JSON. On failure, the original JSON is returned unchanged.
Set or create a value at a path.
Auto-creates intermediate objects and arrays as needed.
json = m_lv.JsonSetValue(json, path, value)Dim json As String
json = m_lv.JsonCreateObject()
' Set simple values
json = m_lv.JsonSetValue(json, "name", "Imran")
json = m_lv.JsonSetValue(json, "age", 30)
json = m_lv.JsonSetValue(json, "active", True)
' Auto-creates nested objects
json = m_lv.JsonSetValue(json, "address.city", "London")
json = m_lv.JsonSetValue(json, "address.country", "UK")
' Update existing value
json = m_lv.JsonSetValue(json, "name", "Ali")
' Set array element (auto-creates array with null padding)
json = m_lv.JsonSetValue(json, "scores[0]", 95)
json = m_lv.JsonSetValue(json, "scores[1]", 88)
Debug.Print m_lv.JsonPrettyPrint(json)Reg-Free uses the suffixed name JsonSetValue2 to avoid a COM name collision on the pool's dispatch interface.
json = pool.JsonSetValue2(idx, json, "name", "Imran")
json = pool.JsonSetValue2(idx, json, "address.city", "London")The standalone ILV2Json object exposes this as SetValue: "Set or create value at path. Auto-creates intermediate paths."
Dim lvj As Object
Set lvj = CreateObject("LiteView2.Json")
Dim result As String
result = lvj.SetValue(json, path, value)Remove a key from an object or an element from an array.
json = m_lv.JsonRemoveKey(json, path)Dim json As String
json = "{""name"":""Imran"",""temp"":""delete-me"",""age"":30}"
' Remove object key
json = m_lv.JsonRemoveKey(json, "temp")
Debug.Print json ' {"name":"Imran","age":30}
' Remove array element
json = "{""items"":[""a"",""b"",""c""]}"
json = m_lv.JsonRemoveKey(json, "items[1]")
Debug.Print json ' {"items":["a","c"]}Reg-Free uses the suffixed name JsonRemoveKey2 to avoid a COM name collision.
json = pool.JsonRemoveKey2(idx, json, "temp")The standalone ILV2Json object exposes this as Remove: "Remove key or array element at path."
Dim lvj As Object
Set lvj = CreateObject("LiteView2.Json")
Dim result As String
result = lvj.Remove(json, path)Gotchas
Remove, not RemoveKey/JsonRemoveKey.Append a value to the end of an array at a given path.
Creates the array if it doesn't exist.
json = m_lv.JsonAppend(json, path, value)Dim json As String
json = m_lv.JsonCreateObject()
json = m_lv.JsonAppend(json, "tags", "vba")
json = m_lv.JsonAppend(json, "tags", "com")
json = m_lv.JsonAppend(json, "tags", "webview2")
Debug.Print json ' {"tags":["vba","com","webview2"]}json = pool.JsonAppend(idx, json, "tags", "vba")The standalone ILV2Json object exposes this as Append: "Append value to array at path."
Dim lvj As Object
Set lvj = CreateObject("LiteView2.Json")
Dim result As String
result = lvj.Append(json, path, value)Insert a value at a specific array index.
Existing elements shift right.
json = m_lv.JsonInsert(json, path, index, value)Dim json As String
json = "[""a"",""c"",""d""]"
json = m_lv.JsonInsert(json, "", 1, "b")
Debug.Print json ' ["a","b","c","d"]json = pool.JsonInsert(idx, json, "", 1, "b")The standalone ILV2Json object exposes this as Insert: "Insert value at array index."
Dim lvj As Object
Set lvj = CreateObject("LiteView2.Json")
Dim result As String
result = lvj.Insert(json, path, index, value)Merge two JSON objects.
Shallow merge replaces top-level keys; deep merge recurses into nested objects.
json = m_lv.JsonMerge(base, overlay, deep)Dim base As String, overlay As String, json As String
base = "{""name"":""Imran"",""address"":{""city"":""London"",""zip"":""SW1""}}"
overlay = "{""age"":30,""address"":{""city"":""Manchester""}}"
' Shallow merge — address is replaced entirely
json = m_lv.JsonMerge(base, overlay, False)
' {"name":"Imran","address":{"city":"Manchester"},"age":30}
' Deep merge — address.city updated, address.zip preserved
json = m_lv.JsonMerge(base, overlay, True)
' {"name":"Imran","address":{"city":"Manchester","zip":"SW1"},"age":30}json = pool.JsonMerge(idx, base, overlay, True)The standalone ILV2Json object exposes this as Merge: "Merge two JSON objects. Set deep=True for recursive merge."
Dim lvj As Object
Set lvj = CreateObject("LiteView2.Json")
Dim result As String
result = lvj.Merge(json1, json2, deep)Empty a container at a path.
Objects become {}, arrays become [].
json = m_lv.JsonClear(json, path)Dim json As String
json = "{""cache"":{""a"":1,""b"":2},""tags"":[""x"",""y""]}"
json = m_lv.JsonClear(json, "cache") ' cache becomes {}
json = m_lv.JsonClear(json, "tags") ' tags becomes []json = pool.JsonClear(idx, json, "cache")The standalone ILV2Json object exposes this as Clear: "Clear array or object at path (empty it)."
Dim lvj As Object
Set lvj = CreateObject("LiteView2.Json")
Dim result As String
result = lvj.Clear(json, path)Remove null entries from a JSON array at path.
Pass empty path for root array. This member is not present in the manually-curated core sections of the source — it is documented only in the auto-synced reference, both as a control/pool method (JsonCompact, tagged Mode: Both) and as a standalone-object method (Compact, tagged Mode: ILV2Json standalone object) with identical wording.
result = ctrl.JsonCompact(jsonString, path)Dim result As String
result = ctrl.JsonCompact(jsonString, path)Dim result As String
result = pool.JsonCompact(idx, jsonString, path)Dim lvj As Object
Set lvj = CreateObject("LiteView2.Json")
Dim result As String
result = lvj.Compact(json, path)Format JSON with 2-space indentation for debugging.
Max 5 MB input.
m_lv.JsonPrettyPrint(json)Debug.Print m_lv.JsonPrettyPrint("{""name"":""Imran"",""scores"":[95,88]}")
' {
' "name": "Imran",
' "scores": [
' 95,
' 88
' ]
' }Debug.Print pool.JsonPrettyPrint(idx, json)The standalone ILV2Json object exposes this as PrettyPrint: "Pretty-print JSON with indentation."
Dim lvj As Object
Set lvj = CreateObject("LiteView2.Json")
Dim result As String
result = lvj.PrettyPrint(json)Gotchas
PrettyPrint entry does not repeat or contradict it.Flatten nested JSON to a flat object with dot-notation keys.
flat = m_lv.JsonFlatten(json)Dim flat As String
flat = m_lv.JsonFlatten("{""user"":{""name"":""Imran"",""address"":{""city"":""London""}}}")
Debug.Print flat ' {"user.name":"Imran","user.address.city":"London"}Dim flat As String
flat = pool.JsonFlatten(idx, json)The standalone ILV2Json object exposes this as Flatten: "Flatten nested JSON. E.g. {a:{b:1}} becomes {"a.b":1}."
Dim lvj As Object
Set lvj = CreateObject("LiteView2.Json")
Dim result As String
result = lvj.Flatten(json)Convert a flat dot-notation object back to nested JSON.
nested = m_lv.JsonUnflatten(flat)Dim nested As String
nested = m_lv.JsonUnflatten("{""user.name"":""Imran"",""user.address.city"":""London""}")
Debug.Print nested ' {"user":{"name":"Imran","address":{"city":"London"}}}Dim nested As String
nested = pool.JsonUnflatten(idx, flat)The standalone ILV2Json object exposes this as Unflatten: "Unflatten dot-notation JSON back to nested structure."
Dim lvj As Object
Set lvj = CreateObject("LiteView2.Json")
Dim result As String
result = lvj.Unflatten(json)Convert a JSON array to a VBA 2D array (for Excel ranges or data processing).
No header row — data starts at arr(1,1). (Contrast with JsonToRowArray, which adds a header row.)
arr = m_lv.JsonToArray(json)Dim json As String
json = "[{""Date"":""2024-01-01"",""Open"":100,""Close"":105}," & _
"{""Date"":""2024-01-02"",""Open"":105,""Close"":110}]"
Dim arr As Variant
arr = m_lv.JsonToArray(json)
' arr is now a 2D array: arr(1,1)="2024-01-01", arr(1,2)=100, etc.Dim arr As Variant
arr = pool.JsonToArray(idx, json)The standalone ILV2Json object exposes this as ToArray: "Validate and return JSON array (enforces MaxSize limit)."
Dim lvj As Object
Set lvj = CreateObject("LiteView2.Json")
Dim result As Variant
result = lvj.ToArray(json)Gotchas
ToArray description is noticeably terser than, and does not explicitly restate, the Registered/Reg-Free description ("convert a JSON array to a VBA 2D array"); it is presented here as the same-named member rather than a confirmed identical behavior, since the source does not state outright that the two are equivalent.See also JsonToRowArray, JsonToArrayMaxSize
Max JSON size in bytes for JsonToArray and JsonToRowArray.
Type: Long. R/W. Default: 52428800 (50MB).
m_lv.JsonToArrayMaxSize [= value]' Registered
Debug.Print m_lv.JsonToArrayMaxSize ' 52428800
m_lv.JsonToArrayMaxSize = 104857600 ' Set to 100MB' Reg-Free
' Not available as a pool property - use defaultGotchas
pool.JsonToArrayMaxSize setter documented.See also JsonToArray, JsonToRowArray, MaxSize
Maximum JSON size in bytes for Parse/ToArray/ToRowArray on the standalone ILV2Json object.
Default 50MB. Range: 1KB–500MB. Wider scope than JsonToArrayMaxSize: it also governs Parse, and the source states an explicit min/max range that JsonToArrayMaxSize does not.
pool.GetMaxSize browserIndex, valueCaution
This property is tagged Mode: ILV2Json (standalone object), but unlike every other ILV2Json member (which is always exercised via Dim lvj As Object: Set lvj = CreateObject("LiteView2.Json") then lvj.Method(...)), the documented Get/Set code samples use the Reg-Free pool + browser-index calling convention instead:
' As literally documented — Get:
Dim value As Long
pool.GetMaxSize browserIndex, value
' As literally documented — Set:
pool.SetMaxSize browserIndex, newValue ' newValue As LongThe source does not explain this — it may mean the property is actually reached through the pool (and the "ILV2Json standalone" mode tag is wrong), or the code sample itself may be the error (and it should read lvj.GetMaxSize/lvj.SetMaxSize with no browser index, or a plain lvj.MaxSize property get/let). Both the tag and the sample are reproduced verbatim above; do not assume either one over the other without checking the IDL directly.
Gotchas
pool.GetMaxSize / pool.SetMaxSize) rather than lvj.MaxSize — an unresolved contradiction; see the caution above.Parse, ToArray, and ToRowArray on the standalone object — wider scope than the control's JsonToArrayMaxSize, which only governs JsonToArray/JsonToRowArray and does not document a min/max range.See also JsonToArrayMaxSize, Parse, JsonToArray, JsonToRowArray
Convert a JSON array of objects to a 2D VBA array.
Row 1 contains column headers; subsequent rows contain typed values. Respects JsonToArrayMaxSize.
arr = m_lv.JsonToRowArray(json)Dim json As String
json = "[{""Name"":""Imran"",""Age"":30,""Active"":true}," & _
"{""Name"":""Ali"",""Age"":25,""Active"":false}]"
Dim arr As Variant
arr = m_lv.JsonToRowArray(json)
' arr(1,1)="Name" arr(1,2)="Age" arr(1,3)="Active" ← headers
' arr(2,1)="Imran" arr(2,2)=30 arr(2,3)=True ← data
' arr(3,1)="Ali" arr(3,2)=25 arr(3,3)=False
' Loop through data rows
Dim r As Long, c As Long
For r = 2 To UBound(arr, 1)
For c = 1 To UBound(arr, 2)
Debug.Print arr(r, c);
Next c
Debug.Print
Next rReg-Free uses the suffixed name JsonToRowArray2 to avoid a COM name collision.
Dim arr As Variant
arr = pool.JsonToRowArray2(idx, json)The standalone ILV2Json object exposes this as ToRowArray: "Convert JSON array-of-objects to 2D SAFEARRAY. Row 1=headers, rows 2+=data."
Dim lvj As Object
Set lvj = CreateObject("LiteView2.Json")
Dim result As Variant
result = lvj.ToRowArray(json)Type mapping, documented for JsonToRowArray/JsonToRowArray2 (not restated separately for the standalone form):
| JSON Type | VBA Type |
|---|---|
| string | vbString |
| number (integer) | vbLong |
| number (decimal) | vbDouble |
| boolean | vbBoolean |
| null | vbEmpty |
| object / array | vbString (serialized JSON) |
See also JsonToArray, JsonToArrayMaxSize
If input is a JSON string literal (double-encoded), unwrap one layer and unescape.
Otherwise return unchanged. This member is not present in the manually-curated core sections; it is documented only in the auto-synced reference, tagged Mode: Both (Registered + Reg-Free), plus a standalone-object form with identical wording.
result = ctrl.JsonUnwrapString(jsonValue)Dim result As String
result = ctrl.JsonUnwrapString(jsonValue)Dim result As String
result = pool.JsonUnwrapString(idx, jsonValue)The standalone ILV2Json object exposes this as UnwrapString.
Dim lvj As Object
Set lvj = CreateObject("LiteView2.Json")
Dim result As String
result = lvj.UnwrapString(jsonValue)Two structural notes apply across the whole ILV2Json handle system, stated here once rather than repeated per-member:
...H / ...HP handle-accelerated forms: Count, Exists, GetItem, GetKeys (H only, no HP), GetValue, and GetValues. None of the mutation methods (Append, Clear, Compact, CreateArray, CreateObject, Flatten, Insert, Merge, PrettyPrint, Remove, SetValue, ToArray, ToRowArray, Unflatten, UnwrapString) or IsValid/Parse/CompilePath themselves have a handle-based variant documented.Parse or CompilePath is documented anywhere in the source.High-level automation helpers using CSS selectors (Playwright-style automation API).
Wait for an element matching a CSS selector to appear in the DOM.
Returns True if found, False on timeout. Polls every 50ms using ElementExistsBySelector, pumping messages between checks so the UI stays responsive.
m_lv.WaitForElement(selector As String, [timeoutMs As Long]) As Booleanm_lv.Navigate "https://example.com/login"
' Wait up to 10 seconds for login form
If m_lv.WaitForElement("#loginForm", 10000) Then
Debug.Print "Form appeared!"
Else
Debug.Print "Timed out waiting for form"
End If
' Wait for dynamic content after AJAX load
m_lv.ClickElementBySelector ".load-more-btn"
If m_lv.WaitForElement(".new-results", 5000) Then
Debug.Print "Results loaded"
End IfIf pool.WaitForElement(idx, "#dashboard", 10000) Then
Debug.Print "Dashboard ready"
End IfGotchas
timeoutMs is 0, the SynchronousTimeOut property value is used (default 30s).Get the text content of the first element matching a CSS selector.
m_lv.GetTextBySelector(selector As String) As StringDim welcome As String
welcome = m_lv.GetTextBySelector(".welcome-message")
Debug.Print welcome
Dim price As String
price = m_lv.GetTextBySelector("#product-price .amount")
Debug.Print "Price: " & priceDim txt As String
txt = pool.GetTextBySelector(idx, ".status-text")Navigate to a site, wait for dynamic content, interact, and read results (illustrates WaitForElement and GetTextBySelector combined with element-interaction methods documented elsewhere in this reference: SetElementValueById, ClickElementBySelector):
Registered:
' Navigate
m_lv.Navigate "https://example.com/login"
' Wait for login form
If m_lv.WaitForElement("#loginForm", 10000) Then
' Fill form
m_lv.SetElementValueById "email", "user@example.com"
m_lv.SetElementValueById "password", "secret123"
' Submit via CSS selector
m_lv.ClickElementBySelector "#loginForm button[type='submit']"
' Wait for dashboard
If m_lv.WaitForElement(".dashboard-panel", 15000) Then
Dim msg As String
msg = m_lv.GetTextBySelector(".welcome-message")
Debug.Print "Logged in: " & msg
End If
End IfNote
Naming pattern across this category: the Registered-mode control exposes each item as a native COM property/method on m_lv. Reg-Free mode's pool object exposes the equivalent capability through explicit GetXxx/SetXxx (or bare action) methods taking idx as the first argument, since the late-bound pool interface does not carry real property accessors. Where the auto-synced IDL reference tags a property entry "Mode: Registered Only," that tag describes the COM property-accessor syntax specifically — the Reg-Free equivalent still exists, under the GetXxx/SetXxx method names documented alongside it below. This is a consistent convention, not a contradiction, and applies to every R/W property in this category.
Open the download manager dialog.
m_lv.OpenDownloadDialogm_lv.OpenDownloadDialogpool.OpenDownloadDialog idxClose the download manager dialog.
m_lv.CloseDownloadDialogm_lv.CloseDownloadDialogpool.CloseDownloadDialog idxCheck if the download dialog is currently open. Type: Boolean. Read-only.
m_lv.IsDownloadDialogOpen As Boolean ' Read-onlyIf m_lv.IsDownloadDialogOpen Then m_lv.CloseDownloadDialogIf pool.IsDownloadDialogOpen(idx) Then pool.CloseDownloadDialog idxPause an active download by its download ID.
m_lv.PauseDownload downloadIdm_lv.PauseDownload downloadIdpool.PauseDownload idx, downloadIdResume a paused download by ID.
m_lv.ResumeDownload downloadIdm_lv.ResumeDownload downloadIdpool.ResumeDownload idx, downloadIdCancel a download by ID.
m_lv.CancelDownload downloadIdm_lv.CancelDownload downloadIdpool.CancelDownload idx, downloadIdGet or set whether downloads are allowed. Type: Boolean. R/W.
m_lv.AllowDownloads As Boolean ' R/WDebug.Print m_lv.AllowDownloads
m_lv.AllowDownloads = False ' Block all downloadsDebug.Print pool.GetAllowDownloads(idx)
pool.SetAllowDownloads idx, FalseGotchas
GetAllowDownloads (returns Boolean) and SetAllowDownloads (takes browserIndex, allowed) are both tagged Mode: Reg-Free Only — matching the base entry's naming convention, not a conflict with it.Get or set the default download folder. Type: String. R/W.
m_lv.DefaultDownloadFolderPath As String ' R/WDebug.Print m_lv.DefaultDownloadFolderPath
m_lv.DefaultDownloadFolderPath = "C:\Downloads"Debug.Print pool.GetDefaultDownloadFolderPath(idx)
pool.SetDefaultDownloadFolderPath idx, "C:\Downloads"Gotchas
GetDefaultDownloadFolderPath (returns String, via browserIndex) and SetDefaultDownloadFolderPath (takes browserIndex, folderPath) are both tagged Mode: Reg-Free Only — matching the base entry.Download dialog corner position. Type: LV2_DOWNLOAD_DIALOG_CORNER_ALIGNMENT. R/W.
m_lv.DefaultDownloadDialogCornerAlignment As LV2_DOWNLOAD_DIALOG_CORNER_ALIGNMENT ' R/Wm_lv.DefaultDownloadDialogCornerAlignment = 3 ' LV2_DOWNLOAD_CORNER_BOTTOM_RIGHTpool.SetDefaultDownloadDialogCornerAlignment idx, 3Dim result As LV2_DOWNLOAD_DIALOG_CORNER_ALIGNMENT
result = pool.GetDefaultDownloadDialogCornerAlignment(idx)Gotchas
GetDefaultDownloadDialogCornerAlignment), tagged Mode: Reg-Free Only.Download dialog margin from corner, in pixels. Type: Long. R/W.
m_lv.DefaultDownloadDialogMarginX / DefaultDownloadDialogMarginY As Long ' R/Wm_lv.DefaultDownloadDialogMarginX = 20
m_lv.DefaultDownloadDialogMarginY = 20Dim value As Long
value = m_lv.DefaultDownloadDialogMarginX
m_lv.DefaultDownloadDialogMarginX = newValue ' newValue As Long
value = m_lv.DefaultDownloadDialogMarginY
m_lv.DefaultDownloadDialogMarginY = newValue ' newValue As Longpool.SetDefaultDownloadDialogMarginX idx, 20
pool.SetDefaultDownloadDialogMarginY idx, 20Dim result As Long
result = pool.GetDefaultDownloadDialogMarginX(idx)
result = pool.GetDefaultDownloadDialogMarginY(idx)Gotchas
DefaultDownloadDialogMarginX and DefaultDownloadDialogMarginY as separate entries, each tagged Mode: Registered Only (describing the COM property-accessor form specifically), and adds the Get direction shown above — not present in the base Registered entry.GetDefaultDownloadDialogMarginX, GetDefaultDownloadDialogMarginY), each tagged Mode: Reg-Free Only.Clear the browser cache.
m_lv.ClearCachem_lv.ClearCachepool.ClearCache idxClear all cookies.
m_lv.ClearCookiesm_lv.ClearCookiespool.ClearCookies idxClear browsing data by bitmask of LiteView2BrowsingDataKinds.
Parameters: a Long bitmask combining LiteView2BrowsingDataKinds flags. The source documents these two flag combinations by example:
LV2_DATA_DISK_CACHE (256) + LV2_DATA_COOKIES (64) — clears cache + cookiesLV2_DATA_ALL_PROFILE (16384) — clears everythingm_lv.ClearBrowsingData flags' Clear cache + cookies
m_lv.ClearBrowsingData 256 + 64 ' LV2_DATA_DISK_CACHE + LV2_DATA_COOKIES
' Clear everything
m_lv.ClearBrowsingData 16384 ' LV2_DATA_ALL_PROFILEpool.ClearBrowsingData idx, 256 + 64Clear browsing data within a specific time range.
Known issue in 1.0.1.0
ClearBrowsingDataInTimeRange returns without an error but clears nothing. Use ClearBrowsingData (all time) or ClearCookies instead.
Parameters: Long bitmask of LiteView2BrowsingDataKinds, start time, end time.
m_lv.ClearBrowsingDataInTimeRange flags, startTime, endTime' Clear last 24 hours of cookies
m_lv.ClearBrowsingDataInTimeRange 64, Now - 1, Nowpool.ClearBrowsingDataInTimeRange idx, 64, Now - 1, NowGet cookies for a URL as a JSON string.
Parameters: URL (String). Returns a String containing JSON.
cookiesJson = m_lv.GetCookies(url)Dim cookiesJson As String
cookiesJson = m_lv.GetCookies("https://www.example.com")
Debug.Print cookiesJsonDim cookiesJson As String
cookiesJson = pool.GetCookies(idx, "https://www.example.com")Set a cookie.
Parameters (in order, per the documented example): name, value, domain, path — all String.
m_lv.SetCookie name, value, domain, pathm_lv.SetCookie "sessionId", "abc123", ".example.com", "/"pool.SetCookie idx, "sessionId", "abc123", ".example.com", "/"Clear all cookies for the current profile.
m_lv.ClearProfileCookiesm_lv.ClearProfileCookiespool.ClearProfileCookies idxPrint the current page to the default printer.
m_lv.PrintPagem_lv.PrintPage' Use ShowPrintDialog instead
pool.ShowPrintDialog idxGotchas
See alsoShowPrintDialog
Show the system print dialog.
m_lv.ShowPrintDialogm_lv.ShowPrintDialogpool.ShowPrintDialog idxSee alsoPrintPage
Print the current page to a PDF file.
m_lv.PrintToPdf pathDim path As String
path = Environ("USERPROFILE") & "\Desktop\Report_" & _
Format(Now(), "yyyymmdd_hhnnss") & ".pdf"
m_lv.PrintToPdf path
' Fires PrintToPdfCompleted event when doneDim path As String
path = Environ("USERPROFILE") & "\Desktop\Report.pdf"
pool.PrintToPdf idx, pathGotchas
PrintToPdfCompleted event for the success callback.PrintToPdf is fire-and-forget — check Dir(path) <> "" on next user action, or wrap in a host-object callback if you need a result signal.See alsoPrintToPdfCompleted, HasPrintToPdf
Print using the current print settings (no dialog).
m_lv.PrintWithSettingsm_lv.PrintOrientation = 1 ' Landscape
m_lv.PrintShouldPrintBackgrounds = True
m_lv.PrintWithSettingspool.SetPrintOrientation idx, 1
pool.SetPrintShouldPrintBackgrounds idx, True
pool.PrintWithSettings idxSee alsoPrintOrientation, PrintShouldPrintBackgrounds
Print to PDF using the current print settings.
m_lv.PrintToPdfWithSettings pathm_lv.PrintOrientation = 1
m_lv.PrintMarginTop = 0.25
m_lv.PrintMarginBottom = 0.25
m_lv.PrintToPdfWithSettings Environ("USERPROFILE") & "\Desktop\Report.pdf"pool.SetPrintOrientation idx, 1
pool.SetPrintMarginTop idx, 0.25
pool.SetPrintMarginBottom idx, 0.25
pool.PrintToPdfWithSettings idx, Environ("USERPROFILE") & "\Desktop\Report.pdf"See alsoPrintOrientation, PrintMarginTop, PrintMarginBottom, PrintToPdf
Capture a screenshot of the page to a PNG file.
m_lv.CaptureScreenshot pathm_lv.CaptureScreenshot Environ("USERPROFILE") & "\Desktop\screenshot.png"
' Fires CaptureScreenshotCompleted eventpool.CaptureScreenshot idx, Environ("USERPROFILE") & "\Desktop\screenshot.png"Gotchas
CaptureScreenshotCompleted event for the success callback.Dir(path) <> "" on next user action.See alsoCaptureScreenshotCompleted, HasCapturePreview
Show the Save As dialog for the current page.
m_lv.ShowSaveAsDialogm_lv.ShowSaveAsDialogpool.ShowSaveAsDialog idxOpen the browser task manager window (debugging).
m_lv.OpenTaskManagerWindowm_lv.OpenTaskManagerWindowpool.OpenTaskManagerWindow idxPrint orientation. Type: LV2_PRINT_ORIENTATION (0 = Portrait, 1 = Landscape). Read/Write.
m_lv.PrintOrientation [= LV2_PRINT_ORIENTATION]Debug.Print m_lv.PrintOrientation ' 0 (Portrait)
m_lv.PrintOrientation = 1 ' LandscapeDim orient As Long
pool.GetPrintOrientation idx, orient
pool.SetPrintOrientation idx, 1Gotchas
ByRef, not a function return — the calling convention differs from the Registered property syntax.Print scale factor. Type: Double. Read/Write. Default: 1.0.
m_lv.PrintScaleFactor [= Double]Debug.Print m_lv.PrintScaleFactor
m_lv.PrintScaleFactor = 0.75 ' 75% scaleDim sf As Double
pool.GetPrintScaleFactor idx, sf
pool.SetPrintScaleFactor idx, 0.75Print margins in inches. Type: Double. Read/Write.
m_lv.PrintMarginTop / PrintMarginBottom / PrintMarginLeft / PrintMarginRight [= Double]m_lv.PrintMarginTop = 0.5
m_lv.PrintMarginBottom = 0.5
m_lv.PrintMarginLeft = 0.75
m_lv.PrintMarginRight = 0.75
Debug.Print m_lv.PrintMarginTop ' 0.5pool.SetPrintMarginTop idx, 0.5
pool.SetPrintMarginBottom idx, 0.5
pool.SetPrintMarginLeft idx, 0.75
pool.SetPrintMarginRight idx, 0.75Print page dimensions in inches. Type: Double. Read/Write.
m_lv.PrintPageWidth / PrintPageHeight [= Double]m_lv.PrintPageWidth = 8.5
m_lv.PrintPageHeight = 11.0
Debug.Print m_lv.PrintPageWidth & " x " & m_lv.PrintPageHeightpool.SetPrintPageWidth idx, 8.5
pool.SetPrintPageHeight idx, 11.0Print background graphics. Type: Boolean. Read/Write.
m_lv.PrintShouldPrintBackgrounds [= Boolean]m_lv.PrintShouldPrintBackgrounds = Truepool.SetPrintShouldPrintBackgrounds idx, TruePrint only the current selection. Type: Boolean. Read/Write.
m_lv.PrintShouldPrintSelectionOnly [= Boolean]m_lv.PrintShouldPrintSelectionOnly = Truepool.SetPrintShouldPrintSelectionOnly idx, TruePrint header and footer. Type: Boolean. Read/Write.
m_lv.PrintShouldPrintHeaderAndFooter [= Boolean]m_lv.PrintShouldPrintHeaderAndFooter = Truepool.SetPrintShouldPrintHeaderAndFooter idx, TrueCustom header title for printing. Type: String. Read/Write.
m_lv.PrintHeaderTitle [= String]m_lv.PrintHeaderTitle = "My Report"
Debug.Print m_lv.PrintHeaderTitlepool.SetPrintHeaderTitle idx, "My Report"Custom footer URI for printing. Type: String. Read/Write.
m_lv.PrintFooterUri [= String]m_lv.PrintFooterUri = "https://myapp.com/reports"pool.SetPrintFooterUri idx, "https://myapp.com/reports"Number of copies to print. Type: Long. Read/Write.
m_lv.PrintCopies [= Long]m_lv.PrintCopies = 2pool.SetPrintCopies idx, 2Pages per side (N-up printing). Type: Long. Read/Write.
m_lv.PrintPagesPerSide [= Long]m_lv.PrintPagesPerSide = 2pool.SetPrintPagesPerSide idx, 2Page ranges for printing. Type: String. Read/Write. Example: "1-3,5,7".
m_lv.PrintPageRanges [= String]m_lv.PrintPageRanges = "1-3,5"
Debug.Print m_lv.PrintPageRangespool.SetPrintPageRanges idx, "1-3,5"Check if PrintToPdf is supported by the runtime. Type: Boolean. Read-only.
m_lv.HasPrintToPdfIf m_lv.HasPrintToPdf Then
m_lv.PrintToPdf "C:\output.pdf"
End IfIf pool.HasPrintToPdf(idx) Then
pool.PrintToPdf idx, "C:\output.pdf"
End IfGotchas
idx and returns the Boolean (pool.HasPrintToPdf(idx)), rather than a plain property read.See alsoPrintToPdf
Check if CapturePreview (screenshot) is supported. Type: Boolean. Read-only.
m_lv.HasCapturePreviewDebug.Print "Screenshot support: " & m_lv.HasCapturePreviewDebug.Print "Screenshot support: " & pool.HasCapturePreview(idx)See alsoCaptureScreenshot
These five members let you build and issue HTTP requests with a custom method, headers, and body, and let you supply a synthetic response for a request the control intercepts. CreateWebResourceRequest and CreateWebResourceResponse return an opaque Long handle; that handle is what NavigateWithCustomRequest and NavigateWithWebResourceRequestSync consume.
Note on parameter order: NavigateWithWebResourceRequest takes arguments in the order (url, method, postData, headers), while CreateWebResourceRequest takes them in the order (method, url, headers, postData). This is not a typo in this reference — the two argument orders are documented differently in the source and must each be used as shown.
Create a custom HTTP request object. Returns a Long handle for use with NavigateWithCustomRequest or NavigateWithWebResourceRequestSync.
Parameter order: (method, url, headers, postData).
hReq = m_lv.CreateWebResourceRequest(method, url, headers, postData) As LongDim hReq As Long
hReq = m_lv.CreateWebResourceRequest("GET", "https://api.example.com/data", _
"Authorization: Bearer token123", "")
Debug.Print "Request handle: " & hReqDim hReq As Long
hReq = pool.CreateWebResourceRequest(idx, "GET", "https://api.example.com/data", _
"Authorization: Bearer token123", "")See alsoNavigateWithCustomRequest, NavigateWithWebResourceRequestSync
Create a custom HTTP response object for use in the WebResourceRequested event. Returns a Long handle.
Parameter order: (body, statusCode, statusText, headers).
hResp = m_lv.CreateWebResourceResponse(body, statusCode, statusText, headers) As LongDim hResp As Long
hResp = m_lv.CreateWebResourceResponse("<h1>Blocked</h1>", 403, "Forbidden", _
"Content-Type: text/html")Dim hResp As Long
hResp = pool.CreateWebResourceResponse(idx, "<h1>Blocked</h1>", 403, "Forbidden", _
"Content-Type: text/html")See alsoWebResourceRequested
Navigate with a custom HTTP method, POST data, and headers, in one call (no handle needed).
Parameter order: (url, method, postData, headers).
m_lv.NavigateWithWebResourceRequest url, method, postData, headersm_lv.NavigateWithWebResourceRequest _
"https://api.example.com/submit", _
"POST", _
"name=John&email=john@example.com", _
"Content-Type: application/x-www-form-urlencoded"pool.NavigateWithWebResourceRequest idx, _
"https://api.example.com/submit", _
"POST", _
"name=John&email=john@example.com", _
"Content-Type: application/x-www-form-urlencoded"Navigate using a request handle from CreateWebResourceRequest, and block until the load completes. Same synchronous-wait behavior as NavigateSync: inside inline events and host-object methods it fails at once with &H80040203 (use NavigateWithCustomRequest there), and in 1.0.1.0 its wait ends at the next NavigationCompleted (known issue, see NavigateSync).
m_lv.NavigateWithWebResourceRequestSync hReqDim hReq As Long
hReq = m_lv.CreateWebResourceRequest("POST", "https://api.example.com/data", _
"Content-Type: application/json", "{""key"":""value""}")
m_lv.NavigateWithWebResourceRequestSync hReqDim hReq As Long
hReq = pool.CreateWebResourceRequest(idx, "POST", "https://api.example.com/data", _
"Content-Type: application/json", "{""key"":""value""}")
pool.NavigateWithWebResourceRequestSync idx, hReqSee alsoCreateWebResourceRequest, NavigateWithCustomRequest
Navigate using a request handle from CreateWebResourceRequest (does not block for load completion).
m_lv.NavigateWithCustomRequest hReqDim hReq As Long
hReq = m_lv.CreateWebResourceRequest("POST", "https://api.example.com/submit", _
"Content-Type: application/json", "{""data"":1}")
m_lv.NavigateWithCustomRequest hReqDim hReq As Long
hReq = pool.CreateWebResourceRequest(idx, "POST", "https://api.example.com/submit", _
"Content-Type: application/json", "{""data"":1}")
pool.NavigateWithCustomRequest idx, hReqSee alsoCreateWebResourceRequest, NavigateWithWebResourceRequestSync
Set HTTP Basic Authentication credentials for a URI pattern.
m_lv.SetBasicAuthCredentials uriPattern, username, passwordm_lv.SetBasicAuthCredentials "https://api.example.com/*", "username", "password"
m_lv.Navigate "https://api.example.com/dashboard"pool.SetBasicAuthCredentials idx, "https://api.example.com/*", "username", "password"
pool.Navigate idx, "https://api.example.com/dashboard"Clear all stored Basic Authentication credentials.
m_lv.ClearBasicAuthCredentialsm_lv.ClearBasicAuthCredentialspool.ClearBasicAuthCredentials idxAdd a URL filter for web resource interception.
Second parameter is a resource-type code; the source documents 0 = all resource types and 3 = Image as example values — no complete enumeration of the resource-type codes is given elsewhere in the reviewed source.
m_lv.AddWebResourceRequestedFilter urlPattern, resourceType' Block ad networks (0 = all resource types)
m_lv.AddWebResourceRequestedFilter "*.doubleclick.net*", 0
m_lv.AddWebResourceRequestedFilter "*.googlesyndication.com*", 0
' Block only images from a domain (3 = Image)
m_lv.AddWebResourceRequestedFilter "*.ads.example.com*", 3pool.AddWebResourceRequestedFilter idx, "*.doubleclick.net*", 0Gotchas
0 = all resource types, 3 = Image); no complete enumeration of the codes is given elsewhere in the reviewed source.See alsoRemoveWebResourceRequestedFilter, SetWebResourceBlocking
Remove a web resource interception filter (same (urlPattern, resourceType) signature as AddWebResourceRequestedFilter).
m_lv.RemoveWebResourceRequestedFilter urlPattern, resourceTypem_lv.RemoveWebResourceRequestedFilter "*.doubleclick.net*", 0pool.RemoveWebResourceRequestedFilter idx, "*.doubleclick.net*", 0See alsoAddWebResourceRequestedFilter
Remove all web resource interception filters.
m_lv.ClearAllWebResourceRequestedFiltersm_lv.ClearAllWebResourceRequestedFilterspool.ClearAllWebResourceRequestedFilters idxEnable or disable blocking of matched web resources (matches are established via AddWebResourceRequestedFilter).
m_lv.SetWebResourceBlocking enabledm_lv.AddWebResourceRequestedFilter "*.ads.example.com*", 0
m_lv.SetWebResourceBlocking Truepool.SetWebResourceBlocking idx, TrueSee alsoAddWebResourceRequestedFilter, IsWebResourceBlockingEnabled
Check if web resource blocking is active. Type: Boolean. Read-only.
m_lv.IsWebResourceBlockingEnabled As Boolean ' Read-onlyDebug.Print "Blocking enabled: " & m_lv.IsWebResourceBlockingEnabledDebug.Print "Blocking: " & pool.IsWebResourceBlockingEnabled(idx)Gotchas
IsWebResourceBlockingEnabled) rather than a Get-prefixed alias — it is called as a function taking idx.See alsoSetWebResourceBlocking
Get all active filters as a JSON array (returns String).
m_lv.GetWebResourceRequestedFilters As String ' JSON arrayDebug.Print m_lv.GetWebResourceRequestedFiltersDebug.Print pool.GetWebResourceRequestedFilters(idx)Gotchas
(Property) in the source, unlike the members immediately above and below it.Get count of blocked requests since last reset. Type: Long. Read-only.
m_lv.BlockedRequestCount As Long ' Read-onlyDebug.Print "Blocked: " & m_lv.BlockedRequestCountDim result As Long
result = pool.GetBlockedRequestCount(idx)
Debug.Print "Blocked: " & resultGotchas
GetBlockedRequestCount, not BlockedRequestCount — the member name itself changes between modes (contrast with IsWebResourceBlockingEnabled above, which keeps its name unchanged in Reg-Free). This is documented as-is in the source, not a contradiction — just an inconsistency in the mode-specific naming convention.See alsoResetBlockedRequestCount, IsWebResourceBlockingEnabled
Reset the blocked request counter to zero.
m_lv.ResetBlockedRequestCountm_lv.ResetBlockedRequestCountpool.ResetBlockedRequestCount idxSee alsoBlockedRequestCount
Enable firing of WebResourceResponseReceived events.
m_lv.EnableWebResourceResponseTracking enabledm_lv.EnableWebResourceResponseTracking Truepool.EnableWebResourceResponseTracking idx, TrueSee alsoWebResourceResponseReceived
Both proxy properties are documented as Pre-init only — they must be set before the control/browser instance is initialized; this restriction is not stated for any other member in this category.
Get or set the proxy server. Type: String. Read/Write. Pre-init only.
In Registered mode this is a native property. In Reg-Free mode it is exposed as separate SetProxyServer / GetProxyServer methods (confirmed by the auto-synced API reference, which tags both Mode: Reg-Free Only and declares the return type of GetProxyServer as String).
m_lv.ProxyServer As String ' Read/Write, Pre-init onlym_lv.ProxyServer = "http://proxy.example.com:8080"
Debug.Print m_lv.ProxyServerpool.SetProxyServer idx, "http://proxy.example.com:8080"
Dim result As String
result = pool.GetProxyServer(idx)
Debug.Print resultGotchas
SetProxyServer / GetProxyServer methods rather than a single property; confirmed by the auto-synced API reference, which tags both Mode: Reg-Free Only and declares GetProxyServer's return type as String.See alsoProxyBypassList
Proxy bypass list (semicolon-separated). Type: String. Read/Write. Pre-init only.
In Registered mode this is a native property. In Reg-Free mode it is exposed as separate SetProxyBypassList / GetProxyBypassList methods (confirmed by the auto-synced API reference, tagged Mode: Reg-Free Only, GetProxyBypassList returns String).
m_lv.ProxyBypassList As String ' Read/Write, Pre-init onlym_lv.ProxyBypassList = "localhost;*.local;192.168.*"pool.SetProxyBypassList idx, "localhost;*.local;192.168.*"
Dim result As String
result = pool.GetProxyBypassList(idx)Gotchas
SetProxyBypassList / GetProxyBypassList methods rather than a single property; confirmed by the auto-synced API reference, tagged Mode: Reg-Free Only, GetProxyBypassList returns String.See alsoProxyServer
Ignore SSL certificate errors. Type: Boolean. Read/Write. Source comment: "USE WITH CAUTION" — dev/test only.
In Registered mode this is a native property. In Reg-Free mode it is exposed as separate SetIgnoreCertificateErrors / GetIgnoreCertificateErrors methods (confirmed by the auto-synced API reference, tagged Mode: Reg-Free Only, GetIgnoreCertificateErrors returns Boolean).
m_lv.IgnoreCertificateErrors As Boolean ' Read/Writem_lv.IgnoreCertificateErrors = True ' USE WITH CAUTIONpool.SetIgnoreCertificateErrors idx, True
Dim result As Boolean
result = pool.GetIgnoreCertificateErrors(idx)Gotchas
SetIgnoreCertificateErrors / GetIgnoreCertificateErrors methods rather than a single property; confirmed by the auto-synced API reference, tagged Mode: Reg-Free Only, with GetIgnoreCertificateErrors returning Boolean.Enable SSO using the OS primary account for intranet sites. Type: Boolean. Read/Write.
In Registered mode this is a native property. In Reg-Free mode it is exposed as separate SetAllowSingleSignOnUsingOSPrimaryAccount / GetAllowSingleSignOnUsingOSPrimaryAccount methods (confirmed by the auto-synced API reference, tagged Mode: Reg-Free Only, GetAllowSingleSignOnUsingOSPrimaryAccount returns Boolean).
m_lv.AllowSingleSignOnUsingOSPrimaryAccount As Boolean ' Read/Writem_lv.AllowSingleSignOnUsingOSPrimaryAccount = Truepool.SetAllowSingleSignOnUsingOSPrimaryAccount idx, True
Dim result As Boolean
result = pool.GetAllowSingleSignOnUsingOSPrimaryAccount(idx)Gotchas
SetAllowSingleSignOnUsingOSPrimaryAccount / GetAllowSingleSignOnUsingOSPrimaryAccount methods rather than a single property; confirmed by the auto-synced API reference, tagged Mode: Reg-Free Only, GetAllowSingleSignOnUsingOSPrimaryAccount returns Boolean.Check if enhanced authentication features are available. Type: Boolean. Read-only.
m_lv.HasAuthenticationSupport As Boolean ' Read-onlyDebug.Print "Auth support: " & m_lv.HasAuthenticationSupportDebug.Print "Auth support: " & pool.HasAuthenticationSupport(idx)Gotchas
HasAuthenticationSupport) rather than a Get-prefixed alias — it is called as a function taking idx.Canonical variables used below: Registered mode = m_lv As LiteView2Lib.LiteView2Ctrl; Reg-Free mode = pool As Object and idx As Long (the browser index returned by the pool's create call).
For every Reg-Free member, the underlying pool call is a Get/Set-prefixed method (or, for two members noted below, an indexed property) taking idx as the first argument. Where the pool method's name drops or adds a word relative to the Registered property name (e.g. property AreDefaultContextMenusEnabled vs. pool method ContextMenusEnabled), that is called out explicitly so a lookup by either name succeeds.
Enable/disable right-click context menus.
AreDefaultContextMenusEnabled As Boolean ' R/WDebug.Print m_lv.AreDefaultContextMenusEnabled
m_lv.AreDefaultContextMenusEnabled = False ' Disable right-click menuDebug.Print pool.GetContextMenusEnabled(idx)
pool.SetContextMenusEnabled idx, FalseGotchas
ContextMenusEnabled, not DefaultContextMenusEnabled.Enable/disable DevTools (F12).
AreDevToolsEnabled As Boolean ' R/Wm_lv.AreDevToolsEnabled = False ' Disable F12pool.SetDevToolsEnabled idx, False
Debug.Print pool.GetDevToolsEnabled(idx) ' Get variant, from Additional API ReferenceGotchas
DevToolsEnabled.Enable/disable the status bar.
IsStatusBarEnabled As Boolean ' R/Wm_lv.IsStatusBarEnabled = Falsepool.SetStatusBarEnabled idx, False
Debug.Print pool.GetStatusBarEnabled(idx) ' Get variant, from Additional API ReferenceGotchas
StatusBarEnabled.Get or set zoom level.
ZoomFactor As Double ' R/W, 1.0 = 100%Debug.Print "Zoom: " & m_lv.ZoomFactor * 100 & "%"
m_lv.ZoomFactor = 1.5 ' 150%Debug.Print "Zoom: " & pool.GetZoom(idx) * 100 & "%"
pool.SetZoom idx, 1.5Gotchas
Zoom, not ZoomFactor (GetZoom/SetZoom).ZoomFactorChanged (Registered mode, DISPID 26), fires when the zoom level changes.See alsoZoomFactorChanged
Enable/disable Ctrl+scroll zoom.
IsZoomControlEnabled As Boolean ' R/Wm_lv.IsZoomControlEnabled = Falsepool.SetIsZoomControlEnabled idx, False
Debug.Print pool.GetIsZoomControlEnabled(idx) ' Get variant, from Additional API ReferenceEnable/disable pinch zoom (touch).
IsPinchZoomEnabled As Boolean ' R/Wm_lv.IsPinchZoomEnabled = Falsepool.SetIsPinchZoomEnabled idx, False
Debug.Print pool.GetIsPinchZoomEnabled(idx) ' Get variant, from Additional API ReferenceEnable/disable browser accelerator keys (Ctrl+F, F5, etc.).
AreBrowserAcceleratorKeysEnabled As Boolean ' R/Wm_lv.AreBrowserAcceleratorKeysEnabled = Falsepool.SetAreBrowserAcceleratorKeysEnabled idx, False
Debug.Print pool.GetAreBrowserAcceleratorKeysEnabled(idx) ' Get variant, from Additional API ReferenceEnable/disable swipe navigation (touch back/forward).
IsSwipeNavigationEnabled As Boolean ' R/Wm_lv.IsSwipeNavigationEnabled = Falsepool.SetIsSwipeNavigationEnabled idx, False
Debug.Print pool.GetIsSwipeNavigationEnabled(idx) ' Get variant, from Additional API ReferenceEnable/disable drag and drop.
IsDragDropEnabled As Boolean ' Write-onlym_lv.IsDragDropEnabled = Falsepool.IsDragDropEnabled(idx) = FalseGotchas
Set-prefixed method — this differs from the calling convention used by every other member in this category.Get or set the user agent string.
UserAgent As String ' R/WDebug.Print m_lv.UserAgent
m_lv.UserAgent = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) MyApp/1.0"Debug.Print pool.GetUserAgent(idx)
pool.SetUserAgent idx, "Mozilla/5.0 MyApp/1.0"Preferred language (BCP 47).
Language As String ' R/W, pre-init onlym_lv.Language = "de-DE"
Debug.Print m_lv.Languagepool.SetLanguage idx, "de-DE"
Debug.Print pool.GetLanguage(idx)Gotchas
SetLanguage explicitly repeats the pre-init restriction ("Set preferred language (BCP 47) for the browser. Pre-init only.") — set it before the browser for this index is created.Enable/disable default script dialogs (alert, confirm, prompt).
DefaultScriptDialogsEnabled As Boolean ' R/Wm_lv.DefaultScriptDialogsEnabled = False ' Suppress JS alert/confirmpool.SetDefaultScriptDialogsEnabled idx, False
Debug.Print pool.GetDefaultScriptDialogsEnabled(idx) ' Get variant, from Additional API ReferenceEnable/disable built-in error pages.
IsBuiltInErrorPageEnabled As Boolean ' R/Wm_lv.IsBuiltInErrorPageEnabled = False ' Show custom error handlingpool.SetBuiltInErrorPageEnabled idx, False
Debug.Print pool.GetBuiltInErrorPageEnabled(idx) ' Get variant, from Additional API ReferenceGotchas
BuiltInErrorPageEnabled.Two scopes exist for both settings: the plain (browser-instance-level) property and a Profile-prefixed (profile-level) property. Both are documented identically in the source apart from the "Profile" qualifier — no additional distinction is given.
Enable/disable password autosave.
IsPasswordAutosaveEnabled As Boolean ' R/Wm_lv.IsPasswordAutosaveEnabled = Falsepool.SetIsPasswordAutosaveEnabled idx, False
Debug.Print pool.GetIsPasswordAutosaveEnabled(idx) ' Get variant, from Additional API ReferenceEnable/disable general form autofill.
IsGeneralAutofillEnabled As Boolean ' R/Wm_lv.IsGeneralAutofillEnabled = Falsepool.SetIsGeneralAutofillEnabled idx, False
Debug.Print pool.GetIsGeneralAutofillEnabled(idx) ' Get variant, from Additional API ReferenceProfile-level password autosave.
ProfileIsPasswordAutosaveEnabled As Boolean ' R/Wm_lv.ProfileIsPasswordAutosaveEnabled = Falsepool.SetProfileIsPasswordAutosaveEnabled idx, False
Debug.Print pool.GetProfileIsPasswordAutosaveEnabled(idx) ' Get variant, from Additional API ReferenceProfile-level general autofill.
ProfileIsGeneralAutofillEnabled As Boolean ' R/Wm_lv.ProfileIsGeneralAutofillEnabled = Falsepool.SetProfileIsGeneralAutofillEnabled idx, False
Debug.Print pool.GetProfileIsGeneralAutofillEnabled(idx) ' Get variant, from Additional API ReferenceSmartScreen reputation checking for downloads.
IsReputationCheckingRequired As Boolean ' R/Wm_lv.IsReputationCheckingRequired = Falsepool.SetIsReputationCheckingRequired idx, False
Debug.Print pool.GetIsReputationCheckingRequired(idx) ' Get variant, from Additional API ReferenceTracking prevention level.
PreferredTrackingPreventionLevel As LV2_TRACKING_PREVENTION ' R/W, 0-3m_lv.PreferredTrackingPreventionLevel = 3 ' Strictpool.SetPreferredTrackingPreventionLevel idx, 3
Dim result As LV2_TRACKING_PREVENTION
result = pool.GetPreferredTrackingPreventionLevel(idx)Gotchas
LV2_TRACKING_PREVENTION.See alsoLV2_TRACKING_PREVENTION (Enums Reference)
Color scheme preference.
PreferredColorScheme As LV2_COLOR_SCHEME ' R/W, 0=Auto 1=Light 2=Darkm_lv.PreferredColorScheme = 2 ' Force dark modepool.SetPreferredColorScheme idx, 2
Dim result As LV2_COLOR_SCHEME
result = pool.GetPreferredColorScheme(idx)Gotchas
LV2_COLOR_SCHEME.See alsoLV2_COLOR_SCHEME (Enums Reference)
Enable non-client region support (custom title bar).
NonClientRegionSupportEnabled As Boolean ' R/Wm_lv.NonClientRegionSupportEnabled = Truepool.SetNonClientRegionSupportEnabled idx, True
Debug.Print pool.GetNonClientRegionSupportEnabled(idx) ' Get variant, from Additional API ReferenceSet the default background color (ARGB format).
pool.SetDefaultBackgroundColor idx, argb As Long ' Reg-Free only' Not available as direct method in registered modepool.SetDefaultBackgroundColor idx, &HFFFFFFFF ' White
pool.SetDefaultBackgroundColor idx, &H00000000 ' TransparentGotchas
The four members below must be set before the underlying WebView2 environment is created — in Registered mode, before WebViewReady fires (e.g., in Form_Load); in Reg-Free mode, before the pool's create call for that browser (CreateInControl).
Chromium command-line arguments.
AdditionalBrowserArguments As String ' R/W, pre-init only' Set BEFORE WebViewReady fires (e.g., in Form_Load)
m_lv.AdditionalBrowserArguments = "--disable-features=msSmartScreenProtection"pool.AdditionalBrowserArguments = "--disable-features=msSmartScreenProtection"
' Must be set before CreateInControlGotchas
WebViewReady fires in Registered mode (e.g., in Form_Load), or before the pool's CreateInControl call in Reg-Free mode.Enable/disable GPU acceleration.
EnableHardwareAcceleration As Boolean ' R/W, pre-init onlym_lv.EnableHardwareAcceleration = False ' Disable GPU (troubleshooting)pool.EnableHardwareAcceleration = FalseGotchas
WebViewReady in Registered mode; before CreateInControl in Reg-Free mode).Path to fixed-version WebView2 runtime.
BrowserExecutableFolder As String ' R/W, pre-init onlym_lv.BrowserExecutableFolder = "C:\WebView2Runtime\130.0.0.0"pool.BrowserExecutableFolder = "C:\WebView2Runtime\130.0.0.0"Gotchas
WebViewReady in Registered mode; before CreateInControl in Reg-Free mode).Minimum WebView2 runtime version required.
RequiredMinimumVersion As String ' R/W, pre-init onlym_lv.RequiredMinimumVersion = "120.0.0.0"' Not available in poolGotchas
Auto-fit WebView when control is resized.
SmartResize As Boolean ' R/W, default Truem_lv.SmartResize = False ' Manage size manually' Not available in pool - pool uses Resize methodGotchas
Resize method instead.Memory usage target.
MemoryUsageTargetLevel As Long ' R/W, 0=Normal 1=Lowm_lv.MemoryUsageTargetLevel = 1 ' Low memory modepool.MemoryUsageTargetLevel(idx) = 1Gotchas
Set-prefixed method — the same convention as IsDragDropEnabled above.See alsoIsDragDropEnabled
Use hex strings for large integers in events (VB6 compatibility).
EventsUseHexadecimal As Boolean ' R/Wm_lv.EventsUseHexadecimal = True
' Events will now fire Hex variants (e.g., NavigationCompletedHex)' Not applicable - pool uses different event mechanismGotchas
Auto-recover WebView on renderer crash.
AutoRecoverOnCrash As Boolean ' R/Wm_lv.AutoRecoverOnCrash = True' Not available in pool - handle ProcessFailed manuallyGotchas
ProcessFailed event manually in Reg-Free mode.See alsoProcessFailed
The following two properties were confirmed present in the source (lines 5417-5445) but not captured under any category during the first drafting pass; added here during the completeness audit so Part II remains an exhaustive reference.
Popup handling mode.
PopupMode As LV2_POPUP_MODE ' R/W, 0=Allow 1=Block 2=SameWindow' Registered
m_lv.PopupMode = 1 ' Block all popups' Reg-Free
pool.SetPopupMode idx, 1
Dim mode As Long
mode = pool.GetPopupMode(idx)See alsoLV2_POPUP_MODE (Enums Reference)
Navigate popups in the same window instead of opening a new one.
OpenPopupsInSameWindow As Boolean ' R/W' Registered
m_lv.OpenPopupsInSameWindow = True' Reg-Free
pool.SetOpenPopupsInSameWindow idx, True
Dim same As Boolean
same = pool.GetOpenPopupsInSameWindow(idx)The following find-in-page methods and properties were confirmed present in the source (lines 4748-4816) but not captured under any category during the first drafting pass; added here during the completeness audit. Taught briefly in Part I, Chapter 11.
Start finding text in the current page.
Parameters: search text, case-sensitive (Boolean), whole-word-only (Boolean).
StartFind(searchText As String, caseSensitive As Boolean, wholeWordOnly As Boolean)' Registered
m_lv.StartFind "search term", False, False ' case-insensitive, partial match' Reg-Free
pool.StartFind idx, "search term", False, FalseNavigate between find matches.
FindNext() / FindPrevious()' Registered
m_lv.FindNext
m_lv.FindPrevious' Reg-Free
pool.FindNext idx
pool.FindPrevious idxStop the find session.
Parameter: clearSelection (False = keep the current match selected/highlighted, True = clear it).
StopFind(clearSelection As Boolean)' Registered
m_lv.StopFind False' Reg-Free
pool.StopFind idx, FalseFind-in-page state and behavior properties: FindMatchCount, FindActiveMatchIndex, IsFindActive, SuppressFindUI, FindHighlightAllMatches.
FindMatchCount As Long (R); FindActiveMatchIndex As Long (R); IsFindActive As Boolean (R); SuppressFindUI As Boolean (R/W); FindHighlightAllMatches As Boolean (R/W)' Registered
Debug.Print "Matches: " & m_lv.FindMatchCount
Debug.Print "Active: " & m_lv.FindActiveMatchIndex
Debug.Print "Active session: " & m_lv.IsFindActive
m_lv.SuppressFindUI = True
m_lv.FindHighlightAllMatches = True' Reg-Free
Debug.Print "Matches: " & pool.GetFindMatchCount(idx)
Debug.Print "Active: " & pool.GetFindActiveMatchIndex(idx)
Debug.Print "Active session: " & pool.IsFindActive(idx)
pool.SetSuppressFindUI idx, True
pool.SetFindHighlightAllMatches idx, True
Dim suppressed As Boolean, highlightAll As Boolean
suppressed = pool.GetSuppressFindUI(idx)
highlightAll = pool.GetFindHighlightAllMatches(idx)Gotchas
FindMatchCount, FindActiveMatchIndex, and IsFindActive are read-only; SuppressFindUI and FindHighlightAllMatches are read/write.Reg-Free pool mode manages multiple independent browser instances, each addressable by an integer index (idx) returned at creation time. Each instance can optionally be bound to a named, on-disk profile for session isolation, including an InPrivate variant. Registered mode manages a single browser per control instance but adds its own set of embedding/window-management members for placing that single browser into an external native window. WebView2 runtime detection and browser-extension enumeration are also documented here.
Create a new browser in the Reg-Free pool.
Returns the integer index used to address that browser in every subsequent pool call.
idx = pool.Create(hwndParent, url, x, y, width, height)Dim idx As Long
idx = pool.Create(Me.hWnd, "https://example.com", 10, 10, 800, 600)
' Or embedded directly in a VBA control at creation time:
idx = pool.CreateInControl("about:blank", Me.FraBrowser)Create a pool browser bound to a named profile, for session isolation across instances.
All three return the new browser's index (Long).
idx = pool.CreateWithProfile(hwndParent, url, x, y, width, height, profileName)Dim idx As Long
idx = pool.CreateWithProfile(Me.hWnd, "about:blank", 0, 0, 800, 600, "ProfileA")
idx = pool.CreateInControlWithProfile("about:blank", Me.FraBrowser, "ProfileA")
idx = pool.CreateWithProfileInPrivate(Me.hWnd, "about:blank", 0, 0, 800, 600, "Private1")Gotchas
CreateWithProfile / CreateWithProfileInPrivate: hwndParent, url, x, y, width, height, profileName. CreateInControlWithProfile: url, control, profileName.Lifecycle control for pool browser instances.
Destroy — destroys the browser asynchronously.DestroySync — destroys the browser synchronously.Park — hides the browser instance without destroying it, so it can be reused later.DestroyAllParked — destroys every currently-parked browser at once.pool.Destroy browserIndexpool.Destroy idx ' Async destroy
pool.DestroySync idx ' Sync destroy
pool.Park idx ' Hide for reuse
pool.DestroyAllParked ' Cleanup parkedInstance counters for the pool.
GetParkedBrowserCount returns the number of browsers currently parked (hidden, held for reuse via Park). GetBrowserCount returns the total number of browsers currently held by the pool.
pool.GetBrowserCountDebug.Print pool.GetParkedBrowserCount
Debug.Print pool.GetBrowserCountPositioning and re-embedding operations for an existing pool browser, addressed by index.
Resize — resize browser. Signature: pool.Resize browserIndex, x, y, width, height.EmbedInControl — embed browser into a VBA control (Frame, Image, etc.). Signature: pool.EmbedInControl browserIndex, control.Reparent — reparent browser to a new window. Signature: pool.Reparent browserIndex, hwndNewParent, x, y, width, height.pool.Resize browserIndex, x, y, width, heightpool.Resize idx, 0, 0, 1024, 768
pool.EmbedInControl idx, Me.FraOtherFrame
pool.Reparent idx, newHwnd, 0, 0, 800, 600Profile identity lookups for a pool browser.
GetProfileName — get the profile name of a browser. GetProfilePath — get the on-disk profile path.
pool.GetProfileName(browserIndex)Debug.Print pool.GetProfileName(idx)
Debug.Print pool.GetProfilePath(idx)Clear browsing data belonging to a pool browser's profile.
ClearProfileBrowsingData — clear specific browsing data for a browser's profile (takes a dataKinds value; the source example uses 16384). ClearProfileBrowsingDataAll — clear ALL browsing data for a browser's profile.
pool.ClearProfileBrowsingData browserIndex, dataKindspool.ClearProfileBrowsingData idx, 16384 ' dataKinds bitmask
pool.ClearProfileBrowsingDataAll idxWebView2 runtime detection, independent of any browser instance.
IsWebView2RuntimeInstalled — check whether the WebView2 runtime is installed; explicitly documented as usable with no browser needed. Returns Boolean.GetWebView2RuntimeVersion — get the installed WebView2 runtime version string. Returns String.GetWebView2RuntimePath — get the WebView2 runtime install path from the registry. Returns String.pool.IsWebView2RuntimeInstalledIf pool.IsWebView2RuntimeInstalled Then
Debug.Print "Version: " & pool.GetWebView2RuntimeVersion
Debug.Print "Path: " & pool.GetWebView2RuntimePath
End IfGet browser extensions list as a JSON array (requires Profile7/8, per the auto-synced description).
pool.GetBrowserExtensionList(browserIndex)Debug.Print pool.GetBrowserExtensionList(idx)' Registered (per auto-synced section only — not corroborated elsewhere):
Dim result As String
result = m_lv.GetBrowserExtensionList()
' Reg-Free:
Dim result As String
result = pool.GetBrowserExtensionList(idx)Gotchas
Embed the (single, Registered-mode) browser in an external, non-VBA window handle.
EmbedInHwnd — takes the parent handle as LngLng (64-bit). Auto-synced description text: "Embed browser in an external window handle for reg-free usage" (description text as documented, despite the member itself being tagged Registered Only).EmbedInHwndByLong — takes the parent handle as Long (32-bit), for VB6/VFP compatibility where LngLng is unavailable.Both signatures: width, height follow the handle parameter.
m_lv.EmbedInHwnd hwndParent, width, heightm_lv.EmbedInHwnd CLngLng(hwndParent), 800, 600
m_lv.EmbedInHwndByLong CLng(hwndParent), 800, 600 ' VB6 compatGotchas
EmbedInHwnd reads "Embed browser in an external window handle for reg-free usage" even though the member itself is tagged Registered Only — a documented inconsistency in the source text.Manage the embedded browser window created via EmbedInHwnd / EmbedInHwndByLong (or the control's normal embedding).
ResizeEmbedded — resize the embedded browser to new dimensions. Signature: width, height.MoveEmbedded — move the embedded browser to a new position. Signature: left, top.CloseEmbedded — close and destroy the embedded browser window. No parameters.m_lv.ResizeEmbedded width, heightm_lv.ResizeEmbedded 1024, 768
m_lv.MoveEmbedded 100, 50
m_lv.CloseEmbeddedSee alsoEmbedInHwnd, EmbedInHwndByLong
Toggle the Bridge Inspector HUD, which shows live JS <-> VBA message traffic.
m_lv.ToggleBridgeInspectorm_lv.ToggleBridgeInspectorpool.ToggleBridgeInspector idxOpen Chrome DevTools (F12).
m_lv.OpenDevToolsWindowm_lv.OpenDevToolsWindowpool.OpenDevToolsWindow idxExecute a Chrome DevTools Protocol method.
First argument is the CDP method name, second is the CDP params encoded as a JSON string. Returns the CDP result as a JSON string.
Waits for the browser's answer (fixed 30 s). Works from ordinary code and inside DevToolsProtocolEventReceived, which is how you answer a CDP event. Inside WebViewReady, other inline events and host-object methods it fails at once with &H80040203 and sends nothing. Reg-Free: a refused call raises &H80004005; GetLastErrorCode(idx) returns &H80040203.
result = m_lv.CallDevToolsProtocolMethod(cdpMethod, paramsJson)Dim result As String
result = m_lv.CallDevToolsProtocolMethod("Page.captureScreenshot", "{""format"":""png""}")
Debug.Print resultDim result As String
result = pool.CallDevToolsProtocolMethod(idx, "Page.captureScreenshot", "{""format"":""png""}")Subscribe to a Chrome DevTools Protocol event by name.
Per the source's own comment, this causes the DevToolsProtocolEventReceived event to fire on matching CDP traffic (event documented in the Events reference, not here).
Mode: Registered (events are delivered). The pool accepts the call, but it raises no events, so nothing is delivered in Reg-Free mode.
m_lv.AddDevToolsProtocolEventHandler eventNamem_lv.AddDevToolsProtocolEventHandler "Network.requestWillBeSent"
' Will fire DevToolsProtocolEventReceived eventpool.AddDevToolsProtocolEventHandler idx, "Network.requestWillBeSent" ' accepted, but no event is ever deliveredSee alsoDevToolsProtocolEventReceived, RemoveDevToolsProtocolEventHandler
Unsubscribe from a DevTools Protocol event previously registered with AddDevToolsProtocolEventHandler.
m_lv.RemoveDevToolsProtocolEventHandler eventNamem_lv.RemoveDevToolsProtocolEventHandler "Network.requestWillBeSent"pool.RemoveDevToolsProtocolEventHandler idx, "Network.requestWillBeSent"See alsoAddDevToolsProtocolEventHandler
Check if DevTools Protocol is available.
Type: Boolean. Read-only.
m_lv.HasDevToolsProtocolDebug.Print "CDP available: " & m_lv.HasDevToolsProtocolDebug.Print "CDP available: " & pool.HasDevToolsProtocol(idx)Enable comprehensive anti-detection (hides all automation markers at once).
m_lv.EnableStealthMode enablem_lv.EnableStealthMode Truepool.EnableStealthMode idx, TrueRemove the navigator.webdriver flag.
m_lv.HideWebDriver enablem_lv.HideWebDriver Truepool.HideWebDriver idx, TrueSpoof WebGL vendor and renderer strings.
Parameters per the documented example: vendor string, renderer string.
m_lv.SpoofWebGL vendor, rendererm_lv.SpoofWebGL "Intel Inc.", "Intel Iris OpenGL Engine"pool.SpoofWebGL idx, "Intel Inc.", "Intel Iris OpenGL Engine"Add noise to the canvas fingerprint to randomize it.
m_lv.RandomizeCanvasFingerprint enablem_lv.RandomizeCanvasFingerprint Truepool.RandomizeCanvasFingerprint idx, TrueSpoof screen dimensions for fingerprint evasion.
The source documents no named parameters beyond the example call: three numeric arguments (1920, 1080, 24 in the example) with no explicit labels given in the text — presumably width, height, and a bit-depth/color-depth value, but this is not stated outright in the source.
m_lv.SpoofScreenSize width, height, colorDepthm_lv.SpoofScreenSize 1920, 1080, 24pool.SpoofScreenSize idx, 1920, 1080, 24Gotchas
Disable WebRTC to prevent IP address leaks.
m_lv.DisableWebRTC enablem_lv.DisableWebRTC Truepool.DisableWebRTC idx, TrueAdd human-like noise to mouse movement events.
m_lv.EnableMouseNoise enablem_lv.EnableMouseNoise Truepool.EnableMouseNoise idx, TrueAdd random delays to actions for human-like timing.
Parameters per the documented example and its inline comment: min delay (ms), max delay (ms) — the example uses a 50-200ms range.
m_lv.EnableTimingNoise minDelayMs, maxDelayMsm_lv.EnableTimingNoise 50, 200 ' 50-200ms random delayspool.EnableTimingNoise idx, 50, 200Check if stealth mode is enabled.
Type: Boolean. Read-only.
m_lv.IsStealthEnabledDebug.Print "Stealth: " & m_lv.IsStealthEnabledDebug.Print "Stealth: " & pool.IsStealthEnabled(idx)Dim result As Boolean
result = pool.GetIsStealthEnabled(idx)Contradiction
The Reg-Free member for reading stealth status is documented two different ways in the source: as an indexed property IsStealthEnabled(idx) in the main API reference, and as a Get-prefixed method GetIsStealthEnabled(idx) in the auto-synced IDL reference. These are not two modes of the same call — they are two different spellings claimed for the same Reg-Free functionality, and the source does not reconcile them. The IDL-derived name (GetIsStealthEnabled) is more likely to reflect the actual compiled type-library member; verify against the type library before depending on either name in new code.
Gotchas
pool.IsStealthEnabled(idx) (main API reference, lines 4904-5158) and pool.GetIsStealthEnabled(idx) (auto-synced IDL reference, tagged Mode: Reg-Free Only, description "Get stealth mode enabled status").GetIsStealthEnabled is more likely to match the actual compiled type library, so verify against the type library before depending on either name.See alsoEnableStealthMode
Configure a CAPTCHA solving service.
Parameters per the example: service name string (e.g. "2captcha"), API key string.
m_lv.SetCaptchaService serviceName, apiKeym_lv.SetCaptchaService "2captcha", "YOUR_API_KEY_HERE"pool.SetCaptchaService idx, "2captcha", "YOUR_API_KEY_HERE"Detect CAPTCHA on the current page.
Returns a JSON string with type, siteKey, pageUrl.
info = m_lv.DetectCaptchaDim info As String
info = m_lv.DetectCaptcha
Debug.Print "CAPTCHA info: " & infoDim info As String
info = pool.DetectCaptcha(idx)Auto-solve a detected CAPTCHA (detect + solve + submit token in one call).
Parameter: timeout in milliseconds. Returns Boolean success.
solved = m_lv.SolveCaptcha(timeoutMs)Dim solved As Boolean
solved = m_lv.SolveCaptcha(30000) ' 30 second timeout
If solved Then Debug.Print "CAPTCHA solved!"Dim solved As Boolean
solved = pool.SolveCaptcha(idx, 30000)Solve reCAPTCHA v2 given a known siteKey, and get the token back.
Parameters: siteKey string, page URL string. Returns the token as a String.
token = m_lv.SolveRecaptchaV2(siteKey, pageUrl)Dim token As String
token = m_lv.SolveRecaptchaV2("6Le-SITE-KEY", "https://www.example.com/form")
Debug.Print "Token: " & tokenDim token As String
token = pool.SolveRecaptchaV2(idx, "6Le-SITE-KEY", "https://www.example.com/form")See alsoSubmitCaptchaToken
Solve hCaptcha given a known siteKey, and get the token back.
Parameters: siteKey string, page URL string. Returns the token as a String.
token = m_lv.SolveHCaptcha(siteKey, pageUrl)Dim token As String
token = m_lv.SolveHCaptcha("SITE-KEY", "https://www.example.com/form")Dim token As String
token = pool.SolveHCaptcha(idx, "SITE-KEY", "https://www.example.com/form")See alsoSubmitCaptchaToken
Submit a solved CAPTCHA token to the page (e.g. a token obtained from SolveRecaptchaV2 or SolveHCaptcha).
m_lv.SubmitCaptchaToken tokenm_lv.SubmitCaptchaToken tokenpool.SubmitCaptchaToken idx, tokenSee alsoSolveRecaptchaV2, SolveHCaptcha
All members in this group are Registered Only — confirmed both by the main API reference (which shows no Reg-Free examples for this group) and by explicit Mode: Registered Only tags on the auto-synced entries.
Open and embed an Office document for editing.
m_lv.OpenOfficeDocument filePathm_lv.OpenOfficeDocument "C:\Documents\Report.docx"Gotchas
ShowDocumentPreview / ShowDocumentPreviewInBrowserFrame instead; these show a preview, not an editable embedded document.See alsoShowDocumentPreview, ShowDocumentPreviewInBrowserFrame
Close the currently embedded Office document.
m_lv.CloseOfficeDocumentm_lv.CloseOfficeDocumentSave the embedded Office document.
m_lv.SaveOfficeDocumentm_lv.SaveOfficeDocumentSave the embedded Office document with a new filename.
Parameter: filePath.
m_lv.SaveOfficeDocumentAs filePathm_lv.SaveOfficeDocumentAs "C:\Documents\Report_v2.docx"Check if an Office document is currently embedded.
Type: Boolean. Read-only.
m_lv.IsOfficeDocumentOpenIf m_lv.IsOfficeDocumentOpen Then
' ...
End IfGet the path of the currently embedded Office document.
Type: String. Read-only.
m_lv.OfficeDocumentPathDebug.Print "Path: " & m_lv.OfficeDocumentPathGet the type of the embedded document: Word, Excel, PowerPoint, or Unknown.
Type: String. Read-only.
m_lv.OfficeDocumentTypeDebug.Print "Type: " & m_lv.OfficeDocumentTypeSee alsoCurrentDocumentType
Get the Word.Application object for automation.
Type: Object. Read-only.
Set wdApp = m_lv.WordApplicationDim wdApp As Object
Set wdApp = m_lv.WordApplicationGet the active Word.Document object for automation.
Type: Object. Read-only.
Set wdDoc = m_lv.WordDocumentDim wdDoc As Object
Set wdDoc = m_lv.WordDocumentThis is a separate, broader viewer than the Office-editing group above: it opens images, PDFs, videos, and Office docs, and is available in both modes.
Open any document type (images, PDFs, videos, Office docs).
Parameters: filePath, documentType (Long; 0 = Auto per the source's inline comment — no other numeric values for this parameter are documented in the source).
m_lv.OpenDocument filePath, documentTypem_lv.OpenDocument "C:\Photos\image.png", 0 ' 0=Auto
m_lv.OpenDocument "C:\Docs\report.pdf", 0pool.OpenDocument idx, "C:\Photos\image.png", 0Gotchas
documentType parameter is a Long where 0 means Auto per the source's inline comment; no other numeric values for this parameter are documented in the source.See alsoCurrentDocumentPath, CurrentDocumentType
Get the file path of the currently open document (as opened via OpenDocument).
m_lv.CurrentDocumentPathDebug.Print m_lv.CurrentDocumentPathDim result As String
result = pool.GetCurrentDocumentPath(idx)Gotchas
CurrentDocumentPath (property) is Registered Only; GetCurrentDocumentPath (method) is Reg-Free Only. This is not a contradiction: each mode simply exposes this value under a different member name, and no part of the source claims the other name works in the opposite mode.See alsoOpenDocument, CurrentDocumentType
Get the type of the currently open document.
Documented values (Registered property description): Image, PDF, Video, Audio, Text, Word, Excel, PowerPoint, Unknown. Note this list is broader than OfficeDocumentType's Word/Excel/PowerPoint/Unknown, since OpenDocument/CurrentDocumentType covers the generic viewer while OfficeDocumentType covers only the embedded-Office-editing path.
m_lv.CurrentDocumentTypeDebug.Print m_lv.CurrentDocumentTypeDim result As String
result = pool.GetCurrentDocumentType(idx)Gotchas
CurrentDocumentType (property) is Registered Only; GetCurrentDocumentType (method) is Reg-Free Only — a per-mode naming split, same as CurrentDocumentPath/GetCurrentDocumentPath.See alsoOpenDocument, CurrentDocumentPath, OfficeDocumentType
Show an Office document preview in a window handle.
Per the auto-synced description: "Completely separate from WebView2. Pass the window handle of a Frame control."
m_lv.ShowDocumentPreview filePath, hwndTargetm_lv.ShowDocumentPreview "C:\Docs\file.pdf", m_lv.EmbeddedParentHwndpool.ShowDocumentPreview idx, "C:\Docs\file.pdf"Note
Mode difference, not a contradiction: both the main API reference and the auto-synced IDL reference agree on this asymmetry — Registered mode's ShowDocumentPreview requires a hwndTarget parameter that Reg-Free mode's ShowDocumentPreview does not take.
Gotchas
filePath and a target window handle (hwndTarget); Reg-Free mode takes only filePath — no target window handle parameter. Both the main API reference and the auto-synced IDL reference agree on this asymmetry, and both state it is a mode difference, not a contradiction.EmbeddedParentHwnd (which returns the embedded parent Frame's window handle) is documented in the embedding/window-management reference, not here.See alsoEmbeddedParentHwnd, ShowDocumentPreviewInBrowserFrame, CloseDocumentPreview
Show an Office document preview in the same Frame as WebView2.
Per the auto-synced description, this automatically hides WebView2.
m_lv.ShowDocumentPreviewInBrowserFrame filePathm_lv.ShowDocumentPreviewInBrowserFrame "C:\Docs\file.pdf"pool.ShowDocumentPreviewInBrowserFrame idx, "C:\Docs\file.pdf"Gotchas
ShowDocumentPreview and CloseDocumentPreview, not this member, but does not state it is unavailable in Reg-Free.See alsoShowDocumentPreview, CloseDocumentPreview
Close any active document preview.
m_lv.CloseDocumentPreviewm_lv.CloseDocumentPreviewpool.CloseDocumentPreview idxSee alsoShowDocumentPreview, ShowDocumentPreviewInBrowserFrame
Activate the license with a company name and a V1 activation string. If a key is not supplied in code, the 30-day Trial Period applies.
The key argument is an opaque V1 activation string. The runtime validates it and enforces any expiry timestamp it carries. Activation is in-memory only — no registry, no file system, no network call. Activation is session-only: the runtime does not persist activation across sessions/process restarts (does not persist across Access restarts). Call ActivateLicense from your Form_Load / UserForm_Initialize on every launch.
ctrl.ActivateLicense companyName, activationKeym_lv.ActivateLicense "My Company", "V1|...|..."
Debug.Print "Status: " & m_lv.LicenseStatus
Debug.Print "Type: " & m_lv.LicenseType
Debug.Print "State: " & m_lv.LicenseState
Debug.Print "Days remaining: " & m_lv.TrialDaysRemaining
Debug.Print "Expiry: " & m_lv.LicenseExpiry
Debug.Print "Version: " & m_lv.LicenseVersion
Debug.Print "Info JSON: " & m_lv.GetLicenseInfo()pool.ActivateLicense "My Company", "V1|...|..."
Debug.Print "Status: " & pool.LicenseStatus
Debug.Print "Type: " & pool.LicenseType
Debug.Print "State: " & pool.LicenseState
Debug.Print "Trial days: " & pool.TrialDaysRemaining
Debug.Print "Info JSON: " & pool.GetLicenseInfo()Note
the Reg-Free usage example above is transcribed exactly as documented in the source, calling LicenseStatus/LicenseType/LicenseState/TrialDaysRemaining as plain properties on pool with no browserIndex/idx argument. Elsewhere in the source (the per-property reference entries below), the Reg-Free getters for these same members are documented as Get* methods that DO take a browserIndex argument (e.g. pool.GetLicenseStatus browserIndex, value). This is a genuine discrepancy in the source between the two documented forms — see the "CONTRADICTIONS" note in each property section below. ActivateLicense itself and GetLicenseInfo are consistently documented, in both places they appear, as not taking a browserIndex/idx argument in Reg-Free mode.
Gotchas
Const in a shared module — modLV2License.bas for Registered Mode (forms call EnsureLicensed m_lv in Form_Load), or modLV2Pool.bas for Reg-Free Mode (GetPool() activates automatically on the first call in the process).ActivateLicense AFTER the first Navigate. The watermark may already have rendered.ActivateLicense inside WebViewReady / OnBrowserReady. By the time those fire, the navigation decision has already used the license state.ActivateLicense is process-wide, not per-form and not per-browser:
ActivateLicense call activates every LiteView2 browser created afterwards in the same Access (or Office) session.CreateInControl call automatically — you do not register the license per browser-index.ActivateLicense once — that's what EnsureLicensed and GetPool handle for you.Recommended pattern (already baked into the canonical templates): the activation key lives in a shared module, never duplicated across forms. Whichever form opens first in the process triggers the activation; every later form inherits the licensed state via the same shared module. You change the key once, in one file.
The shared-module activation IS idempotent. EnsureLicensed checks a flag and exits early on subsequent calls. GetPool only initialises once. So even if a form re-imports the helper or you accidentally call it twice, there's no harm.
Get license status as a string. Possible values: TRIAL, EXPIRED, PRO, ENTERPRISE, EVAL.
An EVAL key returns EVAL while unexpired, and reverts to EXPIRED once its expiry timestamp passes.
value = ctrl.LicenseStatusDim value As String
value = ctrl.LicenseStatusDim value As String
pool.GetLicenseStatus idx, valueGotchas
ActivateLicense usage example (see above) reads this same value in Reg-Free mode as a bare property — pool.LicenseStatus — with no index argument, rather than as pool.GetLicenseStatus idx, value. Both forms are presented verbatim as documented; the source does not reconcile them.See alsoActivateLicense
Get license type as a lowercase string. Possible values: trial, eval, pro, enterprise, none.
The eval value indicates an active Extended Evaluation License (Section 2.3 of the EULA) issued at the Licensor's discretion to MVPs, educators, reviewers, and community contributors.
value = ctrl.LicenseTypeDim value As String
value = ctrl.LicenseTypeDim value As String
pool.GetLicenseType idx, valueGotchas
ActivateLicense usage example reads this value in Reg-Free mode as a bare property — pool.LicenseType — with no index argument. See the note under ActivateLicense above.See alsoActivateLicense
Current license state as enum (Valid, Trial, Expired, Invalid).
PRO, Enterprise, and Extended Evaluation (EVAL within expiry) all return Valid (value 0). Once an EVAL key passes its expiry timestamp, the state reverts to Expired (value 2). No new enum value is added at the LV2_LICENSE_STATE level for EVAL — it is folded into Valid while the key is unexpired.
Enum LV2_LICENSE_STATE values: 0=Valid, 1=Trial, 2=Expired, 3=Invalid
value = ctrl.LicenseStateDim value As LV2_LICENSE_STATE
value = ctrl.LicenseStateDim value As LV2_LICENSE_STATE
pool.GetLicenseState idx, valueGotchas
ActivateLicense usage example reads this value in Reg-Free mode as a bare property — pool.LicenseState — with no index argument. See the note under ActivateLicense above.See alsoActivateLicense
Get license expiry date as a DATE. Returns 0 for perpetual licenses (PRO, Enterprise) with no expiry.
For an Extended Evaluation (EVAL) license, returns the expiry timestamp encoded in the activation key.
value = ctrl.LicenseExpiryDim value As Date
value = ctrl.LicenseExpiryDim value As Date
pool.GetLicenseExpiry idx, valueGet trial days remaining. Returns 0 outside the 30-day Trial Period (e.g. when a paid license is active, when an Extended Evaluation is active, or when the trial has expired).
For Extended Evaluation keys, days-remaining is exposed via the daysRemaining field of GetLicenseInfo rather than via this property.
value = ctrl.TrialDaysRemainingDim value As Long
value = ctrl.TrialDaysRemainingDim value As Long
pool.GetTrialDaysRemaining idx, valueGotchas
ActivateLicense usage example reads this value in Reg-Free mode as a bare property — pool.TrialDaysRemaining — with no index argument. See the note under ActivateLicense above.Note
Found via the auto-synced-section member search (this entry lives at line 9121 of the source, outside the two ranges that document the other license properties, though it is also referenced by name inside the ActivateLicense example at line 5463/5475).
See alsoActivateLicense, GetLicenseInfo
Get licensed version range.
value = ctrl.LicenseVersionDim value As String
value = ctrl.LicenseVersionDim value As String
pool.GetLicenseVersion idx, valueReturns a small JSON document describing the current license. Read-only, informational only — does not include any credential material (no activation strings or signatures).
The first field is always "schemaVersion":1. Future informational fields will be additive.
info = ctrl.GetLicenseInfo()Example return value (Extended Evaluation):
{"schemaVersion":1,"tier":"EVAL","product":"LV2","company":"Crystal Long",
"expiryUtc":"2027-05-18T23:59:59Z","isPerpetual":false,
"daysRemaining":365,"state":"valid"}Example return value (Trial):
{"schemaVersion":1,"tier":"Trial","product":"LV2","company":"",
"expiryUtc":"2026-06-15T00:00:00Z","isPerpetual":false,
"daysRemaining":12,"state":"trial"}PRO / Enterprise (perpetual): "expiryUtc":null, "isPerpetual":true, "daysRemaining":null.
Use:
Dim info As String
info = m_lv.GetLicenseInfo() ' Registered
info = pool.GetLicenseInfo() ' Reg-Free
Debug.Print infoNote
unlike the other Reg-Free license accessors on this page (GetLicenseStatus, GetLicenseType, GetLicenseState, GetLicenseExpiry, GetLicenseVersion, GetTrialDaysRemaining), GetLicenseInfo in Reg-Free mode takes no browserIndex/idx argument — it is called directly on pool, exactly as documented in both places it appears in the source (the ActivateLicense example and this entry). This matches ActivateLicense's own pool-level (not per-browser-index) scope.
See alsoActivateLicense, LicenseStatus, LicenseType, LicenseState, LicenseExpiry, LicenseVersion, TrialDaysRemaining
The two operating modes use two different notification models, and they are not interchangeable:
ILiteView2Ctrl / _DLiteView2CtrlEvents. Declare the control WithEvents and implement Private Sub m_lv_<EventName>(<args>) handlers.Object, not WithEvents-capable). Instead it is event-driven via named VBA callbacks, registered with pool methods and invoked by the pool on the UI thread:
SetReadyCallback / ClearReadyCallback, invoking a public OnBrowserReady() Sub — replaces the legacy Form_Timer + IsReady() polling pattern.SetWebMessageCallback, targeting a named module-level Sub. (Full reference for SetWebMessageCallback / ClearWebMessageCallback — including the Registered-mode form m_lv.ClearWebMessageCallback — lives in the messaging/host-object category, not here.)PrintToPdfCompleted, CaptureScreenshotCompleted, etc. have no Reg-Free equivalent): check the output file on the next user action, or expose a host object whose method JavaScript calls to signal completion.' Registered Mode — event sink declaration
Private WithEvents m_lv As LiteView2Lib.LiteView2Ctrl
Private Sub Form_Load()
Set m_lv = Me.LiteView2Ctrl1.Object
End SubRegisters a COM callback object/method to invoke when the browser is fully ready.
callbackObject must be STA-affine (a VBA form, VB6 class, etc.). Fires immediately if the browser is already ready at registration time. Fires exactly once per registration. Dispatched on the LiteView2 UI thread via PostMessage — never on WebView2 callback threads.
pool.SetReadyCallback idx, callbackObject, methodNameWebViewReady event instead
Reg-Freesupported
pool.SetReadyCallback idx, callbackObject, methodNamePrivate pool As Object
Private idx As Long
Private Sub Form_Load()
Set pool = GetPool() ' activates license internally
idx = pool.CreateInControl("about:blank", Me.fraBrowser)
pool.SetReadyCallback idx, Me, "OnBrowserReady"
End Sub
' Public — invoked by the pool by name, on the UI thread, when the browser is ready.
' No parameters.
Public Sub OnBrowserReady()
pool.SetLocalContentRoot idx, CurrentProject.Path
pool.SetAreHostObjectsAllowed idx, True
pool.AddHostObjectToScript idx, "vba", Me
pool.Navigate idx, "https://lv2.example/index.html"
End SubGotchas
Form_Timer + IsReady() polling pattern? Delete the timer. Replace the body of Form_Load with a single pool.SetReadyCallback idx, Me, "OnBrowserReady" call. Move whatever ran inside the If pool.IsReady(...) Then block into a public OnBrowserReady() sub on the same form. That is the entire migration.WebViewReady event instead.See alsoWebViewReady, IsReady, ClearReadyCallback
Unregister the ready callback and allow re-registration.
Required in teardown: Form_Unload must call ClearReadyCallback before DestroyBrowserSync.
pool.ClearReadyCallback idxSet m_lv = Nothing
Reg-Freesupported
pool.ClearReadyCallback idxPrivate Sub Form_Unload(Cancel As Integer)
On Error Resume Next
If Not pool Is Nothing And idx <> 0 Then
pool.Stop idx ' halt any in-flight navigation
pool.ClearReadyCallback idx ' detach our callback so it can't fire after teardown
pool.DestroyBrowserSync idx, 2000 ' synchronous browser teardown (max 2 s wait)
idx = 0
End If
Set pool = Nothing
End SubGotchas
Type mismatch / Object required errors).Set m_lv = Nothing in Form_Close.See alsoSetReadyCallback, DestroyBrowserSync
Check if WebView2 is ready.
ctrl.IsReady() / pool.IsReady(idx)Dim result As Boolean
result = ctrl.IsReady()Dim result As Boolean
result = pool.IsReady(idx)Gotchas
IsReady is the basis of the legacy Form_Timer + IsReady() polling pattern that SetReadyCallback supersedes — new code should use SetReadyCallback instead of polling IsReady.WebViewReady event over polling ctrl.IsReady().See alsoSetReadyCallback, WebViewReady
These control which VBA macros JavaScript is permitted to invoke via auto-dispatch, and let you register a specific JS-callable name bound to a specific VBA method — distinct from the readiness callback above.
Set list of allowed VBA macros (comma-separated).
pool.SetAllowedVBAMacros idx, "CB_frmMyForm,CB_frmOther"pool.SetAllowedVBAMacros idx, "CB_frmMyForm,CB_frmOther"See alsoClearAllowedVBAMacros, EnableAutoDispatch, RegisterVbaCallback
Clear allowed VBA macros list.
pool.ClearAllowedVBAMacros idxpool.ClearAllowedVBAMacros idxSee alsoSetAllowedVBAMacros
Enable auto-dispatch of HTML calls to VBA.
pool.EnableAutoDispatch idx, Truepool.EnableAutoDispatch idx, TrueSee alsoIsAutoDispatchEnabled, SetAllowedVBAMacros
Check if auto-dispatch is enabled.
result = pool.IsAutoDispatchEnabled(idx)Dim result As Boolean
result = pool.IsAutoDispatchEnabled(idx)See alsoEnableAutoDispatch
Register VBA callback for JS to call.
pool.RegisterVbaCallback idx, "getData", callbackObj, "GetData"pool.RegisterVbaCallback idx, "getData", callbackObj, "GetData"See alsoUnregisterVbaCallback, SetAllowedVBAMacros
Unregister VBA callback.
pool.UnregisterVbaCallback idxpool.UnregisterVbaCallback idxSee alsoRegisterVbaCallback
Note
Events fire on ILiteView2Ctrl only — the ActiveX (registered) interface. Reg-Free mode uses callbacks (SetReadyCallback/ClearReadyCallback, SetWebMessageCallback) instead of events.
To handle events in VBA, declare the control with WithEvents:
Private WithEvents m_lv As LiteView2Lib.LiteView2Ctrl
Private Sub Form_Load()
Set m_lv = Me.LiteView2Ctrl1.Object
End SubThen implement handlers as Private Sub m_lv_<EventName>(<args>). Or skip WithEvents and use event procedures named after the control (LiteView2Ctrl1_WebViewReady), as the Part I templates do. Use one style per form: a handler named after a variable that is not declared WithEvents never runs, and no error is shown.
Coverage: All 63 events from _DLiteView2CtrlEvents (DISPIDs 1-48, 60-64, 600-609).
Hex variants
NavigationCompletedHex, ContentLoadingHex, DownloadStartingHex, DownloadProgressChangedHex, DownloadCompletedHex fire only when the EventsUseHexadecimal property is True. They carry the same payload as the base event but with LONG values converted to hex BSTRs — a VB6 workaround for values larger than Long.MaxValue. In typical VBA/Access usage, ignore them and handle the base event instead.
Every event below is Registered Mode only; the mode tag is omitted per-event to avoid repetition.
Fired when WebView2 initialization is complete (DISPID 48).
Set up and navigate here (SetLocalContentRoot, AreHostObjectsAllowed, AddHostObjectToScript, Navigate). Calls that wait for the browser fail here at once with &H80040203: read or change the page in NavigationCompleted. Register document scripts and EnableLvFacade earlier, in Form_Load.
Private Sub m_lv_WebViewReady()SetReadyCallback instead
Private Sub m_lv_WebViewReady()
m_lv.SetLocalContentRoot CurrentProject.Path & "\html"
m_lv.Navigate "https://lv2.example/index.html"
End SubSee alsoSetReadyCallback, IsReady
Fired before navigation starts (DISPID 1).
Set Cancel = True to block it.
Private Sub m_lv_NavigationStarting(ByVal url As String, ByRef Cancel As Boolean)Private Sub m_lv_NavigationStarting(ByVal url As String, ByRef Cancel As Boolean)
If InStr(url, "lv2.example") = 0 And Left(url, 6) <> "about:" Then
Cancel = True ' Block external navigation
End If
End SubFired after navigation completes / the page has fully loaded (DISPID 2).
Safe to push data here.
Private Sub m_lv_NavigationCompleted(ByVal url As String, ByVal success As Boolean)Private Sub m_lv_NavigationCompleted(ByVal url As String, ByVal success As Boolean)
If success Then
PushData
Else
MsgBox "Failed to load: " & url
End If
End SubSee alsoNavigationCompletedHex
Fired when EventsUseHexadecimal = True: navigation completes with NavigationId as a BSTR hex string (VB6 Int64 workaround) (DISPID 60).
Private Sub m_lv_NavigationCompletedHex(ByVal url As String, ByVal success As Boolean, ByVal navigationIdHex As String)Private Sub m_lv_NavigationCompletedHex(ByVal url As String, _
ByVal success As Boolean, ByVal navigationIdHex As String)
Debug.Print "Nav ID: " & navigationIdHex
End SubGotchas
EventsUseHexadecimal is True; carries the same payload as NavigationCompleted with the NavigationId as a hex BSTR instead of a Long. See the Hex variants note above.See alsoNavigationCompleted
Fired when the URL changes (DISPID 6).
Private Sub m_lv_SourceChanged(ByVal url As String)Private Sub m_lv_SourceChanged(ByVal url As String)
Me.txtUrl = url
End SubFired when browser history changes (back/forward availability) (DISPID 25).
Private Sub m_lv_HistoryChanged()Private Sub m_lv_HistoryChanged()
Me.btnBack.Enabled = m_lv.CanGoBack
Me.btnForward.Enabled = m_lv.CanGoForward
End SubFired when DOM content has finished loading (the DOMContentLoaded event), before images/scripts finish (DISPID 23).
Private Sub m_lv_DOMContentLoaded(ByVal navigationId As String)Private Sub m_lv_DOMContentLoaded(ByVal navigationId As String)
Debug.Print "DOM ready, nav ID: " & navigationId
End SubFired when content starts loading (DISPID 24).
Private Sub m_lv_ContentLoading(ByVal navigationId As String, ByVal isErrorPage As Boolean)Private Sub m_lv_ContentLoading(ByVal navigationId As String, ByVal isErrorPage As Boolean)
Debug.Print "Loading... Error page: " & isErrorPage
End SubSee alsoContentLoadingHex
Fired when EventsUseHexadecimal = True: content loading with NavigationId as BSTR hex (VB6 Int64 workaround) (DISPID 61).
Private Sub m_lv_ContentLoadingHex(ByVal navigationIdHex As String, ByVal isErrorPage As Boolean)Private Sub m_lv_ContentLoadingHex(ByVal navigationIdHex As String, ByVal isErrorPage As Boolean)
Debug.Print "Loading nav: " & navigationIdHex
End SubGotchas
EventsUseHexadecimal is True; carries the same payload as ContentLoading with the NavigationId as a hex BSTR instead of a Long. See the Hex variants note above.See alsoContentLoading
Fired when JavaScript calls chrome.webview.postMessage(...) (DISPID 3).
Private Sub m_lv_WebMessageReceived(ByVal message As String, ByVal source As String)SetWebMessageCallback instead
Private Sub m_lv_WebMessageReceived(ByVal message As String, ByVal source As String)
If InStr(message, """action"":""save""") > 0 Then
HandleSave message
ElseIf InStr(message, """action"":""refresh""") > 0 Then
RefreshData
End If
End SubGotchas
JsonGetValue and the other JSON helpers never talk to the browser. They are safe in any handler, including WebMessageReceived.See alsoSetWebMessageCallback
Fired when the document title changes (DISPID 4).
Private Sub m_lv_DocumentTitleChanged(ByVal title As String)Private Sub m_lv_DocumentTitleChanged(ByVal title As String)
Me.Caption = title
End SubFired when the favicon of the current document changes (DISPID 600).
Private Sub m_lv_FaviconChanged(ByVal faviconUri As String)Private Sub m_lv_FaviconChanged(ByVal faviconUri As String)
Debug.Print "Favicon: " & faviconUri
End SubFired when status bar text changes (DISPID 29).
Private Sub m_lv_StatusBarTextChanged(ByVal statusText As String)Private Sub m_lv_StatusBarTextChanged(ByVal statusText As String)
Me.lblStatus = statusText
End SubFired when a popup or new window is requested (DISPID 5).
Set Cancel = True to block it.
Private Sub m_lv_NewWindowRequested(ByVal url As String, ByRef Cancel As Boolean)Private Sub m_lv_NewWindowRequested(ByVal url As String, ByRef Cancel As Boolean)
Cancel = True ' Block popups
m_lv.Navigate url ' Navigate in same window instead
End SubFired when window.close() is called from JavaScript (DISPID 27).
Private Sub m_lv_WindowCloseRequested()Private Sub m_lv_WindowCloseRequested()
DoCmd.Close acForm, Me.Name
End SubFired when a focus move is requested (e.g. Tab) (DISPID 604).
Params: reason (LV2_FOCUS_REASON), Handled.
Private Sub m_lv_MoveFocusRequested(ByVal reason As Long, ByRef Handled As Boolean)Private Sub m_lv_MoveFocusRequested(ByVal reason As Long, ByRef Handled As Boolean)
Debug.Print "Focus move requested, reason: " & reason
End SubFired when a JS alert/confirm/prompt dialog opens (DISPID 7).
Notification only — no Cancel/handled output parameter.
Private Sub m_lv_ScriptDialogOpening(ByVal message As String, ByVal dialogType As Long)Private Sub m_lv_ScriptDialogOpening(ByVal message As String, ByVal dialogType As Long)
Debug.Print "Dialog type " & dialogType & ": " & message
End SubFired when the Save As dialog is about to show (DISPID 607).
Private Sub m_lv_SaveAsUIShowing(ByRef Cancel As Boolean)Private Sub m_lv_SaveAsUIShowing(ByRef Cancel As Boolean)
Cancel = True ' Block Save As
End SubFired when a context menu is requested (right-click) (DISPID 601).
args (Object) exposes: LocationX, LocationY, PageUri, LinkUri, Handled.
Private Sub m_lv_ContextMenuRequested(ByVal args As Object)Private Sub m_lv_ContextMenuRequested(ByVal args As Object)
Debug.Print "Context menu requested"
End SubFired after a print-to-PDF operation completes (DISPID 8).
Private Sub m_lv_PrintToPdfCompleted(ByVal filePath As String, ByVal success As Boolean)Private Sub m_lv_PrintToPdfCompleted(ByVal filePath As String, ByVal success As Boolean)
If success Then Application.FollowHyperlink filePath
End SubGotchas
Fired after a screenshot capture operation completes (DISPID 9).
Private Sub m_lv_CaptureScreenshotCompleted(ByVal filePath As String, ByVal success As Boolean)Private Sub m_lv_CaptureScreenshotCompleted(ByVal filePath As String, ByVal success As Boolean)
If success Then Debug.Print "Screenshot saved: " & filePath
End SubGotchas
Raised when a CDP event you subscribed to with AddDevToolsProtocolEventHandler occurs (DISPID 10). Some domains must be enabled first, e.g. CallDevToolsProtocolMethod "Network.enable", "{}". eventName is the CDP event name and eventDataJson the payload as JSON.
The event is raised a moment after WebView2's callback has returned, in arrival order, so synchronous calls (CallDevToolsProtocolMethod, ExecuteScript) work inside the handler. An event still waiting when you remove its subscription is not raised. Registered mode only: the Reg-Free pool accepts the subscription but raises no events.
Private Sub m_lv_DevToolsProtocolEventReceived(ByVal eventName As String, ByVal eventDataJson As String)Private Sub m_lv_DevToolsProtocolEventReceived(ByVal eventName As String, ByVal eventDataJson As String)
Debug.Print eventName & ": " & eventDataJson
End SubFired when a download is about to begin (DISPID 11).
Set resultFilePath to redirect the save location, or set Cancel = True to block it.
Private Sub m_lv_DownloadStarting(ByVal url As String, ByVal suggestedFileName As String, ByVal totalBytes As Long, ByVal mimeType As String, ByRef Cancel As Boolean, ByRef resultFilePath As String)Private Sub m_lv_DownloadStarting(ByVal url As String, ByVal suggestedFileName As String, _
ByVal totalBytes As Long, ByVal mimeType As String, _
ByRef Cancel As Boolean, ByRef resultFilePath As String)
resultFilePath = Environ("USERPROFILE") & "\Desktop\" & suggestedFileName
End SubSee alsoDownloadStartingHex
Fired when EventsUseHexadecimal = True: download starting with totalBytes as a BSTR hex string (DISPID 62).
Private Sub m_lv_DownloadStartingHex(ByVal url As String, ByVal suggestedFileName As String, ByVal totalBytesHex As String, ByVal mimeType As String, ByRef Cancel As Boolean, ByRef resultFilePath As String)Private Sub m_lv_DownloadStartingHex(ByVal url As String, ByVal suggestedFileName As String, _
ByVal totalBytesHex As String, ByVal mimeType As String, _
ByRef Cancel As Boolean, ByRef resultFilePath As String)
Debug.Print "Size (hex): " & totalBytesHex
End SubGotchas
EventsUseHexadecimal is True; carries the same payload as DownloadStarting with totalBytes as a hex BSTR instead of a Long. See the Hex variants note above.See alsoDownloadStarting
Fired during download progress (DISPID 12).
Private Sub m_lv_DownloadProgressChanged(ByVal downloadId As Long, ByVal bytesReceived As Long, ByVal totalBytes As Long, ByVal state As Long)Private Sub m_lv_DownloadProgressChanged(ByVal downloadId As Long, ByVal bytesReceived As Long, _
ByVal totalBytes As Long, ByVal state As Long)
If totalBytes > 0 Then
Debug.Print "Progress: " & Int(bytesReceived / totalBytes * 100) & "%"
End If
End SubSee alsoDownloadProgressChangedHex
Fired when EventsUseHexadecimal = True: download progress with downloadId/bytes/total as BSTR hex (DISPID 63).
Private Sub m_lv_DownloadProgressChangedHex(ByVal downloadIdHex As String, ByVal bytesReceivedHex As String, ByVal totalBytesHex As String, ByVal state As Long)Private Sub m_lv_DownloadProgressChangedHex(ByVal downloadIdHex As String, ByVal bytesReceivedHex As String, _
ByVal totalBytesHex As String, ByVal state As Long)
Debug.Print "Download " & downloadIdHex & " state: " & state
End SubGotchas
EventsUseHexadecimal is True; carries the same payload as DownloadProgressChanged with downloadId/bytes/total as hex BSTRs instead of Longs. See the Hex variants note above.See alsoDownloadProgressChanged
Fired when a download completes or fails (DISPID 13).
Private Sub m_lv_DownloadCompleted(ByVal downloadId As Long, ByVal filePath As String, ByVal success As Boolean, ByVal interruptReason As Long)Private Sub m_lv_DownloadCompleted(ByVal downloadId As Long, ByVal filePath As String, _
ByVal success As Boolean, ByVal interruptReason As Long)
If success Then
MsgBox "Downloaded: " & filePath
Else
Debug.Print "Download failed, reason: " & interruptReason
End If
End SubSee alsoDownloadCompletedHex
Fired when EventsUseHexadecimal = True: download completed with downloadId as a BSTR hex string (DISPID 64).
Private Sub m_lv_DownloadCompletedHex(ByVal downloadIdHex As String, ByVal filePath As String, ByVal success As Boolean, ByVal interruptReason As Long)Private Sub m_lv_DownloadCompletedHex(ByVal downloadIdHex As String, ByVal filePath As String, _
ByVal success As Boolean, ByVal interruptReason As Long)
Debug.Print "Download " & downloadIdHex & " done: " & success
End SubGotchas
EventsUseHexadecimal is True; carries the same payload as DownloadCompleted with downloadId as a hex BSTR instead of a Long. See the Hex variants note above.See alsoDownloadCompleted
Fired when the default download dialog open state changes (DISPID 606).
Private Sub m_lv_IsDefaultDownloadDialogOpenChanged()Private Sub m_lv_IsDefaultDownloadDialogOpenChanged()
Debug.Print "Download dialog state changed"
End SubFired when the server requests HTTP Basic Authentication (DISPID 14).
Private Sub m_lv_BasicAuthenticationRequested(ByVal uri As String, ByVal challenge As String, ByRef username As String, ByRef password As String, ByRef handled As Boolean)Private Sub m_lv_BasicAuthenticationRequested(ByVal uri As String, ByVal challenge As String, _
ByRef username As String, ByRef password As String, ByRef handled As Boolean)
username = InputBox("Username for " & uri)
password = InputBox("Password")
handled = True
End SubFired when the server requests a client certificate (DISPID 15).
Private Sub m_lv_ClientCertificateRequested(ByVal uri As String, ByRef handled As Boolean)Private Sub m_lv_ClientCertificateRequested(ByVal uri As String, ByRef handled As Boolean)
Debug.Print "Client cert requested for: " & uri
handled = False ' Let browser handle
End SubFired when an SSL/TLS certificate error is detected (DISPID 16).
action: 0 = deny, 1 = allow.
Private Sub m_lv_ServerCertificateErrorDetected(ByVal uri As String, ByVal errorStatus As Long, ByVal certificateSubject As String, ByRef action As Long)Private Sub m_lv_ServerCertificateErrorDetected(ByVal uri As String, ByVal errorStatus As Long, _
ByVal certificateSubject As String, ByRef action As Long)
Debug.Print "Cert error at " & uri & ": " & certificateSubject
' action = 0 to deny, 1 to allow
End SubFired when a file-save security check starts (DISPID 609).
Private Sub m_lv_SaveFileSecurityCheckStarting(ByVal filePath As String, ByRef Cancel As Boolean)Private Sub m_lv_SaveFileSecurityCheckStarting(ByVal filePath As String, ByRef Cancel As Boolean)
Debug.Print "Security check for: " & filePath
End SubFired when screen capture is about to start (e.g. getDisplayMedia) (DISPID 608).
Private Sub m_lv_ScreenCaptureStarting(ByRef Cancel As Boolean)Private Sub m_lv_ScreenCaptureStarting(ByRef Cancel As Boolean)
Cancel = True ' Block screen capture
End SubFired when the browser process fails or crashes (DISPID 17).
reasonMessage and frameInfo are populated from the SDK when available.
Private Sub m_lv_ProcessFailed(ByVal processKind As Long, ByVal reason As Long, ByVal exitCode As Long, ByVal reasonMessage As String, ByVal frameInfo As String)Private Sub m_lv_ProcessFailed(ByVal processKind As Long, ByVal reason As Long, _
ByVal exitCode As Long, ByVal reasonMessage As String, ByVal frameInfo As String)
Debug.Print "Process failed: " & reasonMessage
End SubFired when WebView2 controller creation finishes (DISPID 34).
Private Sub m_lv_CreateWebViewCompleted(ByVal hResult As Long, ByVal success As Boolean)Private Sub m_lv_CreateWebViewCompleted(ByVal hResult As Long, ByVal success As Boolean)
If Not success Then
MsgBox "WebView2 creation failed: " & Hex(hResult)
End If
End SubFired when the browser process exits (from Environment) (DISPID 605).
Private Sub m_lv_BrowserProcessExited(ByVal exitKind As Long, ByVal processId As Long)Private Sub m_lv_BrowserProcessExited(ByVal exitKind As Long, ByVal processId As Long)
Debug.Print "Browser process " & processId & " exited, kind: " & exitKind
End SubFired when a filtered web resource is requested (DISPID 18).
Set Cancel = True to block it.
Private Sub m_lv_WebResourceRequested(ByVal uri As String, ByVal method As String, ByVal resourceContext As Long, ByRef Cancel As Boolean)Private Sub m_lv_WebResourceRequested(ByVal uri As String, ByVal method As String, _
ByVal resourceContext As Long, ByRef Cancel As Boolean)
If InStr(uri, "ads") > 0 Then Cancel = True
End SubFired when a web resource response is received (DISPID 19).
Private Sub m_lv_WebResourceResponseReceived(ByVal uri As String, ByVal statusCode As Long, ByVal reasonPhrase As String)Private Sub m_lv_WebResourceResponseReceived(ByVal uri As String, ByVal statusCode As Long, _
ByVal reasonPhrase As String)
Debug.Print statusCode & " " & reasonPhrase & " - " & uri
End SubFired when the find match count changes (DISPID 20).
Private Sub m_lv_FindMatchCountChanged(ByVal matchCount As Long)Private Sub m_lv_FindMatchCountChanged(ByVal matchCount As Long)
Me.lblMatches = matchCount & " matches"
End SubFired when the active match index changes (DISPID 21).
Private Sub m_lv_FindActiveMatchChanged(ByVal activeMatchIndex As Long)Private Sub m_lv_FindActiveMatchChanged(ByVal activeMatchIndex As Long)
Me.lblMatch = "Match " & activeMatchIndex & " of " & m_lv.FindMatchCount
End SubFired when a find session completes (DISPID 22).
Private Sub m_lv_FindCompleted(ByVal matchCount As Long)Private Sub m_lv_FindCompleted(ByVal matchCount As Long)
Debug.Print "Find completed: " & matchCount & " matches"
End SubFired when the zoom factor changes (DISPID 26).
Private Sub m_lv_ZoomFactorChanged(ByVal newZoomFactor As Double)Private Sub m_lv_ZoomFactorChanged(ByVal newZoomFactor As Double)
Me.lblZoom = Int(newZoomFactor * 100) & "%"
End SubFired when the fullscreen element state changes (video fullscreen, etc.) (DISPID 28).
Private Sub m_lv_ContainsFullScreenElementChanged(ByVal containsFullScreenElement As Boolean)Private Sub m_lv_ContainsFullScreenElementChanged(ByVal containsFullScreenElement As Boolean)
If containsFullScreenElement Then
' Enter fullscreen mode
End If
End SubFired when the mute state of the document changes (DISPID 31).
Private Sub m_lv_IsMutedChanged(ByVal isMuted As Boolean)Private Sub m_lv_IsMutedChanged(ByVal isMuted As Boolean)
Debug.Print "Muted: " & isMuted
End SubFired when audio playback status changes (DISPID 32).
Private Sub m_lv_IsDocumentPlayingAudioChanged(ByVal isPlayingAudio As Boolean)Private Sub m_lv_IsDocumentPlayingAudioChanged(ByVal isPlayingAudio As Boolean)
Debug.Print "Playing audio: " & isPlayingAudio
End SubFired when an accelerator key (keyboard shortcut) is pressed (DISPID 33).
Private Sub m_lv_AcceleratorKeyPressed(ByVal keyEventKind As Long, ByVal virtualKey As Long, ByVal keyEventLParam As Long, ByRef handled As Boolean)Private Sub m_lv_AcceleratorKeyPressed(ByVal keyEventKind As Long, ByVal virtualKey As Long, _
ByVal keyEventLParam As Long, ByRef handled As Boolean)
If virtualKey = vbKeyF5 Then
handled = True ' Suppress F5 refresh
End If
End SubFired when the control is clicked (DISPID 39).
Known issue in 1.0.1.0
Click, DblClick, KeyPress, MouseEnter and MouseLeave are raised only for input that reaches the control's own window. Over the web content the input goes to WebView2's window, so these events do not fire there. To react to clicks or keys in the page, handle them in JavaScript and send chrome.webview.postMessage(...) to WebMessageReceived.
Private Sub m_lv_Click()Private Sub m_lv_Click()
Debug.Print "Control clicked"
End SubFired when the control is double-clicked (DISPID 40). Known issue in 1.0.1.0: not raised for input over the web content — see Click.
Private Sub m_lv_DblClick()Private Sub m_lv_DblClick()
Debug.Print "Control double-clicked"
End SubFired when a key is pressed while the control has focus (DISPID 41). Known issue in 1.0.1.0: not raised for input over the web content — see Click.
Private Sub m_lv_KeyPress(ByVal keyCode As Long)Private Sub m_lv_KeyPress(ByVal keyCode As Long)
Debug.Print "Key pressed: " & keyCode & " (" & Chr(keyCode) & ")"
End SubFired when the mouse enters the control area (DISPID 42). Known issue in 1.0.1.0: not raised for input over the web content — see Click.
Private Sub m_lv_MouseEnter()Private Sub m_lv_MouseEnter()
Debug.Print "Mouse entered"
End SubFired when the mouse leaves the control area (DISPID 43). Known issue in 1.0.1.0: not raised for input over the web content — see Click.
Private Sub m_lv_MouseLeave()Private Sub m_lv_MouseLeave()
Debug.Print "Mouse left"
End SubFired when the web content receives focus (DISPID 44).
BrowserGotFocus / BrowserLostFocus (DISPID 44 / 45). They were renamed from GotFocus / LostFocus, which the VB6 runtime reserves for its own extender events.
Known issue in 1.0.1.0
LiteView2 does not raise these two events yet. A handler named …_GotFocus binds, at most, to the host's own focus event, not to LiteView2's.
Private Sub m_lv_BrowserGotFocus()Private Sub m_lv_BrowserGotFocus()
Debug.Print "Got focus"
End SubFired when the web content loses focus (DISPID 45). Known issue in 1.0.1.0: not raised yet — see BrowserGotFocus.
Private Sub m_lv_BrowserLostFocus()Private Sub m_lv_BrowserLostFocus()
Debug.Print "Lost focus"
End SubFired when a permission is requested (camera, microphone, geolocation, etc.) (DISPID 30).
state: 0 = Default, 1 = Allow, 2 = Deny.
Private Sub m_lv_PermissionRequested(ByVal uri As String, ByVal permissionKind As Long, ByVal isUserInitiated As Boolean, ByRef state As Long)Private Sub m_lv_PermissionRequested(ByVal uri As String, ByVal permissionKind As Long, _
ByVal isUserInitiated As Boolean, ByRef state As Long)
state = 1 ' Allow (0=Default, 1=Allow, 2=Deny)
End SubFired when a new iFrame is created (DISPID 35).
Private Sub m_lv_FrameCreated(ByVal frameName As String, ByVal frameId As String)Private Sub m_lv_FrameCreated(ByVal frameName As String, ByVal frameId As String)
Debug.Print "Frame created: " & frameName & " (" & frameId & ")"
End SubFired when an iFrame starts navigation (DISPID 36).
Private Sub m_lv_FrameNavigationStarting(ByVal frameId As String, ByVal uri As String, ByVal isRedirected As Boolean, ByRef Cancel As Boolean)Private Sub m_lv_FrameNavigationStarting(ByVal frameId As String, ByVal uri As String, _
ByVal isRedirected As Boolean, ByRef Cancel As Boolean)
Debug.Print "Frame " & frameId & " navigating to: " & uri
End SubFired when an iFrame completes navigation (DISPID 37).
Private Sub m_lv_FrameNavigationCompleted(ByVal frameId As String, ByVal uri As String, ByVal success As Boolean, ByVal webErrorStatus As Long)Private Sub m_lv_FrameNavigationCompleted(ByVal frameId As String, ByVal uri As String, _
ByVal success As Boolean, ByVal webErrorStatus As Long)
Debug.Print "Frame " & frameId & " loaded: " & success
End SubFired when an iFrame is destroyed (DISPID 38).
Private Sub m_lv_FrameDestroyed(ByVal frameId As String)Private Sub m_lv_FrameDestroyed(ByVal frameId As String)
Debug.Print "Frame destroyed: " & frameId
End SubFired when a CAPTCHA is detected on the page (DISPID 46).
Private Sub m_lv_CaptchaDetected(ByVal captchaType As String, ByVal siteKey As String, ByVal pageUrl As String)Private Sub m_lv_CaptchaDetected(ByVal captchaType As String, ByVal siteKey As String, _
ByVal pageUrl As String)
Debug.Print "CAPTCHA found: " & captchaType & " key=" & siteKey
End SubSee alsoCaptchaSolved
Fired when CAPTCHA solving completes (DISPID 47).
Private Sub m_lv_CaptchaSolved(ByVal token As String, ByVal success As Boolean)Private Sub m_lv_CaptchaSolved(ByVal token As String, ByVal success As Boolean)
If success Then
Debug.Print "CAPTCHA solved, token: " & Left(token, 20) & "..."
End If
End SubSee alsoCaptchaDetected
Fired when navigation to an external URI scheme is requested (e.g. mailto:) (DISPID 602).
Private Sub m_lv_LaunchingExternalUriScheme(ByVal uri As String, ByRef Cancel As Boolean)Private Sub m_lv_LaunchingExternalUriScheme(ByVal uri As String, ByRef Cancel As Boolean)
If Left(uri, 7) = "mailto:" Then
Debug.Print "Email link: " & uri
End If
End SubFired when a web notification is received from the document (DISPID 603).
Private Sub m_lv_NotificationReceived(ByVal notification As String)Private Sub m_lv_NotificationReceived(ByVal notification As String)
Debug.Print "Notification: " & notification
End SubThis category documents every enumerated type defined by LiteView2, as listed in the source ENUMS REFERENCE table. Each enum is a Long-valued type used as a property/method return value, a property/method input parameter, or an event parameter elsewhere in the API. Where an enum's use in a specific member is confirmed elsewhere in this document, that member's name, mode, and a short signature are cross-referenced below; the full documentation for that member (description, all parameters, cautions) lives in its own category (Properties Reference / Methods Reference / Events Reference), not here.
Canonical variables used in signatures below: m_lv (Registered, As LiteView2Lib.LiteView2Ctrl), pool (Reg-Free, As Object), idx/browserIndex (Reg-Free browser index, As Long).
Color scheme preference.
Confirmed consuming member: GetPreferredColorScheme — Get preferred color scheme for browser profile.
0 = Auto, 1 = Light, 2 = DarkDim result As LV2_COLOR_SCHEME
result = pool.GetPreferredColorScheme(idx)Gotchas
See alsoGetPreferredColorScheme
Print orientation.
0 = Portrait, 1 = LandscapeGotchas
Popup handling mode.
Confirmed consuming member: GetPopupMode — Get popup mode.
0 = Allow, 1 = Block, 2 = SameWindowDim result As LV2_POPUP_MODE
result = pool.GetPopupMode(idx)Gotchas
GetOpenPopupsInSameWindow (Reg-Free Only, returns Boolean), was found adjacent to GetPopupMode in the source but is a separate, non-enum-typed member and belongs to its own category entry, not this one.See alsoGetPopupMode, GetOpenPopupsInSameWindow
Current license state.
Confirmed consuming member: LicenseState (Property).
0 = Valid, 1 = Trial, 2 = Expired, 3 = Invalid' Registered
Dim value As LV2_LICENSE_STATE
value = m_lv.LicenseState' Reg-Free
Dim value As LV2_LICENSE_STATE
pool.GetLicenseState idx, valueCaution
This note is drawn from the LicenseState property entry: PRO, Enterprise, and Extended Evaluation (EVAL, within its expiry) all return Valid (value 0). Once an EVAL key passes its expiry timestamp, the state reverts to Expired (value 2). No separate enum value is added for EVAL — it is folded into Valid while the key is unexpired, and into Expired once it lapses. There is no LV2_LICENSE_STATE member dedicated to EVAL.
See alsoLicenseState
Reason code for a requested focus move.
Confirmed consuming member: MoveFocusRequested event (DISPID 604). Fires when focus move is requested (e.g. Tab). Params: reason (LV2_FOCUS_REASON), Handled.
0 = TabForward, 1 = TabBackward, 2 = ProgrammaticPrivate Sub m_lv_MoveFocusRequested(ByVal reason As Long, ByRef Handled As Boolean)
' reason holds an LV2_FOCUS_REASON value; the event's parameter is typed As Long, not As LV2_FOCUS_REASON, in the handler signature.
End SubGotchas
As Long, not As LV2_FOCUS_REASON, in the handler signature — reason merely holds an LV2_FOCUS_REASON value.WithEvents event sink.See alsoMoveFocusRequested
Tracking prevention level.
Confirmed consuming member: GetPreferredTrackingPreventionLevel — Get preferred tracking prevention level for browser profile.
0 = None, 1 = Basic, 2 = Balanced, 3 = StrictDim result As LV2_TRACKING_PREVENTION
result = pool.GetPreferredTrackingPreventionLevel(idx)Gotchas
See alsoGetPreferredTrackingPreventionLevel
Memory usage target level.
0 = Normal, 1 = LowGotchas
Scrollbar visual style.
0 = Default, 1 = FluentOverlayGotchas
Bounds/coordinate interpretation mode.
0 = RawPixels, 1 = RasterizationScaleGotchas
Download dialog corner position.
Confirmed consuming member: GetDefaultDownloadDialogCornerAlignment — Get default download dialog corner alignment.
0 = TopLeft, 1 = TopRight, 2 = BottomLeft, 3 = BottomRightDim result As LV2_DOWNLOAD_DIALOG_CORNER_ALIGNMENT
result = pool.GetDefaultDownloadDialogCornerAlignment(idx)Gotchas
See alsoGetDefaultDownloadDialogCornerAlignment
Bitmask of PDF viewer toolbar items to hide (values are combinable, e.g. 1 + 2 hides Save and Print together).
Confirmed consuming members: GetHiddenPdfToolbarItems / SetHiddenPdfToolbarItems.
0 = None, 1 = Save, 2 = Print, 4 = SaveAs, 8 = ZoomIn, 16 = ZoomOut' Get (Reg-Free)
Dim result As Long
result = pool.GetHiddenPdfToolbarItems(idx)
' Set (Reg-Free)
pool.SetHiddenPdfToolbarItems idx, valueGotchas
Long in their signatures (not as LV2_PDF_TOOLBAR_ITEMS), consistent with its use as a combinable bitmask rather than a single enum member.See alsoGetHiddenPdfToolbarItems, SetHiddenPdfToolbarItems
Bitmask of browsing-data categories to clear (values are combinable).
1 = FileSystems, 2 = IndexedDB, 4 = LocalStorage, 8 = WebSQL, 16 = CacheStorage, 32 = DOMStorage, 64 = Cookies, 128 = AllSite, 256 = DiskCache, 512 = DownloadHistory, 1024 = GeneralAutofill, 2048 = PasswordAutosave, 4096 = BrowsingHistory, 8192 = Settings, 16384 = AllProfileGotchas
Resource type of a web request.
Confirmed consuming member: WebResourceRequested event (DISPID 18). Fires when a filtered web resource is requested. Set Cancel=True to block.
0 = All, 1 = Document, 2 = Stylesheet, 3 = Image, 4 = Media, 5 = Font, 6 = Script, 7 = XHR, 8 = Fetch, 9 = TextTrack, 10 = EventSource, 11 = WebSocket, 12 = Manifest, 13 = OtherPrivate Sub m_lv_WebResourceRequested(ByVal uri As String, ByVal method As String, ByVal resourceContext As Long, ByRef Cancel As Boolean)
' resourceContext holds a LiteView2ResourceContext value; the event's parameter is typed As Long, not As LiteView2ResourceContext, in the handler signature.
End SubGotchas
As Long, not As LiteView2ResourceContext, in the handler signature — resourceContext merely holds a LiteView2ResourceContext value.See alsoWebResourceRequested
Part III
The mistakes below are the ones that cost customers the most time: a synchronous call refused inside an event, a lingering msedgewebview2.exe process that won't let Access exit, a license watermark that won't clear, or a button that silently eats its first click. Each entry states what the warning is, the symptom you'll see if you ignore it, and the exact fix. Nothing here is softened or generalized — if the source documents an exact method name, timer, or property, that exact name is reproduced below.
&H80040203)The warning. Methods that wait for the browser's answer cannot complete while WebView2 is still inside one of its own callbacks. Examples: ExecuteScript, ExecuteScriptWithResult, GetCookies, CallDevToolsProtocolMethod, NavigateSync, CallScriptSync, GetScriptProperty / SetScriptProperty, WaitForElement, and AddScriptToExecuteOnDocumentCreated / EnableLvFacade once the browser exists. LiteView2 refuses them in two places:
WebViewReady, NavigationStarting and every other event except the notification events listed below;chrome.webview.hostObjects (sync or async) or RegisterVbaCallback.Symptom. Error &H80040203 (-2147220989) at once. The description names the event or host object and says what to use instead. Nothing was started; WaitForElement just returns False. (LiteView2 1.0.0.0 instead waited out the timeout, 30 s per call, and some calls crashed the host afterwards.)
Where they work. In ordinary code (buttons, timers) and in the notification events NavigationCompleted, NavigationCompletedHex, SourceChanged, ContentLoading, ContentLoadingHex, DOMContentLoaded, DocumentTitleChanged, HistoryChanged, WebMessageReceived and DevToolsProtocolEventReceived. These are raised after WebView2's callback has returned.
The fix inside an event or host call:
ExecuteScriptAsync, PostWebMessageAsJson, Navigate, CallScript, the JSON helpers, DAO.chrome.webview.postMessage and read it in WebMessageReceived (Chapter 4).Application.OnTime in Excel or Word, a Timer control in VB6.Host objects remain the simplest way for the page to call VBA with typed arguments; inside the host method, use only calls that do not wait.
JSON helpers. JsonGetValue, BuildJson and the other JSON helpers never talk to the browser. They are safe everywhere, including WebMessageReceived.
Reg-Free. A refused pool call raises &H80004005; pool.GetLastErrorCode(idx) gives &H80040203.
Note
See Part I, Chapter 6 for the short version of this warning in context.
DestroyBrowserSync leaves msedgewebview2.exe runningThe warning: In Reg-Free Mode, Form_Unload must tear the browser instance down explicitly: pool.ClearReadyCallback idx, then pool.DestroyBrowserSync idx, 2000, then Set pool = Nothing. Registered Mode does not have this problem — the control's own destructor (triggered by Set m_lv = Nothing) handles the equivalent teardown when Access destroys the ActiveX control, and DestroyBrowserSync is a pool-only API that doesn't apply there.
Symptom if ignored: The WebView2 host process keeps running after the form closes and keeps the host application alive with it. Task Manager shows a lingering msedgewebview2.exe — and, in Access, a lingering MSACCESS.EXE — until the OS finally kills them.
The fix — full Reg-Free teardown, in order:
Private Sub Form_Unload(Cancel As Integer)
On Error Resume Next
If Not pool Is Nothing And idx <> 0 Then
pool.Stop idx ' halt any in-flight navigation
pool.ClearReadyCallback idx ' detach our callback so it can't fire after teardown
pool.DestroyBrowserSync idx, 2000 ' synchronous browser teardown (max 2 s wait)
idx = 0
End If
Set pool = Nothing ' release the pool reference
End Subpool.Stop idx halts any navigation still in flight before teardown starts.pool.ClearReadyCallback idx removes the OnBrowserReady callback so it cannot fire against a form that's already unloading — without it, an async ready callback can call back into VBA after the form's references are gone (Type mismatch / Object required).pool.DestroyBrowserSync idx, 2000 is the critical call. It tears down the browser instance synchronously, waiting up to 2 seconds for in-process cleanup. This is the line that prevents the lingering process described above.idx = 0 is a sentinel so a stray callback hitting this form sees an invalid handle and exits cleanly.On Error Resume Next: by the time Form_Unload fires the form may already be unloading because of an earlier error, and a second error popping a MsgBox during shutdown produces a half-stuck form. Any genuine bug is caught earlier, by the On Error GoTo ErrH blocks in Form_Load / OnBrowserReady.The same in-flight-navigation risk (item 1 below) applies to both modes: a navigation still running when the form closes can fire NavigationCompleted against a half-destroyed control, and an async ready callback that hasn't fired yet can trigger after VBA references are gone. Stop and ClearReadyCallback / Set m_lv = Nothing are what guard against those two, respectively, in each mode.
Note
See Part I, Chapter 17 for the short version of this warning in context.
ActivateLicense must run before the first Navigate — never inside WebViewReady / OnBrowserReadyThe warning: Two hard rules on timing:
ActivateLicense after the first Navigate.ActivateLicense inside WebViewReady / OnBrowserReady.Symptom if ignored:
Navigate: the trial watermark may already have rendered on the page, and it will not retroactively clear.WebViewReady / OnBrowserReady: by the time those events fire, the navigation decision has already used the (unlicensed) license state — activating at that point is too late to change what's about to load.The fix: Call ActivateLicense from Form_Load / UserForm_Initialize, on every launch — activation is session-only and does not persist across restarts. Keep the key in one shared module rather than pasting it into per-form code: modLV2License.bas (Registered) exports EnsureLicensed(ctrl), which each form calls once in Form_Load; modLV2Pool.bas (Reg-Free) activates the license automatically, inside GetPool(), on the first call in the process. Both helpers are idempotent — EnsureLicensed checks a flag and exits early on later calls, GetPool only initializes once — so calling them more than once causes no harm. Rotating the key means editing one file, not every form.
ActivateLicense is process-wide, not per-form and not per-browser: one successful call activates every LiteView2 browser created afterward in the same Access/Office session, and the Reg-Free pool shares the activated license across every CreateInControl call automatically — you do not register the license per browser-index.
Registered Mode:
m_lv.ActivateLicense "My Company", "V1|...|..."
Debug.Print "Status: " & m_lv.LicenseStatus
Debug.Print "Type: " & m_lv.LicenseType
Debug.Print "State: " & m_lv.LicenseState
Debug.Print "Days remaining: " & m_lv.TrialDaysRemaining
Debug.Print "Expiry: " & m_lv.LicenseExpiry
Debug.Print "Version: " & m_lv.LicenseVersion
Debug.Print "Info JSON: " & m_lv.GetLicenseInfo()Reg-Free:
pool.ActivateLicense "My Company", "V1|...|..."
Debug.Print "Status: " & pool.LicenseStatus
Debug.Print "Type: " & pool.LicenseType
Debug.Print "State: " & pool.LicenseState
Debug.Print "Trial days: " & pool.TrialDaysRemaining
Debug.Print "Info JSON: " & pool.GetLicenseInfo()The key argument is an opaque V1 activation string. The runtime validates it and enforces any expiry timestamp it carries. Activation is in-memory only for the current process — no registry, no file system, no network call. If no key is supplied in code, the 30-day Trial Period applies.
Note
elsewhere in this cookbook, the Reg-Free LicenseStatus / LicenseType / LicenseState / TrialDaysRemaining properties are also documented as index-scoped Get-methods — pool.GetLicenseStatus browserIndex, value and similarly for the others — in the auto-synced IDL reference, which conflicts with the index-free pool.LicenseStatus form shown above. The index-free form is used here because it matches the process-wide activation semantics documented in this section (one activation call covers every browser in the pool). See Part II → Licensing for the full picture on both forms.
Note
See Part I, Chapter 21 for the short version of this warning in context.
Click with MouseDownThe warning: Any external button whose action needs to fire on the very first physical mouse click needs a MouseDown handler alongside its Click handler, whenever focus may currently be inside the LiteView2 browser surface.
Symptom if ignored: The button's Click event does not fire on the first click. Access uses that first click on an external control to move focus off the browser and onto the control; the click that does that is spent moving focus rather than triggering Click. The action only fires starting from the second click, which looks like an intermittent, hard-to-reproduce dead button to anyone testing it once.
The fix: Keep Click for keyboard-driven activation (Enter/Space while the button has focus), and add a MouseDown handler with the same action so the first physical mouse click also fires it, regardless of where focus was a moment before.
Note
See Part I, Chapter 2 for the short version of this warning in context.
SetLocalContentRoot before Navigate; AddHostObjectToScript before the Navigate that uses itThe warning: SetLocalContentRoot, host-object registration, and the first Navigate all belong in the ready event — LiteView2Ctrl1_WebViewReady (Registered) or OnBrowserReady (Reg-Free) — not in Form_Load. Within the ready handler, the order is fixed: SetLocalContentRoot → AreHostObjectsAllowed → AddHostObjectToScript → Navigate. Host objects specifically must be added before the navigation that uses them. Two calls belong in Form_Load, after EnsureLicensed: EnableLvFacade and AddScriptToExecuteOnDocumentCreated. Before the browser exists they are queued and applied before the first page. Inside WebViewReady they fail at once with &H80040203.
Symptom if ignored:
SetLocalContentRoot, AddHostObjectToScript, or Navigate from Form_Load (before WebViewReady/OnBrowserReady has fired): the calls silently no-op, race with WebView2's own initialization, or leave a stuck trial watermark — with no error raised to tell you why.Navigate before AddHostObjectToScript for an object the page needs: the page loads without that host object registered, so script that expects it (e.g. window.chrome.webview.hostObjects.vba) will not find it.The fix:
' ❌ WRONG — Form_Load runs BEFORE WebView2 is ready.
' These calls silently no-op, race with init, or cause a stuck watermark.
Private Sub Form_Load()
Set m_lv = Me.LiteView2Ctrl1.Object
m_lv.SetLocalContentRoot CurrentProject.Path ' ❌ wait for WebViewReady
m_lv.AddHostObjectToScript "vba", Me ' ❌ wait for WebViewReady
m_lv.Navigate "https://lv2.example/index.html" ' ❌ wait for WebViewReady
End Sub' ✅ CORRECT — Form_Load only acquires the typed reference + activates the license.
' Activation key lives in modLV2License.bas, not duplicated here.
Private Sub Form_Load()
Set m_lv = Me.LiteView2Ctrl1.Object
EnsureLicensed m_lv ' from modLV2License — once per process
End Sub
Private Sub LiteView2Ctrl1_WebViewReady()
m_lv.SetLocalContentRoot CurrentProject.Path
m_lv.AreHostObjectsAllowed = True
m_lv.AddHostObjectToScript "vba", Me
m_lv.Navigate "https://lv2.example/index.html"
End SubThe same anti-pattern applies to Reg-Free Mode: Set pool = GetPool() (which activates the license internally), CreateInControl, and SetReadyCallback belong in Form_Load; SetLocalContentRoot / SetAreHostObjectsAllowed / AddHostObjectToScript / Navigate all move into OnBrowserReady.
Following these three rules — activation key in a shared module, SetLocalContentRoot before Navigate, AddHostObjectToScript before Navigate — is, per this cookbook, what makes most startup problems disappear.
Note
See Part I, Chapter 5 for the short version of this warning in context.
Before chasing a specific symptom below, confirm the three basics that account for most reported problems: SetLocalContentRoot and AddHostObjectToScript are called before Navigate, all three live inside the ready handler (not Form_Load), and the activation key lives in one shared module (modLV2License for Registered, modLV2Pool for Reg-Free) rather than being pasted into a form. If those hold, use the table below to jump to the symptom you're seeing, then read the matching detail section for the code fix.
Almost every startup symptom in this section — the browser never appearing ready, Navigate doing nothing, host objects being undefined in JavaScript — comes back to one rule: WebViewReady (Registered) and OnBrowserReady (Reg-Free) fire asynchronously, after Form_Load has already returned. Form_Load should only acquire the control reference and activate the license. Everything that talks to the page — SetLocalContentRoot, AreHostObjectsAllowed, AddHostObjectToScript, Navigate — belongs inside the ready handler, in that order. Two calls belong in Form_Load, after EnsureLicensed: EnableLvFacade and AddScriptToExecuteOnDocumentCreated. Before the browser exists they are queued and applied before the first page. Inside WebViewReady they fail at once with &H80040203.
' ❌ WRONG — Form_Load runs BEFORE WebView2 is ready.
' These calls silently no-op, race with init, or cause a stuck watermark.
Private Sub Form_Load()
Set m_lv = Me.LiteView2Ctrl1.Object
m_lv.SetLocalContentRoot CurrentProject.Path ' ❌ wait for WebViewReady
m_lv.AddHostObjectToScript "vba", Me ' ❌ wait for WebViewReady
m_lv.Navigate "https://lv2.example/index.html" ' ❌ wait for WebViewReady
End Sub' ✅ CORRECT — Form_Load only acquires the typed reference + activates the license.
Private Sub Form_Load()
Set m_lv = Me.LiteView2Ctrl1.Object
EnsureLicensed m_lv ' from modLV2License — once per process
End Sub
Private Sub LiteView2Ctrl1_WebViewReady()
m_lv.SetLocalContentRoot CurrentProject.Path
m_lv.AreHostObjectsAllowed = True
m_lv.AddHostObjectToScript "vba", Me
m_lv.Navigate "https://lv2.example/index.html"
End SubReg-Free mode follows the same split: Set pool = GetPool() (which activates the license), CreateInControl, and SetReadyCallback go in Form_Load; SetLocalContentRoot, SetAreHostObjectsAllowed, AddHostObjectToScript, and Navigate move into OnBrowserReady. None of these calls raise an error when placed too early — they silently no-op or fail — which is why the symptom always looks like "nothing happened" rather than a clear exception.
| Symptom | Most likely cause | Fix |
|---|---|---|
Browser never becomes ready (WebViewReady / OnBrowserReady never fires) | Setup code was placed in Form_Load instead of the ready handler, so it silently no-oped — this is usually mistaken for the event itself not firing. In Reg-Free mode, SetReadyCallback may not have actually been registered. | Move SetLocalContentRoot / AddHostObjectToScript / Navigate into the ready handler (see above). Confirm SetReadyCallback runs in Form_Load alongside CreateInControl. |
Navigate does nothing | Navigate executed before SetLocalContentRoot, or called from Form_Load before the control is ready. | Call SetLocalContentRoot BEFORE the first Navigate, both inside WebViewReady / OnBrowserReady. |
| Local files don't load — "Can't reach this page" or 404 | SetLocalContentRoot not called, called with the wrong folder, or the URL points outside the mapped folder. | Map CurrentProject.Path; verify the target file exists in that folder; use forward slashes in the URL. See "Wrong local content root" below. |
| Local content loads from the wrong folder, or an old file keeps appearing | The disk folder passed to SetLocalContentRoot doesn't directly contain the HTML you're navigating to (wrong root level or subfolder confusion). | Pick the folder that directly contains the target HTML; don't over-shoot to a parent, and don't double up a subfolder in both the mapped root and the URL. See table below. |
window.chrome.webview.hostObjects.sync.vba is undefined in JS | AddHostObjectToScript was called AFTER Navigate. Host objects only bind on the NEXT navigation. | Order inside the ready handler: SetLocalContentRoot → AreHostObjectsAllowed → AddHostObjectToScript → Navigate. |
| JavaScript cannot call VBA (host object exists, method call fails or does nothing) | AreHostObjectsAllowed wasn't set True, or the method the page is calling isn't a Public Sub in the form's code module. | Set AreHostObjectsAllowed = True before AddHostObjectToScript; make every method the page calls via hostObjects.sync.vba.<name>() a Public Sub. |
A call fails at once with error &H80040203 (-2147220989) | It was made inside an inline event (WebViewReady, NavigationStarting, …) or inside a VBA method the page called through a host object; the call waits for the browser, which cannot answer there. Nothing was started. | Use ExecuteScriptAsync / PostWebMessageAsJson / Navigate there, or move the call to NavigationCompleted, WebMessageReceived, a button or a timer. Reg-Free: the pool reports &H80004005; GetLastErrorCode(idx) = &H80040203. |
A call times out with &H800705B4 | The page did not answer within SynchronousTimeOut (ExecuteScript) or the fixed limit of that call. | Check the page's JavaScript; raise SynchronousTimeOut for long scripts. ExecuteScriptWithResult reports this as succeeded = False instead of an error. |
| First click on a form button does nothing; second click works | Access uses the first click on an external button to move focus to that button; Click doesn't fire on that click. | Mirror Click into a MouseDown handler for any button reachable while focus is inside the browser. Keep Click for keyboard activation. |
Set m_lv = Me.LiteView2Ctrl1 raises "Type mismatch" | Missing .Object — Access's form-control wrapper isn't QueryInterface-compatible with the typed coclass. | Set m_lv = Me.LiteView2Ctrl1.Object. |
IntelliSense doesn't show LiteView2 methods after m_lv. | "LiteView2 1.0 Type Library" not checked in Tools → References, OR m_lv declared As Object (late-bound). | Check the type library reference; declare Private m_lv As LiteView2Lib.LiteView2Ctrl. |
| Reg-Free ready/message callback does not fire | It's asynchronous and hasn't fired yet, ClearReadyCallback detached it early, or a stray callback is silently no-oping against the idx = 0 teardown sentinel. | Confirm SetReadyCallback ran in Form_Load; confirm nothing calls ClearReadyCallback before the browser had a chance to become ready. |
Access/Excel doesn't exit; msedgewebview2.exe / MSACCESS.EXE linger in Task Manager | Reg-Free mode is missing DestroyBrowserSync in Form_Unload. The WebView2 host process keeps the Office process alive. | Add pool.ClearReadyCallback idx, pool.DestroyBrowserSync idx, 2000, then Set pool = Nothing to Form_Unload. |
| Watermark renders despite a valid key | Activation didn't happen before the first navigation — EnsureLicensed not called from Form_Load, LV2_KEY empty, or ActivateLicense called too late (after Navigate, or from inside the ready handler). | Confirm the shared license module runs EnsureLicensed / activates inside GetPool() before any navigation. Activation must happen before the first Navigate, never inside WebViewReady / OnBrowserReady. |
| Stale cookies / session data — old content or a stale login persists across runs | SetLocalContentRoot always maps to the same fixed virtual host (https://lv2.example/) regardless of which disk folder is mapped, so cookies/storage set under that origin persist across app restarts and across different mapped folders. | Call ClearCookies for cookies and/or ClearBrowsingData (e.g. LV2_DATA_ALL_PROFILE) for cache and other storage — see the "Stale cookies / session data" detail below and Part II → Cookies and Browsing Data. |
| Downloads don't happen, or a download dialog seems stuck | AllowDownloads is False, or HiddenDownloadUI is hiding an in-progress download dialog the user expects to see. | Check AllowDownloads is True if downloads should be allowed; if HiddenDownloadUI is on, check the target folder before assuming a hidden download failed. |
PrintToPdf doesn't produce a file, or you're not sure it's supported | Runtime doesn't support it (unchecked HasPrintToPdf), or — Reg-Free only — the call is fire-and-forget and the file simply hasn't finished writing yet. | Gate the call behind HasPrintToPdf. In Reg-Free mode, don't treat an immediately-missing PDF as a failure — the call doesn't report completion. |
| Reg-Free browsers won't create / suspected runtime problem | The WebView2 Runtime is missing or an unexpected version on the machine. | Check IsWebView2RuntimeInstalled, GetWebView2RuntimeVersion, GetWebView2RuntimePath (Reg-Free / pool only). |
Browsers/profiles not cleaning up; repeated stale msedgewebview2.exe processes | DestroyBrowserSync wasn't called for every browser index, or wasn't given enough time to finish. | Call pool.DestroyBrowserSync idx, 2000 for every index in teardown; audit/force-clear stragglers with the pool's parked-browser APIs (see below). |
This is almost always a mapping problem, not a missing-file problem in the way it first appears. SetLocalContentRoot folder maps a folder on disk to the fixed virtual host https://lv2.example/, one-to-one — the folder you pass becomes the URL root. See "Wrong local content root" immediately below for the mapping table and the specific mistakes that produce this symptom. If the mapping is correct, the last thing to check is that the target file physically exists in the mapped folder — WebView2 shows its own "page can't be reached" error in that case, which looks like a LiteView2 problem but isn't.
| Disk path you pass | URL root | Example file on disk | URL to navigate to |
|---|---|---|---|
C:\Apps\MyDB\ | https://lv2.example/ | C:\Apps\MyDB\index.html | https://lv2.example/index.html |
C:\Apps\MyDB\ | https://lv2.example/ | C:\Apps\MyDB\pages\admin.html | https://lv2.example/pages/admin.html |
CurrentProject.Path (e.g. C:\Apps\MyDB) | https://lv2.example/ | the form's folder | https://lv2.example/<any-file-in-that-folder> |
Common mistakes:
C:\Apps\ but your HTML lives in C:\Apps\MyDB\index.html. The URL must then be https://lv2.example/MyDB/index.html, not /index.html. Pick the folder that directly contains your HTML; don't over-shoot to a parent.CurrentProject.Path & "\html" and then navigate to https://lv2.example/html/index.html. The \html part already became the new root — the correct URL is https://lv2.example/index.html, with no html prefix.https://lv2.example/index.html works; https://lv2.example\index.html does not. Use forward slashes in URLs only.See Part II → Local Content & Navigation for the full reference.
window.chrome.webview.hostObjects.sync.vba is undefinedCheck it defensively from the page before calling into it, and remember the object only exists after a navigation that happened after AddHostObjectToScript ran:
if (window.chrome && window.chrome.webview &&
window.chrome.webview.hostObjects.sync.vba) {
// safe to call window.chrome.webview.hostObjects.sync.vba.<Method>(...)
} else {
console.error("vba host object not bound yet");
}If this check fails on a page you expect to have the host object, the almost-certain cause is AddHostObjectToScript running after Navigate for that page load — see the ready-handler order above.
Beyond the ordering rule above, two more documented requirements have to hold:
AreHostObjectsAllowed = True must be set before AddHostObjectToScript — it's the gate in the documented ready-handler order (SetLocalContentRoot → AreHostObjectsAllowed → AddHostObjectToScript → Navigate).hostObjects.sync.vba.<name>() must exist as a Public Sub in the form's code module — this is called out explicitly as a checklist item.Private Sub LiteView2Ctrl1_WebViewReady()
m_lv.SetLocalContentRoot CurrentProject.Path
m_lv.AreHostObjectsAllowed = True
m_lv.AddHostObjectToScript "vba", Me
m_lv.Navigate "https://lv2.example/index.html"
End Sub
Public Sub SaveCustomer(ByVal custName As String)
' Must be Public — the page calls this by name.
MsgBox "Saving " & custName
End Sub<input id="txtName-4" type="text">
<button onclick="save()">Save</button>
<script>
function save() {
window.chrome.webview.hostObjects.sync.vba.SaveCustomer(
document.getElementById('txtName').value);
}
</script>If the call appears to do nothing, recheck the ready-handler order above — a page that hasn't finished navigating yet has nothing to execute against.
If a call raises &H80040203, it was made inside an inline event or a host-object call; see Critical Safety Warnings #1. WebMessageReceived is not such a place: you can parse and act on the message inline:
' Fine: WebMessageReceived runs after WebView2's callback has returned.
Private Sub LiteView2Ctrl1_WebMessageReceived(ByVal message As String, ByVal source As String)
Dim orderId As Variant
orderId = m_lv.JsonGetValue(message, "orderId")
' ... act on orderId; ExecuteScript and the other synchronous calls work here too ...
End SubCaution
a host object is the simplest way for the page to call a VBA method directly. Inside that method, use only calls that do not wait for the browser (ExecuteScriptAsync, PostWebMessageAsJson, the JSON helpers, DAO). A synchronous ExecuteScript there fails with &H80040203.
Access uses the first click on any form button to move focus to that button when focus is currently inside an embedded ActiveX control (LiteView2, the legacy WebBrowser control, Microsoft DatePicker — all the same). On that first click, the button's Click event does not fire; the user has to click again. This is long-standing Access behavior, not a LiteView2 bug.
Private Sub cmdSave_Click()
DoSave ' the actual work
End Sub
Private Sub cmdSave_MouseDown(Button As Integer, Shift As Integer, _
X As Single, Y As Single)
If Button = 1 Then DoSave ' left mouse button only
End Sub
Private Sub DoSave()
If m_lv Is Nothing Then Exit Sub
m_lv.ExecuteScriptAsync "saveContent()"
End SubMouseDown fires the instant the mouse button goes down, regardless of focus state, so the action runs on the first physical click. Keep the Click handler so keyboard activation (Enter / Spacebar) still works. Apply this to any button clicked while focus may be inside the LiteView2 control; buttons triggered only from VBA code (e.g. cmdSave.Value = True) don't need it.
Set m_lv = Me.LiteView2Ctrl1 raises "Type mismatch" because Access's form-control wrapper isn't QueryInterface-compatible with the typed coclass. The fix is one word:
Set m_lv = Me.LiteView2Ctrl1.Object.Object peels Access's wrapper to expose the underlying ActiveX control.
Two independent causes, check both:
m_lv is declared As Object (late-bound) instead of the typed coclass.Private m_lv As LiteView2Lib.LiteView2Ctrl ' not: Private m_lv As ObjectSetReadyCallback is registered in Form_Load, alongside CreateInControl; OnBrowserReady then fires later, asynchronously, once the browser is actually ready — checking for its effects immediately after Form_Load returns will look exactly like it never fired.
Two other documented mechanisms can also make it look silent:
ClearReadyCallback detaches the callback so it can't fire after a form unloads. If something calls it too early — before the browser had a chance to become ready — the callback is legitimately gone.idx = 0 as a sentinel "so a stray callback hitting this form sees an invalid handle and exits cleanly." If your own callback code checks idx before acting, a callback arriving after an earlier, unexpected teardown will silently no-op.See "Access/Excel does not exit" below for the full Reg-Free teardown sequence these two calls belong to.
Reg-Free Mode requires explicit teardown. Without it, the WebView2 host process (msedgewebview2.exe) keeps the Office process alive after the form closes — Task Manager shows lingering msedgewebview2.exe and MSACCESS.EXE / EXCEL.EXE until the OS eventually kills them.
Private Sub Form_Unload(Cancel As Integer)
On Error Resume Next
If Not pool Is Nothing And idx <> 0 Then
pool.Stop idx ' halt any in-flight navigation
pool.ClearReadyCallback idx ' detach our callback so it can't fire after teardown
pool.DestroyBrowserSync idx, 2000 ' synchronous browser teardown (max 2 s wait)
idx = 0
End If
Set pool = Nothing ' release the pool reference
End Subpool.DestroyBrowserSync idx, 2000 is the critical call — it tears down the browser instance synchronously, waiting up to 2 seconds for in-process cleanup. Skipping it is the single most common cause of this symptom. On Error Resume Next is deliberate here too: by the time Form_Unload runs, the form may already be closing because of an earlier error, and a second error popping a MsgBox during shutdown produces a form the user can't dismiss cleanly.
Registered Mode does not need DestroyBrowserSync — it's a pool-only API. Set m_lv = Nothing in Form_Close (after m_lv.Stop) is sufficient; the control's own destructor handles equivalent cleanup when Access tears the form down.
Activation is process-wide, not per-form and not per-browser: one successful ActivateLicense call (via EnsureLicensed or inside GetPool()) activates every LiteView2 browser created afterwards in the same Access/Office session, and the Reg-Free pool shares it across every CreateInControl call automatically. It does not persist across process restarts — each fresh Access/Office session must activate once.
Given that, a watermark despite a key you know is valid comes down to one of:
EnsureLicensed isn't actually being called from Form_Load (Registered), or the modLV2Pool bootstrap's ActivateLicense call inside GetPool() was never wired up (Reg-Free).LV2_KEY is empty or wrong in the shared module.ActivateLicense was called too late — after the first Navigate (the watermark may already have rendered), or from inside WebViewReady / OnBrowserReady (by the time those fire, the navigation decision has already used the license state).The activation key must live in exactly one shared module (modLV2License.bas for Registered, modLV2Pool.bas for Reg-Free) — never duplicated into per-form code. The shared-module call is idempotent (EnsureLicensed checks a flag and exits early on repeat calls; GetPool only initializes once), so calling it more than once by accident is harmless — the danger is calling it too late or not at all, not calling it twice.
To confirm activation actually took effect in Reg-Free mode, you can read back the status:
Debug.Print "License status: " & pool.LicenseStatusNote
The source documents license-status reads two different ways in Reg-Free mode: an index-free pool.LicenseStatus property (used above, consistent with activation being process-wide rather than per-browser) in one section, and a pool.GetLicenseStatus browserIndex, value Get-method call in another. This chapter uses the index-free form as primary since it matches the documented process-wide semantics; see Part II → Licensing for the full picture.
SetLocalContentRoot always maps to the same fixed virtual host, https://lv2.example/, regardless of which disk folder you point it at (see the path-mapping table above). Because the browser sees the same origin every time, cookies, cache, and page-owned localStorage/sessionStorage set on an earlier run — or under a previous SetLocalContentRoot target — persist under that origin across app restarts. A page can look like it's "ignoring" new content in the mapped folder when what's actually happening is stale storage from an earlier session under that same origin.
LiteView2 documents dedicated APIs for exactly this — reach for these first:
ClearCookies clears all cookies; ClearProfileCookies clears cookies for the current Reg-Free profile.ClearBrowsingData, using the bitmask constants documented in Part II → Cookies and Browsing Data (e.g. LV2_DATA_DISK_CACHE, or LV2_DATA_ALL_PROFILE to clear everything at once).ClearBrowsingDataInTimeRange, for a "clear the last 24 hours" style reset without wiping everything. Known issue in 1.0.1.0: ClearBrowsingDataInTimeRange returns without an error but clears nothing. Use ClearBrowsingData (all time) or ClearCookies instead.' Registered — clear cookies and everything else for a full reset
m_lv.ClearCookies
m_lv.ClearBrowsingData 16384 ' LV2_DATA_ALL_PROFILECookies, cache/browsing data, and page-owned localStorage/sessionStorage are three distinct things (Chapter 12 covers the distinction in full) — but ClearBrowsingData's bitmask reaches all of them, so a full reset does not require leaving VBA. If you specifically need to clear storage from inside the page itself (for example, a page-side "reset" button), ExecuteScriptAsync "localStorage.clear(); sessionStorage.clear();" remains an option — but it does not touch cookies or the HTTP cache, so it is not a substitute for ClearCookies/ClearBrowsingData when the actual symptom is a stuck login or stale cookies.
Two properties documented for downloads explain most reports:
AllowDownloads is False. Downloads are blocked outright — nothing will happen when a page-initiated download is triggered.HiddenDownloadUI hides the in-progress download dialog. A user who expects to see a visible download progress window, and doesn't, will report the download as "stuck," when it's actually still running with its UI hidden.Check AllowDownloads first if downloads should be allowed at all; if HiddenDownloadUI is intentionally on, check the target folder for the completed file before treating a "stuck" download as a bug. See Part II → Downloads & Printing for exact signatures and defaults.
Gate PrintToPdf behind the capability check — not every WebView2 runtime supports it:
' Registered
If m_lv.HasPrintToPdf Then
m_lv.PrintToPdf "C:\output.pdf"
End If' Reg-Free
If pool.HasPrintToPdf(idx) Then
pool.PrintToPdf idx, "C:\output.pdf"
End IfCaution
In Reg-Free mode, PrintToPdf is fire-and-forget — the call returns immediately and doesn't report success, failure, or when the file has finished writing. A PDF that isn't sitting in the target folder the instant the call returns isn't necessarily a bug; check again a moment later before treating it as a failure.
The same capability-check pattern applies to HasCapturePreview for screenshot support:
' Registered
Debug.Print "Screenshot support: " & m_lv.HasCapturePreview' Reg-Free
Debug.Print "Screenshot support: " & pool.HasCapturePreview(idx)Reg-Free mode exposes runtime detection directly on the pool (Pool-Only — no Registered equivalent in this material):
If pool.IsWebView2RuntimeInstalled Then
Debug.Print "Version: " & pool.GetWebView2RuntimeVersion
Debug.Print "Path: " & pool.GetWebView2RuntimePath
End IfIf IsWebView2RuntimeInstalled returns False, the machine is missing the WebView2 Runtime that LiteView2 depends on for both modes — Reg-Free browsers won't create, and this is the first thing to check before assuming a code-level bug.
The one call that matters for cleanup is documented in full under "Access/Excel does not exit" above: pool.DestroyBrowserSync idx, 2000 must run for every browser index your form created, in teardown, before releasing the pool reference. Skipping it — or not giving it enough time — is why lingering msedgewebview2.exe processes show up in Task Manager.
For auditing and forcing cleanup of browsers that were parked rather than fully destroyed, the pool API also documents GetParkedBrowserCount and DestroyAllParked. This material doesn't cover their exact signatures — see Part II → Reg-Free Pool Management for the full reference — but the pattern to reach for is: use GetParkedBrowserCount to confirm whether stragglers actually exist before assuming a leak, and DestroyAllParked to force-clear them rather than debugging each index by hand.