<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