Home | History | Annotate | Download | only in cts
      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