summaryrefslogtreecommitdiffstats
path: root/mobile/android/geckoview/src/androidTest/java/org/mozilla/geckoview/test/ContentDelegateMultipleSessionsTest.kt
blob: a871c09a5a6e2fa8ad7418fd91437258cb2b49a0 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
/* -*- Mode: Java; c-basic-offset: 4; tab-width: 4; indent-tabs-mode: nil; -*-
 * Any copyright is dedicated to the Public Domain.
   http://creativecommons.org/publicdomain/zero/1.0/ */

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.Assume.assumeThat
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() {
        // TODO: Bug 1673952
        assumeThat(sessionRule.env.isFission, equalTo(false))

        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)
            }
        })

        newSession.loadTestPath(HELLO_HTML_PATH)
        newSession.waitForPageStop()

        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)
    }
}