1 // Copyright 2017 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4 
5 package org.chromium.android_webview.test;
6 
7 import static org.chromium.android_webview.test.OnlyRunIn.ProcessMode.MULTI_PROCESS;
8 
9 import android.util.Pair;
10 
11 import androidx.test.filters.SmallTest;
12 
13 import org.junit.After;
14 import org.junit.Assert;
15 import org.junit.Before;
16 import org.junit.Rule;
17 import org.junit.Test;
18 import org.junit.runner.RunWith;
19 
20 import org.chromium.android_webview.AwContents;
21 import org.chromium.android_webview.AwRenderProcess;
22 import org.chromium.android_webview.AwRenderProcessGoneDetail;
23 import org.chromium.android_webview.renderer_priority.RendererPriority;
24 import org.chromium.base.task.PostTask;
25 import org.chromium.base.test.util.CallbackHelper;
26 import org.chromium.base.test.util.CommandLineFlags;
27 import org.chromium.base.test.util.DisabledTest;
28 import org.chromium.base.test.util.Feature;
29 import org.chromium.content_public.browser.UiThreadTaskTraits;
30 import org.chromium.content_public.browser.test.util.TestThreadUtils;
31 import org.chromium.content_public.common.ContentUrlConstants;
32 import org.chromium.net.test.util.TestWebServer;
33 
34 import java.util.ArrayList;
35 import java.util.List;
36 import java.util.concurrent.TimeUnit;
37 
38 /**
39  * Tests for AwContentsClient.onRenderProcessGone callback.
40  */
41 @RunWith(AwJUnit4ClassRunner.class)
42 public class AwContentsClientOnRenderProcessGoneTest {
43     private static final String TAG = "AwRendererGone";
44     @Rule
45     public AwActivityTestRule mActivityTestRule = new AwActivityTestRule();
46 
47     private TestWebServer mWebServer;
48     private RenderProcessGoneTestAwContentsClient mContentsClient;
49     private AwTestContainerView mTestView;
50     private AwContents mAwContents;
51 
52     @Before
setUp()53     public void setUp() throws Exception {
54         mWebServer = TestWebServer.start();
55         mContentsClient = new RenderProcessGoneTestAwContentsClient();
56         mTestView = mActivityTestRule.createAwTestContainerViewOnMainSync(mContentsClient);
57         mAwContents = mTestView.getAwContents();
58     }
59 
60     @After
tearDown()61     public void tearDown() {
62         mWebServer.shutdown();
63     }
64 
addPageToTestServer(String httpPath, String html)65     private String addPageToTestServer(String httpPath, String html) {
66         List<Pair<String, String>> headers = new ArrayList<Pair<String, String>>();
67         headers.add(Pair.create("Content-Type", "text/html"));
68         headers.add(Pair.create("Cache-Control", "no-store"));
69         return mWebServer.setResponse(httpPath, html, headers);
70     }
71 
72     private static class GetRenderProcessGoneHelper extends CallbackHelper {
73         private AwRenderProcessGoneDetail mDetail;
74 
getAwRenderProcessGoneDetail()75         public AwRenderProcessGoneDetail getAwRenderProcessGoneDetail() {
76             assert getCallCount() > 0;
77             return mDetail;
78         }
79 
notifyCalled(AwRenderProcessGoneDetail detail)80         public void notifyCalled(AwRenderProcessGoneDetail detail) {
81             mDetail = detail;
82             notifyCalled();
83         }
84     }
85 
86     private static class RenderProcessGoneTestAwContentsClient extends TestAwContentsClient {
87 
88         private GetRenderProcessGoneHelper mGetRenderProcessGoneHelper;
89 
RenderProcessGoneTestAwContentsClient()90         public RenderProcessGoneTestAwContentsClient() {
91             mGetRenderProcessGoneHelper = new GetRenderProcessGoneHelper();
92         }
93 
getGetRenderProcessGoneHelper()94         public GetRenderProcessGoneHelper getGetRenderProcessGoneHelper() {
95             return mGetRenderProcessGoneHelper;
96         }
97 
98         @Override
onRenderProcessGone(AwRenderProcessGoneDetail detail)99         public boolean onRenderProcessGone(AwRenderProcessGoneDetail detail) {
100             mGetRenderProcessGoneHelper.notifyCalled(detail);
101             return true;
102         }
103     }
104 
105     interface Terminator {
terminate()106         void terminate();
107     }
108 
createAndTerminateRenderProcess( Terminator terminator, boolean expectCrash)109     private AwRenderProcess createAndTerminateRenderProcess(
110             Terminator terminator, boolean expectCrash) throws Throwable {
111         GetRenderProcessGoneHelper helper = mContentsClient.getGetRenderProcessGoneHelper();
112 
113         final AwRenderProcess renderProcess =
114                 TestThreadUtils.runOnUiThreadBlocking(() -> mAwContents.getRenderProcess());
115 
116         // Ensure that the renderer has started.
117         mActivityTestRule.loadUrlSync(mAwContents, mContentsClient.getOnPageFinishedHelper(),
118                 ContentUrlConstants.ABOUT_BLANK_DISPLAY_URL);
119 
120         // Terminate the renderer.
121         PostTask.runOrPostTask(UiThreadTaskTraits.DEFAULT, () -> terminator.terminate());
122 
123         // Assert that onRenderProcessGone is called once.
124         int callCount = helper.getCallCount();
125         helper.waitForCallback(callCount, 1, CallbackHelper.WAIT_TIMEOUT_SECONDS * 5,
126                 TimeUnit.SECONDS);
127         Assert.assertEquals(callCount + 1, helper.getCallCount());
128         Assert.assertEquals(helper.getAwRenderProcessGoneDetail().didCrash(), expectCrash);
129         Assert.assertEquals(
130                 RendererPriority.HIGH, helper.getAwRenderProcessGoneDetail().rendererPriority());
131 
132         return renderProcess;
133     }
134 
135     @Test
136     @DisabledTest // http://crbug.com/689292
137     @Feature({"AndroidWebView"})
138     @SmallTest
139     @OnlyRunIn(MULTI_PROCESS)
testOnRenderProcessCrash()140     public void testOnRenderProcessCrash() throws Throwable {
141         createAndTerminateRenderProcess(() -> { mAwContents.loadUrl("chrome://crash"); }, true);
142     }
143 
144     @Test
145     @Feature({"AndroidWebView"})
146     @SmallTest
147     @OnlyRunIn(MULTI_PROCESS)
testOnRenderProcessKill()148     public void testOnRenderProcessKill() throws Throwable {
149         createAndTerminateRenderProcess(() -> { mAwContents.loadUrl("chrome://kill"); }, false);
150     }
151 
152     @Test
153     @Feature({"AndroidWebView"})
154     @SmallTest
155     @OnlyRunIn(MULTI_PROCESS)
testRenderProcessTermination()156     public void testRenderProcessTermination() throws Throwable {
157         createAndTerminateRenderProcess(
158                 () -> { mAwContents.getRenderProcess().terminate(); }, false);
159     }
160 
161     @Test
162     @Feature({"AndroidWebView"})
163     @SmallTest
164     @OnlyRunIn(MULTI_PROCESS)
testRenderProcessDifferentAfterRestart()165     public void testRenderProcessDifferentAfterRestart() throws Throwable {
166         AwRenderProcess renderProcess1 = createAndTerminateRenderProcess(
167                 () -> { mAwContents.getRenderProcess().terminate(); }, false);
168         AwRenderProcess renderProcess2 = createAndTerminateRenderProcess(
169                 () -> { mAwContents.getRenderProcess().terminate(); }, false);
170         Assert.assertNotEquals(renderProcess1, renderProcess2);
171     }
172 
173     @Test
174     @Feature({"AndroidWebView"})
175     @SmallTest
176     @OnlyRunIn(MULTI_PROCESS)
testRenderProcessCanNotTerminateBeforeStart()177     public void testRenderProcessCanNotTerminateBeforeStart() throws Throwable {
178         Assert.assertFalse(TestThreadUtils.runOnUiThreadBlocking(
179                 () -> mAwContents.getRenderProcess().terminate()));
180     }
181 
182     @Test
183     @Feature({"AndroidWebView"})
184     @SmallTest
185     @OnlyRunIn(MULTI_PROCESS)
testRenderProcessSameBeforeAndAfterStart()186     public void testRenderProcessSameBeforeAndAfterStart() throws Throwable {
187         AwRenderProcess renderProcessBeforeStart =
188                 TestThreadUtils.runOnUiThreadBlocking(() -> mAwContents.getRenderProcess());
189 
190         // Ensure that the renderer has started.
191         mActivityTestRule.loadUrlSync(mAwContents, mContentsClient.getOnPageFinishedHelper(),
192                 ContentUrlConstants.ABOUT_BLANK_DISPLAY_URL);
193 
194         AwRenderProcess renderProcessAfterStart =
195                 TestThreadUtils.runOnUiThreadBlocking(() -> mAwContents.getRenderProcess());
196 
197         Assert.assertEquals(renderProcessBeforeStart, renderProcessAfterStart);
198     }
199 
200     @Test
201     @Feature({"AndroidWebView"})
202     @SmallTest
203     @OnlyRunIn(MULTI_PROCESS)
204     // The RenderDocument feature has a "level" parameter. This enables the feature and sets the
205     // default level to "crashed-frame" to enable replacing the render frame host of crashed frames
206     // with a new render frame host (instead of resuing the old one). See
207     // https://go/force-field-trials-docs for the syntax of these flags.
208     @CommandLineFlags.Add({
209             "enable-features=RenderDocument<RenderDocument",
210             "force-fieldtrials=RenderDocument/Group1",
211             "force-fieldtrial-params=RenderDocument.Group1:level/crashed-frame",
212     })
213     public void
testNavigationAfterCrashAndJavaScript()214     testNavigationAfterCrashAndJavaScript() throws Throwable {
215         // In https://crbug.com/1006814, a crashed frame, reinitialized by running JS fails to
216         // navigate.
217         mActivityTestRule.getAwSettingsOnUiThread(mAwContents).setJavaScriptEnabled(true);
218         // Crash the frame.
219         AwRenderProcess renderProcess1 = createAndTerminateRenderProcess(
220                 () -> { mAwContents.getRenderProcess().terminate(); }, false);
221         // Run JS in the frame.
222         Assert.assertEquals("3",
223                 mActivityTestRule.executeJavaScriptAndWaitForResult(
224                         mAwContents, mContentsClient, "1+2"));
225         // Navigate to somewhere else. This should not crash the frame.
226         final String content = "some content";
227         final String httpPathOnServer = addPageToTestServer("/page.html", content);
228         mActivityTestRule.loadUrlSync(
229                 mAwContents, mContentsClient.getOnPageFinishedHelper(), httpPathOnServer);
230         // Result is a string, so needs "".
231         Assert.assertEquals("\"" + content + "\"",
232                 mActivityTestRule.executeJavaScriptAndWaitForResult(
233                         mAwContents, mContentsClient, "document.body.textContent"));
234     }
235 }
236