1 // Copyright 2019 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 android.net.Uri;
8 import android.webkit.JavascriptInterface;
9 
10 import androidx.test.filters.MediumTest;
11 import androidx.test.filters.SmallTest;
12 
13 import org.junit.Assert;
14 import org.junit.Before;
15 import org.junit.ClassRule;
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.JsReplyProxy;
22 import org.chromium.android_webview.ScriptReference;
23 import org.chromium.android_webview.WebMessageListener;
24 import org.chromium.android_webview.test.TestAwContentsClient.OnReceivedTitleHelper;
25 import org.chromium.android_webview.test.util.CommonResources;
26 import org.chromium.base.test.util.Batch;
27 import org.chromium.base.test.util.Feature;
28 import org.chromium.content_public.browser.MessagePort;
29 import org.chromium.content_public.browser.test.util.TestCallbackHelperContainer.OnPageFinishedHelper;
30 import org.chromium.content_public.browser.test.util.TestThreadUtils;
31 import org.chromium.net.test.EmbeddedTestServer;
32 import org.chromium.net.test.EmbeddedTestServerRule;
33 import org.chromium.net.test.util.TestWebServer;
34 
35 import java.util.concurrent.LinkedBlockingQueue;
36 
37 /**
38  * Test suite for JavaScript Java interaction.
39  */
40 @RunWith(AwJUnit4ClassRunner.class)
41 @Batch(Batch.PER_CLASS)
42 public class JsJavaInteractionTest {
43     @Rule
44     public AwActivityTestRule mActivityTestRule = new AwActivityTestRule();
45     @ClassRule
46     public static EmbeddedTestServerRule sTestServerRule = new EmbeddedTestServerRule();
47 
48     private static final String RESOURCE_PATH = "/android_webview/test/data";
49     private static final String POST_MESSAGE_SIMPLE_HTML =
50             RESOURCE_PATH + "/post_message_simple.html";
51     private static final String POST_MESSAGE_WITH_PORTS_HTML =
52             RESOURCE_PATH + "/post_message_with_ports.html";
53     private static final String POST_MESSAGE_REPEAT_HTML =
54             RESOURCE_PATH + "/post_message_repeat.html";
55     private static final String POST_MESSAGE_REPLY_HTML =
56             RESOURCE_PATH + "/post_message_receives_reply.html";
57     private static final String FILE_URI = "file:///android_asset/asset_file.html";
58     private static final String HELLO_WORLD_HTML = RESOURCE_PATH + "/hello_world.html";
59 
60     private static final String HELLO = "Hello";
61     private static final String NEW_TITLE = "new_title";
62     private static final String JS_OBJECT_NAME = "myObject";
63     private static final String JS_OBJECT_NAME_2 = "myObject2";
64     private static final String DATA_HTML = "<html><body>data</body></html>";
65     private static final int MESSAGE_COUNT = 10000;
66 
67     private EmbeddedTestServer mTestServer;
68     private TestAwContentsClient mContentsClient;
69     private AwContents mAwContents;
70     private TestWebMessageListener mListener;
71 
72     private static class TestWebMessageListener implements WebMessageListener {
73         private LinkedBlockingQueue<Data> mQueue = new LinkedBlockingQueue<>();
74 
75         public static class Data {
76             public String mMessage;
77             public Uri mSourceOrigin;
78             public boolean mIsMainFrame;
79             public JsReplyProxy mReplyProxy;
80             public MessagePort[] mPorts;
81 
Data(String message, Uri sourceOrigin, boolean isMainFrame, JsReplyProxy replyProxy, MessagePort[] ports)82             public Data(String message, Uri sourceOrigin, boolean isMainFrame,
83                     JsReplyProxy replyProxy, MessagePort[] ports) {
84                 mMessage = message;
85                 mSourceOrigin = sourceOrigin;
86                 mIsMainFrame = isMainFrame;
87                 mReplyProxy = replyProxy;
88                 mPorts = ports;
89             }
90         }
91 
92         @Override
onPostMessage(String message, Uri sourceOrigin, boolean isMainFrame, JsReplyProxy replyProxy, MessagePort[] ports)93         public void onPostMessage(String message, Uri sourceOrigin, boolean isMainFrame,
94                 JsReplyProxy replyProxy, MessagePort[] ports) {
95             mQueue.add(new Data(message, sourceOrigin, isMainFrame, replyProxy, ports));
96         }
97 
waitForOnPostMessage()98         public Data waitForOnPostMessage() throws Exception {
99             return AwActivityTestRule.waitForNextQueueElement(mQueue);
100         }
101 
hasNoMoreOnPostMessage()102         public boolean hasNoMoreOnPostMessage() {
103             return mQueue.isEmpty();
104         }
105     }
106 
107     @Before
setUp()108     public void setUp() throws Exception {
109         mContentsClient = new TestAwContentsClient();
110         final AwTestContainerView testContainerView =
111                 mActivityTestRule.createAwTestContainerViewOnMainSync(mContentsClient);
112         mAwContents = testContainerView.getAwContents();
113         mListener = new TestWebMessageListener();
114         mActivityTestRule.getAwSettingsOnUiThread(mAwContents).setJavaScriptEnabled(true);
115         mTestServer = sTestServerRule.getServer();
116     }
117 
118     @Test
119     @SmallTest
120     @Feature({"AndroidWebView", "JsJavaInteraction"})
testPostMessageSimple()121     public void testPostMessageSimple() throws Throwable {
122         addWebMessageListenerOnUiThread(mAwContents, JS_OBJECT_NAME, new String[] {"*"}, mListener);
123 
124         final String url = loadUrlFromPath(POST_MESSAGE_SIMPLE_HTML);
125 
126         TestWebMessageListener.Data data = mListener.waitForOnPostMessage();
127 
128         assertUrlHasOrigin(url, data.mSourceOrigin);
129         Assert.assertEquals(HELLO, data.mMessage);
130         Assert.assertTrue(data.mIsMainFrame);
131         Assert.assertEquals(0, data.mPorts.length);
132 
133         Assert.assertTrue(mListener.hasNoMoreOnPostMessage());
134     }
135 
136     @Test
137     @SmallTest
138     @Feature({"AndroidWebView", "JsJavaInteraction"})
testPostMessage_LoadData_MessageHasStringNullOrigin()139     public void testPostMessage_LoadData_MessageHasStringNullOrigin() throws Throwable {
140         addWebMessageListenerOnUiThread(mAwContents, JS_OBJECT_NAME, new String[] {"*"}, mListener);
141 
142         final String html = "<html><head><script>myObject.postMessage('Hello');</script></head>"
143                 + "<body></body></html>";
144 
145         // This uses loadDataAsync() which is equivalent to WebView#loadData(...).
146         mActivityTestRule.loadHtmlSync(
147                 mAwContents, mContentsClient.getOnPageFinishedHelper(), html);
148 
149         TestWebMessageListener.Data data = mListener.waitForOnPostMessage();
150 
151         // Note that the source origin is a non-null string of n, u, l, l.
152         Assert.assertNotNull(data.mSourceOrigin);
153         Assert.assertEquals("null", data.mSourceOrigin.toString());
154 
155         Assert.assertEquals(HELLO, data.mMessage);
156         Assert.assertTrue(data.mIsMainFrame);
157         Assert.assertEquals(0, data.mPorts.length);
158 
159         Assert.assertTrue(mListener.hasNoMoreOnPostMessage());
160     }
161 
162     @Test
163     @SmallTest
164     @Feature({"AndroidWebView", "JsJavaInteraction"})
testPostMessageWithPorts()165     public void testPostMessageWithPorts() throws Throwable {
166         addWebMessageListenerOnUiThread(mAwContents, JS_OBJECT_NAME, new String[] {"*"}, mListener);
167 
168         final String url = loadUrlFromPath(POST_MESSAGE_WITH_PORTS_HTML);
169 
170         TestWebMessageListener.Data data = mListener.waitForOnPostMessage();
171 
172         final MessagePort[] ports = data.mPorts;
173         assertUrlHasOrigin(url, data.mSourceOrigin);
174         Assert.assertEquals(HELLO, data.mMessage);
175         Assert.assertEquals(1, ports.length);
176 
177         // JavaScript code in the page will change the title to NEW_TITLE if postMessage on
178         // this port succeed.
179         final OnReceivedTitleHelper onReceivedTitleHelper =
180                 mContentsClient.getOnReceivedTitleHelper();
181         final int titleCallCount = onReceivedTitleHelper.getCallCount();
182         ports[0].postMessage(NEW_TITLE, new MessagePort[0]);
183         onReceivedTitleHelper.waitForCallback(titleCallCount);
184 
185         Assert.assertEquals(NEW_TITLE, onReceivedTitleHelper.getTitle());
186 
187         Assert.assertTrue(mListener.hasNoMoreOnPostMessage());
188     }
189 
190     @Test
191     @MediumTest
192     @Feature({"AndroidWebView", "JsJavaInteraction"})
testPostMessageRepeated()193     public void testPostMessageRepeated() throws Throwable {
194         addWebMessageListenerOnUiThread(mAwContents, JS_OBJECT_NAME, new String[] {"*"}, mListener);
195 
196         final String url = loadUrlFromPath(POST_MESSAGE_REPEAT_HTML);
197         for (int i = 0; i < MESSAGE_COUNT; ++i) {
198             TestWebMessageListener.Data data = mListener.waitForOnPostMessage();
199             assertUrlHasOrigin(url, data.mSourceOrigin);
200             Assert.assertEquals(HELLO + ":" + i, data.mMessage);
201         }
202 
203         Assert.assertTrue(mListener.hasNoMoreOnPostMessage());
204     }
205 
206     @Test
207     @SmallTest
208     @Feature({"AndroidWebView", "JsJavaInteraction"})
testPostMessageFromIframeWorks()209     public void testPostMessageFromIframeWorks() throws Throwable {
210         addWebMessageListenerOnUiThread(mAwContents, JS_OBJECT_NAME, new String[] {"*"}, mListener);
211 
212         final String frameUrl = mTestServer.getURL(POST_MESSAGE_SIMPLE_HTML);
213         final String html = createCrossOriginAccessTestPageHtml(frameUrl);
214 
215         // Load a cross origin iframe page.
216         mActivityTestRule.loadDataWithBaseUrlSync(mAwContents,
217                 mContentsClient.getOnPageFinishedHelper(), html, "text/html", false,
218                 "http://www.google.com", null);
219 
220         TestWebMessageListener.Data data = mListener.waitForOnPostMessage();
221 
222         assertUrlHasOrigin(frameUrl, data.mSourceOrigin);
223         Assert.assertEquals(HELLO, data.mMessage);
224         Assert.assertFalse(data.mIsMainFrame);
225         Assert.assertEquals(0, data.mPorts.length);
226 
227         Assert.assertTrue(mListener.hasNoMoreOnPostMessage());
228     }
229 
230     @Test
231     @SmallTest
232     @Feature({"AndroidWebView", "JsJavaInteraction"})
testAddWebMessageListenerAfterPageLoadWontAffectCurrentPage()233     public void testAddWebMessageListenerAfterPageLoadWontAffectCurrentPage() throws Throwable {
234         loadUrlFromPath(POST_MESSAGE_SIMPLE_HTML);
235 
236         // Add WebMessageListener after the page loaded won't affect the current page.
237         addWebMessageListenerOnUiThread(mAwContents, JS_OBJECT_NAME, new String[] {"*"}, mListener);
238 
239         // Check that we don't have a JavaScript object named JS_OBJECT_NAME
240         Assert.assertFalse(hasJavaScriptObject(
241                 JS_OBJECT_NAME, mActivityTestRule, mAwContents, mContentsClient));
242 
243         // We shouldn't have executed postMessage on JS_OBJECT_NAME either.
244         Assert.assertTrue(mListener.hasNoMoreOnPostMessage());
245     }
246 
247     @Test
248     @SmallTest
249     @Feature({"AndroidWebView", "JsJavaInteraction"})
testAddTheSameWebMessageListenerForDifferentJsObjectsWorks()250     public void testAddTheSameWebMessageListenerForDifferentJsObjectsWorks() throws Throwable {
251         addWebMessageListenerOnUiThread(mAwContents, JS_OBJECT_NAME, new String[] {"*"}, mListener);
252         addWebMessageListenerOnUiThread(
253                 mAwContents, JS_OBJECT_NAME_2, new String[] {"*"}, mListener);
254 
255         loadUrlFromPath(POST_MESSAGE_SIMPLE_HTML);
256 
257         TestWebMessageListener.Data data = mListener.waitForOnPostMessage();
258         Assert.assertEquals(HELLO, data.mMessage);
259 
260         mActivityTestRule.executeJavaScriptAndWaitForResult(
261                 mAwContents, mContentsClient, JS_OBJECT_NAME_2 + ".postMessage('" + HELLO + "');");
262 
263         TestWebMessageListener.Data data2 = mListener.waitForOnPostMessage();
264         Assert.assertEquals(HELLO, data2.mMessage);
265     }
266 
267     @Test
268     @SmallTest
269     @Feature({"AndroidWebView", "JsJavaInteraction"})
testFragmentNavigationWontDoJsInjection()270     public void testFragmentNavigationWontDoJsInjection() throws Throwable {
271         String url = loadUrlFromPath(POST_MESSAGE_SIMPLE_HTML);
272 
273         // Add WebMessageListener after the page loaded won't affect the current page.
274         addWebMessageListenerOnUiThread(mAwContents, JS_OBJECT_NAME, new String[] {"*"}, mListener);
275 
276         // Load with fragment url.
277         mActivityTestRule.loadUrlSync(
278                 mAwContents, mContentsClient.getOnPageFinishedHelper(), url + "#fragment");
279 
280         // Check that we don't have a JavaScript object named JS_OBJECT_NAME
281         Assert.assertFalse(hasJavaScriptObject(
282                 JS_OBJECT_NAME, mActivityTestRule, mAwContents, mContentsClient));
283 
284         // We shouldn't have executed postMessage on JS_OBJECT_NAME either.
285         Assert.assertTrue(mListener.hasNoMoreOnPostMessage());
286     }
287 
288     @Test
289     @SmallTest
290     @Feature({"AndroidWebView", "JsJavaInteraction"})
testAddWebMessageListenerAffectsRendererInitiatedNavigation()291     public void testAddWebMessageListenerAffectsRendererInitiatedNavigation() throws Throwable {
292         // TODO(crbug.com/969842): We'd either replace the following html file with a file contains
293         // no JavaScript code or add a test to ensure that evaluateJavascript() won't
294         // over-trigger DidClearWindowObject.
295         loadUrlFromPath(POST_MESSAGE_WITH_PORTS_HTML);
296 
297         // Add WebMessageListener after the page loaded.
298         addWebMessageListenerOnUiThread(mAwContents, JS_OBJECT_NAME, new String[] {"*"}, mListener);
299 
300         // Check that we don't have a JavaScript object named JS_OBJECT_NAME
301         Assert.assertFalse(hasJavaScriptObject(
302                 JS_OBJECT_NAME, mActivityTestRule, mAwContents, mContentsClient));
303         Assert.assertTrue(mListener.hasNoMoreOnPostMessage());
304 
305         // Navigate to a different web page from renderer and wait until the page loading finished.
306         final String url = mTestServer.getURL(POST_MESSAGE_SIMPLE_HTML);
307         final OnPageFinishedHelper onPageFinishedHelper = mContentsClient.getOnPageFinishedHelper();
308         final int currentCallCount = onPageFinishedHelper.getCallCount();
309         TestThreadUtils.runOnUiThreadBlocking(
310                 ()
311                         -> mAwContents.evaluateJavaScriptForTests(
312                                 "window.location.href = '" + url + "';", null));
313         onPageFinishedHelper.waitForCallback(currentCallCount);
314 
315         // We should expect one onPostMessage event.
316         TestWebMessageListener.Data data = mListener.waitForOnPostMessage();
317 
318         assertUrlHasOrigin(url, data.mSourceOrigin);
319         Assert.assertEquals(HELLO, data.mMessage);
320         Assert.assertEquals(0, data.mPorts.length);
321 
322         Assert.assertTrue(mListener.hasNoMoreOnPostMessage());
323     }
324 
325     @Test
326     @SmallTest
327     @Feature({"AndroidWebView", "JsJavaInteraction"})
testAddWebMessageListenerWontAffectOtherAwContents()328     public void testAddWebMessageListenerWontAffectOtherAwContents() throws Throwable {
329         // Create another AwContents object.
330         final TestAwContentsClient awContentsClient = new TestAwContentsClient();
331         final AwTestContainerView awTestContainerView =
332                 mActivityTestRule.createAwTestContainerViewOnMainSync(awContentsClient);
333         final AwContents otherAwContents = awTestContainerView.getAwContents();
334         AwActivityTestRule.enableJavaScriptOnUiThread(otherAwContents);
335 
336         addWebMessageListenerOnUiThread(mAwContents, JS_OBJECT_NAME, new String[] {"*"}, mListener);
337 
338         final String url = mTestServer.getURL(POST_MESSAGE_SIMPLE_HTML);
339         mActivityTestRule.loadUrlSync(mAwContents, mContentsClient.getOnPageFinishedHelper(), url);
340         mActivityTestRule.loadUrlSync(
341                 otherAwContents, awContentsClient.getOnPageFinishedHelper(), url);
342 
343         // Verify that WebMessageListener was set successfually on mAwContents.
344         TestWebMessageListener.Data data = mListener.waitForOnPostMessage();
345 
346         assertUrlHasOrigin(url, data.mSourceOrigin);
347         Assert.assertEquals(HELLO, data.mMessage);
348         Assert.assertEquals(0, data.mPorts.length);
349 
350         Assert.assertTrue(mListener.hasNoMoreOnPostMessage());
351 
352         // Verify that we don't have myObject injected to otherAwContents.
353         Assert.assertFalse(hasJavaScriptObject(
354                 JS_OBJECT_NAME, mActivityTestRule, otherAwContents, awContentsClient));
355     }
356 
357     @Test
358     @SmallTest
359     @Feature({"AndroidWebView", "JsJavaInteraction"})
testAddWebMessageListenerAllowsCertainUrlWorksWithIframe()360     public void testAddWebMessageListenerAllowsCertainUrlWorksWithIframe() throws Throwable {
361         final String frameUrl = mTestServer.getURL(POST_MESSAGE_SIMPLE_HTML);
362         final String html = createCrossOriginAccessTestPageHtml(frameUrl);
363         addWebMessageListenerOnUiThread(
364                 mAwContents, JS_OBJECT_NAME, new String[] {parseOrigin(frameUrl)}, mListener);
365 
366         mActivityTestRule.loadDataWithBaseUrlSync(mAwContents,
367                 mContentsClient.getOnPageFinishedHelper(), html, "text/html", false,
368                 "http://www.google.com", null);
369 
370         // The iframe should have myObject injected.
371         TestWebMessageListener.Data data = mListener.waitForOnPostMessage();
372         Assert.assertEquals(HELLO, data.mMessage);
373 
374         Assert.assertTrue(mListener.hasNoMoreOnPostMessage());
375 
376         // Verify that the main frame has no myObject injected.
377         Assert.assertFalse(hasJavaScriptObject(
378                 JS_OBJECT_NAME, mActivityTestRule, mAwContents, mContentsClient));
379     }
380 
381     @Test
382     @SmallTest
383     @Feature({"AndroidWebView", "JsJavaInteraction"})
testRemoveWebMessageListener_preventInjectionForNextPageLoad()384     public void testRemoveWebMessageListener_preventInjectionForNextPageLoad() throws Throwable {
385         addWebMessageListenerOnUiThread(mAwContents, JS_OBJECT_NAME, new String[] {"*"}, mListener);
386 
387         // Load the the page.
388         loadUrlFromPath(POST_MESSAGE_SIMPLE_HTML);
389 
390         TestWebMessageListener.Data data = mListener.waitForOnPostMessage();
391         Assert.assertEquals(HELLO, data.mMessage);
392 
393         Assert.assertTrue(mListener.hasNoMoreOnPostMessage());
394 
395         // Remove WebMessageListener will disable injection for next page load.
396         removeWebMessageListenerOnUiThread(mAwContents, JS_OBJECT_NAME);
397 
398         loadUrlFromPath(POST_MESSAGE_SIMPLE_HTML);
399 
400         // Should have no myObject injected.
401         Assert.assertFalse(hasJavaScriptObject(
402                 JS_OBJECT_NAME, mActivityTestRule, mAwContents, mContentsClient));
403     }
404 
405     @Test
406     @SmallTest
407     @Feature({"AndroidWebView", "JsJavaInteraction"})
testRemoveWebMessageListener_cutJsJavaMappingImmediately()408     public void testRemoveWebMessageListener_cutJsJavaMappingImmediately() throws Throwable {
409         addWebMessageListenerOnUiThread(mAwContents, JS_OBJECT_NAME, new String[] {"*"}, mListener);
410 
411         // Load the the page.
412         loadUrlFromPath(POST_MESSAGE_SIMPLE_HTML);
413 
414         TestWebMessageListener.Data data = mListener.waitForOnPostMessage();
415         Assert.assertEquals(HELLO, data.mMessage);
416 
417         Assert.assertTrue(mListener.hasNoMoreOnPostMessage());
418 
419         // Remove WebMessageListener will disable injection for next page load.
420         removeWebMessageListenerOnUiThread(mAwContents, JS_OBJECT_NAME);
421 
422         // Should still have myObject.
423         Assert.assertTrue(hasJavaScriptObject(
424                 JS_OBJECT_NAME, mActivityTestRule, mAwContents, mContentsClient));
425 
426         // But posting message on myObject will be dropped.
427         mActivityTestRule.executeJavaScriptAndWaitForResult(
428                 mAwContents, mContentsClient, JS_OBJECT_NAME + ".postMessage('" + HELLO + "');");
429         Assert.assertTrue(mListener.hasNoMoreOnPostMessage());
430     }
431 
432     @Test
433     @SmallTest
434     @Feature({"AndroidWebView", "JsJavaInteraction"})
testRemoveWebMessageListener_removeWithNoAddWebMessageListener()435     public void testRemoveWebMessageListener_removeWithNoAddWebMessageListener() throws Throwable {
436         // Call removeWebMessageListener() without addWebMessageListener() shouldn't fail.
437         removeWebMessageListenerOnUiThread(mAwContents, JS_OBJECT_NAME);
438 
439         loadUrlFromPath(POST_MESSAGE_SIMPLE_HTML);
440     }
441 
442     @Test
443     @SmallTest
444     @Feature({"AndroidWebView", "JsJavaInteraction"})
testRemoveWebMessageListener_removeBeforeLoadPage()445     public void testRemoveWebMessageListener_removeBeforeLoadPage() throws Throwable {
446         addWebMessageListenerOnUiThread(mAwContents, JS_OBJECT_NAME, new String[] {"*"}, mListener);
447         removeWebMessageListenerOnUiThread(mAwContents, JS_OBJECT_NAME);
448 
449         loadUrlFromPath(POST_MESSAGE_SIMPLE_HTML);
450 
451         Assert.assertFalse(hasJavaScriptObject(
452                 JS_OBJECT_NAME, mActivityTestRule, mAwContents, mContentsClient));
453     }
454 
455     @Test
456     @SmallTest
457     @Feature({"AndroidWebView", "JsJavaInteraction"})
testRemoveWebMessageListener_extraRemove()458     public void testRemoveWebMessageListener_extraRemove() throws Throwable {
459         addWebMessageListenerOnUiThread(mAwContents, JS_OBJECT_NAME, new String[] {"*"}, mListener);
460         removeWebMessageListenerOnUiThread(mAwContents, JS_OBJECT_NAME);
461         // Extra removeWebMessageListener() does nothing.
462         removeWebMessageListenerOnUiThread(mAwContents, JS_OBJECT_NAME);
463 
464         loadUrlFromPath(POST_MESSAGE_SIMPLE_HTML);
465 
466         Assert.assertFalse(hasJavaScriptObject(
467                 JS_OBJECT_NAME, mActivityTestRule, mAwContents, mContentsClient));
468     }
469 
470     @Test
471     @MediumTest
472     @Feature({"AndroidWebView", "JsJavaInteraction"})
testAllowedOriginsWorksForVariousBaseUrls()473     public void testAllowedOriginsWorksForVariousBaseUrls() throws Throwable {
474         // Set a typical rule.
475         addWebMessageListenerOnUiThread(mAwContents, JS_OBJECT_NAME,
476                 new String[] {"https://www.example.com:443"}, mListener);
477 
478         Assert.assertTrue(
479                 isJsObjectInjectedWhenLoadingUrl("https://www.example.com", JS_OBJECT_NAME));
480         Assert.assertFalse(
481                 isJsObjectInjectedWhenLoadingUrl("https://www.example.com:8080", JS_OBJECT_NAME));
482         Assert.assertFalse(
483                 isJsObjectInjectedWhenLoadingUrl("http://www.example.com", JS_OBJECT_NAME));
484         Assert.assertFalse(isJsObjectInjectedWhenLoadingUrl("http://example.com", JS_OBJECT_NAME));
485         Assert.assertFalse(
486                 isJsObjectInjectedWhenLoadingUrl("https://www.google.com", JS_OBJECT_NAME));
487         Assert.assertFalse(isJsObjectInjectedWhenLoadingUrl("file://etc", JS_OBJECT_NAME));
488         Assert.assertFalse(isJsObjectInjectedWhenLoadingUrl("ftp://example.com", JS_OBJECT_NAME));
489         Assert.assertFalse(isJsObjectInjectedWhenLoadingUrl(null, JS_OBJECT_NAME));
490 
491         // Inject to all frames.
492         addWebMessageListenerOnUiThread(
493                 mAwContents, JS_OBJECT_NAME_2, new String[] {"*"}, mListener);
494 
495         Assert.assertTrue(
496                 isJsObjectInjectedWhenLoadingUrl("https://www.example.com", JS_OBJECT_NAME_2));
497         Assert.assertTrue(
498                 isJsObjectInjectedWhenLoadingUrl("https://www.example.com:8080", JS_OBJECT_NAME_2));
499         Assert.assertTrue(
500                 isJsObjectInjectedWhenLoadingUrl("http://www.example.com", JS_OBJECT_NAME_2));
501         Assert.assertTrue(isJsObjectInjectedWhenLoadingUrl("http://example.com", JS_OBJECT_NAME_2));
502         Assert.assertTrue(
503                 isJsObjectInjectedWhenLoadingUrl("https://www.google.com", JS_OBJECT_NAME_2));
504         Assert.assertTrue(isJsObjectInjectedWhenLoadingUrl("file://etc", JS_OBJECT_NAME_2));
505         Assert.assertTrue(isJsObjectInjectedWhenLoadingUrl("ftp://example.com", JS_OBJECT_NAME_2));
506         Assert.assertTrue(isJsObjectInjectedWhenLoadingUrl(null, JS_OBJECT_NAME_2));
507 
508         // WebView doesn't support ftp with loadUrl() but ftp scheme could happen with
509         // loadDataWithBaseUrl().
510         final String jsObjectName3 = JS_OBJECT_NAME + "3";
511         addWebMessageListenerOnUiThread(
512                 mAwContents, jsObjectName3, new String[] {"ftp://"}, mListener);
513         // ftp is a standard scheme, so the origin will be "ftp://example.com", however we don't
514         // support host rule for ftp://, so it won't do the injection.
515         Assert.assertFalse(isJsObjectInjectedWhenLoadingUrl("ftp://example.com", jsObjectName3));
516 
517         // file scheme.
518         final String jsObjectName4 = JS_OBJECT_NAME + "4";
519         addWebMessageListenerOnUiThread(
520                 mAwContents, jsObjectName4, new String[] {"file://"}, mListener);
521         Assert.assertTrue(isJsObjectInjectedWhenLoadingUrl("file://etc", jsObjectName4));
522 
523         // Pass an URI instead of origin shouldn't work.
524         final String jsObjectName5 = JS_OBJECT_NAME + "5";
525         try {
526             addWebMessageListenerOnUiThread(mAwContents, jsObjectName5,
527                     new String[] {"https://www.example.com/index.html"}, mListener);
528             Assert.fail("allowedOriginRules shouldn't be url like");
529         } catch (RuntimeException e) {
530             // Should catch IllegalArgumentException in the end of the re-throw chain.
531             Assert.assertTrue(getRootCauseException(e) instanceof IllegalArgumentException);
532         }
533         Assert.assertFalse(
534                 isJsObjectInjectedWhenLoadingUrl("https://www.example.com", jsObjectName5));
535     }
536 
537     @Test
538     @MediumTest
539     @Feature({"AndroidWebView", "JsJavaInteraction"})
testDontAllowAddWebMessageLitenerWithTheSameJsObjectName()540     public void testDontAllowAddWebMessageLitenerWithTheSameJsObjectName() {
541         addWebMessageListenerOnUiThread(mAwContents, JS_OBJECT_NAME, new String[] {"*"}, mListener);
542         try {
543             addWebMessageListenerOnUiThread(
544                     mAwContents, JS_OBJECT_NAME, new String[] {"*"}, new TestWebMessageListener());
545             Assert.fail("Shouldn't allow the same Js object name be added more than once.");
546         } catch (RuntimeException e) {
547             // Should catch IllegalArgumentException in the end of the re-throw chain.
548             Assert.assertTrue(getRootCauseException(e) instanceof IllegalArgumentException);
549         }
550     }
551 
552     @Test
553     @MediumTest
554     @Feature({"AndroidWebView", "JsJavaInteraction"})
testAddWebMessageListener_SameOrigins()555     public void testAddWebMessageListener_SameOrigins() throws Throwable {
556         final String[] allowedOriginRules =
557                 new String[] {"https://www.example.com", "https://www.allowed.com"};
558         addWebMessageListenerOnUiThread(mAwContents, JS_OBJECT_NAME, allowedOriginRules, mListener);
559 
560         Assert.assertTrue(
561                 isJsObjectInjectedWhenLoadingUrl("https://www.example.com", JS_OBJECT_NAME));
562         Assert.assertTrue(
563                 isJsObjectInjectedWhenLoadingUrl("https://www.allowed.com", JS_OBJECT_NAME));
564         Assert.assertFalse(
565                 isJsObjectInjectedWhenLoadingUrl("https://www.noinjection.com", JS_OBJECT_NAME));
566 
567         // Call addWebMessageListener() with the same set of origins.
568         addWebMessageListenerOnUiThread(
569                 mAwContents, JS_OBJECT_NAME_2, allowedOriginRules, mListener);
570 
571         Assert.assertTrue(
572                 isJsObjectInjectedWhenLoadingUrl("https://www.example.com", JS_OBJECT_NAME_2));
573         Assert.assertTrue(
574                 isJsObjectInjectedWhenLoadingUrl("https://www.allowed.com", JS_OBJECT_NAME_2));
575         Assert.assertFalse(
576                 isJsObjectInjectedWhenLoadingUrl("https://www.noinjection.com", JS_OBJECT_NAME_2));
577     }
578 
579     @Test
580     @MediumTest
581     @Feature({"AndroidWebView", "JsJavaInteraction"})
testAddWebMessageListener_OverlappingSetOfOrigins()582     public void testAddWebMessageListener_OverlappingSetOfOrigins() throws Throwable {
583         final String[] allowedOriginRules1 =
584                 new String[] {"https://www.example.com", "https://www.allowed.com"};
585         addWebMessageListenerOnUiThread(
586                 mAwContents, JS_OBJECT_NAME, allowedOriginRules1, mListener);
587 
588         Assert.assertTrue(
589                 isJsObjectInjectedWhenLoadingUrl("https://www.example.com", JS_OBJECT_NAME));
590         Assert.assertTrue(
591                 isJsObjectInjectedWhenLoadingUrl("https://www.allowed.com", JS_OBJECT_NAME));
592         Assert.assertFalse(isJsObjectInjectedWhenLoadingUrl("https://www.ok.com", JS_OBJECT_NAME));
593         Assert.assertFalse(
594                 isJsObjectInjectedWhenLoadingUrl("https://www.noinjection.com", JS_OBJECT_NAME));
595 
596         final String[] allowedOriginRules2 =
597                 new String[] {"https://www.example.com", "https://www.ok.com"};
598         // Call addWebMessageListener with overlapping set of origins.
599         addWebMessageListenerOnUiThread(
600                 mAwContents, JS_OBJECT_NAME_2, allowedOriginRules2, mListener);
601 
602         Assert.assertTrue(
603                 isJsObjectInjectedWhenLoadingUrl("https://www.example.com", JS_OBJECT_NAME_2));
604         Assert.assertFalse(
605                 isJsObjectInjectedWhenLoadingUrl("https://www.allowed.com", JS_OBJECT_NAME_2));
606         Assert.assertTrue(isJsObjectInjectedWhenLoadingUrl("https://www.ok.com", JS_OBJECT_NAME_2));
607         Assert.assertFalse(
608                 isJsObjectInjectedWhenLoadingUrl("https://www.noinjection.com", JS_OBJECT_NAME_2));
609 
610         // Remove the listener should remove the js object from the next navigation.
611         removeWebMessageListenerOnUiThread(mAwContents, JS_OBJECT_NAME_2);
612 
613         Assert.assertFalse(
614                 isJsObjectInjectedWhenLoadingUrl("https://www.example.com", JS_OBJECT_NAME_2));
615         Assert.assertFalse(
616                 isJsObjectInjectedWhenLoadingUrl("https://www.allowed.com", JS_OBJECT_NAME_2));
617         Assert.assertFalse(
618                 isJsObjectInjectedWhenLoadingUrl("https://www.ok.com", JS_OBJECT_NAME_2));
619         Assert.assertFalse(
620                 isJsObjectInjectedWhenLoadingUrl("https://www.noinjection.com", JS_OBJECT_NAME_2));
621     }
622 
623     @Test
624     @MediumTest
625     @Feature({"AndroidWebView", "JsJavaInteraction"})
testAddWebMessageListener_dontInjectWhenMatchesImplicitRules()626     public void testAddWebMessageListener_dontInjectWhenMatchesImplicitRules() throws Throwable {
627         // allowedOriginRules is an empty String array, shouldn't inject the object to any frame.
628         addWebMessageListenerOnUiThread(mAwContents, JS_OBJECT_NAME, new String[0], mListener);
629 
630         // following are some origins allowed by implicit rules.
631         Assert.assertFalse(isJsObjectInjectedWhenLoadingUrl("http://127.0.0.1", JS_OBJECT_NAME));
632         Assert.assertFalse(isJsObjectInjectedWhenLoadingUrl("https://127.0.0.1", JS_OBJECT_NAME));
633         Assert.assertFalse(isJsObjectInjectedWhenLoadingUrl("http://localhost", JS_OBJECT_NAME));
634         Assert.assertFalse(isJsObjectInjectedWhenLoadingUrl("http://169.254.0.1", JS_OBJECT_NAME));
635         Assert.assertFalse(isJsObjectInjectedWhenLoadingUrl("http://localhost6", JS_OBJECT_NAME));
636         Assert.assertFalse(isJsObjectInjectedWhenLoadingUrl("http://[::1]", JS_OBJECT_NAME));
637     }
638 
639     @Test
640     @MediumTest
641     @Feature({"AndroidWebView", "JsJavaInteraction"})
testAddWebMessageListener_NonOverlappingSetOfOrigins()642     public void testAddWebMessageListener_NonOverlappingSetOfOrigins() throws Throwable {
643         final String[] allowedOriginRules1 =
644                 new String[] {"https://www.example.com", "https://www.allowed.com"};
645         addWebMessageListenerOnUiThread(
646                 mAwContents, JS_OBJECT_NAME, allowedOriginRules1, mListener);
647 
648         Assert.assertTrue(
649                 isJsObjectInjectedWhenLoadingUrl("https://www.example.com", JS_OBJECT_NAME));
650         Assert.assertTrue(
651                 isJsObjectInjectedWhenLoadingUrl("https://www.allowed.com", JS_OBJECT_NAME));
652         Assert.assertFalse(isJsObjectInjectedWhenLoadingUrl("https://www.ok.com", JS_OBJECT_NAME));
653         Assert.assertFalse(
654                 isJsObjectInjectedWhenLoadingUrl("https://www.noinjection.com", JS_OBJECT_NAME));
655 
656         final String[] allowedOriginRules2 = new String[] {"https://www.ok.com"};
657         // Call addWebMessageListener with non-overlapping set of origins.
658         addWebMessageListenerOnUiThread(
659                 mAwContents, JS_OBJECT_NAME_2, allowedOriginRules2, mListener);
660 
661         Assert.assertFalse(
662                 isJsObjectInjectedWhenLoadingUrl("https://www.example.com", JS_OBJECT_NAME_2));
663         Assert.assertFalse(
664                 isJsObjectInjectedWhenLoadingUrl("https://www.allowed.com", JS_OBJECT_NAME_2));
665         Assert.assertTrue(isJsObjectInjectedWhenLoadingUrl("https://www.ok.com", JS_OBJECT_NAME_2));
666         Assert.assertFalse(
667                 isJsObjectInjectedWhenLoadingUrl("https://www.noinjection.com", JS_OBJECT_NAME_2));
668         Assert.assertFalse(isJsObjectInjectedWhenLoadingUrl("", JS_OBJECT_NAME_2));
669     }
670 
671     @Test
672     @MediumTest
673     @Feature({"AndroidWebView", "JsJavaInteraction"})
testJsReplyProxyWorks()674     public void testJsReplyProxyWorks() throws Throwable {
675         addWebMessageListenerOnUiThread(mAwContents, JS_OBJECT_NAME, new String[] {"*"}, mListener);
676 
677         final String url = loadUrlFromPath(POST_MESSAGE_REPLY_HTML);
678 
679         TestWebMessageListener.Data data = mListener.waitForOnPostMessage();
680 
681         // JavaScript code in the page will change the title to NEW_TITLE if postMessage to proxy
682         // succeed.
683         final OnReceivedTitleHelper onReceivedTitleHelper =
684                 mContentsClient.getOnReceivedTitleHelper();
685         final int titleCallCount = onReceivedTitleHelper.getCallCount();
686         data.mReplyProxy.postMessage(NEW_TITLE);
687         onReceivedTitleHelper.waitForCallback(titleCallCount);
688 
689         Assert.assertEquals(NEW_TITLE, onReceivedTitleHelper.getTitle());
690 
691         Assert.assertTrue(mListener.hasNoMoreOnPostMessage());
692     }
693 
694     @Test
695     @MediumTest
696     @Feature({"AndroidWebView", "JsJavaInteraction"})
testJsReplyProxyReplyToTheCorrectJsObject()697     public void testJsReplyProxyReplyToTheCorrectJsObject() throws Throwable {
698         final TestWebMessageListener webMessageListener2 = new TestWebMessageListener();
699         addWebMessageListenerOnUiThread(mAwContents, JS_OBJECT_NAME, new String[] {"*"}, mListener);
700         addWebMessageListenerOnUiThread(
701                 mAwContents, JS_OBJECT_NAME_2, new String[] {"*"}, webMessageListener2);
702 
703         final String url = loadUrlFromPath(POST_MESSAGE_REPLY_HTML);
704 
705         // Listener for myObject.
706         final String listener1 = "function (event) {"
707                 + "  " + JS_OBJECT_NAME + ".postMessage('ack1' + event.data);"
708                 + "}";
709 
710         // Listener for myObject2.
711         final String listener2 = "function (event) {"
712                 + "  " + JS_OBJECT_NAME_2 + ".postMessage('ack2' + event.data);"
713                 + "}";
714 
715         // Add two different js objects.
716         addEventListener(listener1, "listener1", JS_OBJECT_NAME, mActivityTestRule, mAwContents,
717                 mContentsClient);
718         addEventListener(listener2, "listener2", JS_OBJECT_NAME_2, mActivityTestRule, mAwContents,
719                 mContentsClient);
720 
721         TestWebMessageListener.Data data = mListener.waitForOnPostMessage();
722 
723         final String message = "message";
724         mActivityTestRule.executeJavaScriptAndWaitForResult(mAwContents, mContentsClient,
725                 JS_OBJECT_NAME_2 + ".postMessage('" + message + "');");
726         TestWebMessageListener.Data data2 = webMessageListener2.waitForOnPostMessage();
727 
728         Assert.assertEquals(message, data2.mMessage);
729 
730         // Targeting myObject.
731         data.mReplyProxy.postMessage(HELLO);
732         // Targeting myObject2.
733         data2.mReplyProxy.postMessage(HELLO);
734 
735         TestWebMessageListener.Data replyData1 = mListener.waitForOnPostMessage();
736         TestWebMessageListener.Data replyData2 = webMessageListener2.waitForOnPostMessage();
737 
738         Assert.assertEquals("ack1" + HELLO, replyData1.mMessage);
739         Assert.assertEquals("ack2" + HELLO, replyData2.mMessage);
740 
741         Assert.assertTrue(mListener.hasNoMoreOnPostMessage());
742         Assert.assertTrue(webMessageListener2.hasNoMoreOnPostMessage());
743     }
744 
745     @Test
746     @MediumTest
747     @Feature({"AndroidWebView", "JsJavaInteraction"})
testJsReplyProxyDropsMessageIfJsObjectIsGone()748     public void testJsReplyProxyDropsMessageIfJsObjectIsGone() throws Throwable {
749         addWebMessageListenerOnUiThread(mAwContents, JS_OBJECT_NAME, new String[] {"*"}, mListener);
750 
751         final String url = loadUrlFromPath(POST_MESSAGE_REPLY_HTML);
752 
753         TestWebMessageListener.Data data = mListener.waitForOnPostMessage();
754 
755         JsReplyProxy proxy = data.mReplyProxy;
756 
757         // Load the same url again.
758         loadUrlFromPath(POST_MESSAGE_REPLY_HTML);
759         TestWebMessageListener.Data data2 = mListener.waitForOnPostMessage();
760 
761         // Use the previous JsReplyProxy to send message. It should drop the message.
762         proxy.postMessage(NEW_TITLE);
763 
764         // Call evaluateJavascript to make sure the previous postMessage() call is reached to
765         // renderer if it should, since these messages are in sequence.
766         Assert.assertTrue(hasJavaScriptObject(
767                 JS_OBJECT_NAME, mActivityTestRule, mAwContents, mContentsClient));
768 
769         // Title shouldn't change.
770         Assert.assertNotEquals(NEW_TITLE, mActivityTestRule.getTitleOnUiThread(mAwContents));
771 
772         Assert.assertTrue(mListener.hasNoMoreOnPostMessage());
773     }
774 
775     @Test
776     @MediumTest
777     @Feature({"AndroidWebView", "JsJavaInteraction"})
testJsAddAndRemoveEventListener()778     public void testJsAddAndRemoveEventListener() throws Throwable {
779         addWebMessageListenerOnUiThread(mAwContents, JS_OBJECT_NAME, new String[] {"*"}, mListener);
780         loadUrlFromPath(POST_MESSAGE_SIMPLE_HTML);
781 
782         JsReplyProxy proxy = mListener.waitForOnPostMessage().mReplyProxy;
783 
784         final String listener1 = "function (event) {"
785                 + "  if (window.receivedCount1) {"
786                 + "    window.receivedCount1++;"
787                 + "  } else {"
788                 + "    window.receivedCount1 = 1;"
789                 + "  }"
790                 + "  " + JS_OBJECT_NAME + ".postMessage('ack1:' + window.receivedCount1);"
791                 + "}";
792 
793         final String listener2 = "function (event) {"
794                 + "  if (window.receivedCount2) {"
795                 + "    window.receivedCount2++;"
796                 + "  } else {"
797                 + "    window.receivedCount2 = 1;"
798                 + "  }"
799                 + "  " + JS_OBJECT_NAME + ".postMessage('ack2:' + window.receivedCount2);"
800                 + "}";
801 
802         addEventListener(listener1, "listener1", JS_OBJECT_NAME, mActivityTestRule, mAwContents,
803                 mContentsClient);
804         addEventListener(listener2, "listener2", JS_OBJECT_NAME, mActivityTestRule, mAwContents,
805                 mContentsClient);
806 
807         // Post message to test both listeners receive message.
808         proxy.postMessage(HELLO);
809 
810         TestWebMessageListener.Data replyData1 = mListener.waitForOnPostMessage();
811         TestWebMessageListener.Data replyData2 = mListener.waitForOnPostMessage();
812 
813         Assert.assertEquals("ack1:1", replyData1.mMessage);
814         Assert.assertEquals("ack2:1", replyData2.mMessage);
815 
816         removeEventListener(
817                 "listener2", JS_OBJECT_NAME, mActivityTestRule, mAwContents, mContentsClient);
818 
819         // Post message again to test if remove works.
820         proxy.postMessage(HELLO);
821 
822         // listener 1 should add message again.
823         TestWebMessageListener.Data replyData3 = mListener.waitForOnPostMessage();
824         Assert.assertEquals("ack1:2", replyData3.mMessage);
825 
826         // Should be no more messages.
827         Assert.assertTrue(mListener.hasNoMoreOnPostMessage());
828     }
829 
830     @Test
831     @MediumTest
832     @Feature({"AndroidWebView", "JsJavaInteraction"})
testJsObjectRemoveOnMessage()833     public void testJsObjectRemoveOnMessage() throws Throwable {
834         addWebMessageListenerOnUiThread(mAwContents, JS_OBJECT_NAME, new String[] {"*"}, mListener);
835 
836         final String url = loadUrlFromPath(POST_MESSAGE_REPLY_HTML);
837 
838         TestWebMessageListener.Data data = mListener.waitForOnPostMessage();
839 
840         // JavaScript code in the page will change the title to NEW_TITLE if postMessage to proxy
841         // succeed.
842         final OnReceivedTitleHelper onReceivedTitleHelper =
843                 mContentsClient.getOnReceivedTitleHelper();
844         final int titleCallCount = onReceivedTitleHelper.getCallCount();
845         data.mReplyProxy.postMessage(NEW_TITLE);
846         onReceivedTitleHelper.waitForCallback(titleCallCount);
847 
848         Assert.assertEquals(NEW_TITLE, onReceivedTitleHelper.getTitle());
849 
850         mActivityTestRule.executeJavaScriptAndWaitForResult(
851                 mAwContents, mContentsClient, JS_OBJECT_NAME + ".onmessage = undefined;");
852         Assert.assertEquals("null",
853                 mActivityTestRule.executeJavaScriptAndWaitForResult(
854                         mAwContents, mContentsClient, JS_OBJECT_NAME + ".onmessage"));
855 
856         Assert.assertTrue(mListener.hasNoMoreOnPostMessage());
857     }
858 
verifyOnPostMessageOriginIsNull()859     private void verifyOnPostMessageOriginIsNull() throws Throwable {
860         mActivityTestRule.executeJavaScriptAndWaitForResult(
861                 mAwContents, mContentsClient, JS_OBJECT_NAME + ".postMessage('Hello');");
862 
863         TestWebMessageListener.Data data = mListener.waitForOnPostMessage();
864 
865         Assert.assertEquals("null", data.mSourceOrigin.toString());
866 
867         Assert.assertEquals(HELLO, data.mMessage);
868         Assert.assertTrue(data.mIsMainFrame);
869         Assert.assertEquals(0, data.mPorts.length);
870 
871         Assert.assertTrue(mListener.hasNoMoreOnPostMessage());
872     }
873 
874     @Test
875     @MediumTest
876     @Feature({"AndroidWebView", "JsJavaInteraction"})
testFileSchemeUrl_setAllowFileAccessFromFile_true()877     public void testFileSchemeUrl_setAllowFileAccessFromFile_true() throws Throwable {
878         mAwContents.getSettings().setAllowFileAccessFromFileURLs(true);
879         addWebMessageListenerOnUiThread(mAwContents, JS_OBJECT_NAME, new String[] {"*"}, mListener);
880         mActivityTestRule.loadUrlSync(
881                 mAwContents, mContentsClient.getOnPageFinishedHelper(), FILE_URI);
882         Assert.assertEquals("\"file://\"",
883                 mActivityTestRule.executeJavaScriptAndWaitForResult(
884                         mAwContents, mContentsClient, "window.origin"));
885 
886         verifyOnPostMessageOriginIsNull();
887     }
888 
889     @Test
890     @MediumTest
891     @Feature({"AndroidWebView", "JsJavaInteraction"})
testFileSchemeUrl_setAllowFileAccessFromFile_false()892     public void testFileSchemeUrl_setAllowFileAccessFromFile_false() throws Throwable {
893         // The default value is false on JELLY_BEAN and above, but we explicitly set this to
894         // false to readability.
895         mAwContents.getSettings().setAllowFileAccessFromFileURLs(false);
896         addWebMessageListenerOnUiThread(mAwContents, JS_OBJECT_NAME, new String[] {"*"}, mListener);
897         mActivityTestRule.loadUrlSync(
898                 mAwContents, mContentsClient.getOnPageFinishedHelper(), FILE_URI);
899         Assert.assertEquals("\"null\"",
900                 mActivityTestRule.executeJavaScriptAndWaitForResult(
901                         mAwContents, mContentsClient, "window.origin"));
902 
903         verifyOnPostMessageOriginIsNull();
904     }
905 
906     @Test
907     @MediumTest
908     @Feature({"AndroidWebView", "JsJavaInteraction"})
testContentSchemeUrl_setAllowFileAccessFromFileURLs_true()909     public void testContentSchemeUrl_setAllowFileAccessFromFileURLs_true() throws Throwable {
910         mAwContents.getSettings().setAllowContentAccess(true);
911         mAwContents.getSettings().setAllowFileAccessFromFileURLs(true);
912         addWebMessageListenerOnUiThread(mAwContents, JS_OBJECT_NAME, new String[] {"*"}, mListener);
913         mActivityTestRule.loadUrlSync(mAwContents, mContentsClient.getOnPageFinishedHelper(),
914                 TestContentProvider.createContentUrl("content_access"));
915         Assert.assertEquals("\"content://\"",
916                 mActivityTestRule.executeJavaScriptAndWaitForResult(
917                         mAwContents, mContentsClient, "window.origin"));
918 
919         verifyOnPostMessageOriginIsNull();
920     }
921 
922     @Test
923     @MediumTest
924     @Feature({"AndroidWebView", "JsJavaInteraction"})
testContentSchemeUrl_setAllowFileAccessFromFileURLs_false()925     public void testContentSchemeUrl_setAllowFileAccessFromFileURLs_false() throws Throwable {
926         mAwContents.getSettings().setAllowContentAccess(true);
927         // The default value is false on JELLY_BEAN and above, but we explicitly set this to
928         // false to readability.
929         mAwContents.getSettings().setAllowFileAccessFromFileURLs(false);
930         addWebMessageListenerOnUiThread(mAwContents, JS_OBJECT_NAME, new String[] {"*"}, mListener);
931         mActivityTestRule.loadUrlSync(mAwContents, mContentsClient.getOnPageFinishedHelper(),
932                 TestContentProvider.createContentUrl("content_access"));
933         Assert.assertEquals("\"null\"",
934                 mActivityTestRule.executeJavaScriptAndWaitForResult(
935                         mAwContents, mContentsClient, "window.origin"));
936 
937         verifyOnPostMessageOriginIsNull();
938     }
939 
940     @Test
941     @SmallTest
942     @Feature({"AndroidWebView"})
testWebMessageListenerForPopupWindow()943     public void testWebMessageListenerForPopupWindow() throws Throwable {
944         TestWebServer webServer = TestWebServer.start();
945 
946         final String popupPath = "/popup.html";
947         final String parentPageHtml = CommonResources.makeHtmlPageFrom("",
948                 "<script>"
949                         + "function tryOpenWindow() {"
950                         + "  var newWindow = window.open('" + popupPath + "');"
951                         + "}</script>");
952 
953         final String popupPageHtml =
954                 CommonResources.makeHtmlPageFrom("<title>popup</title>", "This is a popup window");
955 
956         mActivityTestRule.triggerPopup(mAwContents, mContentsClient, webServer, parentPageHtml,
957                 popupPageHtml, popupPath, "tryOpenWindow()");
958         AwActivityTestRule.PopupInfo popupInfo = mActivityTestRule.createPopupContents(mAwContents);
959         TestAwContentsClient popupContentsClient = popupInfo.popupContentsClient;
960         final AwContents popupContents = popupInfo.popupContents;
961 
962         // App adds WebMessageListener to the child WebView, e.g. in onCreateWindow().
963         addWebMessageListenerOnUiThread(
964                 popupContents, JS_OBJECT_NAME, new String[] {"*"}, mListener);
965 
966         mActivityTestRule.loadPopupContents(mAwContents, popupInfo, null);
967 
968         // Test if the listener was re-injected.
969         mActivityTestRule.executeJavaScriptAndWaitForResult(
970                 popupContents, popupContentsClient, JS_OBJECT_NAME + ".postMessage('Hello');");
971 
972         TestWebMessageListener.Data data = mListener.waitForOnPostMessage();
973 
974         Assert.assertEquals(HELLO, data.mMessage);
975         Assert.assertTrue(mListener.hasNoMoreOnPostMessage());
976 
977         webServer.shutdown();
978     }
979 
980     @Test
981     @SmallTest
982     @Feature({"AndroidWebView", "JsJavaInteraction"})
testPostMessage_JsObjectName_Number()983     public void testPostMessage_JsObjectName_Number() throws Throwable {
984         checkInjectAndAccessJsObjectNameAsWindowProperty("123");
985     }
986 
987     @Test
988     @SmallTest
989     @Feature({"AndroidWebView", "JsJavaInteraction"})
testPostMessage_JsObjectName_Symbol()990     public void testPostMessage_JsObjectName_Symbol() throws Throwable {
991         checkInjectAndAccessJsObjectNameAsWindowProperty("*");
992     }
993 
994     @Test
995     @SmallTest
996     @Feature({"AndroidWebView", "JsJavaInteraction"})
testPostMessage_JsObjectName_Keyword()997     public void testPostMessage_JsObjectName_Keyword() throws Throwable {
998         checkInjectAndAccessJsObjectNameAsWindowProperty("var");
999     }
1000 
1001     @Test
1002     @MediumTest
1003     @Feature({"AndroidWebView", "JsJavaInteraction"})
testDocumentStartJavaScript_addJavascriptInterfaceShouldBeAvaliable()1004     public void testDocumentStartJavaScript_addJavascriptInterfaceShouldBeAvaliable()
1005             throws Throwable {
1006         final LinkedBlockingQueue<String> javascriptInterfaceQueue = new LinkedBlockingQueue<>();
1007         AwActivityTestRule.addJavascriptInterfaceOnUiThread(mAwContents, new Object() {
1008             @JavascriptInterface
1009             public void send(String message) {
1010                 javascriptInterfaceQueue.add(message);
1011             }
1012         }, "javaBridge");
1013         addDocumentStartJavaScriptOnUiThread(
1014                 mAwContents, "javaBridge.send('" + HELLO + "');", new String[] {"*"});
1015 
1016         loadUrlFromPath(HELLO_WORLD_HTML);
1017 
1018         String message = AwActivityTestRule.waitForNextQueueElement(javascriptInterfaceQueue);
1019 
1020         Assert.assertEquals(HELLO, message);
1021     }
1022 
1023     @Test
1024     @MediumTest
1025     @Feature({"AndroidWebView", "JsJavaInteraction"})
testDocumentStartJavaScript_jsObjectShouldBeAvaliable()1026     public void testDocumentStartJavaScript_jsObjectShouldBeAvaliable() throws Throwable {
1027         addWebMessageListenerOnUiThread(mAwContents, JS_OBJECT_NAME, new String[] {"*"}, mListener);
1028         addDocumentStartJavaScriptOnUiThread(
1029                 mAwContents, JS_OBJECT_NAME + ".postMessage('" + HELLO + "');", new String[] {"*"});
1030 
1031         String url = loadUrlFromPath(HELLO_WORLD_HTML);
1032 
1033         TestWebMessageListener.Data data = mListener.waitForOnPostMessage();
1034 
1035         assertUrlHasOrigin(url, data.mSourceOrigin);
1036         Assert.assertEquals(HELLO, data.mMessage);
1037         Assert.assertTrue(data.mIsMainFrame);
1038         Assert.assertEquals(0, data.mPorts.length);
1039 
1040         Assert.assertTrue(mListener.hasNoMoreOnPostMessage());
1041     }
1042 
1043     @Test
1044     @MediumTest
1045     @Feature({"AndroidWebView", "JsJavaInteraction"})
testDocumentStartJavaScript_runBeforeUserScript()1046     public void testDocumentStartJavaScript_runBeforeUserScript() throws Throwable {
1047         addDocumentStartJavaScriptOnUiThread(mAwContents,
1048                 JS_OBJECT_NAME + ".postMessage('" + HELLO + "1');", new String[] {"*"});
1049         addWebMessageListenerOnUiThread(mAwContents, JS_OBJECT_NAME, new String[] {"*"}, mListener);
1050 
1051         // POST_MESSAGE_SIMPLE_HTML will post HELLO message.
1052         String url = loadUrlFromPath(POST_MESSAGE_SIMPLE_HTML);
1053 
1054         TestWebMessageListener.Data data = mListener.waitForOnPostMessage();
1055 
1056         assertUrlHasOrigin(url, data.mSourceOrigin);
1057         Assert.assertEquals(HELLO + "1", data.mMessage);
1058         Assert.assertTrue(data.mIsMainFrame);
1059         Assert.assertEquals(0, data.mPorts.length);
1060 
1061         TestWebMessageListener.Data data2 = mListener.waitForOnPostMessage();
1062         Assert.assertEquals(HELLO, data2.mMessage);
1063         Assert.assertTrue(data2.mIsMainFrame);
1064 
1065         Assert.assertTrue(mListener.hasNoMoreOnPostMessage());
1066     }
1067 
1068     @Test
1069     @MediumTest
1070     @Feature({"AndroidWebView", "JsJavaInteraction"})
testDocumentStartJavaScript_multipleScripts()1071     public void testDocumentStartJavaScript_multipleScripts() throws Throwable {
1072         addWebMessageListenerOnUiThread(mAwContents, JS_OBJECT_NAME, new String[] {"*"}, mListener);
1073         addDocumentStartJavaScriptOnUiThread(mAwContents,
1074                 JS_OBJECT_NAME + ".postMessage('" + HELLO + "0');", new String[] {"*"});
1075         addDocumentStartJavaScriptOnUiThread(mAwContents,
1076                 JS_OBJECT_NAME + ".postMessage('" + HELLO + "1');", new String[] {"*"});
1077 
1078         String url = loadUrlFromPath(HELLO_WORLD_HTML);
1079 
1080         for (int i = 0; i < 2; ++i) {
1081             TestWebMessageListener.Data data = mListener.waitForOnPostMessage();
1082 
1083             assertUrlHasOrigin(url, data.mSourceOrigin);
1084             Assert.assertEquals(HELLO + Integer.toString(i), data.mMessage);
1085             Assert.assertTrue(data.mIsMainFrame);
1086             Assert.assertEquals(0, data.mPorts.length);
1087         }
1088 
1089         Assert.assertTrue(mListener.hasNoMoreOnPostMessage());
1090     }
1091 
1092     @Test
1093     @MediumTest
1094     @Feature({"AndroidWebView", "JsJavaInteraction"})
testDocumentStartJavaScript_callAgainAfterPageLoad()1095     public void testDocumentStartJavaScript_callAgainAfterPageLoad() throws Throwable {
1096         addWebMessageListenerOnUiThread(mAwContents, JS_OBJECT_NAME, new String[] {"*"}, mListener);
1097         addDocumentStartJavaScriptOnUiThread(mAwContents,
1098                 JS_OBJECT_NAME + ".postMessage('" + HELLO + "0');", new String[] {"*"});
1099 
1100         String url = loadUrlFromPath(HELLO_WORLD_HTML);
1101 
1102         addDocumentStartJavaScriptOnUiThread(mAwContents,
1103                 JS_OBJECT_NAME + ".postMessage('" + HELLO + "1');", new String[] {"*"});
1104         TestWebMessageListener.Data data = mListener.waitForOnPostMessage();
1105 
1106         assertUrlHasOrigin(url, data.mSourceOrigin);
1107         Assert.assertEquals(HELLO + "0", data.mMessage);
1108         Assert.assertTrue(data.mIsMainFrame);
1109         Assert.assertEquals(0, data.mPorts.length);
1110 
1111         // Load the page again.
1112         loadUrlFromPath(HELLO_WORLD_HTML);
1113 
1114         for (int i = 0; i < 2; ++i) {
1115             TestWebMessageListener.Data data2 = mListener.waitForOnPostMessage();
1116 
1117             Assert.assertEquals(HELLO + Integer.toString(i), data2.mMessage);
1118             Assert.assertTrue(data2.mIsMainFrame);
1119         }
1120 
1121         Assert.assertTrue(mListener.hasNoMoreOnPostMessage());
1122     }
1123 
1124     @Test
1125     @MediumTest
1126     @Feature({"AndroidWebView", "JsJavaInteraction"})
testDocumentStartJavaScript_allowedOriginsRulesWithDifferentBaseUrl()1127     public void testDocumentStartJavaScript_allowedOriginsRulesWithDifferentBaseUrl()
1128             throws Throwable {
1129         // With a standard origin rule.
1130         final String testObjectName = "test";
1131         addDocumentStartJavaScriptOnUiThread(mAwContents, "let " + testObjectName + " = {};",
1132                 new String[] {"https://www.example.com:443"});
1133 
1134         Assert.assertTrue(didScriptRunWhenLoading("https://www.example.com", testObjectName));
1135         Assert.assertFalse(didScriptRunWhenLoading("https://www.example.com:8080", testObjectName));
1136         Assert.assertFalse(didScriptRunWhenLoading("http://www.example.com", testObjectName));
1137         Assert.assertFalse(didScriptRunWhenLoading("http://example.com", testObjectName));
1138         Assert.assertFalse(didScriptRunWhenLoading("https://www.google.com", testObjectName));
1139         Assert.assertFalse(didScriptRunWhenLoading("file://etc", testObjectName));
1140         Assert.assertFalse(didScriptRunWhenLoading("ftp://example.com", testObjectName));
1141         Assert.assertFalse(didScriptRunWhenLoading(null, testObjectName));
1142 
1143         // Match all the origins.
1144         final String testObjectName2 = testObjectName + "2";
1145         addDocumentStartJavaScriptOnUiThread(
1146                 mAwContents, "let " + testObjectName2 + " = {};", new String[] {"*"});
1147 
1148         Assert.assertTrue(didScriptRunWhenLoading("https://www.example.com", testObjectName2));
1149         Assert.assertTrue(didScriptRunWhenLoading("https://www.example.com:8080", testObjectName2));
1150         Assert.assertTrue(didScriptRunWhenLoading("http://www.example.com", testObjectName2));
1151         Assert.assertTrue(didScriptRunWhenLoading("http://example.com", testObjectName2));
1152         Assert.assertTrue(didScriptRunWhenLoading("https://www.google.com", testObjectName2));
1153         Assert.assertTrue(didScriptRunWhenLoading("file://etc", testObjectName2));
1154         Assert.assertTrue(didScriptRunWhenLoading("ftp://example.com", testObjectName2));
1155         Assert.assertTrue(didScriptRunWhenLoading(null, testObjectName2));
1156         // data: scheme could be matched with "*".
1157         final String html = "<html><body><div>data</div></body></html>";
1158         mActivityTestRule.loadHtmlSync(
1159                 mAwContents, mContentsClient.getOnPageFinishedHelper(), html);
1160         Assert.assertTrue(hasJavaScriptObject(
1161                 testObjectName2, mActivityTestRule, mAwContents, mContentsClient));
1162 
1163         // Wrong origin rule.
1164         final String testObjectName5 = testObjectName + "5";
1165         try {
1166             addDocumentStartJavaScriptOnUiThread(mAwContents, "let " + testObjectName5 + " = {};",
1167                     new String[] {"https://www.example.com/index.html"});
1168             Assert.fail("You cannot use a full URL for allowedOriginRules.");
1169         } catch (RuntimeException e) {
1170             // Should catch IllegalArgumentException in the end of the re-throw chain.
1171             Assert.assertTrue("The exception should be an IllegalArgumentException",
1172                     getRootCauseException(e) instanceof IllegalArgumentException);
1173         }
1174         Assert.assertFalse(didScriptRunWhenLoading("https://www.example.com", testObjectName5));
1175     }
1176 
1177     @Test
1178     @MediumTest
1179     @Feature({"AndroidWebView", "JsJavaInteraction"})
testDocumentStartJavaScript_willRunInIframe()1180     public void testDocumentStartJavaScript_willRunInIframe() throws Throwable {
1181         addWebMessageListenerOnUiThread(mAwContents, JS_OBJECT_NAME, new String[] {"*"}, mListener);
1182         final String script = "if (window.location.origin !== 'http://www.google.com') {"
1183                 + "  " + JS_OBJECT_NAME + ".postMessage('" + HELLO + "');"
1184                 + "}";
1185         // Since we are matching both origins, the script will run in both iframe and main frame,
1186         // but it will send message in only iframe.
1187         addDocumentStartJavaScriptOnUiThread(mAwContents, script, new String[] {"*"});
1188 
1189         final String frameUrl = mTestServer.getURL(HELLO_WORLD_HTML);
1190         final String html = createCrossOriginAccessTestPageHtml(frameUrl);
1191 
1192         // Load a cross origin iframe page, the www.google.com page is the main frame, test server
1193         // page is the iframe.
1194         mActivityTestRule.loadDataWithBaseUrlSync(mAwContents,
1195                 mContentsClient.getOnPageFinishedHelper(), html, "text/html", false,
1196                 "http://www.google.com", null);
1197 
1198         TestWebMessageListener.Data data = mListener.waitForOnPostMessage();
1199 
1200         assertUrlHasOrigin(frameUrl, data.mSourceOrigin);
1201         Assert.assertEquals(HELLO, data.mMessage);
1202         Assert.assertFalse(data.mIsMainFrame);
1203         Assert.assertEquals(0, data.mPorts.length);
1204 
1205         Assert.assertTrue(mListener.hasNoMoreOnPostMessage());
1206     }
1207 
1208     @Test
1209     @MediumTest
1210     @Feature({"AndroidWebView", "JsJavaInteraction"})
testDocumentStartJavaScript_removeScript()1211     public void testDocumentStartJavaScript_removeScript() throws Throwable {
1212         addWebMessageListenerOnUiThread(mAwContents, JS_OBJECT_NAME, new String[] {"*"}, mListener);
1213         ScriptReference[] references = new ScriptReference[2];
1214         for (int i = 0; i < 2; ++i) {
1215             final String script =
1216                     JS_OBJECT_NAME + ".postMessage('" + HELLO + Integer.toString(i) + "');";
1217             // Since we are matching both origins, the script will run in both iframe and main
1218             // frame, but it will send message in only iframe.
1219             references[i] =
1220                     addDocumentStartJavaScriptOnUiThread(mAwContents, script, new String[] {"*"});
1221         }
1222 
1223         final String url = loadUrlFromPath(HELLO_WORLD_HTML);
1224 
1225         for (int i = 0; i < 2; ++i) {
1226             TestWebMessageListener.Data data = mListener.waitForOnPostMessage();
1227 
1228             assertUrlHasOrigin(url, data.mSourceOrigin);
1229             Assert.assertEquals(HELLO + Integer.toString(i), data.mMessage);
1230         }
1231 
1232         TestThreadUtils.runOnUiThreadBlocking(() -> references[0].remove());
1233         // Load the page again.
1234         loadUrlFromPath(HELLO_WORLD_HTML);
1235 
1236         TestWebMessageListener.Data data = mListener.waitForOnPostMessage();
1237         Assert.assertEquals(HELLO + "1", data.mMessage);
1238 
1239         TestThreadUtils.runOnUiThreadBlocking(() -> references[1].remove());
1240         // Load the page again.
1241         loadUrlFromPath(HELLO_WORLD_HTML);
1242 
1243         Assert.assertTrue(mListener.hasNoMoreOnPostMessage());
1244     }
1245 
1246     @Test
1247     @MediumTest
1248     @Feature({"AndroidWebView", "JsJavaInteraction"})
testDocumentStartJavaScript_doubleRemoveScript()1249     public void testDocumentStartJavaScript_doubleRemoveScript() throws Throwable {
1250         addWebMessageListenerOnUiThread(mAwContents, JS_OBJECT_NAME, new String[] {"*"}, mListener);
1251 
1252         final String script = JS_OBJECT_NAME + ".postMessage('" + HELLO + "');";
1253         ScriptReference reference =
1254                 addDocumentStartJavaScriptOnUiThread(mAwContents, script, new String[] {"*"});
1255 
1256         final String url = loadUrlFromPath(HELLO_WORLD_HTML);
1257 
1258         TestWebMessageListener.Data data = mListener.waitForOnPostMessage();
1259 
1260         assertUrlHasOrigin(url, data.mSourceOrigin);
1261         Assert.assertEquals(HELLO, data.mMessage);
1262 
1263         // Remove twice, the second time should take no effect.
1264         TestThreadUtils.runOnUiThreadBlocking(() -> reference.remove());
1265         TestThreadUtils.runOnUiThreadBlocking(() -> reference.remove());
1266         // Load the page again.
1267         loadUrlFromPath(HELLO_WORLD_HTML);
1268 
1269         Assert.assertTrue(mListener.hasNoMoreOnPostMessage());
1270 
1271         // Remove twice again, should have no effect.
1272         TestThreadUtils.runOnUiThreadBlocking(() -> reference.remove());
1273         TestThreadUtils.runOnUiThreadBlocking(() -> reference.remove());
1274         // Load the page again.
1275         loadUrlFromPath(HELLO_WORLD_HTML);
1276 
1277         Assert.assertTrue(mListener.hasNoMoreOnPostMessage());
1278     }
1279 
checkInjectAndAccessJsObjectNameAsWindowProperty(String jsObjName)1280     private void checkInjectAndAccessJsObjectNameAsWindowProperty(String jsObjName)
1281             throws Throwable {
1282         addWebMessageListenerOnUiThread(mAwContents, jsObjName, new String[] {"*"}, mListener);
1283 
1284         String html = "<html><head><script>window['" + jsObjName + "'].postMessage('Hello');"
1285                 + "</script></head><body><div>postMessage</div></body></html>";
1286         mActivityTestRule.loadDataWithBaseUrlSync(mAwContents,
1287                 mContentsClient.getOnPageFinishedHelper(), html, "text/html", false,
1288                 "http://www.google.com", null);
1289 
1290         TestWebMessageListener.Data data = mListener.waitForOnPostMessage();
1291 
1292         Assert.assertEquals("http://www.google.com", data.mSourceOrigin.toString());
1293         Assert.assertEquals(HELLO, data.mMessage);
1294         Assert.assertTrue(data.mIsMainFrame);
1295         Assert.assertEquals(0, data.mPorts.length);
1296 
1297         Assert.assertTrue(mListener.hasNoMoreOnPostMessage());
1298     }
1299 
isJsObjectInjectedWhenLoadingUrl( final String baseUrl, final String jsObjectName)1300     private boolean isJsObjectInjectedWhenLoadingUrl(
1301             final String baseUrl, final String jsObjectName) throws Throwable {
1302         mActivityTestRule.loadDataWithBaseUrlSync(mAwContents,
1303                 mContentsClient.getOnPageFinishedHelper(), DATA_HTML, "text/html", false, baseUrl,
1304                 null);
1305         return hasJavaScriptObject(jsObjectName, mActivityTestRule, mAwContents, mContentsClient);
1306     }
1307 
1308     // The script is trying to set a global JavaScript object, so it is essentially the same
1309     // with isJsObjectInjectedWhenLoadingUrl(). Having a wrapper method to make it clear for
1310     // the context.
didScriptRunWhenLoading(final String baseUrl, final String objectName)1311     private boolean didScriptRunWhenLoading(final String baseUrl, final String objectName)
1312             throws Throwable {
1313         return isJsObjectInjectedWhenLoadingUrl(baseUrl, objectName);
1314     }
1315 
loadUrlFromPath(String path)1316     private String loadUrlFromPath(String path) throws Exception {
1317         final String url = mTestServer.getURL(path);
1318         mActivityTestRule.loadUrlSync(mAwContents, mContentsClient.getOnPageFinishedHelper(), url);
1319         return url;
1320     }
1321 
assertUrlHasOrigin(final String url, final Uri origin)1322     private static void assertUrlHasOrigin(final String url, final Uri origin) {
1323         Uri uriFromServer = Uri.parse(url);
1324         Assert.assertEquals(uriFromServer.getScheme(), origin.getScheme());
1325         Assert.assertEquals(uriFromServer.getHost(), origin.getHost());
1326         Assert.assertEquals(uriFromServer.getPort(), origin.getPort());
1327     }
1328 
createCrossOriginAccessTestPageHtml(final String frameUrl)1329     private static String createCrossOriginAccessTestPageHtml(final String frameUrl) {
1330         return "<html>"
1331                 + "<body><div>I have an iframe</ div>"
1332                 + "  <iframe src ='" + frameUrl + "'></iframe>"
1333                 + "</body></html>";
1334     }
1335 
addDocumentStartJavaScriptOnUiThread( final AwContents awContents, final String script, final String[] allowedOriginRules)1336     private static ScriptReference addDocumentStartJavaScriptOnUiThread(
1337             final AwContents awContents, final String script, final String[] allowedOriginRules) {
1338         return TestThreadUtils.runOnUiThreadBlockingNoException(
1339                 () -> awContents.addDocumentStartJavaScript(script, allowedOriginRules));
1340     }
1341 
addWebMessageListenerOnUiThread(final AwContents awContents, final String jsObjectName, final String[] allowedOriginRules, final WebMessageListener listener)1342     private static void addWebMessageListenerOnUiThread(final AwContents awContents,
1343             final String jsObjectName, final String[] allowedOriginRules,
1344             final WebMessageListener listener) {
1345         TestThreadUtils.runOnUiThreadBlocking(
1346                 () -> awContents.addWebMessageListener(jsObjectName, allowedOriginRules, listener));
1347     }
1348 
removeWebMessageListenerOnUiThread( final AwContents awContents, final String jsObjectName)1349     private static void removeWebMessageListenerOnUiThread(
1350             final AwContents awContents, final String jsObjectName) {
1351         TestThreadUtils.runOnUiThreadBlocking(
1352                 () -> awContents.removeWebMessageListener(jsObjectName));
1353     }
1354 
hasJavaScriptObject(final String jsObjectName, final AwActivityTestRule rule, final AwContents awContents, final TestAwContentsClient contentsClient)1355     private static boolean hasJavaScriptObject(final String jsObjectName,
1356             final AwActivityTestRule rule, final AwContents awContents,
1357             final TestAwContentsClient contentsClient) throws Throwable {
1358         final String result = rule.executeJavaScriptAndWaitForResult(
1359                 awContents, contentsClient, "typeof " + jsObjectName + " !== 'undefined'");
1360         return result.equals("true");
1361     }
1362 
addEventListener(final String func, final String funcName, String jsObjectName, final AwActivityTestRule rule, final AwContents awContents, final TestAwContentsClient contentsClient)1363     private static void addEventListener(final String func, final String funcName,
1364             String jsObjectName, final AwActivityTestRule rule, final AwContents awContents,
1365             final TestAwContentsClient contentsClient) throws Throwable {
1366         String code = "let " + funcName + " = " + func + ";";
1367         code += jsObjectName + ".addEventListener('message', " + funcName + ");";
1368         rule.executeJavaScriptAndWaitForResult(awContents, contentsClient, code);
1369     }
1370 
removeEventListener(final String funcName, final String jsObjectName, final AwActivityTestRule rule, final AwContents awContents, final TestAwContentsClient contentsClient)1371     private static void removeEventListener(final String funcName, final String jsObjectName,
1372             final AwActivityTestRule rule, final AwContents awContents,
1373             final TestAwContentsClient contentsClient) throws Throwable {
1374         String code = jsObjectName + ".removeEventListener('message', " + funcName + ")";
1375         rule.executeJavaScriptAndWaitForResult(awContents, contentsClient, code);
1376     }
1377 
parseOrigin(String url)1378     private static String parseOrigin(String url) {
1379         final Uri uri = Uri.parse(url);
1380         final StringBuilder sb = new StringBuilder();
1381         final String scheme = uri.getScheme();
1382         final int port = uri.getPort();
1383 
1384         if (!scheme.isEmpty()) {
1385             sb.append(scheme);
1386             sb.append("://");
1387         }
1388         sb.append(uri.getHost());
1389         if (port != -1) {
1390             sb.append(":");
1391             sb.append(port);
1392         }
1393         return sb.toString();
1394     }
1395 
getRootCauseException(Throwable exception)1396     private static Throwable getRootCauseException(Throwable exception) {
1397         while (exception.getCause() != null) {
1398             exception = exception.getCause();
1399         }
1400         return exception;
1401     }
1402 }
1403