GeckoView Engine
Omni renders the web with Mozilla GeckoView v145 — the same engine that powers Firefox. This page covers how the runtime is managed and how the app talks to it.
Why GeckoView, not WebView
| GeckoView | Android WebView | |
|---|---|---|
| Engine version | Bundled & pinned (v145) | Whatever the OS ships |
| WebExtensions | ✓ Full | ✗ |
| Private context | ✓ Real isolation | Limited |
| Modern web platform | ✓ | Varies by OEM |
| APK cost | Larger (~tens of MB) | ~0 (system) |
The trade-off is APK size, which Omni manages with per-ABI splits (see Build System).
The runtime singleton
A GeckoRuntime is expensive — it boots the whole engine. Omni creates exactly one, lazily, and shares it across every tab:
private var geckoRuntime: GeckoRuntime? = null
fun getGeckoRuntime(context: Context): GeckoRuntime {
return geckoRuntime ?: run {
val settings = GeckoRuntimeSettings.Builder()
.consoleOutput(true) // feed the dev console
.extensionsProcess(true) // isolate extension code
.build()
GeckoRuntime.getDefault(context, settings).also {
geckoRuntime = it
installBuiltInExtensions(it) // register bundled .xpi
}
}
}
Sessions and tabs
Each tab owns a GeckoSession. Sessions are configured per tab — most importantly the private-browsing context:
fun createSession(isPrivate: Boolean): GeckoSession {
val settings = GeckoSessionSettings.Builder()
.usePrivateMode(isPrivate) // isolated cookie jar + no disk cache
.useTrackingProtection(
if (isPrivate) GeckoSessionSettings.TRACKING_PROTECTION_DEFAULT
else GeckoSessionSettings.TRACKING_PROTECTION_NONE
)
.build()
return GeckoSession(settings)
}
Private sessions get a fully isolated context: cookies, cache and storage are in-memory only and vanish when the session closes.
Delegates
GeckoView communicates through delegates — interfaces the app implements and attaches to a session. Omni wires up (among others):
NavigationDelegate— URL loads, redirects, andonNewSessionfor popup blocking.ContentDelegate— title, meta, fullscreen requests.ProgressDelegate— page load progress for the toolbar.PermissionDelegate— camera/mic/location prompts.SessionFinder— find-in-page.
Popup blocking at the engine
Rather than closing popups after they appear, Omni rejects them at creation. NavigationDelegate.onNewSession inspects the opener and returns null for unwanted popups, so the window never renders.
WebExtension interop
Extensions register on the shared runtime via webExtensionController. Bundled extensions use native messaging (omniApp) to push data to Kotlin — this is how the Media Grabber reports stream URLs to MediaInterceptor.
// Receive a message from an extension's content script
runtime.webExtensionController.setNativeMessagingDelegate(
"omniApp",
object : WebExtension.MessageDelegate {
override fun onMessage(msg: Any, sender: WebExtension.MessageSender): GeckoResult<Any> {
handleStreamCandidate(msg as Map<*, *>)
return GeckoResult.fromValue(null)
}
}
)
Error handling
When a page fails to load, Omni shows a native GeckoErrorScreen (defined in MainActivity) with retry and offline-cache options instead of the engine’s default error page.
GeckoView is pinned to 145.0.20251124145406 from Mozilla’s content-filtered Maven repo. Engine upgrades are a deliberate, tested step — never a floating dependency.