Commit 10022f2f authored by Christian Sadilek's avatar Christian Sadilek Committed by Jonathan Almeida
Browse files

Closes #3877: Beta Uplift: Recover when content process gets killed

parent 33ba9715
Loading
Loading
Loading
Loading
+25 −2
Original line number Diff line number Diff line
@@ -495,12 +495,35 @@ class GeckoEngineSession(
        override fun onCrash(session: GeckoSession) {
            stateBeforeCrash = lastSessionState

            geckoSession.close()
            createGeckoSession()
            recoverGeckoSession()

            notifyObservers { onCrash() }
        }

        override fun onKill(session: GeckoSession) {
            // The content process of this session got killed (resources reclaimed by Android).
            // Let's recover and restore the last known state.

            val state = lastSessionState

            recoverGeckoSession()

            state?.let { geckoSession.restoreState(it) }

            notifyObservers { onProcessKilled() }
        }

        private fun recoverGeckoSession() {
            // Recover the GeckoSession after the process getting killed or crashing. We create a
            // new underlying GeckoSession.
            // Eventually we may be able to re-use the same GeckoSession by re-opening it. However
            // that seems to have caused issues:
            // https://github.com/mozilla-mobile/android-components/issues/3640

            geckoSession.close()
            createGeckoSession()
        }

        override fun onFullScreen(session: GeckoSession, fullScreen: Boolean) {
            notifyObservers { onFullScreenChange(fullScreen) }
        }
+5 −0
Original line number Diff line number Diff line
@@ -44,6 +44,11 @@ class GeckoEngineView @JvmOverloads constructor(
        override fun onCrash() {
            rebind()
        }

        override fun onProcessKilled() {
            rebind()
        }

        override fun onAppPermissionRequest(permissionRequest: PermissionRequest) = Unit
        override fun onContentPermissionRequest(permissionRequest: PermissionRequest) = Unit
    }
+26 −0
Original line number Diff line number Diff line
@@ -1795,6 +1795,32 @@ class GeckoEngineSessionTest {
        assertEquals(LoadUrlFlags.BYPASS_CLASSIFIER, GeckoSession.LOAD_FLAGS_BYPASS_CLASSIFIER)
    }

    @Test
    fun `onKill will recover, restore state and notify observers`() {
        val engineSession = GeckoEngineSession(mock(),
                geckoSessionProvider = geckoSessionProvider)

        captureDelegates()

        var observerNotified = false

        engineSession.register(object : EngineSession.Observer {
            override fun onProcessKilled() {
                observerNotified = true
            }
        })

        val mockedState: GeckoSession.SessionState = mock()
        progressDelegate.value.onSessionStateChange(geckoSession, mockedState)

        verify(geckoSession, never()).restoreState(mockedState)

        contentDelegate.value.onKill(geckoSession)

        verify(geckoSession).restoreState(mockedState)
        assertTrue(observerNotified)
    }

    private fun mockGeckoSession(): GeckoSession {
        val session = mock<GeckoSession>()
        whenever(session.settings).thenReturn(
+22 −2
Original line number Diff line number Diff line
@@ -15,8 +15,8 @@ import org.junit.Assert.assertNotNull
import org.junit.Assert.assertNull
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.Mockito
import org.mockito.Mockito.never
import org.mockito.Mockito.reset
import org.mockito.Mockito.times
import org.mockito.Mockito.verify
import org.mozilla.geckoview.GeckoResult
@@ -106,6 +106,26 @@ class GeckoEngineViewTest {
        verify(engineSession).unregister(any())
    }

    @Test
    fun `View will rebind session if process gets killed`() {
        val engineView = GeckoEngineView(context)
        val engineSession = mock<GeckoEngineSession>()
        val geckoSession = mock<GeckoSession>()
        val geckoView = mock<NestedGeckoView>()

        whenever(engineSession.geckoSession).thenReturn(geckoSession)
        engineView.currentGeckoView = geckoView

        engineView.render(engineSession)

        reset(geckoView)
        verify(geckoView, never()).setSession(geckoSession)

        engineView.observer.onProcessKilled()

        verify(geckoView).setSession(geckoSession)
    }

    @Test
    fun `View will rebind session if session crashed`() {
        val engineView = GeckoEngineView(context)
@@ -118,7 +138,7 @@ class GeckoEngineViewTest {

        engineView.render(engineSession)

        Mockito.reset(geckoView)
        reset(geckoView)
        verify(geckoView, never()).setSession(geckoSession)

        engineView.observer.onCrash()
+3 −0
Original line number Diff line number Diff line
@@ -15,6 +15,9 @@ permalink: /changelog/
* **support-test**
  * Fixed [#3893](https://github.com/mozilla-mobile/android-components/issues/3893) Moving WebserverRule to support-test.

* **browser-engine-gecko-beta**
  * The component now handles situations where the Android system kills the content process (without killing the main app process) in order to reclaim resources. In those situations the component will automatically recover and restore the last known state of those sessions.

# 7.0.0

* [Commits](https://github.com/mozilla-mobile/android-components/compare/v6.0.2...v7.0.0)