SuspendMiddleware.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 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.EngineState
import mozilla.components.concept.engine.EngineSession
import mozilla.components.concept.engine.EngineSessionState
import mozilla.components.lib.state.Middleware
import mozilla.components.lib.state.MiddlewareContext

/**
 * [Middleware] implementation responsible for suspending an [EngineSession].
 *
 * Suspending an [EngineSession] means that we will take the last [EngineSessionState], attach that
 * to [EngineState] and then clear the [EngineSession] reference and close it. The next time we
 * need an [EngineSession] for this tab we will create a new instance and restore the attached
 * [EngineSessionState].
 */
internal class SuspendMiddleware(
    private val scope: CoroutineScope,
) : Middleware<BrowserState, BrowserAction> {
    override fun invoke(
        context: MiddlewareContext<BrowserState, BrowserAction>,
        next: (BrowserAction) -> Unit,
        action: BrowserAction,
    ) {
        when (action) {
            is EngineAction.SuspendEngineSessionAction -> suspend(context, action.tabId)
            is EngineAction.KillEngineSessionAction -> {
                next(action)
                suspend(context, action.tabId)
            }
            else -> next(action)
        }
    }

    private fun suspend(
        context: MiddlewareContext<BrowserState, BrowserAction>,
        sessionId: String,
    ) {
        val tab = context.state.findTabOrCustomTab(sessionId) ?: return

        // First we unlink (which clearsEngineSession and state)
        context.dispatch(
            EngineAction.UnlinkEngineSessionAction(
                tab.id,
            ),
        )

        // Now we can close the unlinked EngineSession (on the main thread).
        scope.launch {
            tab.engineState.engineSession?.close()
        }
    }
}