1 /* 2 * Copyright (C) 2015 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 android.webkit.cts; 18 19 import android.os.Handler; 20 import android.os.HandlerThread; 21 import android.os.Looper; 22 import android.net.Uri; 23 import android.test.ActivityInstrumentationTestCase2; 24 import android.webkit.WebMessage; 25 import android.webkit.WebMessagePort; 26 import android.webkit.WebView; 27 28 import com.android.compatibility.common.util.NullWebViewUtils; 29 import com.android.compatibility.common.util.PollingCheck; 30 import com.google.common.util.concurrent.SettableFuture; 31 32 import junit.framework.Assert; 33 34 import java.util.concurrent.ArrayBlockingQueue; 35 import java.util.concurrent.BlockingQueue; 36 37 public class PostMessageTest extends ActivityInstrumentationTestCase2<WebViewCtsActivity> { 38 public static final long TIMEOUT = 20000L; 39 40 private WebView mWebView; 41 private WebViewOnUiThread mOnUiThread; 42 43 private static final String WEBVIEW_MESSAGE = "from_webview"; 44 private static final String BASE_URI = "http://www.example.com"; 45 46 public PostMessageTest() { 47 super("android.webkit.cts", WebViewCtsActivity.class); 48 } 49 50 @Override 51 protected void setUp() throws Exception { 52 super.setUp(); 53 final WebViewCtsActivity activity = getActivity(); 54 mWebView = activity.getWebView(); 55 if (mWebView != null) { 56 mOnUiThread = new WebViewOnUiThread(mWebView); 57 mOnUiThread.getSettings().setJavaScriptEnabled(true); 58 } 59 } 60 61 @Override 62 protected void tearDown() throws Exception { 63 if (mOnUiThread != null) { 64 mOnUiThread.cleanUp(); 65 } 66 super.tearDown(); 67 } 68 69 private static final String TITLE_FROM_POST_MESSAGE = 70 "<!DOCTYPE html><html><body>" 71 + " <script>" 72 + " var received = '';" 73 + " onmessage = function (e) {" 74 + " received += e.data;" 75 + " document.title = received; };" 76 + " </script>" 77 + "</body></html>"; 78 79 // Acks each received message from the message channel with a seq number. 80 private static final String CHANNEL_MESSAGE = 81 "<!DOCTYPE html><html><body>" 82 + " <script>" 83 + " var counter = 0;" 84 + " onmessage = function (e) {" 85 + " var myPort = e.ports[0];" 86 + " myPort.onmessage = function (f) {" 87 + " myPort.postMessage(f.data + counter++);" 88 + " }" 89 + " }" 90 + " </script>" 91 + "</body></html>"; 92 93 private void loadPage(String data) { 94 mOnUiThread.loadDataWithBaseURLAndWaitForCompletion(BASE_URI, data, 95 "text/html", "UTF-8", null); 96 } 97 98 private void waitForTitle(final String title) { 99 new PollingCheck(TIMEOUT) { 100 @Override 101 protected boolean check() { 102 return mOnUiThread.getTitle().equals(title); 103 } 104 }.run(); 105 } 106 107 /** 108 * This should remain functionally equivalent to 109 * androidx.webkit.PostMessageTest#testSimpleMessageToMainFrame. Modifications to this test 110 * should be reflected in that test as necessary. See http://go/modifying-webview-cts. 111 */ 112 // Post a string message to main frame and make sure it is received. 113 public void testSimpleMessageToMainFrame() throws Throwable { 114 verifyPostMessageToOrigin(Uri.parse(BASE_URI)); 115 } 116 117 /** 118 * This should remain functionally equivalent to 119 * androidx.webkit.PostMessageTest#testWildcardOriginMatchesAnything. Modifications to this 120 * test should be reflected in that test as necessary. See http://go/modifying-webview-cts. 121 */ 122 // Post a string message to main frame passing a wildcard as target origin 123 public void testWildcardOriginMatchesAnything() throws Throwable { 124 verifyPostMessageToOrigin(Uri.parse("*")); 125 } 126 127 /** 128 * This should remain functionally equivalent to 129 * androidx.webkit.PostMessageTest#testEmptyStringOriginMatchesAnything. Modifications to 130 * this test should be reflected in that test as necessary. See http://go/modifying-webview-cts. 131 */ 132 // Post a string message to main frame passing an empty string as target origin 133 public void testEmptyStringOriginMatchesAnything() throws Throwable { 134 verifyPostMessageToOrigin(Uri.parse("")); 135 } 136 137 private void verifyPostMessageToOrigin(Uri origin) throws Throwable { 138 if (!NullWebViewUtils.isWebViewAvailable()) { 139 return; 140 } 141 loadPage(TITLE_FROM_POST_MESSAGE); 142 WebMessage message = new WebMessage(WEBVIEW_MESSAGE); 143 mOnUiThread.postWebMessage(message, origin); 144 waitForTitle(WEBVIEW_MESSAGE); 145 } 146 147 /** 148 * This should remain functionally equivalent to 149 * androidx.webkit.PostMessageTest#testMultipleMessagesToMainFrame. Modifications to this 150 * test should be reflected in that test as necessary. See http://go/modifying-webview-cts. 151 */ 152 // Post multiple messages to main frame and make sure they are received in 153 // correct order. 154 public void testMultipleMessagesToMainFrame() throws Throwable { 155 if (!NullWebViewUtils.isWebViewAvailable()) { 156 return; 157 } 158 loadPage(TITLE_FROM_POST_MESSAGE); 159 for (int i = 0; i < 10; i++) { 160 mOnUiThread.postWebMessage(new WebMessage(Integer.toString(i)), 161 Uri.parse(BASE_URI)); 162 } 163 waitForTitle("0123456789"); 164 } 165 166 /** 167 * This should remain functionally equivalent to 168 * androidx.webkit.PostMessageTest#testMessageChannel. Modifications to this test should be 169 * reflected in that test as necessary. See http://go/modifying-webview-cts. 170 */ 171 // Create a message channel and make sure it can be used for data transfer to/from js. 172 public void testMessageChannel() throws Throwable { 173 if (!NullWebViewUtils.isWebViewAvailable()) { 174 return; 175 } 176 loadPage(CHANNEL_MESSAGE); 177 final WebMessagePort[] channel = mOnUiThread.createWebMessageChannel(); 178 WebMessage message = new WebMessage(WEBVIEW_MESSAGE, new WebMessagePort[]{channel[1]}); 179 mOnUiThread.postWebMessage(message, Uri.parse(BASE_URI)); 180 final int messageCount = 3; 181 final BlockingQueue<String> queue = new ArrayBlockingQueue<>(messageCount); 182 WebkitUtils.onMainThreadSync(() -> { 183 for (int i = 0; i < messageCount; i++) { 184 channel[0].postMessage(new WebMessage(WEBVIEW_MESSAGE + i)); 185 } 186 channel[0].setWebMessageCallback(new WebMessagePort.WebMessageCallback() { 187 @Override 188 public void onMessage(WebMessagePort port, WebMessage message) { 189 queue.add(message.getData()); 190 } 191 }); 192 }); 193 194 // Wait for all the responses to arrive. 195 for (int i = 0; i < messageCount; i++) { 196 // The JavaScript code simply appends an integer counter to the end of the message it 197 // receives, which is why we have a second i on the end. 198 String expectedMessageFromJavascript = WEBVIEW_MESSAGE + i + "" + i; 199 assertEquals(expectedMessageFromJavascript, 200 WebkitUtils.waitForNextQueueElement(queue)); 201 } 202 } 203 204 /** 205 * This should remain functionally equivalent to 206 * androidx.webkit.PostMessageTest#testClose. Modifications to this test should be reflected in 207 * that test as necessary. See http://go/modifying-webview-cts. 208 */ 209 // Test that a message port that is closed cannot used to send a message 210 public void testClose() throws Throwable { 211 if (!NullWebViewUtils.isWebViewAvailable()) { 212 return; 213 } 214 loadPage(CHANNEL_MESSAGE); 215 final WebMessagePort[] channel = mOnUiThread.createWebMessageChannel(); 216 WebMessage message = new WebMessage(WEBVIEW_MESSAGE, new WebMessagePort[]{channel[1]}); 217 mOnUiThread.postWebMessage(message, Uri.parse(BASE_URI)); 218 WebkitUtils.onMainThreadSync(() -> { 219 try { 220 channel[0].close(); 221 channel[0].postMessage(new WebMessage(WEBVIEW_MESSAGE)); 222 } catch (IllegalStateException ex) { 223 // expect to receive an exception 224 return; 225 } 226 Assert.fail("A closed port cannot be used to transfer messages"); 227 }); 228 } 229 230 // Sends a new message channel from JS to Java. 231 private static final String CHANNEL_FROM_JS = 232 "<!DOCTYPE html><html><body>" 233 + " <script>" 234 + " var counter = 0;" 235 + " var mc = new MessageChannel();" 236 + " var received = '';" 237 + " mc.port1.onmessage = function (e) {" 238 + " received = e.data;" 239 + " document.title = e.data;" 240 + " };" 241 + " onmessage = function (e) {" 242 + " var myPort = e.ports[0];" 243 + " myPort.postMessage('', [mc.port2]);" 244 + " };" 245 + " </script>" 246 + "</body></html>"; 247 248 /** 249 * This should remain functionally equivalent to 250 * androidx.webkit.PostMessageTest#testReceiveMessagePort. Modifications to this test should 251 * be reflected in that test as necessary. See http://go/modifying-webview-cts. 252 */ 253 // Test a message port created in JS can be received and used for message transfer. 254 public void testReceiveMessagePort() throws Throwable { 255 final String hello = "HELLO"; 256 if (!NullWebViewUtils.isWebViewAvailable()) { 257 return; 258 } 259 loadPage(CHANNEL_FROM_JS); 260 final WebMessagePort[] channel = mOnUiThread.createWebMessageChannel(); 261 WebMessage message = new WebMessage(WEBVIEW_MESSAGE, new WebMessagePort[]{channel[1]}); 262 mOnUiThread.postWebMessage(message, Uri.parse(BASE_URI)); 263 WebkitUtils.onMainThreadSync(() -> { 264 channel[0].setWebMessageCallback(new WebMessagePort.WebMessageCallback() { 265 @Override 266 public void onMessage(WebMessagePort port, WebMessage message) { 267 message.getPorts()[0].postMessage(new WebMessage(hello)); 268 } 269 }); 270 }); 271 waitForTitle(hello); 272 } 273 274 /** 275 * This should remain functionally equivalent to 276 * androidx.webkit.PostMessageTest#testWebMessageHandler. Modifications to this test should 277 * be reflected in that test as necessary. See http://go/modifying-webview-cts. 278 */ 279 // Ensure the callback is invoked on the correct Handler. 280 public void testWebMessageHandler() throws Throwable { 281 if (!NullWebViewUtils.isWebViewAvailable()) { 282 return; 283 } 284 loadPage(CHANNEL_MESSAGE); 285 final WebMessagePort[] channel = mOnUiThread.createWebMessageChannel(); 286 WebMessage message = new WebMessage(WEBVIEW_MESSAGE, new WebMessagePort[]{channel[1]}); 287 mOnUiThread.postWebMessage(message, Uri.parse(BASE_URI)); 288 final int messageCount = 1; 289 final SettableFuture<Boolean> messageHandlerThreadFuture = SettableFuture.create(); 290 291 // Create a new thread for the WebMessageCallback. 292 final HandlerThread messageHandlerThread = new HandlerThread("POST_MESSAGE_THREAD"); 293 messageHandlerThread.start(); 294 final Handler messageHandler = new Handler(messageHandlerThread.getLooper()); 295 296 WebkitUtils.onMainThreadSync(() -> { 297 channel[0].postMessage(new WebMessage(WEBVIEW_MESSAGE)); 298 channel[0].setWebMessageCallback(new WebMessagePort.WebMessageCallback() { 299 @Override 300 public void onMessage(WebMessagePort port, WebMessage message) { 301 messageHandlerThreadFuture.set( 302 messageHandlerThread.getLooper().isCurrentThread()); 303 } 304 }, messageHandler); 305 }); 306 assertTrue("Wait for all the responses to arrive and assert correct thread", 307 WebkitUtils.waitForFuture(messageHandlerThreadFuture)); 308 } 309 310 /** 311 * This should remain functionally equivalent to 312 * androidx.webkit.PostMessageTest#testWebMessageDefaultHandler. Modifications to this test 313 * should be reflected in that test as necessary. See http://go/modifying-webview-cts. 314 */ 315 // Ensure the callback is invoked on the MainLooper by default. 316 public void testWebMessageDefaultHandler() throws Throwable { 317 if (!NullWebViewUtils.isWebViewAvailable()) { 318 return; 319 } 320 loadPage(CHANNEL_MESSAGE); 321 final WebMessagePort[] channel = mOnUiThread.createWebMessageChannel(); 322 WebMessage message = new WebMessage(WEBVIEW_MESSAGE, new WebMessagePort[]{channel[1]}); 323 mOnUiThread.postWebMessage(message, Uri.parse(BASE_URI)); 324 final int messageCount = 1; 325 final SettableFuture<Boolean> messageMainLooperFuture = SettableFuture.create(); 326 327 WebkitUtils.onMainThreadSync(() -> { 328 channel[0].postMessage(new WebMessage(WEBVIEW_MESSAGE)); 329 channel[0].setWebMessageCallback(new WebMessagePort.WebMessageCallback() { 330 @Override 331 public void onMessage(WebMessagePort port, WebMessage message) { 332 messageMainLooperFuture.set(Looper.getMainLooper().isCurrentThread()); 333 } 334 }); 335 }); 336 assertTrue("Response should be on the main thread", 337 WebkitUtils.waitForFuture(messageMainLooperFuture)); 338 } 339 } 340