Home | History | Annotate | Download | only in webkit
      1 /*
      2  * Copyright 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 org.junit.Assert.assertEquals;
     20 import static org.junit.Assume.assumeTrue;
     21 
     22 import android.support.test.filters.MediumTest;
     23 import android.support.test.runner.AndroidJUnit4;
     24 import android.webkit.JavascriptInterface;
     25 import android.webkit.WebResourceRequest;
     26 import android.webkit.WebResourceResponse;
     27 import android.webkit.WebView;
     28 
     29 import org.junit.After;
     30 import org.junit.Before;
     31 import org.junit.Test;
     32 import org.junit.runner.RunWith;
     33 
     34 import java.io.ByteArrayInputStream;
     35 import java.util.ArrayList;
     36 import java.util.List;
     37 import java.util.concurrent.Callable;
     38 
     39 @MediumTest
     40 @RunWith(AndroidJUnit4.class)
     41 public class ServiceWorkerClientCompatTest {
     42 
     43     // The BASE_URL does not matter since the tests will intercept the load, but it should be https
     44     // for the Service Worker registration to succeed.
     45     private static final String BASE_URL = "https://www.example.com/";
     46     private static final String INDEX_URL = BASE_URL + "index.html";
     47     private static final String SW_URL = BASE_URL + "sw.js";
     48     private static final String FETCH_URL = BASE_URL + "fetch.html";
     49 
     50     private static final String JS_INTERFACE_NAME = "Android";
     51     private static final int POLLING_TIMEOUT = 10 * 1000;
     52 
     53     // static HTML page always injected instead of the url loaded.
     54     private static final String INDEX_RAW_HTML =
     55             "<!DOCTYPE html>\n"
     56                     + "<html>\n"
     57                     + "  <body>\n"
     58                     + "    <script>\n"
     59                     + "      navigator.serviceWorker.register('sw.js').then(function(reg) {\n"
     60                     + "         " + JS_INTERFACE_NAME + ".registrationSuccess();\n"
     61                     + "      }).catch(function(err) {\n"
     62                     + "         console.error(err);\n"
     63                     + "      });\n"
     64                     + "    </script>\n"
     65                     + "  </body>\n"
     66                     + "</html>\n";
     67     private static final String SW_RAW_HTML = "fetch('fetch.html');";
     68     private static final String SW_UNREGISTER_RAW_JS =
     69             "navigator.serviceWorker.getRegistration().then(function(r) {"
     70                     + "  r.unregister().then(function(success) {"
     71                     + "    if (success) " + JS_INTERFACE_NAME + ".unregisterSuccess();"
     72                     + "    else console.error('unregister() was not successful');"
     73                     + "  });"
     74                     + "}).catch(function(err) {"
     75                     + "   console.error(err);"
     76                     + "});";
     77 
     78     private JavascriptStatusReceiver mJavascriptStatusReceiver;
     79     private WebViewOnUiThread mOnUiThread;
     80 
     81     // Both this test and WebViewOnUiThread need to override some of the methods on WebViewClient,
     82     // so this test subclasses the WebViewClient from WebViewOnUiThread.
     83     private static class InterceptClient extends WebViewOnUiThread.WaitForLoadedClient {
     84 
     85         InterceptClient(WebViewOnUiThread webViewOnUiThread) throws Exception {
     86             super(webViewOnUiThread);
     87         }
     88 
     89         @Override
     90         public WebResourceResponse shouldInterceptRequest(WebView view,
     91                 WebResourceRequest request) {
     92             // Only return content for INDEX_URL, deny all other requests.
     93             try {
     94                 if (request.getUrl().toString().equals(INDEX_URL)) {
     95                     return new WebResourceResponse("text/html", "utf-8",
     96                             new ByteArrayInputStream(INDEX_RAW_HTML.getBytes("UTF-8")));
     97                 }
     98             } catch (java.io.UnsupportedEncodingException e) { }
     99             return new WebResourceResponse("text/html", "UTF-8", null);
    100         }
    101     }
    102 
    103     public static class InterceptServiceWorkerClient extends ServiceWorkerClientCompat {
    104         private List<WebResourceRequest> mInterceptedRequests = new ArrayList<WebResourceRequest>();
    105 
    106         @Override
    107         public WebResourceResponse shouldInterceptRequest(WebResourceRequest request) {
    108             // Records intercepted requests and only return content for SW_URL.
    109             mInterceptedRequests.add(request);
    110             try {
    111                 if (request.getUrl().toString().equals(SW_URL)) {
    112                     return new WebResourceResponse("application/javascript", "utf-8",
    113                             new ByteArrayInputStream(SW_RAW_HTML.getBytes("UTF-8")));
    114                 }
    115             } catch (java.io.UnsupportedEncodingException e) { }
    116             return new WebResourceResponse("text/html", "UTF-8", null);
    117         }
    118 
    119         List<WebResourceRequest> getInterceptedRequests() {
    120             return mInterceptedRequests;
    121         }
    122     }
    123 
    124     @Before
    125     public void setUp() throws Exception {
    126         mOnUiThread = new WebViewOnUiThread();
    127         mOnUiThread.getSettings().setJavaScriptEnabled(true);
    128 
    129         mJavascriptStatusReceiver = new JavascriptStatusReceiver();
    130         mOnUiThread.addJavascriptInterface(mJavascriptStatusReceiver, JS_INTERFACE_NAME);
    131         mOnUiThread.setWebViewClient(new InterceptClient(mOnUiThread));
    132     }
    133 
    134     @After
    135     public void tearDown() throws Exception {
    136         if (mOnUiThread != null) {
    137             mOnUiThread.cleanUp();
    138         }
    139     }
    140 
    141     // Test correct invocation of shouldInterceptRequest for Service Workers.
    142     @Test
    143     public void testServiceWorkerClientInterceptCallback() throws Exception {
    144         assumeTrue(WebViewFeature.isFeatureSupported(WebViewFeature.SERVICE_WORKER_BASIC_USAGE));
    145         assumeTrue(WebViewFeature.isFeatureSupported(
    146                 WebViewFeature.SERVICE_WORKER_SHOULD_INTERCEPT_REQUEST));
    147 
    148         final InterceptServiceWorkerClient mInterceptServiceWorkerClient =
    149                 new InterceptServiceWorkerClient();
    150         ServiceWorkerControllerCompat swController = ServiceWorkerControllerCompat.getInstance();
    151         swController.setServiceWorkerClient(mInterceptServiceWorkerClient);
    152 
    153         mOnUiThread.loadUrlAndWaitForCompletion(INDEX_URL);
    154 
    155         Callable<Boolean> registrationSuccess = new Callable<Boolean>() {
    156             @Override
    157             public Boolean call() {
    158                 return mJavascriptStatusReceiver.mRegistrationSuccess;
    159             }
    160         };
    161         PollingCheck.check("JS could not register Service Worker", POLLING_TIMEOUT,
    162                 registrationSuccess);
    163 
    164         Callable<Boolean> receivedRequest = new Callable<Boolean>() {
    165             @Override
    166             public Boolean call() {
    167                 return mInterceptServiceWorkerClient.getInterceptedRequests().size() >= 2;
    168             }
    169         };
    170         PollingCheck.check("Service Worker intercept callbacks not invoked", POLLING_TIMEOUT,
    171                 receivedRequest);
    172 
    173         List<WebResourceRequest> requests = mInterceptServiceWorkerClient.getInterceptedRequests();
    174         assertEquals(2, requests.size());
    175         assertEquals(SW_URL, requests.get(0).getUrl().toString());
    176         assertEquals(FETCH_URL, requests.get(1).getUrl().toString());
    177 
    178         // Clean-up, make sure to unregister the Service Worker.
    179         mOnUiThread.evaluateJavascript(SW_UNREGISTER_RAW_JS, null);
    180         Callable<Boolean> unregisterSuccess = new Callable<Boolean>() {
    181             @Override
    182             public Boolean call() {
    183                 return mJavascriptStatusReceiver.mUnregisterSuccess;
    184             }
    185         };
    186         PollingCheck.check("JS could not unregister Service Worker", POLLING_TIMEOUT,
    187                 unregisterSuccess);
    188     }
    189 
    190     // Object added to the page via AddJavascriptInterface() that is used by the test Javascript to
    191     // notify back to Java if the Service Worker registration was successful.
    192     public static final class JavascriptStatusReceiver {
    193         public volatile boolean mRegistrationSuccess = false;
    194         public volatile boolean mUnregisterSuccess = false;
    195 
    196         @JavascriptInterface
    197         public void registrationSuccess() {
    198             mRegistrationSuccess = true;
    199         }
    200 
    201         @JavascriptInterface
    202         public void unregisterSuccess() {
    203             mUnregisterSuccess = true;
    204         }
    205     }
    206 }
    207