CreateEngineSessionMiddleware.kt
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
package mozilla.components.browser.state.engine.middleware
import androidx.annotation.MainThread
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.launch
import mozilla.components.browser.state.action.BrowserAction
import mozilla.components.browser.state.action.EngineAction
import mozilla.components.browser.state.selector.findTabOrCustomTab
import mozilla.components.browser.state.state.BrowserState
import mozilla.components.browser.state.state.SessionState
import mozilla.components.concept.engine.Engine
import mozilla.components.concept.engine.EngineSession
import mozilla.components.lib.state.Middleware
import mozilla.components.lib.state.MiddlewareContext
import mozilla.components.lib.state.Store
import mozilla.components.support.base.log.logger.Logger
/**
* [Middleware] responsible for creating [EngineSession] instances whenever an [EngineAction.CreateEngineSessionAction]
* is getting dispatched.
*/
internal class CreateEngineSessionMiddleware(
private val engine: Engine,
private val scope: CoroutineScope,
) : Middleware<BrowserState, BrowserAction> {
private val logger = Logger("CreateEngineSessionMiddleware")
override fun invoke(
context: MiddlewareContext<BrowserState, BrowserAction>,
next: (BrowserAction) -> Unit,
action: BrowserAction,
) {
if (action is EngineAction.CreateEngineSessionAction) {
val engineState = context.state.findTabOrCustomTab(action.tabId)?.engineState
if (engineState?.initializing == false && engineState.engineSession == null && !engineState.crashed) {
context.dispatch(EngineAction.UpdateEngineSessionInitializingAction(action.tabId, true))
createEngineSession(context.store, action)
next(action)
} else {
// Initialization is in progress by a pending CreateEngineSessionAction. Let's
// schedule dispatching the follow-up action when the engine session is ready.
// We launch this on main to guarantee this happens after the engine session
// is created which has been launched on main already at this point.
action.followupAction?.let {
scope.launch {
context.store.dispatch(it)
}
}
}
} else {
next(action)
}
}
private fun createEngineSession(
store: Store<BrowserState, BrowserAction>,
action: EngineAction.CreateEngineSessionAction,
) {
logger.debug("Request to create engine session for tab ${action.tabId}")
scope.launch {
// We only need to ask for an EngineSession here. If needed this method will internally
// create one and dispatch a LinkEngineSessionAction to add it to BrowserState.
getOrCreateEngineSession(
engine,
logger,
store,
action.tabId,
action.includeParent,
)
action.followupAction?.let {
store.dispatch(it)
}
}
}
}
@MainThread
@Suppress("ReturnCount")
private fun getOrCreateEngineSession(
engine: Engine,
logger: Logger,
store: Store<BrowserState, BrowserAction>,
tabId: String,
includeParent: Boolean,
): EngineSession? {
val tab = store.state.findTabOrCustomTab(tabId)
if (tab == null) {
logger.warn("Requested engine session for tab. But tab does not exist. ($tabId)")
return null
}
if (tab.engineState.crashed) {
logger.warn("Not creating engine session, since tab is crashed. Waiting for restore.")
return null
}
tab.engineState.engineSession?.let {
logger.debug("Engine session already exists for tab $tabId")
return it
}
return createEngineSession(engine, logger, store, tab, includeParent)
}
@MainThread
private fun createEngineSession(
engine: Engine,
logger: Logger,
store: Store<BrowserState, BrowserAction>,
tab: SessionState,
includeParent: Boolean,
): EngineSession {
val engineSession = engine.createSession(tab.content.private, tab.contextId).apply {
// The engineSession's desktopMode needs to be updated based on the tab's desktopMode value,
// because the tab's desktopMode value can be different from the Browser-wide desktopMode
// settings, which is stored in defaultSettings and used to create the EngineSession.
toggleDesktopMode(enable = tab.content.desktopMode, reload = false)
}
logger.debug("Created engine session for tab ${tab.id}")
val engineSessionState = tab.engineState.engineSessionState
val skipLoading = if (engineSessionState != null) {
engineSession.restoreState(engineSessionState)
} else {
false
}
store.dispatch(
EngineAction.LinkEngineSessionAction(
tab.id,
engineSession,
skipLoading = skipLoading,
includeParent = includeParent,
),
)
return engineSession
}