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

GeckoViewAndroid WebView
Engine versionBundled & pinned (v145)Whatever the OS ships
WebExtensions✓ Full
Private context✓ Real isolationLimited
Modern web platformVaries by OEM
APK costLarger (~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):

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.

Version pinning

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.

Related