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