<lambda>null1 /* -*- Mode: Java; c-basic-offset: 4; tab-width: 4; indent-tabs-mode: nil; -*-
2 * Any copyright is dedicated to the Public Domain.
3 http://creativecommons.org/publicdomain/zero/1.0/ */
4
5 package org.mozilla.geckoview.test
6
7 import org.mozilla.geckoview.test.rule.GeckoSessionTestRule.AssertCalled
8 import org.mozilla.geckoview.test.rule.GeckoSessionTestRule.IgnoreCrash
9 import org.mozilla.geckoview.test.util.Callbacks
10
11 import androidx.annotation.AnyThread
12 import androidx.test.filters.MediumTest
13 import androidx.test.ext.junit.runners.AndroidJUnit4
14 import org.hamcrest.Matchers.*
15 import org.junit.Assume.assumeThat
16 import org.junit.Before
17 import org.junit.Test
18 import org.junit.runner.RunWith
19 import org.mozilla.geckoview.*
20
21
22 @RunWith(AndroidJUnit4::class)
23 @MediumTest
24 class ContentDelegateMultipleSessionsTest : BaseSessionTest() {
25 val contentProcNameRegex = ".*:tab\\d+$".toRegex()
26
27 @AnyThread
28 fun killAllContentProcesses() {
29 val contentProcessPids = sessionRule.getAllSessionPids()
30 for (pid in contentProcessPids) {
31 sessionRule.killContentProcess(pid)
32 }
33 }
34
35 fun resetContentProcesses() {
36 val isMainSessionAlreadyOpen = mainSession.isOpen()
37 killAllContentProcesses()
38
39 if (isMainSessionAlreadyOpen) {
40 mainSession.waitUntilCalled(object : Callbacks.ContentDelegate {
41 @AssertCalled(count = 1)
42 override fun onKill(session: GeckoSession) {
43 }
44 })
45 }
46
47 mainSession.open()
48 }
49
50 fun getE10sProcessCount(): Int {
51 val extensionProcessPref = "extensions.webextensions.remote"
52 val isExtensionProcessEnabled = (sessionRule.getPrefs(extensionProcessPref)[0] as Boolean)
53 val e10sProcessCountPref = "dom.ipc.processCount"
54 var numContentProcesses = (sessionRule.getPrefs(e10sProcessCountPref)[0] as Int)
55
56 if (isExtensionProcessEnabled && numContentProcesses > 1) {
57 // Extension process counts against the content process budget
58 --numContentProcesses
59 }
60
61 return numContentProcesses
62 }
63
64 // This function ensures that a second GeckoSession that shares the same
65 // content process as mainSession is returned to the test:
66 //
67 // First, we assume that we're starting with a known initial state with respect
68 // to sessions and content processes:
69 // * mainSession is the only session, it is open, and its content process is the only
70 // content process (but note that the content process assigned to mainSession is
71 // *not* guaranteed to be ":tab0").
72 // * With multi-e10s configured to run N content processes, we create and open
73 // an additional N content processes. With the default e10s process allocation
74 // scheme, this means that the first N-1 new sessions we create each get their
75 // own content process. The Nth new session is assigned to the same content
76 // process as mainSession, which is the session we want to return to the test.
77 fun getSecondGeckoSession(): GeckoSession {
78 val numContentProcesses = getE10sProcessCount()
79
80 // If we change the content process allocation scheme, this function will need to be
81 // fixed to ensure that we still have two test sessions in at least one content
82 // process (with one of those sessions being mainSession).
83 val additionalSessions = Array(numContentProcesses) { _ -> sessionRule.createOpenSession() }
84
85 // The second session that shares a process with mainSession should be at
86 // the end of the array.
87 return additionalSessions.last()
88 }
89
90 @Before
91 fun setup() {
92 resetContentProcesses()
93 }
94
95 @IgnoreCrash
96 @Test fun crashContentMultipleSessions() {
97 // TODO: Bug 1673952
98 assumeThat(sessionRule.env.isFission, equalTo(false))
99
100 val newSession = getSecondGeckoSession()
101
102 // We can inadvertently catch the `onCrash` call for the cached session if we don't specify
103 // individual sessions here. Therefore, assert 'onCrash' is called for the two sessions
104 // individually...
105 val mainSessionCrash = GeckoResult<Void>()
106 val newSessionCrash = GeckoResult<Void>()
107
108 // ...but we use GeckoResult.allOf for waiting on the aggregated results
109 val allCrashesFound = GeckoResult.allOf(mainSessionCrash, newSessionCrash)
110
111 sessionRule.delegateUntilTestEnd(object : Callbacks.ContentDelegate {
112 fun reportCrash(session: GeckoSession) {
113 if (session == mainSession) {
114 mainSessionCrash.complete(null)
115 } else if (session == newSession) {
116 newSessionCrash.complete(null)
117 }
118 }
119 // Slower devices may not catch crashes in a timely manner, so we check to see
120 // if either `onKill` or `onCrash` is called
121 override fun onCrash(session: GeckoSession) {
122 reportCrash(session)
123 }
124 override fun onKill(session: GeckoSession) {
125 reportCrash(session)
126 }
127 })
128
129 newSession.loadTestPath(HELLO_HTML_PATH)
130 newSession.waitForPageStop()
131
132 mainSession.loadUri(CONTENT_CRASH_URL)
133
134 sessionRule.waitForResult(allCrashesFound)
135 }
136
137 @IgnoreCrash
138 @Test fun killContentMultipleSessions() {
139 val newSession = getSecondGeckoSession()
140
141 val mainSessionKilled = GeckoResult<Void>()
142 val newSessionKilled = GeckoResult<Void>()
143
144 val allKillEventsReceived = GeckoResult.allOf(mainSessionKilled, newSessionKilled)
145
146 sessionRule.delegateUntilTestEnd(object : Callbacks.ContentDelegate {
147 override fun onKill(session: GeckoSession) {
148 if (session == mainSession) {
149 mainSessionKilled.complete(null)
150 } else if (session == newSession) {
151 newSessionKilled.complete(null)
152 }
153 }
154 })
155
156 killAllContentProcesses()
157
158 sessionRule.waitForResult(allKillEventsReceived)
159 }
160 }
161