1 /*
2  * Copyright (C) 2018 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package androidx.webkit;
18 
19 import static junit.framework.Assert.assertEquals;
20 
21 import android.net.Uri;
22 import android.support.test.InstrumentationRegistry;
23 import android.support.test.filters.MediumTest;
24 import android.support.test.filters.SdkSuppress;
25 import android.support.test.runner.AndroidJUnit4;
26 import android.webkit.WebView;
27 
28 import androidx.webkit.WebMessagePortCompat.WebMessageCallbackCompat;
29 
30 import junit.framework.Assert;
31 
32 import org.junit.After;
33 import org.junit.Before;
34 import org.junit.Test;
35 import org.junit.runner.RunWith;
36 
37 import java.util.concurrent.CountDownLatch;
38 
39 @MediumTest
40 @RunWith(AndroidJUnit4.class)
41 public class PostMessageTest {
42     public static final long TIMEOUT = 6000L;
43 
44     private WebView mWebView;
45     private WebViewOnUiThread mOnUiThread;
46 
47     private static final String WEBVIEW_MESSAGE = "from_webview";
48     private static final String BASE_URI = "http://www.example.com";
49 
50     @Before
setUp()51     public void setUp() throws Exception {
52         mOnUiThread = new WebViewOnUiThread();
53         mOnUiThread.getSettings().setJavaScriptEnabled(true);
54     }
55 
56     @After
tearDown()57     public void tearDown() throws Exception {
58         if (mOnUiThread != null) {
59             mOnUiThread.cleanUp();
60         }
61     }
62 
63     private static final String TITLE_FROM_POST_MESSAGE =
64             "<!DOCTYPE html><html><body>"
65                     + "    <script>"
66                     + "        var received = '';"
67                     + "        onmessage = function (e) {"
68                     + "            received += e.data;"
69                     + "            document.title = received; };"
70                     + "    </script>"
71                     + "</body></html>";
72 
73     // Acks each received message from the message channel with a seq number.
74     private static final String CHANNEL_MESSAGE =
75             "<!DOCTYPE html><html><body>"
76                     + "    <script>"
77                     + "        var counter = 0;"
78                     + "        onmessage = function (e) {"
79                     + "            var myPort = e.ports[0];"
80                     + "            myPort.onmessage = function (f) {"
81                     + "                myPort.postMessage(f.data + counter++);"
82                     + "            }"
83                     + "        }"
84                     + "   </script>"
85                     + "</body></html>";
86 
loadPage(String data)87     private void loadPage(String data) {
88         mOnUiThread.loadDataWithBaseURLAndWaitForCompletion(BASE_URI, data,
89                 "text/html", "UTF-8", null);
90     }
91 
waitForTitle(final String title)92     private void waitForTitle(final String title) {
93         new PollingCheck(TIMEOUT) {
94             @Override
95             protected boolean check() {
96                 return mOnUiThread.getTitle().equals(title);
97             }
98         }.run();
99     }
100 
101     // Post a string message to main frame and make sure it is received.
102     @Test
103     @SdkSuppress(minSdkVersion = 23) // TODO(gsennton) activate this test for pre-M devices when we
104     // can pre-install a WebView APK containing support for the WebView Support Library, see
105     // b/73454652.
testSimpleMessageToMainFrame()106     public void testSimpleMessageToMainFrame() throws Throwable {
107         verifyPostMessageToOrigin(Uri.parse(BASE_URI));
108     }
109 
110     // Post a string message to main frame passing a wildcard as target origin
111     @Test
112     @SdkSuppress(minSdkVersion = 23) // TODO (gsennton) remove this restriction when we can
113     // pre-install a WebView APK containing support for the WebView Support Library, see b/73454652.
testWildcardOriginMatchesAnything()114     public void testWildcardOriginMatchesAnything() throws Throwable {
115         verifyPostMessageToOrigin(Uri.parse("*"));
116     }
117 
118     // Post a string message to main frame passing an empty string as target origin
119     @Test
120     @SdkSuppress(minSdkVersion = 23) // TODO(gsennton) activate this test for pre-M devices when we
121     // can pre-install a WebView APK containing support for the WebView Support Library, see
122     // b/73454652.
testEmptyStringOriginMatchesAnything()123     public void testEmptyStringOriginMatchesAnything() throws Throwable {
124         verifyPostMessageToOrigin(Uri.parse(""));
125     }
126 
verifyPostMessageToOrigin(Uri origin)127     private void verifyPostMessageToOrigin(Uri origin) throws Throwable {
128         loadPage(TITLE_FROM_POST_MESSAGE);
129         WebMessageCompat message = new WebMessageCompat(WEBVIEW_MESSAGE);
130         mOnUiThread.postWebMessageCompat(message, origin);
131         waitForTitle(WEBVIEW_MESSAGE);
132     }
133 
134     // Post multiple messages to main frame and make sure they are received in
135     // correct order.
136     @Test
137     @SdkSuppress(minSdkVersion = 23) // TODO(gsennton) activate this test for pre-M devices when we
138     // can pre-install a WebView APK containing support for the WebView Support Library, see
139     // b/73454652.
testMultipleMessagesToMainFrame()140     public void testMultipleMessagesToMainFrame() throws Throwable {
141         loadPage(TITLE_FROM_POST_MESSAGE);
142         for (int i = 0; i < 10; i++) {
143             mOnUiThread.postWebMessageCompat(new WebMessageCompat(Integer.toString(i)),
144                     Uri.parse(BASE_URI));
145         }
146         waitForTitle("0123456789");
147     }
148 
149     // Create a message channel and make sure it can be used for data transfer to/from js.
150     @Test
151     @SdkSuppress(minSdkVersion = 23) // TODO(gsennton) activate this test for pre-M devices when we
152     // can pre-install a WebView APK containing support for the WebView Support Library, see
153     // b/73454652.
testMessageChannel()154     public void testMessageChannel() throws Throwable {
155         loadPage(CHANNEL_MESSAGE);
156         final WebMessagePortCompat[] channel = mOnUiThread.createWebMessageChannelCompat();
157         WebMessageCompat message =
158                 new WebMessageCompat(WEBVIEW_MESSAGE, new WebMessagePortCompat[]{channel[1]});
159         mOnUiThread.postWebMessageCompat(message, Uri.parse(BASE_URI));
160         final int messageCount = 3;
161         final CountDownLatch latch = new CountDownLatch(messageCount);
162         InstrumentationRegistry.getInstrumentation().runOnMainSync(new Runnable() {
163             @Override
164             public void run() {
165                 for (int i = 0; i < messageCount; i++) {
166                     channel[0].postMessage(new WebMessageCompat(WEBVIEW_MESSAGE + i));
167                 }
168                 channel[0].setWebMessageCallback(new WebMessageCallbackCompat() {
169                     @Override
170                     public void onMessage(WebMessagePortCompat port, WebMessageCompat message) {
171                         int i = messageCount - (int) latch.getCount();
172                         assertEquals(WEBVIEW_MESSAGE + i + i, message.getData());
173                         latch.countDown();
174                     }
175                 });
176             }
177         });
178         // Wait for all the responses to arrive.
179         boolean ignore = latch.await(TIMEOUT, java.util.concurrent.TimeUnit.MILLISECONDS);
180     }
181 
182     // Test that a message port that is closed cannot used to send a message
183     @Test
184     @SdkSuppress(minSdkVersion = 23) // TODO(gsennton) activate this test for pre-M devices when we
185     // can pre-install a WebView APK containing support for the WebView Support Library, see
186     // b/73454652.
testClose()187     public void testClose() throws Throwable {
188         loadPage(CHANNEL_MESSAGE);
189         final WebMessagePortCompat[] channel = mOnUiThread.createWebMessageChannelCompat();
190         WebMessageCompat message =
191                 new WebMessageCompat(WEBVIEW_MESSAGE, new WebMessagePortCompat[]{channel[1]});
192         mOnUiThread.postWebMessageCompat(message, Uri.parse(BASE_URI));
193         InstrumentationRegistry.getInstrumentation().runOnMainSync(new Runnable() {
194             @Override
195             public void run() {
196                 try {
197                     channel[0].close();
198                     channel[0].postMessage(new WebMessageCompat(WEBVIEW_MESSAGE));
199                 } catch (IllegalStateException ex) {
200                     // expect to receive an exception
201                     return;
202                 }
203                 Assert.fail("A closed port cannot be used to transfer messages");
204             }
205         });
206     }
207 
208     // Sends a new message channel from JS to Java.
209     private static final String CHANNEL_FROM_JS =
210             "<!DOCTYPE html><html><body>"
211                     + "    <script>"
212                     + "        var counter = 0;"
213                     + "        var mc = new MessageChannel();"
214                     + "        var received = '';"
215                     + "        mc.port1.onmessage = function (e) {"
216                     + "               received = e.data;"
217                     + "               document.title = e.data;"
218                     + "        };"
219                     + "        onmessage = function (e) {"
220                     + "            var myPort = e.ports[0];"
221                     + "            myPort.postMessage('', [mc.port2]);"
222                     + "        };"
223                     + "   </script>"
224                     + "</body></html>";
225 
226     // Test a message port created in JS can be received and used for message transfer.
227     @Test
228     @SdkSuppress(minSdkVersion = 23) // TODO(gsennton) activate this test for pre-M devices when we
229     // can pre-install a WebView APK containing support for the WebView Support Library, see
230     // b/73454652.
testReceiveMessagePort()231     public void testReceiveMessagePort() throws Throwable {
232         final String hello = "HELLO";
233         loadPage(CHANNEL_FROM_JS);
234         final WebMessagePortCompat[] channel = mOnUiThread.createWebMessageChannelCompat();
235         WebMessageCompat message =
236                 new WebMessageCompat(WEBVIEW_MESSAGE, new WebMessagePortCompat[]{channel[1]});
237         mOnUiThread.postWebMessageCompat(message, Uri.parse(BASE_URI));
238         InstrumentationRegistry.getInstrumentation().runOnMainSync(new Runnable() {
239             @Override
240             public void run() {
241                 channel[0].setWebMessageCallback(new WebMessageCallbackCompat() {
242                     @Override
243                     public void onMessage(WebMessagePortCompat port, WebMessageCompat message) {
244                         message.getPorts()[0].postMessage(new WebMessageCompat(hello));
245                     }
246                 });
247             }
248         });
249         waitForTitle(hello);
250     }
251 }
252