Source code

Revision control

Copy as Markdown

Other Tools

/* -*- Mode: Java; c-basic-offset: 4; tab-width: 4; indent-tabs-mode: nil; -*-
* Any copyright is dedicated to the Public Domain.
package org.mozilla.geckoview.test
import androidx.annotation.AnyThread
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.MediumTest
import org.hamcrest.Matchers.* // ktlint-disable no-wildcard-imports
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import org.mozilla.geckoview.* // ktlint-disable no-wildcard-imports
import org.mozilla.geckoview.GeckoSession.ContentDelegate
import org.mozilla.geckoview.test.rule.GeckoSessionTestRule.AssertCalled
import org.mozilla.geckoview.test.rule.GeckoSessionTestRule.IgnoreCrash
@RunWith(AndroidJUnit4::class)
@MediumTest
class ContentDelegateMultipleSessionsTest : BaseSessionTest() {
val contentProcNameRegex = ".*:tab\\d+$".toRegex()
@AnyThread
fun killAllContentProcesses() {
val contentProcessPids = sessionRule.getAllSessionPids()
for (pid in contentProcessPids) {
sessionRule.killContentProcess(pid)
}
}
fun resetContentProcesses() {
val isMainSessionAlreadyOpen = mainSession.isOpen()
killAllContentProcesses()
if (isMainSessionAlreadyOpen) {
mainSession.waitUntilCalled(object : ContentDelegate {
@AssertCalled(count = 1)
override fun onKill(session: GeckoSession) {
}
})
}
mainSession.open()
}
fun getE10sProcessCount(): Int {
val extensionProcessPref = "extensions.webextensions.remote"
val isExtensionProcessEnabled = (sessionRule.getPrefs(extensionProcessPref)[0] as Boolean)
val e10sProcessCountPref = "dom.ipc.processCount"
var numContentProcesses = (sessionRule.getPrefs(e10sProcessCountPref)[0] as Int)
if (isExtensionProcessEnabled && numContentProcesses > 1) {
// Extension process counts against the content process budget
--numContentProcesses
}
return numContentProcesses
}
// This function ensures that a second GeckoSession that shares the same
// content process as mainSession is returned to the test:
//
// First, we assume that we're starting with a known initial state with respect
// to sessions and content processes:
// * mainSession is the only session, it is open, and its content process is the only
// content process (but note that the content process assigned to mainSession is
// *not* guaranteed to be ":tab0").
// * With multi-e10s configured to run N content processes, we create and open
// an additional N content processes. With the default e10s process allocation
// scheme, this means that the first N-1 new sessions we create each get their
// own content process. The Nth new session is assigned to the same content
// process as mainSession, which is the session we want to return to the test.
fun getSecondGeckoSession(): GeckoSession {
val numContentProcesses = getE10sProcessCount()
// If we change the content process allocation scheme, this function will need to be
// fixed to ensure that we still have two test sessions in at least one content
// process (with one of those sessions being mainSession).
val additionalSessions = Array(numContentProcesses) { _ -> sessionRule.createOpenSession() }
// The second session that shares a process with mainSession should be at
// the end of the array.
return additionalSessions.last()
}
@Before
fun setup() {
resetContentProcesses()
}
@IgnoreCrash
@Test
fun crashContentMultipleSessions() {
// We need to make sure all sessions in a given content process receive onCrash
// or onKill. To test this, we need to make sure we have two tabs sharing the same process.
val newSession = getSecondGeckoSession()
// We can inadvertently catch the `onCrash` call for the cached session if we don't specify
// individual sessions here. Therefore, assert 'onCrash' is called for the two sessions
// individually...
val mainSessionCrash = GeckoResult<Void>()
val newSessionCrash = GeckoResult<Void>()
// ...but we use GeckoResult.allOf for waiting on the aggregated results
val allCrashesFound = GeckoResult.allOf(mainSessionCrash, newSessionCrash)
sessionRule.delegateUntilTestEnd(object : ContentDelegate {
fun reportCrash(session: GeckoSession) {
if (session == mainSession) {
mainSessionCrash.complete(null)
} else if (session == newSession) {
newSessionCrash.complete(null)
}
}
// Slower devices may not catch crashes in a timely manner, so we check to see
// if either `onKill` or `onCrash` is called
override fun onCrash(session: GeckoSession) {
reportCrash(session)
}
override fun onKill(session: GeckoSession) {
reportCrash(session)
}
})
mainSession.loadUri(CONTENT_CRASH_URL)
sessionRule.waitForResult(allCrashesFound)
}
@IgnoreCrash
@Test
fun killContentMultipleSessions() {
val newSession = getSecondGeckoSession()
val mainSessionKilled = GeckoResult<Void>()
val newSessionKilled = GeckoResult<Void>()
val allKillEventsReceived = GeckoResult.allOf(mainSessionKilled, newSessionKilled)
sessionRule.delegateUntilTestEnd(object : ContentDelegate {
override fun onKill(session: GeckoSession) {
if (session == mainSession) {
mainSessionKilled.complete(null)
} else if (session == newSession) {
newSessionKilled.complete(null)
}
}
})
killAllContentProcesses()
sessionRule.waitForResult(allKillEventsReceived)
}
}