Bug 1622944: Update xMultipleSessions junit tests to be sensitive to dom.ipc.processCount; r=geckoview-reviewers,agi
authorAaron Klotz <aklotz@mozilla.com>
Tue, 28 Apr 2020 21:50:06 +0000
changeset 526577 929422dd5bc617b86714d9140d23f6ceb20fe6b4
parent 526576 897f08396a2f09f5b4c4fc3d354bd6e3f0acdcd2
child 526578 f561a7e5d4098862bf430fc5d61fb4997c3a42da
push id37358
push useropoprus@mozilla.com
push dateWed, 29 Apr 2020 03:05:14 +0000
treeherdermozilla-central@6bb8423186c1 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersgeckoview-reviewers, agi
bugs1622944
milestone77.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1622944: Update xMultipleSessions junit tests to be sensitive to dom.ipc.processCount; r=geckoview-reviewers,agi We need to move these tests to their own class so that we can add a `@Before` function to set up the expected start conditions. We also need to allocate the second session in such a way that it is hosted by the same content process as `mainSession`. The previous scheme of waiting on each `GeckoResult` independently caused deadlocks if the results are resolved in a different order, so I replaced those two waits with a `GeckoView.allOf` result that will Just Work (TM). Note that this scheme works both in single-e10s and multi-e10s configurations. Differential Revision: https://phabricator.services.mozilla.com/D67067
mobile/android/geckoview/src/androidTest/java/org/mozilla/geckoview/test/ContentDelegateMultipleSessionsTest.kt
mobile/android/geckoview/src/androidTest/java/org/mozilla/geckoview/test/ContentDelegateTest.kt
copy from mobile/android/geckoview/src/androidTest/java/org/mozilla/geckoview/test/ContentDelegateTest.kt
copy to mobile/android/geckoview/src/androidTest/java/org/mozilla/geckoview/test/ContentDelegateMultipleSessionsTest.kt
--- a/mobile/android/geckoview/src/androidTest/java/org/mozilla/geckoview/test/ContentDelegateTest.kt
+++ b/mobile/android/geckoview/src/androidTest/java/org/mozilla/geckoview/test/ContentDelegateMultipleSessionsTest.kt
@@ -27,130 +27,117 @@ import android.util.SparseArray
 import android.view.Surface
 import android.view.View
 import android.view.ViewStructure
 import android.view.autofill.AutofillId
 import android.view.autofill.AutofillValue
 import org.hamcrest.Matchers.*
 import org.json.JSONObject
 import org.junit.Assume.assumeThat
+import org.junit.Before
 import org.junit.Test
 import org.junit.runner.RunWith
 import org.mozilla.gecko.GeckoAppShell
 import org.mozilla.geckoview.*
 import org.mozilla.geckoview.test.rule.GeckoSessionTestRule
 import org.mozilla.geckoview.test.rule.GeckoSessionTestRule.NullDelegate
 
 
 @RunWith(AndroidJUnit4::class)
 @MediumTest
-class ContentDelegateTest : BaseSessionTest() {
-    @Test fun titleChange() {
-        sessionRule.session.loadTestPath(TITLE_CHANGE_HTML_PATH)
+class ContentDelegateMultipleSessionsTest : BaseSessionTest() {
+    val contentProcNameRegex = ".*:tab\\d+$".toRegex()
 
-        sessionRule.waitUntilCalled(object : Callbacks.ContentDelegate {
-            @AssertCalled(count = 2)
-            override fun onTitleChange(session: GeckoSession, title: String?) {
-                assertThat("Title should match", title,
-                           equalTo(forEachCall("Title1", "Title2")))
+    @AnyThread
+    fun killAllContentProcesses() {
+        val context = GeckoAppShell.getApplicationContext()
+        val manager = context.getSystemService(Context.ACTIVITY_SERVICE) as ActivityManager
+        for (info in manager.runningAppProcesses) {
+            if (info.processName.matches(contentProcNameRegex)) {
+                Process.killProcess(info.pid)
             }
-        })
+        }
     }
 
-    @Test fun download() {
-        // disable test on pgo for frequently failing Bug 1543355
-        assumeThat(sessionRule.env.isDebugBuild, equalTo(true))
-        sessionRule.session.loadTestPath(DOWNLOAD_HTML_PATH)
-
-        sessionRule.waitUntilCalled(object : Callbacks.NavigationDelegate, Callbacks.ContentDelegate {
-
-            @AssertCalled(count = 2)
-            override fun onLoadRequest(session: GeckoSession,
-                                       request: LoadRequest):
-                                       GeckoResult<AllowOrDeny>? {
-                return null
-            }
+    fun resetContentProcesses() {
+        val isMainSessionAlreadyOpen = mainSession.isOpen()
+        killAllContentProcesses()
 
-            @AssertCalled(false)
-            override fun onNewSession(session: GeckoSession, uri: String): GeckoResult<GeckoSession>? {
-                return null
-            }
+        if (isMainSessionAlreadyOpen) {
+            mainSession.waitUntilCalled(object : Callbacks.ContentDelegate {
+                @AssertCalled(count = 1)
+                override fun onKill(session: GeckoSession) {
+                }
+            })
+        }
 
-            @AssertCalled(count = 1)
-            override fun onExternalResponse(session: GeckoSession, response: GeckoSession.WebResponseInfo) {
-                assertThat("Uri should start with data:", response.uri, startsWith("data:"))
-                assertThat("Content type should match", response.contentType, equalTo("text/plain"))
-                assertThat("Content length should be non-zero", response.contentLength, greaterThan(0L))
-                assertThat("Filename should match", response.filename, equalTo("download.txt"))
-            }
-        })
+        mainSession.open()
     }
 
-    @IgnoreCrash
-    @Test fun crashContent() {
-        // This test doesn't make sense without multiprocess
-        assumeThat(sessionRule.env.isMultiprocess, equalTo(true))
+    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)
 
-        mainSession.loadUri(CONTENT_CRASH_URL)
-        mainSession.waitUntilCalled(object : Callbacks.ContentDelegate {
-            @AssertCalled(count = 1)
-            override fun onCrash(session: GeckoSession) {
-                assertThat("Session should be closed after a crash",
-                           session.isOpen, equalTo(false))
-            }
-        })
+        if (isExtensionProcessEnabled && numContentProcesses > 1) {
+            // Extension process counts against the content process budget
+            --numContentProcesses 
+        }
 
-        // Recover immediately
-        mainSession.open()
-        mainSession.loadTestPath(HELLO_HTML_PATH)
-        mainSession.waitUntilCalled(object: Callbacks.ProgressDelegate {
-            @AssertCalled(count = 1)
-            override fun onPageStop(session: GeckoSession, success: Boolean) {
-                assertThat("Page should load successfully", success, equalTo(true))
-            }
-        })
+        return numContentProcesses
     }
 
-    @IgnoreCrash
-    @WithDisplay(width = 10, height = 10)
-    @Test fun crashContent_tapAfterCrash() {
-        // This test doesn't make sense without multiprocess
-        assumeThat(sessionRule.env.isMultiprocess, equalTo(true))
+    // 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()
 
-        mainSession.delegateUntilTestEnd(object : Callbacks.ContentDelegate {
-            override fun onCrash(session: GeckoSession) {
-                mainSession.open()
-                mainSession.loadTestPath(HELLO_HTML_PATH)
-            }
-        })
+        // 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() }
 
-        mainSession.synthesizeTap(5, 5)
-        mainSession.loadUri(CONTENT_CRASH_URL)
-        mainSession.waitForPageStop()
+        // The second session that shares a process with mainSession should be at
+        // the end of the array.
+        return additionalSessions.last()
+    }
 
-        mainSession.synthesizeTap(5, 5)
-        mainSession.reload()
-        mainSession.waitForPageStop()
+    @Before
+    fun setup() {
+        resetContentProcesses()
     }
 
     @IgnoreCrash
     @Test fun crashContentMultipleSessions() {
         // This test doesn't make sense without multiprocess
         assumeThat(sessionRule.env.isMultiprocess, equalTo(true))
 
-        // XXX we need to make sure all sessions in a given content process receive onCrash().
-        // If we add multiple content processes, this test will need to be fixed to ensure the
-        // test sessions go into the same one.
-        val newSession = sessionRule.createOpenSession()
+        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.
+        // 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 : Callbacks.ContentDelegate {
             fun reportCrash(session: GeckoSession) {
                 if (session == mainSession) {
                     mainSessionCrash.complete(null)
                 } else if (session == newSession) {
                     newSessionCrash.complete(null)
                 }
             }
@@ -164,361 +151,37 @@ class ContentDelegateTest : BaseSessionT
             }
         })
 
         newSession.loadTestPath(HELLO_HTML_PATH)
         newSession.waitForPageStop()
 
         mainSession.loadUri(CONTENT_CRASH_URL)
 
-        sessionRule.waitForResult(newSessionCrash)
-        sessionRule.waitForResult(mainSessionCrash)
-    }
-
-    @AnyThread
-    fun killContentProcess() {
-        val context = GeckoAppShell.getApplicationContext()
-        val manager = context.getSystemService(Context.ACTIVITY_SERVICE) as ActivityManager
-        for (info in manager.runningAppProcesses) {
-            if (info.processName.endsWith(":tab0")) {
-                Process.killProcess(info.pid)
-            }
-        }
-    }
-
-    @IgnoreCrash
-    @Test fun killContent() {
-        assumeThat(sessionRule.env.isMultiprocess, equalTo(true))
-        assumeThat(sessionRule.env.isDebugBuild && sessionRule.env.isX86,
-                equalTo(false))
-
-        killContentProcess()
-        mainSession.waitUntilCalled(object : Callbacks.ContentDelegate {
-            @AssertCalled(count = 1)
-            override fun onKill(session: GeckoSession) {
-                assertThat("Session should be closed after being killed",
-                        session.isOpen, equalTo(false))
-            }
-        })
-
-        mainSession.open()
-        mainSession.loadTestPath(HELLO_HTML_PATH)
-        mainSession.waitUntilCalled(object : Callbacks.ProgressDelegate {
-            @AssertCalled(count = 1)
-            override fun onPageStop(session: GeckoSession, success: Boolean) {
-                assertThat("Page should load successfully", success, equalTo(true))
-            }
-        })
+        sessionRule.waitForResult(allCrashesFound)
     }
 
     @IgnoreCrash
     @Test fun killContentMultipleSessions() {
         assumeThat(sessionRule.env.isMultiprocess, equalTo(true))
-        assumeThat(sessionRule.env.isDebugBuild && sessionRule.env.isX86,
-                equalTo(false))
 
-        val newSession = sessionRule.createOpenSession()
-        killContentProcess()
-
-        val remainingSessions = mutableListOf(newSession, mainSession)
-        while (remainingSessions.isNotEmpty()) {
-            sessionRule.waitUntilCalled(object : Callbacks.ContentDelegate {
-                @AssertCalled(count = 1)
-                override fun onKill(session: GeckoSession) {
-                    remainingSessions.remove(session)
-                }
-            })
-        }
-    }
-
-    private fun goFullscreen() {
-        sessionRule.setPrefsUntilTestEnd(mapOf("full-screen-api.allow-trusted-requests-only" to false))
-        mainSession.loadTestPath(FULLSCREEN_PATH)
-        mainSession.waitForPageStop()
-        mainSession.evaluateJS("document.querySelector('#fullscreen').requestFullscreen(); true")
-        sessionRule.waitUntilCalled(object : Callbacks.ContentDelegate {
-            @AssertCalled(count = 1)
-            override  fun onFullScreen(session: GeckoSession, fullScreen: Boolean) {
-                assertThat("Div went fullscreen", fullScreen, equalTo(true))
-            }
-        })
-    }
+        val newSession = getSecondGeckoSession()
 
-    private fun waitForFullscreenExit() {
-        sessionRule.waitUntilCalled(object : Callbacks.ContentDelegate {
-            @AssertCalled(count = 1)
-            override  fun onFullScreen(session: GeckoSession, fullScreen: Boolean) {
-                assertThat("Div left fullscreen", fullScreen, equalTo(false))
-            }
-        })
-    }
-
-    @Test fun fullscreen() {
-        goFullscreen()
-        mainSession.evaluateJS("document.exitFullscreen(); true")
-        waitForFullscreenExit()
-    }
-
-    @Test fun sessionExitFullscreen() {
-        goFullscreen()
-        mainSession.exitFullScreen()
-        waitForFullscreenExit()
-    }
+        val mainSessionKilled = GeckoResult<Void>()
+        val newSessionKilled = GeckoResult<Void>()
 
-    @Test fun firstComposite() {
-        val display = mainSession.acquireDisplay()
-        val texture = SurfaceTexture(0)
-        texture.setDefaultBufferSize(100, 100)
-        val surface = Surface(texture)
-        display.surfaceChanged(surface, 100, 100)
-        mainSession.loadTestPath(HELLO_HTML_PATH)
-        sessionRule.waitUntilCalled(object : Callbacks.ContentDelegate {
-            @AssertCalled(count = 1)
-            override fun onFirstComposite(session: GeckoSession) {
-            }
-        })
-        display.surfaceDestroyed()
-        display.surfaceChanged(surface, 100, 100)
-        sessionRule.waitUntilCalled(object : Callbacks.ContentDelegate {
-            @AssertCalled(count = 1)
-            override fun onFirstComposite(session: GeckoSession) {
-            }
-        })
-        display.surfaceDestroyed()
-        mainSession.releaseDisplay(display)
-    }
+        val allKillEventsReceived = GeckoResult.allOf(mainSessionKilled, newSessionKilled)
 
-    @WithDisplay(width = 10, height = 10)
-    @Test fun firstContentfulPaint() {
-        mainSession.loadTestPath(HELLO_HTML_PATH)
-        sessionRule.waitUntilCalled(object : Callbacks.ContentDelegate {
-            @AssertCalled(count = 1)
-            override fun onFirstContentfulPaint(session: GeckoSession) {
-            }
-        })
-    }
-
-    @Test fun webAppManifestPref() {
-        val initialState = sessionRule.runtime.settings.getWebManifestEnabled()
-        val jsToRun = "document.querySelector('link[rel=manifest]').relList.supports('manifest');"
-
-        // Check pref'ed off
-        sessionRule.runtime.settings.setWebManifestEnabled(false)
-        mainSession.loadTestPath(HELLO_HTML_PATH)
-        sessionRule.waitForPageStop(mainSession)
-
-        var result = equalTo(mainSession.evaluateJS(jsToRun) as Boolean)
-
-        assertThat("Disabling pref makes relList.supports('manifest') return false", false, result)
-
-        // Check pref'ed on
-        sessionRule.runtime.settings.setWebManifestEnabled(true)
-        mainSession.loadTestPath(HELLO_HTML_PATH)
-        sessionRule.waitForPageStop(mainSession)
-
-        result = equalTo(mainSession.evaluateJS(jsToRun) as Boolean)
-        assertThat("Enabling pref makes relList.supports('manifest') return true", true, result)
-
-        sessionRule.runtime.settings.setWebManifestEnabled(initialState)
-    }
-
-    @Test fun webAppManifest() {
-        mainSession.loadTestPath(HELLO_HTML_PATH)
-        mainSession.waitUntilCalled(object : Callbacks.All {
-            @AssertCalled(count = 1)
-            override fun onPageStop(session: GeckoSession, success: Boolean) {
-                assertThat("Page load should succeed", success, equalTo(true))
-            }
-
-            @AssertCalled(count = 1)
-            override fun onWebAppManifest(session: GeckoSession, manifest: JSONObject) {
-                // These values come from the manifest at assets/www/manifest.webmanifest
-                assertThat("name should match", manifest.getString("name"), equalTo("App"))
-                assertThat("short_name should match", manifest.getString("short_name"), equalTo("app"))
-                assertThat("display should match", manifest.getString("display"), equalTo("standalone"))
-
-                // The color here is "cadetblue" converted to #aarrggbb.
-                assertThat("theme_color should match", manifest.getString("theme_color"), equalTo("#ff5f9ea0"))
-                assertThat("background_color should match", manifest.getString("background_color"), equalTo("#eec0ffee"))
-                assertThat("start_url should match", manifest.getString("start_url"), endsWith("/assets/www/start/index.html"))
-
-                val icon = manifest.getJSONArray("icons").getJSONObject(0);
-
-                val iconSrc = Uri.parse(icon.getString("src"))
-                assertThat("icon should have a valid src", iconSrc, notNullValue())
-                assertThat("icon src should be absolute", iconSrc.isAbsolute, equalTo(true))
-                assertThat("icon should have sizes", icon.getString("sizes"),  not(isEmptyOrNullString()))
-                assertThat("icon type should match", icon.getString("type"), equalTo("image/gif"))
-            }
-        })
-    }
-
-    @Test fun viewportFit() {
-        mainSession.loadTestPath(VIEWPORT_PATH)
-        mainSession.waitUntilCalled(object : Callbacks.All {
-            @AssertCalled(count = 1)
-            override fun onPageStop(session: GeckoSession, success: Boolean) {
-                assertThat("Page load should succeed", success, equalTo(true))
-            }
-
-            @AssertCalled(count = 1)
-            override fun onMetaViewportFitChange(session: GeckoSession, viewportFit: String) {
-                assertThat("viewport-fit should match", viewportFit, equalTo("cover"))
+        sessionRule.delegateUntilTestEnd(object : Callbacks.ContentDelegate {
+            override fun onKill(session: GeckoSession) {
+                if (session == mainSession) {
+                    mainSessionKilled.complete(null)
+                } else if (session == newSession) {
+                    newSessionKilled.complete(null)
+                }
             }
         })
 
-        mainSession.loadTestPath(HELLO_HTML_PATH)
-        mainSession.waitUntilCalled(object : Callbacks.All {
-            @AssertCalled(count = 1)
-            override fun onPageStop(session: GeckoSession, success: Boolean) {
-                assertThat("Page load should succeed", success, equalTo(true))
-            }
-
-            @AssertCalled(count = 1)
-            override fun onMetaViewportFitChange(session: GeckoSession, viewportFit: String) {
-                assertThat("viewport-fit should match", viewportFit, equalTo("auto"))
-            }
-        })
-    }
-
-    /**
-     * Preferences to induce wanted behaviour.
-     */
-    private fun setHangReportTestPrefs(timeout: Int = 20000) {
-        sessionRule.setPrefsUntilTestEnd(mapOf(
-                "dom.max_script_run_time" to 1,
-                "dom.max_chrome_script_run_time" to 1,
-                "dom.max_ext_content_script_run_time" to 1,
-                "dom.ipc.cpow.timeout" to 100,
-                "browser.hangNotification.waitPeriod" to timeout
-        ))
-    }
-
-    /**
-     * With no delegate set, the default behaviour is to stop hung scripts.
-     */
-    @NullDelegate(GeckoSession.ContentDelegate::class)
-    @Test fun stopHungProcessDefault() {
-        setHangReportTestPrefs()
-        mainSession.loadTestPath(HUNG_SCRIPT)
-        sessionRule.delegateUntilTestEnd(object : Callbacks.ProgressDelegate {
-            @AssertCalled(count = 1)
-            override fun onPageStop(session: GeckoSession, success: Boolean) {
-                assertThat("The script did not complete.",
-                        sessionRule.session.evaluateJS("document.getElementById(\"content\").innerHTML") as String,
-                        equalTo("Started"))
-            }
-        })
-        sessionRule.waitForPageStop(mainSession)
-    }
-
-    /**
-     * With no overriding implementation for onSlowScript, the default behaviour is to stop hung
-     * scripts.
-     */
-    @Test fun stopHungProcessNull() {
-        setHangReportTestPrefs()
-        sessionRule.delegateUntilTestEnd(object : GeckoSession.ContentDelegate, Callbacks.ProgressDelegate {
-            // default onSlowScript returns null
-            @AssertCalled(count = 1)
-            override fun onPageStop(session: GeckoSession, success: Boolean) {
-                assertThat("The script did not complete.",
-                        sessionRule.session.evaluateJS("document.getElementById(\"content\").innerHTML") as String,
-                        equalTo("Started"))
-            }
-        })
-        mainSession.loadTestPath(HUNG_SCRIPT)
-        sessionRule.waitForPageStop(mainSession)
-    }
+        killAllContentProcesses()
 
-    /**
-     * Test that, with a 'do nothing' delegate, the hung process completes after its delay
-     */
-    @Test fun stopHungProcessDoNothing() {
-        setHangReportTestPrefs()
-        var scriptHungReportCount = 0
-        sessionRule.delegateUntilTestEnd(object : GeckoSession.ContentDelegate, Callbacks.ProgressDelegate {
-            @AssertCalled()
-            override fun onSlowScript(geckoSession: GeckoSession, scriptFileName: String): GeckoResult<SlowScriptResponse> {
-                scriptHungReportCount += 1;
-                return GeckoResult.fromValue(null)
-            }
-            @AssertCalled(count = 1)
-            override fun onPageStop(session: GeckoSession, success: Boolean) {
-                assertThat("The delegate was informed of the hang repeatedly", scriptHungReportCount, greaterThan(1))
-                assertThat("The script did complete.",
-                        sessionRule.session.evaluateJS("document.getElementById(\"content\").innerHTML") as String,
-                        equalTo("Finished"))
-            }
-        })
-        mainSession.loadTestPath(HUNG_SCRIPT)
-        sessionRule.waitForPageStop(mainSession)
-    }
-
-    /**
-     * Test that the delegate is called and can stop a hung script
-     */
-    @Test fun stopHungProcess() {
-        setHangReportTestPrefs()
-        sessionRule.delegateUntilTestEnd(object : GeckoSession.ContentDelegate, Callbacks.ProgressDelegate {
-            @AssertCalled(count = 1, order = [1])
-            override fun onSlowScript(geckoSession: GeckoSession, scriptFileName: String): GeckoResult<SlowScriptResponse> {
-                return GeckoResult.fromValue(SlowScriptResponse.STOP)
-            }
-            @AssertCalled(count = 1, order = [2])
-            override fun onPageStop(session: GeckoSession, success: Boolean) {
-                assertThat("The script did not complete.",
-                        sessionRule.session.evaluateJS("document.getElementById(\"content\").innerHTML") as String,
-                        equalTo("Started"))
-            }
-        })
-        mainSession.loadTestPath(HUNG_SCRIPT)
-        sessionRule.waitForPageStop(mainSession)
-    }
-
-    /**
-     * Test that the delegate is called and can continue executing hung scripts
-     */
-    @Test fun stopHungProcessWait() {
-        setHangReportTestPrefs()
-        sessionRule.delegateUntilTestEnd(object : GeckoSession.ContentDelegate, Callbacks.ProgressDelegate {
-            @AssertCalled(count = 1, order = [1])
-            override fun onSlowScript(geckoSession: GeckoSession, scriptFileName: String): GeckoResult<SlowScriptResponse> {
-                return GeckoResult.fromValue(SlowScriptResponse.CONTINUE)
-            }
-            @AssertCalled(count = 1, order = [2])
-            override fun onPageStop(session: GeckoSession, success: Boolean) {
-                assertThat("The script did complete.",
-                        sessionRule.session.evaluateJS("document.getElementById(\"content\").innerHTML") as String,
-                        equalTo("Finished"))
-            }
-        })
-        mainSession.loadTestPath(HUNG_SCRIPT)
-        sessionRule.waitForPageStop(mainSession)
-    }
-
-    /**
-     * Test that the delegate is called and paused scripts re-notify after the wait period
-     */
-    @Test fun stopHungProcessWaitThenStop() {
-        setHangReportTestPrefs(500)
-        var scriptWaited = false
-        sessionRule.delegateUntilTestEnd(object : GeckoSession.ContentDelegate, Callbacks.ProgressDelegate {
-            @AssertCalled(count = 2, order = [1, 2])
-            override fun onSlowScript(geckoSession: GeckoSession, scriptFileName: String): GeckoResult<SlowScriptResponse> {
-                return if (!scriptWaited) {
-                    scriptWaited = true;
-                    GeckoResult.fromValue(SlowScriptResponse.CONTINUE)
-                } else {
-                    GeckoResult.fromValue(SlowScriptResponse.STOP)
-                }
-            }
-            @AssertCalled(count = 1, order = [3])
-            override fun onPageStop(session: GeckoSession, success: Boolean) {
-                assertThat("The script did not complete.",
-                        sessionRule.session.evaluateJS("document.getElementById(\"content\").innerHTML") as String,
-                        equalTo("Started"))
-            }
-        })
-        mainSession.loadTestPath(HUNG_SCRIPT)
-        sessionRule.waitForPageStop(mainSession)
+        sessionRule.waitForResult(allKillEventsReceived)
     }
 }
--- a/mobile/android/geckoview/src/androidTest/java/org/mozilla/geckoview/test/ContentDelegateTest.kt
+++ b/mobile/android/geckoview/src/androidTest/java/org/mozilla/geckoview/test/ContentDelegateTest.kt
@@ -126,76 +126,35 @@ class ContentDelegateTest : BaseSessionT
         mainSession.loadUri(CONTENT_CRASH_URL)
         mainSession.waitForPageStop()
 
         mainSession.synthesizeTap(5, 5)
         mainSession.reload()
         mainSession.waitForPageStop()
     }
 
-    @IgnoreCrash
-    @Test fun crashContentMultipleSessions() {
-        // This test doesn't make sense without multiprocess
-        assumeThat(sessionRule.env.isMultiprocess, equalTo(true))
-
-        // XXX we need to make sure all sessions in a given content process receive onCrash().
-        // If we add multiple content processes, this test will need to be fixed to ensure the
-        // test sessions go into the same one.
-        val newSession = sessionRule.createOpenSession()
-
-        // 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>()
-        sessionRule.delegateUntilTestEnd(object : Callbacks.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)
-            }
-        })
-
-        newSession.loadTestPath(HELLO_HTML_PATH)
-        newSession.waitForPageStop()
-
-        mainSession.loadUri(CONTENT_CRASH_URL)
-
-        sessionRule.waitForResult(newSessionCrash)
-        sessionRule.waitForResult(mainSessionCrash)
-    }
-
     @AnyThread
-    fun killContentProcess() {
+    fun killAllContentProcesses() {
         val context = GeckoAppShell.getApplicationContext()
         val manager = context.getSystemService(Context.ACTIVITY_SERVICE) as ActivityManager
+        val expr = ".*:tab\\d+$".toRegex()
         for (info in manager.runningAppProcesses) {
-            if (info.processName.endsWith(":tab0")) {
+            if (info.processName.matches(expr)) {
                 Process.killProcess(info.pid)
             }
         }
     }
 
     @IgnoreCrash
     @Test fun killContent() {
         assumeThat(sessionRule.env.isMultiprocess, equalTo(true))
         assumeThat(sessionRule.env.isDebugBuild && sessionRule.env.isX86,
                 equalTo(false))
 
-        killContentProcess()
+        killAllContentProcesses()
         mainSession.waitUntilCalled(object : Callbacks.ContentDelegate {
             @AssertCalled(count = 1)
             override fun onKill(session: GeckoSession) {
                 assertThat("Session should be closed after being killed",
                         session.isOpen, equalTo(false))
             }
         })
 
@@ -204,36 +163,16 @@ class ContentDelegateTest : BaseSessionT
         mainSession.waitUntilCalled(object : Callbacks.ProgressDelegate {
             @AssertCalled(count = 1)
             override fun onPageStop(session: GeckoSession, success: Boolean) {
                 assertThat("Page should load successfully", success, equalTo(true))
             }
         })
     }
 
-    @IgnoreCrash
-    @Test fun killContentMultipleSessions() {
-        assumeThat(sessionRule.env.isMultiprocess, equalTo(true))
-        assumeThat(sessionRule.env.isDebugBuild && sessionRule.env.isX86,
-                equalTo(false))
-
-        val newSession = sessionRule.createOpenSession()
-        killContentProcess()
-
-        val remainingSessions = mutableListOf(newSession, mainSession)
-        while (remainingSessions.isNotEmpty()) {
-            sessionRule.waitUntilCalled(object : Callbacks.ContentDelegate {
-                @AssertCalled(count = 1)
-                override fun onKill(session: GeckoSession) {
-                    remainingSessions.remove(session)
-                }
-            })
-        }
-    }
-
     private fun goFullscreen() {
         sessionRule.setPrefsUntilTestEnd(mapOf("full-screen-api.allow-trusted-requests-only" to false))
         mainSession.loadTestPath(FULLSCREEN_PATH)
         mainSession.waitForPageStop()
         mainSession.evaluateJS("document.querySelector('#fullscreen').requestFullscreen(); true")
         sessionRule.waitUntilCalled(object : Callbacks.ContentDelegate {
             @AssertCalled(count = 1)
             override  fun onFullScreen(session: GeckoSession, fullScreen: Boolean) {