Four separate layers
An in-game web feature is not just a URL. Treat it as four cooperating layers with separate ownership and lifetimes:
- Native view: a Scaleform or Wulf owner decides route, size, focus, loading UI, parentage, and teardown.
- Browser runtime:
IBrowserControllerandWebBrowsercreate the native web view, expose its texture, deliver messages, and destroy it. - Web-to-client capabilities: the handler collection defines which native commands the current page may call.
- Remote page and services: JavaScript routes, HTTP APIs, WebSockets, and push systems own web state independently from the client bridge.
Ownership and lifecycle
The native owner normally chooses a browser ID and calls the controller to load a URL. The controller creates a WebBrowser, while the view attaches handlers and waits for readiness before exposing the browser texture. Teardown must reverse both sides: detach view-owned listeners and ask the browser controller to delete the owned browser.
Keep these readiness stages separate:
- The Python wrapper exists.
- The native web view exists.
- Navigation has completed.
- The page DOM and application are ready.
- The browser texture is ready for the owning UI.
Navigation invalidates injected JavaScript state and page object references. View closure invalidates the browser owner even if an asynchronous page or network callback completes later.
Handler collections are capabilities
The page does not receive every native browser command automatically. Its owning view installs a specific handler collection: shared browsers, store views, notifications, competitive-mode pages, and other feature owners use different sets.
- Find the actual factory passed to the browser instance; do not infer capabilities from the URL.
- Inspect each registered command family and its schema.
- Do not attach a broad shared collection to an arbitrary page as a compatibility shortcut.
- Reuse an underlying native service only after verifying that its lifecycle and context are valid outside the original view.
The handler set is both an ownership boundary and a capability boundary. A similar-looking page in another route may deliberately receive a different native API.
Web-to-client message contract
The page sends serialized commands through the native host function. Current handlers parse an outer command schema, select an exact registered handler, validate that handler's parameters, run it, and optionally send a serialized response back to the page.
{
"command": "command_family",
"params": {"operation": "read_summary"},
"web_id": "request-42"
}
{
"command": "command_family",
"data": {"status": "ok"},
"web_id": "request-42"
}
Illustrative: the field roles match the current bridge, but command names and parameter schemas belong to the registered handler. web_id is the response-correlation key; a command family alone cannot distinguish concurrent requests.
For the inspected non-generator @w2c
wrapper, every normal return is passed to the callback, including implicit Python
None. Its context serializes that as "data": null, rather than
omitting the reply. Real void UI-control handlers
use this path. Their payload is not a standardized success value: action meaning
and any explicit result/error object belong to the individual handler.
Serialization and callback dispatch do not establish browser delivery or frontend promise completion. An exception escaping the handler can be logged by the browser owner without an automatic correlated error response. These source and inert callback checks cover the normal non-generator path and that error boundary; generator dispatch, native transport and actual UI operations were not exercised.
Responses, events, and web networking
Do not collapse every update into one channel. The embedded page may receive data from several independent paths:
sendMessagereturns a correlated response to a web-to-client command.sendEventdelivers an unsolicited client-to-web event.- HTTP responses come from remote services.
- WebSockets or native reactive channels can deliver additional live updates.
Two paths may update the same frontend store while having different authentication, ordering, retry, and teardown rules. Identify the producer before choosing a hook or cache.
Query augmentation follows the URL owner's marker policy
The current addParamsToUrlQuery helper
parses the existing query, updates its parameter mapping and re-encodes it. It is not a
lossless append. Its default keepBlankValues=False drops existing blank-valued
parameters, including bare presence markers. A supplied key replaces that key's old value
list rather than appending to it; a supplied list can encode repeated values.
The inspected external-link handler treats the presence of an empty marker as one handling input and explicitly preserves blanks when adding its own parameter. Marker presence is only one of its gates. Use the owning handler's query policy rather than assuming an empty value is irrelevant.
from helpers.http.url_formatters import addParamsToUrlQuery
# url comes from the verified owner; this owner requires blank markers.
updated_url = addParamsToUrlQuery(
url,
{'view': ['detail']},
keepBlankValues=True,
)
Rebuilding the query can change ordering and escaping even when decoded values agree. Inspect parsed keys and values rather than promising byte-for-byte URL preservation. Preserving a marker does not establish browser readiness, registered bridge capabilities or successful navigation.
Current helper/caller source and selected pure-method controls checked blank-marker handling,
replacement of repeated values and encoding of reserved characters. The controls used the
host's urllib.parse adapter; Python 2 byte/Unicode behavior, query ordering, real
endpoints, browser navigation and live routing were not tested.
Why an external browser is only a partial substitute
Opening a page in a desktop browser may establish an ordinary web session, but it does not recreate the game's host function, registered handler collection, native dialogs and navigation, resource resolution, client events, browser texture, or in-game input lifecycle.
Use an external browser only when the desired workflow is genuinely web-owned. If the page expects native inventory, vehicle, comparison, sound, tooltip, or navigation commands, the external version can look correct and still be incomplete.
Safe JavaScript investigation
WebBrowser.executeJavascript can run code in a known browser frame, but it does not directly return a Python value. Observations need an explicit, bounded return channel such as an existing bridge or a deliberately intercepted custom navigation.
- Inject only into a browser whose owner and current page are known.
- Make wrappers idempotent and preserve original
fetchor XHR behavior. - Clone fetch responses before reading their bodies.
- Bound event count, message size, and persisted output.
- Redact tokens, cookies, authorization data, account identifiers, and complete authenticated payloads.
- Remove navigation filters and discard partial messages during teardown.
- Observe read paths first; do not trigger undocumented server mutations as discovery.
DOM patches are page-version-specific and disappear on navigation or recreation. Treat injection as an investigation tool, not a durable substitute for the page's own state model.
Source-first workflow
- Find the native owner, browser ID, URL builder, size, focus flags, texture-ready path, and deletion path.
- Find the exact handler factory and enumerate registered command schemas.
- Trace the URL to server settings, route helpers, or feature configuration instead of copying a logged URL.
- Inspect current page assets and source maps for routes, bridge calls, HTTP helpers, and live subscriptions.
- Observe bounded live traffic only when source is insufficient.
- Reconcile the web behavior with native controllers, typed requesters, and caches before building a parallel path.
Mark hosts, private endpoints, bundle hashes, route names, event names, schemas, and browser internals as version-sensitive. Verify caller sites, registrations, and firing sites against the client build you target.
Investigation starting points
WebBrowser.pyfor native browser lifetime, messaging, events, filters, and JavaScript execution.BrowserController.pyfor browser creation, tracking, and deletion.- Shared Scaleform and Wulf browser views for texture readiness and view ownership.
web.web_client_apifor the inspected command schemas, handler dispatch, callbacks, and client events.- Feature-owned
create*WebHandlersfactories for the actual capability set.
For nearby lifecycle and UI boundaries, see GUI Frameworks, Async & Networking, and Local Tools & Bridges.